1
0

wmpwman v26.10.22

A Window Maker dockapp to help with power management.

Co-authored-by: Tomek Wojcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wojcik <labs@tomekwojcik.pl>
This commit is contained in:
2026-10-22 18:15:01 +00:00
committed by Tomek Wójcik
parent a6ebba2e95
commit 1340aed49b
5 changed files with 565 additions and 0 deletions

1
packages/wmpwman/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/wmpwman

21
packages/wmpwman/Makefile Normal file
View File

@@ -0,0 +1,21 @@
# Co-Authored by: Claude (Sonnet 5.5 Medium)
CC ?= cc
CFLAGS ?= -O2 -Wall -Wextra -Wno-incompatible-pointer-types
CFLAGS += $(shell pkg-config --cflags x11 dockapp 2>/dev/null)
LDLIBS = $(shell pkg-config --libs x11 xext xpm dockapp 2>/dev/null)
PREFIX ?= /usr/local
SOURCES = src/wmpwman.c
.PHONY: install clean
wmpwman: $(SOURCES)
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LDLIBS)
install: wmpwman
install -D -m 755 wmpwman $(DESTDIR)$(PREFIX)/bin/wmpwman
clean:
rm -f wmpwman

View File

@@ -0,0 +1,35 @@
# wmpwman
A Window Maker dockapp to help with power management.
![OMG DOCKAPP](screenshots/wmpwman.png "Screenshot of the dockapp")
## Requirements
* Xorg-7,
* libXext,
* libXpm,
* [libdockapp v0.7.3](https://www.dockapps.net/libdockapp),
* a WM that supports Window Maker dockapps (tested with Window Maker 0.96.0).
## Installation
Build wmpwman by running the following command:
```
make
```
Now, as the _root_ user:
```
make install
```
The binary will be installed in `/usr/local/bin/wmpwman`.
## Changelog
### v26.10.22
* Initial release.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,508 @@
/*
* wmpwman - a Window Maker dockapp to help with power management.
* Co-Authored by: Claude (Sonnet 5.5 Medium)
*/
#define _GNU_SOURCE
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <spawn.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <libdockapp/dockapp.h>
#define WMPWMAN_VERSION "26.10.22"
#define VERSION "wmpwman version v" WMPWMAN_VERSION "\nCreated by Tomek Wójcik | For teh lulz"
#define DOCK_SIZE 59
#define NUM_BUTTONS 3
#define BTN_X 4
#define BTN_W 51
#define BTN_H 14
#define BTN_GAP 4
#define BTN_TOP 4
#define NO_PRESSED_BUTTON -1
#define SHUTDOWN_DELAY_MS 5000
#define TICK_MS 200
#pragma mark - Globals
struct WMPWMan {
Pixmap pixmap;
Pixmap mask;
unsigned short width;
unsigned short height;
};
struct WMPWMan *app;
static GC graphics_context;
static unsigned long color_background,
color_button,
color_button_pressed,
color_light,
color_dark,
color_button_title,
color_button_shutdown_background;
static XFontStruct *font;
static const char *const button_titles[NUM_BUTTONS] = {
"LOCK",
"SLEEP",
"SHUTDOWN",
};
static const char *const font_candidates[] = {
"5x8",
"5x7",
"6x10",
"fixed",
NULL,
};
// button currently held down, or NO_PRESSED_BUTTON
static int pressed_button = NO_PRESSED_BUTTON;
static int is_shutdown_armed;
static int last_shutdown_seconds;
static struct timespec shutdown_deadline;
# pragma mark - Helpers
static unsigned long alloc_color(const char *name) {
XColor color, exact;
Colormap cmap = DefaultColormap(DADisplay, DefaultScreen(DADisplay));
if (!XAllocNamedColor(DADisplay, cmap, name, &color, &exact)) {
DAWarning("can't allocate color %s", name);
return BlackPixel(DADisplay, DefaultScreen(DADisplay));
}
return color.pixel;
}
static XFontStruct *load_font(void) {
const char *const *name;
XFontStruct *result;
for (name = font_candidates; *name; name++) {
result = XLoadQueryFont(DADisplay, *name);
if (result) {
DAWarning("selected font: %s", *name);
return result;
}
}
DAWarning("no usable font found, labels disabled");
return NULL;
}
static int calculate_button_y(int i) {
return BTN_TOP + i * (BTN_H + BTN_GAP);
}
// Returns button index at (x, y), or NO_PRESSED_BUTTON.
static int hit_test(int x, int y) {
int i;
for (i = 0; i < NUM_BUTTONS; i++) {
int button_y = calculate_button_y(i);
if (x >= BTN_X && x < BTN_X + BTN_W && y >= button_y && y < button_y + BTN_H) {
return i;
}
}
return NO_PRESSED_BUTTON;
}
static long current_shutdown_deadline_ms(void) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (shutdown_deadline.tv_sec - now.tv_sec) * 1000L
+ (shutdown_deadline.tv_nsec - shutdown_deadline.tv_nsec) / 1000000L;
}
static int current_shutdown_deadline_seconds(void) {
return (int)((current_shutdown_deadline_ms() + 999) / 1000);
}
static void shutdown_arm(void) {
clock_gettime(CLOCK_MONOTONIC, &shutdown_deadline);
shutdown_deadline.tv_sec += SHUTDOWN_DELAY_MS / 1000;
is_shutdown_armed = 1;
last_shutdown_seconds = current_shutdown_deadline_seconds();
DASetTimeout(TICK_MS);
}
static void shutdown_disarm(void) {
is_shutdown_armed = 0;
DASetTimeout(-1);
}
static void draw_button(int i, int down) {
int button_x = BTN_X,
button_y = calculate_button_y(i),
width = BTN_W,
height = BTN_H;
unsigned long top_left = down ? color_dark : color_light;
unsigned long bot_right = down ? color_light : color_dark;
// face
if (i == 2 && is_shutdown_armed) {
XSetForeground(
DADisplay,
graphics_context,
color_button_shutdown_background
);
} else {
XSetForeground(
DADisplay,
graphics_context,
down ? color_button_pressed : color_button
);
}
XFillRectangle(
DADisplay,
app->pixmap,
graphics_context,
button_x,
button_y,
width,
height
);
// bevel: top & left
XSetForeground(DADisplay, graphics_context, top_left);
XDrawLine(
DADisplay,
app->pixmap,
graphics_context,
button_x,
button_y,
button_x + width - 1,
button_y
);
XDrawLine(
DADisplay,
app->pixmap,
graphics_context,
button_x,
button_y,
button_x,
button_y + height - 1
);
// bevel: bottom & right
XSetForeground(DADisplay, graphics_context, bot_right);
XDrawLine(
DADisplay,
app->pixmap,
graphics_context,
button_x,
button_y + height - 1,
button_x + width - 1,
button_y + height - 1
);
XDrawLine(
DADisplay,
app->pixmap,
graphics_context,
button_x + width - 1,
button_y,
button_x + width - 1,
button_y + height - 1
);
if (font) {
const char *title = button_titles[i];
int title_length = (int)strlen(title);
int direction, font_ascent, font_descent;
XCharStruct overall;
int title_x, title_y;
XTextExtents(
font,
title,
title_length,
&direction,
&font_ascent,
&font_descent,
&overall
);
title_x = button_x + (width - (overall.rbearing - overall.lbearing)) / 2 - overall.lbearing;
title_y = button_y + (height + overall.ascent - overall.descent) / 2;
XSetForeground(DADisplay, graphics_context, color_button_title);
XDrawString(
DADisplay,
app->pixmap,
graphics_context,
title_x,
title_y,
title,
title_length
);
}
}
static void redraw(void) {
int i;
XSetForeground(DADisplay, graphics_context, color_background);
XFillRectangle(
DADisplay,
app->pixmap,
graphics_context,
0,
0,
DOCK_SIZE,
DOCK_SIZE
);
for (i = 0; i < NUM_BUTTONS; i++) {
draw_button(i, i == pressed_button);
}
DASetPixmap(app->pixmap);
}
static void spawn_detached(char const* argv[]) {
posix_spawn_file_actions_t file_actions;
posix_spawnattr_t attributes;
sigset_t sigdefault;
pid_t pid;
int return_code;
posix_spawn_file_actions_init(&file_actions);
posix_spawn_file_actions_addopen(
&file_actions, 0, "/dev/null", O_RDONLY, 0
);
sigemptyset(&sigdefault);
sigaddset(&sigdefault, SIGCHLD);
posix_spawnattr_init(&attributes);
posix_spawnattr_setsigdefault(&attributes, &sigdefault);
posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETSIGDEF);
return_code = posix_spawnp(
&pid, argv[0], &file_actions, &attributes, argv, environ
);
if (return_code != 0) {
DAWarning("can't spawn %s: %s", argv[0], strerror(return_code));
}
posix_spawn_file_actions_destroy(&file_actions);
posix_spawnattr_destroy(&attributes);
}
static void do_shutdown(void) {
DAWarning("do_shutdown()");
char const *argv[] = {
"sudo",
"/usr/sbin/poweroff",
NULL,
};
spawn_detached(argv);
}
# pragma mark - Button handlers
static void on_button_lock(void) {
DAWarning("on_button_lock");
char const *argv[] = {
"xscreensaver-command",
"-display", DisplayString(DADisplay),
"-lock",
NULL,
};
spawn_detached(argv);
}
static void on_button_sleep(void) {
DAWarning("on_button_sleep");
char const *argv[] = {
"sudo",
"/usr/sbin/pm-suspend",
NULL,
};
spawn_detached(argv);
}
static void on_button_shutdown(void) {
DAWarning("on_button_shutdown");
if (is_shutdown_armed) {
shutdown_disarm();
} else {
shutdown_arm();
}
}
static void (*const handlers[NUM_BUTTONS])(void) = {
on_button_lock, on_button_sleep, on_button_shutdown
};
#pragma mark - libdockapp handlers
static void on_dockapp_button_press(int button, int state, int x, int y) {
(void)state;
if (button != Button1) {
return;
}
pressed_button = hit_test(x, y);
redraw();
}
static void on_dockapp_button_release(int button, int state, int x, int y) {
int released_on;
(void)state;
if (button != Button1) {
return;
}
released_on = hit_test(x, y);
// Fire only if released over the same button that was pressed.
if (pressed_button >= 0 && pressed_button == released_on) {
handlers[pressed_button]();
}
pressed_button = NO_PRESSED_BUTTON;
redraw();
}
static void on_dockapp_mouse_leave(void) {
// Cancel a press if the pointer leaves the dockapp.
if (pressed_button != NO_PRESSED_BUTTON) {
pressed_button = NO_PRESSED_BUTTON;
redraw();
}
}
static void on_dockapp_destroy(void) {
if (font) {
XFreeFont((DADisplay), font);
}
if (graphics_context) {
XFreeGC(DADisplay, graphics_context);
}
if (app->pixmap) {
XFreePixmap(DADisplay, app->pixmap);
}
exit(0);
}
static void on_dockapp_timeout(void) {
DAWarning("on_dockapp_timeout(): %d", current_shutdown_deadline_ms());
if (!is_shutdown_armed) {
return;
}
if (current_shutdown_deadline_ms() <= 0) {
shutdown_disarm();
redraw();
do_shutdown();
return;
}
if (current_shutdown_deadline_seconds() != last_shutdown_seconds) {
last_shutdown_seconds = current_shutdown_deadline_seconds();
redraw();
}
}
#pragma mark - Main
int main(int argc, char **argv) {
XSizeHints *hints;
DASetExpectedVersion(20050716);
DACallbacks callbacks = {
.destroy = on_dockapp_destroy,
.buttonPress = on_dockapp_button_press,
.buttonRelease = on_dockapp_button_release,
.leave = on_dockapp_mouse_leave,
.timeout = on_dockapp_timeout,
};
app = calloc(1, sizeof(struct WMPWMan));
DAParseArguments(
argc,
argv,
NULL,
0,
"A Window Maker dockapp to help with power management",
VERSION
);
DAInitialize("", argv[0], DOCK_SIZE, DOCK_SIZE, argc, argv);
app->pixmap = DAMakePixmap();
graphics_context = XCreateGC(DADisplay, app->pixmap, 0, NULL);
color_background = alloc_color("#8f909f");
color_button = alloc_color("#4b5263");
color_button_pressed = alloc_color("#363b47");
color_light = alloc_color("white");
color_dark = alloc_color("black");
color_button_title = alloc_color("white");
color_button_shutdown_background = alloc_color("#dc3545");
DASetCallbacks(&callbacks);
font = load_font();
if (font) {
XSetFont(DADisplay, graphics_context, font->fid);
}
redraw();
hints = XAllocSizeHints();
if (hints) {
hints->flags |= PMinSize | PMaxSize;
hints->min_width = DOCK_SIZE;
hints->max_width = DOCK_SIZE;
hints->min_height = DOCK_SIZE;
hints->max_height = DOCK_SIZE;
XSetWMNormalHints(DADisplay, DAWindow, hints);
XFree(hints);
}
signal(SIGCHLD, SIG_IGN);
DAShow();
// Omitting this causes the event loop to spin? :dunno:
DASetTimeout(-1);
DAEventLoop();
return 0;
}