diff --git a/xmshelf/build.sh b/xmshelf/build.sh new file mode 100755 index 0000000..d81778c --- /dev/null +++ b/xmshelf/build.sh @@ -0,0 +1,2 @@ +#!/bin/sh +../scripts/buildapp.sh xmshell "$@" diff --git a/xmshelf/src/icons/icon.xbm b/xmshelf/src/icons/icon.xbm new file mode 100644 index 0000000..d19fe07 --- /dev/null +++ b/xmshelf/src/icons/icon.xbm @@ -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, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x3f, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x0c, 0x1e, 0x00, 0xf0, 0x03, 0x30, + 0xfc, 0xf3, 0xff, 0x0f, 0xfc, 0x3f, 0x0c, 0x09, 0x00, 0xf2, 0x13, 0x30, + 0xfc, 0xf9, 0xff, 0x0d, 0xec, 0x3f, 0x0c, 0x09, 0x80, 0x02, 0x50, 0x30, + 0xfc, 0xf9, 0xff, 0x02, 0xd0, 0x3f, 0x8c, 0x04, 0x40, 0x01, 0xa6, 0x30, + 0xfc, 0xfc, 0x7f, 0x81, 0xa1, 0x3f, 0x8c, 0xfc, 0x4f, 0xe1, 0xa0, 0x30, + 0xfc, 0x04, 0x78, 0x41, 0xa0, 0x3f, 0x4c, 0x06, 0x48, 0x41, 0xa0, 0x30, + 0x7c, 0x06, 0x78, 0x81, 0xa0, 0x3f, 0x4c, 0x06, 0x88, 0x82, 0x50, 0x30, + 0x7c, 0x06, 0xf8, 0x03, 0xf0, 0x3f, 0x2c, 0x05, 0x28, 0x0c, 0x0c, 0x31, + 0x3c, 0x07, 0x38, 0xf0, 0x03, 0x3f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x3f, + 0xac, 0x00, 0x00, 0x00, 0x00, 0x35, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x3f, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x0c, 0x00, 0x00, 0x22, 0x00, 0x30, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x0c, 0x00, 0x00, 0x7f, 0x00, 0x30, + 0xfc, 0xff, 0xff, 0xc1, 0xff, 0x3f, 0xcc, 0x3f, 0x00, 0x41, 0x3e, 0x31, + 0x7c, 0xc0, 0xff, 0xe3, 0x23, 0x3f, 0x4c, 0xc0, 0x1f, 0x14, 0x22, 0x31, + 0x7c, 0x00, 0xf0, 0xf7, 0x23, 0x31, 0x4c, 0x00, 0xf0, 0x94, 0x22, 0x31, + 0x7c, 0x00, 0xd0, 0xf7, 0x22, 0x31, 0x4c, 0x00, 0xf0, 0x94, 0x22, 0x31, + 0x7c, 0x00, 0xd0, 0xf7, 0xe2, 0x31, 0x4c, 0x00, 0xf0, 0x94, 0x22, 0x3f, + 0x7c, 0x00, 0xd0, 0xf7, 0x36, 0x31, 0x4c, 0xff, 0xf7, 0x94, 0x22, 0x31, + 0x7c, 0x00, 0xd0, 0xf7, 0x22, 0x31, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x3f, + 0xac, 0x00, 0x00, 0x00, 0x00, 0x35, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x3f, + 0xcc, 0xff, 0xff, 0xff, 0xff, 0x33, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/xmshelf/src/main.c b/xmshelf/src/main.c new file mode 100644 index 0000000..c1920ea --- /dev/null +++ b/xmshelf/src/main.c @@ -0,0 +1,336 @@ +#define _XOPEN_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "icons/icon.xbm" + +XtAppContext application; +typedef enum { + DeviceKindGeneric, + DeviceKindDisk, + DeviceKindRom, + DeviceKindFilesystem, + DeviceKindTTY +} DeviceKind; + +typedef struct { + Widget widget; + DeviceKind kind; + Bool orphaned; + String name; + String mountPoint; + String label; +} Device; + +void loadIconPixmaps (void); +void refreshDevices (void); +void refreshDisks (void); +Device * DeviceNew (String name, DeviceKind kind); +void DeviceDelete (ConstString name); +void DeviceFree (Device *this); +void DeviceSetKind (Device *this, DeviceKind kind); +void DeviceSetMountPoint (Device *this, ConstString mountPoint); +void DeviceSetLabel (Device *this, ConstString label); +int readBlockDevice (FILE *stream, Device **); +int readLsblkPair (FILE *stream, String *key, String *value); +void handleRefresh (Widget, XtPointer, XtPointer); + +int main (int argc, char *argv[]) { + devices = XmdStringMapNew(); + + Widget window = XtVaAppInitialize ( + &application, "Shelf", + NULL, 0, + &argc, argv, + NULL, + XmNtitle, "Shelf", + XmNiconName, "Shelf", + XmNwidth, 256, + XmNheight, 256, + NULL); + + Widget layout = XtVaCreateWidget ( + "layout", xmFormWidgetClass, window, + XmNorientation, XmVERTICAL, + NULL); + Pixmap iconPixmap = XmdLoadBitmapIcon(layout, icon); + XtVaSetValues ( + window, + XmNiconPixmap, iconPixmap, + NULL); + + Pixmap refreshPixmap = XmdLoadBitmapIcon(layout, refresh); + XmString string = XmStringCreateLocalized("Refresh"); + Widget refreshButton = XtVaCreateManagedWidget ( + "refreshButton", xmPushButtonWidgetClass, layout, + XmNleftAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_FORM, + XmNlabelType, XmPIXMAP_AND_STRING, + XmNlabelPixmap, refreshPixmap, + XmNlabelString, string, + NULL); + XmStringFree(string); + XtAddCallback ( + refreshButton, XmNactivateCallback, + handleRefresh, NULL); + + Pixmap mountPixmap = XmdLoadBitmapIcon(layout, mount); + string = XmStringCreateLocalized("Mount"); + Widget mountButton = XtVaCreateManagedWidget ( + "mountButton", xmPushButtonWidgetClass, layout, + XmNleftAttachment, XmATTACH_WIDGET, + XmNleftWidget, refreshButton, + XmNbottomAttachment, XmATTACH_FORM, + XmNlabelType, XmPIXMAP_AND_STRING, + XmNlabelPixmap, mountPixmap, + XmNlabelString, string, + NULL); + XmStringFree(string); + + Pixmap unmountPixmap = XmdLoadBitmapIcon(layout, unmount); + string = XmStringCreateLocalized("Unmount"); + /*Widget unmountButton = */XtVaCreateManagedWidget ( + "unmountButton", xmPushButtonWidgetClass, layout, + XmNleftAttachment, XmATTACH_WIDGET, + XmNleftWidget, mountButton, + XmNbottomAttachment, XmATTACH_FORM, + XmNlabelType, XmPIXMAP_AND_STRING, + XmNlabelPixmap, unmountPixmap, + XmNlabelString, string, + NULL); + XmStringFree(string); + + Widget scroller = XtVaCreateWidget ( + "scroll", xmScrolledWindowWidgetClass, layout, + XmNscrollingPolicy, XmAUTOMATIC, + XmNleftAttachment, XmATTACH_FORM, + XmNtopAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNbottomAttachment, XmATTACH_WIDGET, + XmNbottomWidget, refreshButton, + NULL); + container = XtVaCreateManagedWidget ( + "container", xmContainerWidgetClass, scroller, + XmNlayoutType, XmSPATIAL, + XmNspatialStyle, XmGRID, + NULL); + + loadIconPixmaps(); + refreshDevices(); + + XtManageChild(scroller); + XtManageChild(layout); + XtRealizeWidget(window); + XtAppMainLoop(application); +} + +void loadIconPixmaps (void) { + #define dev(kind, name) \ + iconsLarge[DeviceKind##kind] = XmdLoadBitmapIcon(container, name); \ + iconsSmall[DeviceKind##kind] = XmdLoadBitmapIcon(container, name##_small) + dev(Generic, generic); + dev(Disk, disk); + dev(Rom, rom); + dev(Filesystem, filesystem); + dev(TTY, tty); +} + +void handleRefresh (Widget button, XtPointer clientData, XtPointer callData) { + (void)(button); + (void)(clientData); + (void)(callData); + refreshDevices(); +} + +Bool markDevice (XmdStringMap *map, ConstString key, void *value, void *data) { + (void)(map); + (void)(key); + (void)(data); + Device *this = value; + this->orphaned = True; + return True; +} + +Bool transferDevice (XmdStringMap *map, ConstString key, void *value, void *data) { + (void)(map); + (void)(key); + (void)(data); + Device *this = value; + if (this->orphaned) { + DeviceFree(this); + } else { + XmdStringMapSet(devices, key, value); + } + return True; +} + +void refreshDevices (void) { + XtUnmanageChild(container); + + /* pre-mark all devices as orphaned */ + XmdStringMapIterate(devices, markDevice, NULL); + + /* refresh devices by category */ + refreshDisks(); + + /* create a new map, only moving over extant devices */ + XmdStringMap *oldMap = devices; + devices = XmdStringMapNew(); + XmdStringMapIterate(oldMap, transferDevice, NULL); + XmdStringMapFree(oldMap); + + XtManageChild(container); +} + +String diskName (ConstString directory, ConstString name) { + char fullpath[PATH_MAX]; + snprintf(fullpath, XtNumber(fullpath), "%s/%s", directory, name); + char resolved[PATH_MAX]; + return XtNewString(basename(realpath(fullpath, resolved))); +} + +void refreshDisks (void) { + /* get disks */ + ConstString disksById = "/dev/disk/by-id"; + ConstString disksByLabel = "/dev/disk/by-label"; + ConstString disksByPartuuid = "/dev/disk/by-partuuid"; + + struct dirent **entries = NULL; + int entriesCount = scandir(disksById, &entries, NULL, alphasort); + if (entriesCount < 0) { + /* TODO error message */ + return; + } + for (int index = 0; index < entriesCount; index ++) { + struct dirent *entry = entries[index]; + if (entry->d_name[0] == '.') { + XtFree((char *)(entry)); + continue; + } + String deviceName = diskName(disksById, entry->d_name); + XtFree((char *)(entry)); + + Device *device = XmdStringMapGet(devices, deviceName); + if (device == NULL) { + device = DeviceNew(deviceName, DeviceKindDisk); + } else { + device->orphaned = False; + DeviceSetKind(device, DeviceKindDisk); + } + + XtFree(deviceName); + } + XtFree((char *)(entries)); + + /* determine disk types */ + DIR *dir = opendir(disksByPartuuid); + if (dir == NULL) { + /* TODO error message */ + return; + } + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') continue; + String deviceName = diskName(disksByPartuuid, entry->d_name); + Device *device = XmdStringMapGet(devices, deviceName); + if (device != NULL) DeviceSetKind(device, DeviceKindFilesystem); + XtFree(deviceName); + } + closedir(dir); + + /* determine partition labels */ + dir = opendir(disksByLabel); + if (dir == NULL) { + /* TODO error message */ + return; + } + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') continue; + String deviceName = diskName(disksByLabel, entry->d_name); + Device *device = XmdStringMapGet(devices, deviceName); + if (device != NULL) DeviceSetLabel(device, entry->d_name); + XtFree(deviceName); + } + closedir(dir); +} + +Device *DeviceNew (String name, DeviceKind kind) { + Device *this = XtNew(Device); + this->mountPoint = NULL; + this->label = NULL; + this->name = XtNewString(name); + this->orphaned = False; + XmString string = XmStringCreateLocalized(name); + this->widget = XtVaCreateManagedWidget ( + "device", xmIconGadgetClass, container, + XmNalignment, XmALIGNMENT_BEGINNING, + XmNlabelString, string, + NULL); + XmStringFree(string); + DeviceSetKind(this, kind); + XmdStringMapSet(devices, name, this); + return this; +} + +void DeviceDelete (ConstString name) { + DeviceFree(XmdStringMapSet(devices, name, NULL)); +} + +void DeviceFree (Device *this) { + if (this == NULL) return; + XtDestroyWidget(this->widget); + XtFree((char *)(this->name)); + XtFree((char *)(this->mountPoint)); + XtFree((char *)(this->label)); + XtFree((char *)(this)); +} + +void DeviceSetKind (Device *this, DeviceKind kind) { + XtVaSetValues ( + this->widget, + XmNlargeIconPixmap, iconsLarge[kind], + XmNsmallIconPixmap, iconsSmall[kind], + NULL); + this->kind = kind; +} + +void DeviceSetMountPoint (Device *this, ConstString mountPoint) { + XtFree(this->mountPoint); + this->mountPoint = XtNewString(mountPoint); +} + +void DeviceSetLabel (Device *this, ConstString label) { + XtFree(this->label); + this->label = XtNewString(label); + + XmString string = NULL; + if (label == NULL) { + string = XmStringCreateLocalized(this->name); + } else { + string = XmStringCreateLocalized(this->label); + } + XtVaSetValues ( + this->widget, + XmNlabelString, string, + NULL); + XmStringFree(string); +}