feat(dwm): add dynamic plugin system with xdrawer, dialer, and calllog plugins

This commit is contained in:
Luxferre
2026-10-21 11:11:45 +03:00
parent 7b7ae1dcc5
commit 0a02400505
28 changed files with 2589 additions and 22 deletions
+209 -6
View File
@@ -271,6 +271,8 @@ static void shiftview(const Arg *arg);
static void pushup(const Arg *arg);
static void pushdown(const Arg *arg);
static void readxresources();
static void load_plugins(void);
static void unload_plugins(void);
static pid_t getparentprocess(pid_t p);
static int isdescprocess(pid_t p, pid_t c);
@@ -698,6 +700,7 @@ cleanup(void)
drw_cur_free(drw, cursor[i]);
for (i = 0; i < LENGTH(colors); i++)
free(scheme[i]);
unload_plugins();
XDestroyWindow(dpy, wmcheckwin);
drw_free(drw);
XSync(dpy, False);
@@ -1532,6 +1535,191 @@ is_right_softkey(KeySym ks, unsigned int keycode)
return ks == XF86XK_Back || ks == XF86XK_KbdLcdMenu2 || keycode == 166;
}
#include <dirent.h>
#include <dlfcn.h>
#include "dwm_plugin.h"
#define MAX_DWM_PLUGINS 16
typedef struct {
void *handle;
const DwmPlugin *plugin;
} LoadedDwmPlugin;
static LoadedDwmPlugin loaded_plugins[MAX_DWM_PLUGINS];
static int num_loaded_plugins = 0;
static int
host_is_idle_desktop(void)
{
return (selmon && selmon->sel == NULL && !is_phone_locked()) ? 1 : 0;
}
static int
host_is_phone_locked(void)
{
return is_phone_locked();
}
static void
host_spawn_command(const char *const *argv)
{
if (!argv || !argv[0])
return;
Arg a = { .v = (const void *)argv };
XUngrabKeyboard(dpy, CurrentTime);
XFlush(dpy);
spawn(&a);
}
static void
host_spawn_plugin(const char *plugin_name, const char *arg)
{
if (!plugin_name || !plugin_name[0])
return;
const char *cmd[4];
cmd[0] = "dwm-plugin";
cmd[1] = plugin_name;
cmd[2] = (arg && arg[0]) ? arg : NULL;
cmd[3] = NULL;
Arg a = { .v = cmd };
XUngrabKeyboard(dpy, CurrentTime);
XFlush(dpy);
spawn(&a);
}
static DwmHostApi dwm_host_api = {
.api_version = DWM_PLUGIN_API_VERSION,
.dpy = NULL,
.root = 0,
.screen = 0,
.sw = 0,
.sh = 0,
.bh = 0,
.is_idle_desktop = host_is_idle_desktop,
.is_phone_locked = host_is_phone_locked,
.spawn_command = host_spawn_command,
.spawn_plugin = host_spawn_plugin,
};
static int
is_plugin_loaded(const char *name)
{
int i;
for (i = 0; i < num_loaded_plugins; i++) {
if (loaded_plugins[i].plugin && loaded_plugins[i].plugin->name &&
strcmp(loaded_plugins[i].plugin->name, name) == 0)
return 1;
}
return 0;
}
static void
load_plugins_from_dir(const char *dirpath)
{
DIR *dir;
struct dirent *ent;
char path[512];
if (!dirpath || !dirpath[0])
return;
dir = opendir(dirpath);
if (!dir)
return;
while ((ent = readdir(dir)) != NULL && num_loaded_plugins < MAX_DWM_PLUGINS) {
size_t len = strlen(ent->d_name);
if (len <= 3 || strcmp(ent->d_name + len - 3, ".so") != 0)
continue;
snprintf(path, sizeof(path), "%s/%s", dirpath, ent->d_name);
void *h = dlopen(path, RTLD_NOW | RTLD_LOCAL);
if (!h)
continue;
const DwmPlugin *plug = (const DwmPlugin *)dlsym(h, DWM_PLUGIN_ENTRY_SYMBOL);
if (!plug || plug->api_version != DWM_PLUGIN_API_VERSION ||
!plug->name || is_plugin_loaded(plug->name)) {
dlclose(h);
continue;
}
if (plug->init && plug->init(&dwm_host_api) != 0) {
dlclose(h);
continue;
}
loaded_plugins[num_loaded_plugins].handle = h;
loaded_plugins[num_loaded_plugins].plugin = plug;
num_loaded_plugins++;
}
closedir(dir);
}
static void
load_plugins(void)
{
dwm_host_api.dpy = dpy;
dwm_host_api.root = root;
dwm_host_api.screen = screen;
dwm_host_api.sw = sw;
dwm_host_api.sh = sh;
dwm_host_api.bh = bh;
const char *env_dir = getenv("DWM_PLUGIN_PATH");
if (env_dir && env_dir[0])
load_plugins_from_dir(env_dir);
load_plugins_from_dir("/home/user/.local/lib/dwm/plugins");
load_plugins_from_dir("/usr/lib/dwm/plugins");
}
static void
unload_plugins(void)
{
int i;
for (i = 0; i < num_loaded_plugins; i++) {
if (loaded_plugins[i].plugin && loaded_plugins[i].plugin->cleanup)
loaded_plugins[i].plugin->cleanup();
if (loaded_plugins[i].handle)
dlclose(loaded_plugins[i].handle);
}
num_loaded_plugins = 0;
}
static int
dispatch_plugin_softkey_chord(void)
{
int i;
for (i = 0; i < num_loaded_plugins; i++) {
if (loaded_plugins[i].plugin && loaded_plugins[i].plugin->on_softkey_chord) {
if (loaded_plugins[i].plugin->on_softkey_chord(&dwm_host_api))
return 1;
}
}
return 0;
}
static int
dispatch_plugin_idle_key(KeySym keysym, unsigned int keycode, unsigned int state)
{
int i;
for (i = 0; i < num_loaded_plugins; i++) {
if (loaded_plugins[i].plugin && loaded_plugins[i].plugin->on_idle_key) {
if (loaded_plugins[i].plugin->on_idle_key(&dwm_host_api, keysym, keycode, state))
return 1;
}
}
return 0;
}
static int
dispatch_plugin_global_key(KeySym keysym, unsigned int keycode, unsigned int state, int is_press)
{
int i;
for (i = 0; i < num_loaded_plugins; i++) {
if (loaded_plugins[i].plugin && loaded_plugins[i].plugin->on_global_key) {
if (loaded_plugins[i].plugin->on_global_key(&dwm_host_api, keysym, keycode, state, is_press))
return 1;
}
}
return 0;
}
static pid_t
spawn_pgid(const Arg *arg)
{
@@ -1547,9 +1735,8 @@ spawn_pgid(const Arg *arg)
}
static void
trigger_xdrawer_chord(long long now)
trigger_softkey_chord(long long now)
{
const Arg xdrawercmd = { .v = (const char*[]){ "xdrawer", NULL } };
if (last_menu_pgid > 0) {
kill(-last_menu_pgid, SIGKILL);
last_menu_pgid = 0;
@@ -1563,7 +1750,10 @@ trigger_xdrawer_chord(long long now)
write_softkey_suppress(now + SOFTKEY_SUPPRESS_MS);
XUngrabKeyboard(dpy, CurrentTime);
XFlush(dpy);
spawn(&xdrawercmd);
if (!dispatch_plugin_softkey_chord()) {
const Arg xdrawercmd = { .v = (const char*[]){ "xdrawer", NULL } };
spawn(&xdrawercmd);
}
}
static void
@@ -1631,7 +1821,7 @@ keypress(XEvent *e)
if (last_softkey_mask != 0 && (now - last_softkey_ms) <= SOFTKEY_CANCEL_WINDOW_MS)
combined |= last_softkey_mask;
if (combined == (SOFTKEY_LEFT | SOFTKEY_RIGHT)) {
trigger_xdrawer_chord(now);
trigger_softkey_chord(now);
} else {
softkey_pending = which;
last_softkey_mask = which;
@@ -1645,6 +1835,16 @@ keypress(XEvent *e)
return;
}
if (!is_phone_locked() &&
dispatch_plugin_global_key(keysym, ev->keycode, CLEANMASK(ev->state), 1)) {
return;
}
if (!selmon->sel && !is_phone_locked()) {
if (dispatch_plugin_idle_key(keysym, ev->keycode, CLEANMASK(ev->state)))
return;
}
if (!selmon->sel && CLEANMASK(ev->state) == 0) {
if (keysym == XK_Return) {
const Arg entercmd = { .v = (const char*[]){ "sxmo_appmenu.sh", NULL } };
@@ -2138,7 +2338,7 @@ run(void)
int hw_down = query_softkeys_down();
softkey_pending |= hw_down;
if (softkey_pending == (SOFTKEY_LEFT | SOFTKEY_RIGHT)) {
trigger_xdrawer_chord(now);
trigger_softkey_chord(now);
continue;
}
long long rem = softkey_deadline_ms - now;
@@ -2398,6 +2598,7 @@ setup(void)
lidfd = open("/dev/input/by-path/platform-gpio-keys-event", O_RDONLY | O_NONBLOCK | O_CLOEXEC);
if (lidfd < 0)
lidfd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK | O_CLOEXEC);
load_plugins();
grabkeys();
focus(NULL);
}
@@ -2648,6 +2849,8 @@ unmapnotify(XEvent *e)
setclientstate(c, WithdrawnState);
else
unmanage(c, 0);
} else if (selmon && !selmon->sel) {
focus(NULL);
}
}
@@ -2658,7 +2861,7 @@ updatebars(void)
XSetWindowAttributes wa = {
.override_redirect = True,
.background_pixmap = ParentRelative,
.event_mask = ButtonPressMask|ExposureMask
.event_mask = ButtonPressMask|ExposureMask|KeyPressMask|KeyReleaseMask
};
XClassHint ch = {"dwm", "dwm"};
for (m = mons; m; m = m->next) {