XmPackage stub

This commit is contained in:
Sasha Koshka 2024-02-01 00:49:46 -05:00
parent d34e7fdc88
commit b3b86808ea
3 changed files with 669 additions and 0 deletions

2
xmpackage/build.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
../scripts/buildapp.sh xmpackage "$@"

View File

@ -0,0 +1,27 @@
#define icon_width 48
#define icon_height 48
static unsigned char icon_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00,
0x00, 0x00, 0x78, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x78, 0x00, 0x00,
0x00, 0x80, 0x07, 0xe0, 0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x0e, 0x00,
0x00, 0x78, 0x00, 0x40, 0x38, 0x00, 0x00, 0x1e, 0x80, 0x31, 0x60, 0x00,
0x80, 0x07, 0x40, 0x0e, 0x80, 0x01, 0xc0, 0x01, 0x20, 0x04, 0x00, 0x03,
0xe0, 0x00, 0xc0, 0x08, 0x00, 0x07, 0x60, 0x03, 0x30, 0x07, 0x80, 0x06,
0x60, 0x0c, 0x0c, 0x00, 0x20, 0x06, 0x60, 0x30, 0x03, 0x00, 0x08, 0x06,
0x60, 0xc0, 0x06, 0x00, 0x02, 0x06, 0x60, 0x00, 0x0b, 0x80, 0x60, 0x06,
0x60, 0x00, 0x3c, 0x20, 0x58, 0x06, 0x60, 0x00, 0xf0, 0x08, 0x46, 0x06,
0x60, 0x00, 0xc0, 0x83, 0x41, 0x06, 0x60, 0x00, 0x00, 0x81, 0x60, 0x06,
0x60, 0x00, 0x00, 0x80, 0x18, 0x06, 0x60, 0x00, 0x00, 0x81, 0x06, 0x06,
0x60, 0x00, 0x00, 0x80, 0x01, 0x06, 0x60, 0x00, 0x00, 0x01, 0x00, 0x06,
0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x01, 0x00, 0x06,
0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x01, 0x02, 0x06,
0x60, 0x00, 0x00, 0x80, 0x02, 0x06, 0x60, 0x00, 0x00, 0xa1, 0x02, 0x06,
0xc0, 0x01, 0x00, 0xa8, 0x82, 0x03, 0x80, 0x07, 0x00, 0xa9, 0xe0, 0x01,
0x00, 0x1e, 0x00, 0x28, 0x78, 0x00, 0x00, 0x78, 0x00, 0x09, 0x1e, 0x00,
0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0x80, 0x07, 0xe1, 0x01, 0x00,
0x00, 0x00, 0x1e, 0x78, 0x00, 0x00, 0x00, 0x00, 0x78, 0x1f, 0x00, 0x00,
0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

640
xmpackage/src/main.c Normal file
View File

@ -0,0 +1,640 @@
#define _XOPEN_SOURCE
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/RowColumn.h>
#include <Xm/PushB.h>
#include <Xm/Text.h>
#include <Xm/Form.h>
#include <Xm/PanedW.h>
#include <Xm/TabStack.h>
#include <Xm/List.h>
#include <Xm/Frame.h>
#include <Xm/TextF.h>
#include <Xm/Separator.h>
#include <Xm/ScrolledW.h>
#include <Xm/LabelG.h>
#include <Xmd/Icon.h>
#include <Xmd/Buffer.h>
#include <Xmd/Exec.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/wait.h>
#include "icons/icon.xbm"
Widget createLabeledTextForm (Widget, String, String, XmString);
Widget createScrolledInfoText (Widget, String);
void selectPackage (String);
void updatePackageList (void);
void readPackageList (FILE *);
void handleListModeSelectionChange (Widget, XtPointer, XtPointer);
void handlePackageSelectionChange (Widget, XtPointer, XtPointer);
void handleSearchQueryActivate (Widget, XtPointer, XtPointer);
String parsePackageName (String);
void parseInfoOutput (FILE *, String *, String *, String *, String *);
static XtAppContext application;
static Pixmap iconPixmap;
static struct {
Widget name;
Widget description;
Widget dependencies;
Widget provides;
Widget contents;
String selected;
} packageInfo = { 0 };
static struct {
Widget query;
Widget list;
Widget modeMenu;
enum {
viewModeWorld,
viewModeInstalled,
viewModeUpgradable,
viewModeOrphaned,
viewModeSearch
} viewMode;
XmString viewModeStrings[4];
} packageList = { 0 };
int main (int argc, char *argv[]) {
Widget topLevel = XtVaAppInitialize (
&application, "PackageManager",
NULL, 0,
&argc, argv,
NULL,
XmNtitle, "Package Manager",
XmNiconName, "Package Manager",
NULL);
iconPixmap = XmdLoadBitmapIcon(topLevel, icon);
XtVaSetValues (
topLevel,
XmNiconPixmap, iconPixmap,
NULL);
Widget window = XtVaCreateManagedWidget (
"window", xmMainWindowWidgetClass, topLevel,
NULL);
Widget pane = XtVaCreateWidget (
"pane", xmPanedWindowWidgetClass, window,
XmNorientation, XmHORIZONTAL,
NULL);
/* menu bar */
XmString fileString = XmStringCreateLocalized("File");
XmString systemString = XmStringCreateLocalized("System");
XmString helpString = XmStringCreateLocalized("Help");
Widget menuBar = XmVaCreateSimpleMenuBar (
window, "menuBar",
XmVaCASCADEBUTTON, fileString, 'F',
XmVaCASCADEBUTTON, systemString, 'S',
XmVaCASCADEBUTTON, helpString, 'H',
NULL);
XmStringFree(fileString);
XmStringFree(systemString);
XmStringFree(helpString);
XtManageChild(menuBar);
/* package list pane */
Widget packageListPane = XtVaCreateWidget (
"packageListPane", xmFormWidgetClass, pane,
XmNhorizontalSpacing, 4,
XmNverticalSpacing, 4,
XmNmarginHeight, 2,
XmNmarginWidth, 2,
NULL);
XmString packageSearchLabelString = XmStringCreateLocalized("Search:");
packageList.query = createLabeledTextForm (
packageListPane,
"packageSearchLabel",
"packageSearchText",
packageSearchLabelString);
XmStringFree(packageSearchLabelString);
XtAddCallback (
packageList.query,
XmNactivateCallback, handleSearchQueryActivate,
NULL);
XtVaSetValues (
XtParent(packageList.query),
XmNrightAttachment, XmATTACH_FORM,
XmNtopAttachment, XmATTACH_FORM,
XmNleftAttachment, XmATTACH_FORM,
NULL);
/* list mode selector */
Widget listModeFrame = XtVaCreateWidget (
"listModeFrame", xmFrameWidgetClass, packageListPane,
XmNshadowType, XmSHADOW_IN,
XmNshadowThickness, 1,
XmNleftAttachment, XmATTACH_FORM,
XmNrightAttachment, XmATTACH_FORM,
XmNbottomAttachment, XmATTACH_FORM,
NULL);
Widget listModeRc = XtVaCreateWidget (
"listModeRc", xmRowColumnWidgetClass, listModeFrame,
XmNmarginWidth, 0,
XmNmarginHeight, 0,
NULL);
XmString listModeString = XmStringCreateLocalized("List:");
packageList.viewModeStrings[viewModeWorld] = XmStringCreateLocalized("World");
packageList.viewModeStrings[viewModeInstalled] = XmStringCreateLocalized("Installed");
packageList.viewModeStrings[viewModeUpgradable] = XmStringCreateLocalized("Upgradable");
packageList.viewModeStrings[viewModeOrphaned] = XmStringCreateLocalized("Orphaned");
packageList.viewModeStrings[viewModeSearch] = XmStringCreateLocalized("Search");
packageList.modeMenu = XmVaCreateSimpleOptionMenu (
listModeRc, "packageListMode",
listModeString, 'L', 0, handleListModeSelectionChange,
XmVaPUSHBUTTON, packageList.viewModeStrings[viewModeWorld], 'W', NULL, NULL,
XmVaPUSHBUTTON, packageList.viewModeStrings[viewModeInstalled], 'I', NULL, NULL,
XmVaPUSHBUTTON, packageList.viewModeStrings[viewModeUpgradable], 'U', NULL, NULL,
XmVaPUSHBUTTON, packageList.viewModeStrings[viewModeOrphaned], 'O', NULL, NULL,
XmVaPUSHBUTTON, packageList.viewModeStrings[viewModeSearch], 'S', NULL, NULL,
XmNtearOffModel, XmTEAR_OFF_ENABLED,
NULL);
XmStringFree(listModeString);
XtManageChild(packageList.modeMenu);
XtManageChild(listModeFrame);
XtManageChild(listModeRc);
/* packageList */
packageList.list = XmCreateScrolledList(packageListPane, "packageList", NULL, 0);
XtVaSetValues (
XtParent(packageList.list),
XmNtopAttachment, XmATTACH_WIDGET,
XmNtopWidget, XtParent(packageList.query),
XmNleftAttachment, XmATTACH_FORM,
XmNrightAttachment, XmATTACH_FORM,
XmNbottomAttachment, XmATTACH_WIDGET,
XmNbottomWidget, listModeFrame,
NULL);
XtAddCallback (
packageList.list,
XmNbrowseSelectionCallback, handlePackageSelectionChange,
NULL);
XtManageChild(packageList.list);
/* package viewer */
Widget descriptionPane = XtVaCreateWidget (
"descriptionPane", xmFormWidgetClass, pane,
XmNhorizontalSpacing, 4,
XmNverticalSpacing, 4,
XmNmarginHeight, 2,
XmNmarginWidth, 2,
NULL);
/* package actions */
Widget packageActionsFrame = XtVaCreateWidget (
"packageActionsFrame", xmFrameWidgetClass, descriptionPane,
XmNshadowType, XmSHADOW_OUT,
XmNleftAttachment, XmATTACH_FORM,
XmNrightAttachment, XmATTACH_FORM,
XmNbottomAttachment, XmATTACH_FORM,
NULL);
Widget packageActionsRc = XtVaCreateWidget (
"packageActionsRc", xmRowColumnWidgetClass, packageActionsFrame,
XmNorientation, XmHORIZONTAL,
NULL);
XmString packageAddString = XmStringCreateLocalized("Install");
Widget packageAdd = XtVaCreateManagedWidget (
"packageAdd", xmPushButtonWidgetClass, packageActionsRc,
XmNlabelString, packageAddString,
NULL);
XmStringFree(packageAddString);
XmString packageDeleteString = XmStringCreateLocalized("Remove");
XtVaCreateManagedWidget (
"packageDelete", xmPushButtonWidgetClass, packageActionsRc,
XmNlabelString, packageDeleteString,
NULL);
XmStringFree(packageDeleteString);
XmString packageUpgradeString = XmStringCreateLocalized("Upgrade");
XtVaCreateManagedWidget (
"packageUpgrade", xmPushButtonWidgetClass, packageActionsRc,
XmNlabelString, packageUpgradeString,
NULL);
XmStringFree(packageUpgradeString);
XmString packageFixString = XmStringCreateLocalized("Fix");
XtVaCreateManagedWidget (
"packageFix", xmPushButtonWidgetClass, packageActionsRc,
XmNlabelString, packageFixString,
NULL);
XmStringFree(packageFixString);
XtManageChild(packageActionsRc);
XtManageChild(packageActionsFrame);
/* visually separate package description and actions */
Widget descriptionSeparator = XtVaCreateManagedWidget (
"descriptionSeparator", xmSeparatorWidgetClass, descriptionPane,
XmNleftAttachment, XmATTACH_FORM,
XmNrightAttachment, XmATTACH_FORM,
XmNbottomAttachment, XmATTACH_WIDGET,
XmNbottomWidget, packageAdd,
NULL);
/* package description */
packageInfo.name = XtVaCreateManagedWidget (
"name", xmLabelGadgetClass, descriptionPane,
XmNleftAttachment, XmATTACH_FORM,
XmNrightAttachment, XmATTACH_FORM,
XmNtopAttachment, XmATTACH_FORM,
NULL);
Widget descriptionTabStack = XtVaCreateWidget (
"descriptionTabStack", xmTabStackWidgetClass, descriptionPane,
XmNleftAttachment, XmATTACH_FORM,
XmNrightAttachment, XmATTACH_FORM,
XmNtopAttachment, XmATTACH_WIDGET,
XmNtopWidget, packageInfo.name,
XmNbottomAttachment, XmATTACH_WIDGET,
XmNbottomWidget, descriptionSeparator,
XmNtabMarginWidth, 0,
XmNtabMarginHeight, 0,
XmNtabLabelSpacing, 0,
XmNmarginWidth, 4,
XmNmarginHeight, 4,
XmNtabMode, XmTABS_STACKED,
NULL);
packageInfo.description = createScrolledInfoText(descriptionTabStack, "Description");
packageInfo.dependencies = createScrolledInfoText(descriptionTabStack, "Dependencies");
packageInfo.provides = createScrolledInfoText(descriptionTabStack, "Provides");
packageInfo.contents = createScrolledInfoText(descriptionTabStack, "Contents");
XtManageChild(descriptionTabStack);
/* final setup */
XtManageChild(packageListPane);
XtManageChild(descriptionPane);
XtManageChild(pane);
XtVaSetValues (
window,
XmNmenuBar, menuBar,
XmNworkWindow, pane,
NULL);
updatePackageList();
selectPackage(NULL);
XtRealizeWidget(topLevel);
XtAppMainLoop(application);
}
Widget createLabeledTextForm (
Widget parent,
String labelName,
String textName,
XmString labelString
) {
Widget form = XtVaCreateWidget (
"form", xmFormWidgetClass, parent,
XmNorientation, XmHORIZONTAL,
NULL);
Widget label = XtVaCreateManagedWidget (
labelName, xmLabelGadgetClass, form,
XmNleftAttachment, XmATTACH_FORM,
XmNtopAttachment, XmATTACH_FORM,
XmNbottomAttachment, XmATTACH_FORM,
XmNlabelString, labelString,
NULL);
Widget text = XtVaCreateManagedWidget (
textName, xmTextWidgetClass, form,
XmNleftAttachment, XmATTACH_WIDGET,
XmNleftWidget, label,
XmNtopAttachment, XmATTACH_FORM,
XmNrightAttachment, XmATTACH_FORM,
XmNbottomAttachment, XmATTACH_FORM,
NULL);
XtManageChild (form);
return text;
}
Widget createScrolledInfoText (Widget parent, String name) {
Widget scroll = XtVaCreateWidget (
name, xmScrolledWindowWidgetClass, parent,
NULL);
Widget info = XtVaCreateManagedWidget (
"info", xmTextWidgetClass, scroll,
XmNrows, 10,
XmNcolumns, 60,
XmNpaneMinimum, 35,
XmNeditMode, XmMULTI_LINE_EDIT,
XmNeditable, False,
XmNwordWrap, True,
XmNscrollHorizontal, False,
NULL);
XtManageChild(scroll);
return info;
}
void selectPackage (String name) {
if (packageInfo.selected) XtFree(packageInfo.selected);
packageInfo.selected = name;
XmString packageNameString = NULL;
if (name == NULL) {
packageNameString = XmStringCreateLocalized("No package selected");
XtVaSetValues (
packageInfo.description,
XmNvalue, "",
NULL);
} else {
/* instruct apk to describe package */
pid_t child;
FILE *stream = XmdVaPipedExecPath (
"apk", &child, "r",
"apk", "info", name, "-dswLPR", "--license",
NULL);
if (stream == NULL) {
/* TODO: error popup */
return;
}
/* parse data*/
String description = NULL;
String dependencies = NULL;
String provides = NULL;
String contents = NULL;
parseInfoOutput (
stream,
&description, &dependencies,
&provides, &contents);
fclose(stream);
waitpid(child, NULL, 0);
/* set tab contents to parsed data */
packageNameString = XmStringCreateLocalized(name);
XtVaSetValues (
packageInfo.description,
XmNvalue, description,
NULL);
XtVaSetValues (
packageInfo.dependencies,
XmNvalue, dependencies,
NULL);
XtVaSetValues (
packageInfo.provides,
XmNvalue, provides,
NULL);
XtVaSetValues (
packageInfo.contents,
XmNvalue, contents,
NULL);
XtFree(description);
XtFree(dependencies);
XtFree(provides);
XtFree(contents);
}
XtVaSetValues (
packageInfo.name,
XmNlabelString, packageNameString,
NULL);
XmStringFree(packageNameString);
}
void parseInfoOutput (
FILE *stream,
String *description,
String *dependencies,
String *provides,
String *contents
) {
char nullTerminator = 0;
enum {
stateSkipUntilSpace,
stateGetHeading,
stateGetSection
} state = stateSkipUntilSpace;
int consecutiveNewlines = 0;
XmdBuffer *activeBuffer = NULL;
XmdBuffer *headingBuffer = NULL;
XmdBuffer *descriptionBuffer = XmdBufferNew(char);
XmdBuffer *dependenciesBuffer = XmdBufferNew(char);
XmdBuffer *providesBuffer = XmdBufferNew(char);
XmdBuffer *contentsBuffer = XmdBufferNew(char);
int ch;
while ((ch = fgetc(stream)) != EOF) switch (state) {
/* discard stream until there is a space */
case stateSkipUntilSpace:
if (ch == ' ') {
headingBuffer = XmdBufferNew(char);
state = stateGetHeading;
}
break;
/* what is the next section about? */
case stateGetHeading:
if (ch == ':') {
XmdBufferPush(headingBuffer, &nullTerminator);
String heading = XmdBufferBreak(headingBuffer);
if (strcmp(heading, "depends on") == 0) {
activeBuffer = dependenciesBuffer;
} else if (strcmp(heading, "provides") == 0) {
activeBuffer = providesBuffer;
} else if (strcmp(heading, "contains") == 0) {
activeBuffer = contentsBuffer;
} else {
activeBuffer = descriptionBuffer;
}
XtFree(heading);
consecutiveNewlines = 1;
state = stateGetSection;
fgetc(stream); /* skip newline */
} else {
XmdBufferPush(headingBuffer, &ch);
}
break;
/* read the section data into the active buffer */
case stateGetSection:
if (ch == '\n') {
if (consecutiveNewlines > 0) {
state = stateSkipUntilSpace;
break;
}
consecutiveNewlines ++;
} else {
consecutiveNewlines = 0;
}
XmdBufferPush(activeBuffer, &ch);
break;
}
XmdBufferPush(descriptionBuffer, &nullTerminator);
XmdBufferPush(dependenciesBuffer, &nullTerminator);
XmdBufferPush(providesBuffer, &nullTerminator);
XmdBufferPush(contentsBuffer, &nullTerminator);
*description = XmdBufferBreak(descriptionBuffer);
*dependencies = XmdBufferBreak(dependenciesBuffer);
*provides = XmdBufferBreak(providesBuffer);
*contents = XmdBufferBreak(contentsBuffer);
}
void updatePackageList (void) {
FILE *stream = NULL;
pid_t child = 0;
switch (packageList.viewMode) {
case viewModeWorld:
stream = fopen("/etc/apk/world", "r");
break;
case viewModeInstalled:
stream = XmdVaPipedExecPath (
"apk", &child, "r",
"apk", "list", "--installed",
NULL);
break;
case viewModeUpgradable:
stream = XmdVaPipedExecPath (
"apk", &child, "r",
"apk", "list", "--upgradable",
NULL);
break;
case viewModeOrphaned:
stream = XmdVaPipedExecPath (
"apk", &child, "r",
"apk", "list", "--orphaned",
NULL);
break;
case viewModeSearch:
;String query = NULL;
XtVaGetValues (
packageList.query,
XmNvalue, &query,
NULL);
stream = XmdVaPipedExecPath (
"apk", &child, "r",
"apk", "search", query,
NULL);
XtFree(query);
break;
}
if (stream == NULL) {
/* TODO: error popup */
return;
}
readPackageList(stream);
fclose(stream);
if (child != 0) waitpid(child, NULL, 0);
}
void readPackageList (FILE *list) {
String line = NULL;
size_t lineLength = 0;
char packageName[256] = { 0 };
XmdBuffer *itemsBuffer = XmdBufferNew(XmString);
while (getline(&line, &lineLength, list) != -1) {
sscanf(line, "%256s", packageName);
free(line);
line = NULL;
XmString packageNameString = XmStringCreateLocalized(packageName);
XmdBufferPush(itemsBuffer, &packageNameString);
}
Cardinal length = XmdBufferLength(itemsBuffer);
XmStringTable items = (XmStringTable)(XmdBufferBreak(itemsBuffer));
XtVaSetValues (
packageList.list,
XmNitemCount, length,
XmNitems, items,
NULL);
/* free everything */
if (line) free(line);
for (size_t index = 0; index < length; index ++) {
XmStringFree(items[index]);
}
XtFree((String)(items));
}
void handleListModeSelectionChange (
Widget widget,
XtPointer clientData,
XtPointer callData
) {
(void)(widget);
(void)(callData);
int selected = *(int *)(&clientData);
packageList.viewMode = selected;
updatePackageList();
}
void handlePackageSelectionChange (
Widget widget,
XtPointer clientData,
XtPointer callData
) {
(void)(widget);
(void)(clientData);
XmListCallbackStruct *event = (XmListCallbackStruct*)(callData);
if (event->event == NULL) return;
String choice = (String)(XmStringUnparse (
event->item,
XmFONTLIST_DEFAULT_TAG,
XmCHARSET_TEXT,
XmCHARSET_TEXT,
NULL, 0,
XmOUTPUT_ALL));
selectPackage(parsePackageName(choice));
XtFree(choice);
}
void handleSearchQueryActivate (
Widget widget,
XtPointer clientData,
XtPointer callData
) {
(void)(widget);
(void)(clientData);
(void)(callData);
packageList.viewMode = viewModeSearch;
/* FIXME do this by activating the appropriate button. that way we dont
have to call updatePackageList manually as well. */
Widget button = XmOptionButtonGadget(packageList.modeMenu);
XtVaSetValues (
button,
XmNlabelString, packageList.viewModeStrings[packageList.viewMode],
NULL);
updatePackageList();
}
String parsePackageName (String input) {
XmdBuffer *output = XmdBufferNew(char);
char ch;
Boolean wasDash = False;
for (size_t index = 0; (ch = input[index]); index ++) {
if (wasDash && isdigit(ch)) {
XmdBufferPop(output, NULL);
break;
} else {
wasDash = ch == '-';
XmdBufferPush(output, &ch);
}
}
char nullTerminator = 0;
XmdBufferPush(output, &nullTerminator);
return XmdBufferBreak(output);
}