Xmd/replicants/Battery/src/main.c
2023-11-14 16:22:01 -05:00

293 lines
7.4 KiB
C

#define _XOPEN_SOURCE
#include <Xm/Xm.h>
#include <Xm/Frame.h>
#include <Xm/PushB.h>
#include <Xm/LabelG.h>
#include <Xm/MessageB.h>
#include <Xm/RowColumn.h>
#include <Xm/Separator.h>
#include <Xmd/Icon.h>
#include <Xmd/Exec.h>
#include <Xmd/Replicant.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include "icons/level0.xbm"
#include "icons/level1.xbm"
#include "icons/level2.xbm"
#include "icons/level3.xbm"
#include "icons/level4.xbm"
#include "icons/charging.xbm"
#include "icons/error.xbm"
#include "icons/not.xbm"
typedef enum {
BatteryStateCharging,
BatteryStateNotCharging,
BatteryStateFull,
BatteryStateDischarging,
BatteryStateError
} BatteryState;
typedef struct {
int level;
BatteryState state;
int hours;
int minutes;
int seconds;
} BatteryInfo;
typedef struct {
unsigned long pollInterval;
XtAppContext application;
Widget root;
Widget layout;
Widget icon;
Widget text;
XtIntervalId timer;
Pixmap levels[8];
} BatteryReplicant;
static BatteryInfo getBatteryInfo (void);
static Pixmap selectBatteryIcon (BatteryReplicant *, BatteryInfo);
static void loadAllPixmaps (BatteryReplicant *);
static void batteryPoll (XtPointer, XtIntervalId *);
static void resetBatteryPollTimeout (BatteryReplicant *);
static void handleBatteryInfoDialog (Widget, XtPointer, XtPointer);
static void handleDestroy (Widget, XtPointer, XtPointer);
int Battery_XmdReplicantVersion (void) {
return XmdREPLICANT_VERSION;
}
Widget Battery_XmdReplicantCreate (
XtAppContext application,
Widget parent,
XmdReplicantState *state
) {
(void)(state);
BatteryReplicant *this = XtNew(BatteryReplicant);
memset(this, 0, sizeof(*this));
this->application = application;
String pollIntervalString = XmdReplicantStateQuery(state, "Interval");
if (pollIntervalString != NULL) {
this->pollInterval = 1000 * atoi(pollIntervalString);
}
if (this->pollInterval == 0) this->pollInterval = 2000;
XtFree(pollIntervalString);
this->root = XtVaCreateWidget (
"battery", xmFrameWidgetClass, parent,
XmNshadowType, XmSHADOW_ETCHED_IN,
NULL);
XtAddCallback(this->root, XmNdestroyCallback, handleDestroy, NULL);
this->layout = XtVaCreateWidget (
"batteryLayout", xmRowColumnWidgetClass, this->root,
XmNorientation, XmHORIZONTAL,
NULL);
this->icon = XtVaCreateManagedWidget (
"batteryIcon", xmPushButtonWidgetClass, this->layout,
XmNleftAttachment, XmATTACH_FORM,
XmNlabelType, XmPIXMAP,
NULL);
loadAllPixmaps(this);
XtAddCallback (
this->icon,
XmNactivateCallback, handleBatteryInfoDialog,
NULL);
XtVaCreateManagedWidget (
"batterySeparator", xmSeparatorWidgetClass, this->layout,
XmNorientation, XmVERTICAL,
NULL);
this->text = XtVaCreateManagedWidget (
"batteryText", xmLabelGadgetClass, this->layout,
XmNalignment, XmALIGNMENT_CENTER,
NULL);
batteryPoll(this, NULL);
XtManageChild(this->layout);
XtManageChild(this->root);
return this->root;
}
BatteryInfo getBatteryInfo (void) {
BatteryInfo result = { 0 };
char charging[16] = { 0 };
int battery;
pid_t child;
FILE *stream = XmdVaPipedExecPath (
"acpi", &child, "r",
"acpi", "-b",
NULL);
if (stream == NULL) {
result.state = BatteryStateError;
return result;
}
fscanf (
stream, "Battery %d: %16s %d%%, %d:%d:%d",
&battery, charging, &result.level,
&result.hours, &result.minutes, &result.seconds);
switch (charging[0]) {
case 'C': result.state = BatteryStateCharging; break;
case 'N': result.state = BatteryStateNotCharging; break;
case 'D': result.state = BatteryStateDischarging; break;
case 'F': result.state = BatteryStateFull; break;
default: result.state = BatteryStateError; break;
}
if (fclose(stream) != 0) {
result.state = BatteryStateError;
}
waitpid(child, NULL, 0);
return result;
}
Pixmap selectBatteryIcon (BatteryReplicant *this, BatteryInfo info) {
if (info.state == BatteryStateCharging) {
return this->levels[5];
} else if (info.state == BatteryStateNotCharging) {
return this->levels[7];
} else if (info.state == BatteryStateError) {
return this->levels[6];
} else if (info.level < 15) {
return this->levels[0];
} else if (info.level < 35) {
return this->levels[1];
} else if (info.level < 65) {
return this->levels[2];
} else if (info.level < 85) {
return this->levels[3];
} else {
return this->levels[4];
}
}
void loadAllPixmaps (BatteryReplicant *this) {
this->levels[0] = XmdLoadBitmapIcon(this->icon, level0);
this->levels[1] = XmdLoadBitmapIcon(this->icon, level1);
this->levels[2] = XmdLoadBitmapIcon(this->icon, level2);
this->levels[3] = XmdLoadBitmapIcon(this->icon, level3);
this->levels[4] = XmdLoadBitmapIcon(this->icon, level4);
this->levels[5] = XmdLoadBitmapIcon(this->icon, charging);
this->levels[6] = XmdLoadBitmapIcon(this->icon, error);
this->levels[7] = XmdLoadBitmapIcon(this->icon, not);
}
void batteryPoll (XtPointer clientData, XtIntervalId *timer) {
(void)(clientData);
(void)(timer);
BatteryReplicant *this = clientData;
BatteryInfo info = getBatteryInfo();
XtVaSetValues (
this->icon,
XmNlabelType, XmPIXMAP,
XmNlabelPixmap, selectBatteryIcon(this, info),
NULL);
char buffer[32];
snprintf(buffer, XtNumber(buffer), "%d%%", info.level);
/* if we don't unmanage and then re-manage the child, the text flies to
the top which is rather annoying. this little quirk bent me over and
fucked me for hours. */
XtUnmanageChild(this->text);
XmString string = XmStringCreateLocalized(buffer);
XtVaSetValues (
this->text,
XmNlabelString, string,
NULL);
XmStringFree(string);
XtManageChild(this->text);
resetBatteryPollTimeout(this);
}
void resetBatteryPollTimeout (BatteryReplicant *this) {
this->timer = XtAppAddTimeOut (
this->application, this->pollInterval, batteryPoll, this);
}
void handleBatteryInfoDialog (Widget button, XtPointer clientData, XtPointer callData) {
(void)(callData);
(void)(clientData);
BatteryInfo info = getBatteryInfo();
static const String states[] = {
"Charging",
"Not charging",
"Full",
"Discharging",
"Error"
};
String messageBuffer = NULL;
switch (info.state) {
case BatteryStateCharging:
XtAsprintf (
&messageBuffer,
"%d%%, %s\n"
"%d:%02d:%02d until charged.",
info.level, states[info.state],
info.hours, info.minutes, info.seconds);
break;
case BatteryStateDischarging:
XtAsprintf (
&messageBuffer,
"%d%%, %s\n"
"%d:%02d:%02d until empty.",
info.level, states[info.state],
info.hours, info.minutes, info.seconds);
break;
default:
XtAsprintf (
&messageBuffer,
"%d%%, %s",
info.level, states[info.state]);
break;
}
Arg args[5] = { 0 };
int n = 0;
XmString message = XmStringCreateLocalized(messageBuffer);
XtSetArg(args[n], XmNmessageString, message); n ++;
XmString title = XmStringCreateLocalized("Battery Status");
XtSetArg(args[n], XmNdialogTitle, title); n ++;
Widget dialog = XmCreateInformationDialog(button, "batteryInfo", args, n);
XmStringFree(message);
XmStringFree(title);
XtManageChild(dialog);
XtPopup(XtParent(dialog), XtGrabNone);
}
static void handleDestroy (
Widget widget,
XtPointer clientData,
XtPointer callData
) {
(void)(callData);
(void)(widget);
BatteryReplicant *this = clientData;
XtRemoveTimeOut(this->timer);
for (Cardinal index = 0; index < XtNumber(this->levels); index ++) {
XFreePixmap(XtDisplay(this->layout), this->levels[index]);
}
XtFree((char *)(this));
}