You've already forked ziomekos
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:
1
packages/wmpwman/.gitignore
vendored
Normal file
1
packages/wmpwman/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/wmpwman
|
||||
21
packages/wmpwman/Makefile
Normal file
21
packages/wmpwman/Makefile
Normal 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
|
||||
35
packages/wmpwman/README.md
Normal file
35
packages/wmpwman/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# wmpwman
|
||||
|
||||
A Window Maker dockapp to help with power management.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
BIN
packages/wmpwman/screenshots/wmpwman.png
Normal file
BIN
packages/wmpwman/screenshots/wmpwman.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
508
packages/wmpwman/src/wmpwman.c
Normal file
508
packages/wmpwman/src/wmpwman.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user