#define _XOPEN_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); }