Add modem/networks DWM plugins, Dual-SIM & PIN/PUK support, built-in dmenu views, and game UX fixes

This commit is contained in:
Luxferre
2026-10-24 18:59:48 +03:00
parent 668a4da11d
commit e549aaf413
63 changed files with 4781 additions and 426 deletions
+355 -24
View File
@@ -699,21 +699,46 @@ spawn_detached_sh(const char *cmd)
}
static void
trigger_plugin_open(const char *plugin_name)
trigger_plugin_open(const char *plugin_spec)
{
if (!plugin_spec || !plugin_spec[0])
return;
char plugin_name[64];
const char *arg = NULL;
const char *colon = strchr(plugin_spec, ':');
if (colon) {
size_t nlen = (size_t)(colon - plugin_spec);
if (nlen >= sizeof(plugin_name))
nlen = sizeof(plugin_name) - 1;
memcpy(plugin_name, plugin_spec, nlen);
plugin_name[nlen] = '\0';
arg = colon + 1;
} else {
snprintf(plugin_name, sizeof(plugin_name), "%s", plugin_spec);
}
char fifo_path[128];
snprintf(fifo_path, sizeof(fifo_path), "/tmp/dwm_plugin_%s.fifo", plugin_name);
int fd = open(fifo_path, O_WRONLY | O_NONBLOCK | O_CLOEXEC);
if (fd >= 0) {
const char *msg = (strcmp(plugin_name, "xdrawer") == 0) ? "TOGGLE\n" : "OPEN\n";
char msg[128];
if (strcmp(plugin_name, "xdrawer") == 0)
snprintf(msg, sizeof(msg), "TOGGLE\n");
else if (arg && arg[0])
snprintf(msg, sizeof(msg), "OPEN %s\n", arg);
else
snprintf(msg, sizeof(msg), "OPEN\n");
if (write(fd, msg, strlen(msg)) > 0) {
close(fd);
return;
}
close(fd);
}
char cmd[128];
snprintf(cmd, sizeof(cmd), "dwm-plugin %s", plugin_name);
char cmd[160];
if (arg && arg[0])
snprintf(cmd, sizeof(cmd), "dwm-plugin %s %s", plugin_name, arg);
else
snprintf(cmd, sizeof(cmd), "dwm-plugin %s", plugin_name);
spawn_detached_sh(cmd);
}
@@ -841,9 +866,75 @@ handle_builtin_toggle(const char *toggle_name)
close(fd);
}
} else if (strcmp(toggle_name, "bluetooth") == 0) {
int rc = system("doas sxmo_bluetoothtoggle.sh >/dev/null 2>&1");
int rc = system("SXMO_OS=alpine doas -n sxmo_bluetoothtoggle.sh >/dev/null 2>&1");
(void)rc;
spawn_detached_sh("sxmo_hook_statusbar.sh bluetooth");
} else if (strcmp(toggle_name, "ring") == 0) {
const char *home = getenv("HOME");
if (!home || !home[0])
home = "/home/user";
char dir[256], path[256];
snprintf(dir, sizeof(dir), "%s/.config/sxmo", home);
snprintf(path, sizeof(path), "%s/.noring", dir);
if (access(path, F_OK) == 0) {
unlink(path);
} else {
mkdir(dir, 0755);
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
if (fd >= 0)
close(fd);
}
spawn_detached_sh("sxmo_hook_statusbar.sh audio");
} else if (strcmp(toggle_name, "vibrate") == 0) {
const char *home = getenv("HOME");
if (!home || !home[0])
home = "/home/user";
char dir[256], path[256];
snprintf(dir, sizeof(dir), "%s/.config/sxmo", home);
snprintf(path, sizeof(path), "%s/.novibrate", dir);
if (access(path, F_OK) == 0) {
unlink(path);
} else {
mkdir(dir, 0755);
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
if (fd >= 0)
close(fd);
}
spawn_detached_sh("sxmo_hook_statusbar.sh audio");
} else if (strcmp(toggle_name, "dnd") == 0) {
const char *rdir = getenv("XDG_RUNTIME_DIR");
char path[256];
snprintf(path, sizeof(path), "%s/sxmo.busy", (rdir && rdir[0]) ? rdir : "/run/user/10000");
if (access(path, F_OK) == 0) {
unlink(path);
} else {
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
if (fd >= 0)
close(fd);
}
spawn_detached_sh("sxmo_hook_statusbar.sh audio");
} else if (strcmp(toggle_name, "notif_clear_all") == 0) {
const char *ndir = getenv("SXMO_NOTIFDIR");
char default_dir[256];
if (!ndir || !ndir[0]) {
const char *home = getenv("HOME");
snprintf(default_dir, sizeof(default_dir), "%s/.local/share/sxmo/notifications",
(home && home[0]) ? home : "/home/user");
ndir = default_dir;
}
DIR *d = opendir(ndir);
if (d) {
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
if (ent->d_name[0] == '.')
continue;
char fpath[512];
snprintf(fpath, sizeof(fpath), "%s/%s", ndir, ent->d_name);
unlink(fpath);
}
closedir(d);
}
spawn_detached_sh("sxmo_hook_statusbar.sh notifs");
}
}
@@ -926,6 +1017,27 @@ activate_selection(unsigned int ev_state)
trigger_plugin_open(act + 8);
return;
}
if (strncmp(act, "@notif_open:", 12) == 0) {
const char *fpath = act + 12;
char action_cmd[512] = "";
FILE *fp = fopen(fpath, "r");
if (fp) {
char line1[256];
if (fgets(line1, sizeof(line1), fp) && fgets(action_cmd, sizeof(action_cmd), fp)) {
char *nl = strpbrk(action_cmd, "\r\n");
if (nl)
*nl = '\0';
}
fclose(fp);
}
unlink(fpath);
spawn_detached_sh("sxmo_hook_statusbar.sh notifs");
hide_menu();
menu_mode = MODE_NONE;
if (action_cmd[0] && strcmp(action_cmd, "none") != 0)
spawn_detached_sh(action_cmd);
return;
}
if (strncmp(act, "@toggle:", 8) == 0) {
handle_builtin_toggle(act + 8);
char cur_menu[64];
@@ -1628,6 +1740,135 @@ load_cache_or_hook(const char *cache_name, const char *hook_cmd)
fclose(fp);
}
static void
populate_notifications_menu(void)
{
const char *ndir = getenv("SXMO_NOTIFDIR");
char default_dir[256];
if (!ndir || !ndir[0]) {
const char *home = getenv("HOME");
snprintf(default_dir, sizeof(default_dir), "%s/.local/share/sxmo/notifications",
(home && home[0]) ? home : "/home/user");
ndir = default_dir;
}
DIR *d = opendir(ndir);
if (!d)
return;
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
if (ent->d_name[0] == '.')
continue;
char fpath[512];
snprintf(fpath, sizeof(fpath), "%s/%s", ndir, ent->d_name);
struct stat st;
if (stat(fpath, &st) != 0 || !S_ISREG(st.st_mode))
continue;
FILE *fp = fopen(fpath, "r");
if (!fp)
continue;
char l1[256] = "", l2[256] = "", l3[512] = "";
(void)fgets(l1, sizeof(l1), fp);
(void)fgets(l2, sizeof(l2), fp);
if (fgets(l3, sizeof(l3), fp)) {
char *nl = strpbrk(l3, "\r\n");
if (nl)
*nl = '\0';
}
fclose(fp);
if (!l3[0])
snprintf(l3, sizeof(l3), "%s", ent->d_name);
char label[560], act[560];
snprintf(label, sizeof(label), "%s %s", ICON_BEL, l3);
snprintf(act, sizeof(act), "@notif_open:%s", fpath);
add_menu_item(label, act, 0);
}
closedir(d);
}
static int
cmp_cstr_ptr(const void *a, const void *b)
{
const char *sa = *(const char *const *)a;
const char *sb = *(const char *const *)b;
return strcmp(sa, sb);
}
static void
scan_zoneinfo_recursive(const char *base_dir, const char *rel_dir,
char ***list_io, size_t *count_io, size_t *cap_io)
{
char full_dir[512];
if (rel_dir && rel_dir[0])
snprintf(full_dir, sizeof(full_dir), "%s/%s", base_dir, rel_dir);
else
snprintf(full_dir, sizeof(full_dir), "%s", base_dir);
DIR *d = opendir(full_dir);
if (!d)
return;
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
const char *nm = ent->d_name;
if (nm[0] == '.' || strcmp(nm, "posix") == 0 || strcmp(nm, "right") == 0 ||
strcmp(nm, "zone.tab") == 0 || strcmp(nm, "zone1970.tab") == 0 ||
strcmp(nm, "iso3166.tab") == 0 || strcmp(nm, "leapseconds") == 0 ||
strcmp(nm, "tzdata.zi") == 0 || strcmp(nm, "localtime") == 0 ||
strcmp(nm, "posixrules") == 0)
continue;
char rel_path[512], child_full[512];
if (rel_dir && rel_dir[0])
snprintf(rel_path, sizeof(rel_path), "%s/%s", rel_dir, nm);
else
snprintf(rel_path, sizeof(rel_path), "%s", nm);
snprintf(child_full, sizeof(child_full), "%s/%s", base_dir, rel_path);
struct stat st;
if (stat(child_full, &st) != 0)
continue;
if (S_ISDIR(st.st_mode)) {
scan_zoneinfo_recursive(base_dir, rel_path, list_io, count_io, cap_io);
} else if (S_ISREG(st.st_mode)) {
if (*count_io >= *cap_io) {
size_t ncap = (*cap_io == 0) ? 256 : (*cap_io * 2);
char **nlist = realloc(*list_io, ncap * sizeof(char *));
if (!nlist)
break;
*list_io = nlist;
*cap_io = ncap;
}
(*list_io)[*count_io] = strdup(rel_path);
if ((*list_io)[*count_io])
(*count_io)++;
}
}
closedir(d);
}
static void
populate_timezone_menu(void)
{
char **tz_list = NULL;
size_t tz_count = 0, tz_cap = 0;
scan_zoneinfo_recursive("/usr/share/zoneinfo", "", &tz_list, &tz_count, &tz_cap);
if (tz_count > 1)
qsort(tz_list, tz_count, sizeof(char *), cmp_cstr_ptr);
for (size_t i = 0; i < tz_count; i++) {
char cmd[512];
snprintf(cmd, sizeof(cmd),
"doas -n setup-timezone -z '%s' >/dev/null 2>&1; sxmo_hook_statusbar.sh time",
tz_list[i]);
add_menu_item(tz_list[i], cmd, 0);
free(tz_list[i]);
}
free(tz_list);
}
static void
open_builtin_menu(const char *menu_name, int keep_idx)
{
@@ -1667,7 +1908,7 @@ open_builtin_menu(const char *menu_name, int keep_idx)
int nnotifs = count_notifications();
if (nnotifs > 0) {
snprintf(label, sizeof(label), "%s Notifications (%d)", ICON_BEL, nnotifs);
add_menu_item(label, "sxmo_notifs.sh menu", 0);
add_menu_item(label, "@menu:notifications", 1);
}
const char *sh = getenv("SHELL");
if (!sh || !sh[0])
@@ -1702,7 +1943,7 @@ open_builtin_menu(const char *menu_name, int keep_idx)
if (bt_is_unblocked_fast()) {
snprintf(label, sizeof(label), "%s Bluetooth", ICON_BTH);
add_menu_item(label, "sxmo_bluetoothmenu.sh", 0);
add_menu_item(label, "@plugin:networks:bluetooth", 0);
}
snprintf(label, sizeof(label), "%s Camera", ICON_CAM);
@@ -1720,16 +1961,16 @@ open_builtin_menu(const char *menu_name, int keep_idx)
}
snprintf(label, sizeof(label), "%s Networks", ICON_NET);
add_menu_item(label, "sxmo_networks.sh", 0);
add_menu_item(label, "@plugin:networks", 0);
snprintf(label, sizeof(label), "%s Audio", ICON_MUS);
add_menu_item(label, "sxmo_audio.sh", 0);
add_menu_item(label, "@menu:audio", 1);
snprintf(label, sizeof(label), "%s Modem", ICON_PHN);
add_menu_item(label, "@menu:modem", 1);
add_menu_item(label, "@plugin:modem", 0);
snprintf(label, sizeof(label), "%s Windows", ICON_WIN);
add_menu_item(label, "sxmo_wmmenu.sh", 0);
add_menu_item(label, "@menu:wm", 1);
snprintf(label, sizeof(label), "%s Config", ICON_CFG);
add_menu_item(label, "@menu:config", 1);
@@ -1767,12 +2008,26 @@ open_builtin_menu(const char *menu_name, int keep_idx)
} else if (strcmp(menu_name, "games") == 0) {
reset_session_defaults(1, "Games");
append_active_call_entries();
load_cache_or_hook("sxmo_games.cache", "sxmo_hook_games.sh");
if (access("/usr/bin/snake", X_OK) == 0) {
snprintf(label, sizeof(label), "%s Snake", ICON_GAM);
add_menu_item(label, "snake", 0);
}
if (access("/usr/bin/tetris", X_OK) == 0) {
snprintf(label, sizeof(label), "%s Tetris", ICON_GAM);
add_menu_item(label, "tetris", 0);
}
const char *rdir = getenv("XDG_RUNTIME_DIR");
char gcache[256];
snprintf(gcache, sizeof(gcache), "%s/sxmo_games.cache", (rdir && rdir[0]) ? rdir : "/run/user/10000");
if (access(gcache, R_OK) == 0 || access("/tmp/sxmo_games.cache", R_OK) == 0)
load_cache_or_hook("sxmo_games.cache", "sxmo_hook_games.sh");
else
spawn_detached_sh("sxmo_hook_games.sh >/dev/null 2>&1");
snprintf(label, sizeof(label), "%s System Menu", ICON_MNU);
add_menu_item(label, "@menu:sys", 1);
snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS);
add_menu_item(label, "@close", 0);
} else if (strcmp(menu_name, "modem") == 0) {
} else if (strcmp(menu_name, "scripts") == 0) {
reset_session_defaults(1, "Scripts");
append_active_call_entries();
load_cache_or_hook("sxmo_scripts.cache", "sxmo_hook_scripts.sh");
@@ -1783,16 +2038,87 @@ open_builtin_menu(const char *menu_name, int keep_idx)
} else if (strcmp(menu_name, "modem") == 0) {
reset_session_defaults(1, "Modem");
append_active_call_entries();
snprintf(label, sizeof(label), "%s Modem PIN", ICON_PLK);
add_menu_item(label, "sxmo_unlocksim.sh", 0);
snprintf(label, sizeof(label), "%s Modem Info", ICON_INF);
add_menu_item(label, "sxmo_modeminfo.sh", 0);
snprintf(label, sizeof(label), "%s Modem Log", ICON_PHL);
add_menu_item(label, "sxmo_modemlog.sh", 0);
snprintf(label, sizeof(label), "%s SIM & Dual-SIM Manager", ICON_PHN);
add_menu_item(label, "@plugin:modem", 0);
snprintf(label, sizeof(label), "%s Unlock SIM (PIN / PUK)", ICON_PLK);
add_menu_item(label, "@plugin:modem:unlock", 0);
snprintf(label, sizeof(label), "%s Modem & Radio Status", ICON_INF);
add_menu_item(label, "@plugin:modem:info", 0);
snprintf(label, sizeof(label), "%s Send USSD Code", ICON_PHN);
add_menu_item(label, "@plugin:modem:ussd", 0);
snprintf(label, sizeof(label), "%s Modem Event Log", ICON_PHL);
add_menu_item(label, "@plugin:modem:log", 0);
snprintf(label, sizeof(label), "%s System Menu", ICON_MNU);
add_menu_item(label, "@menu:sys", 1);
snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS);
add_menu_item(label, "@close", 0);
} else if (strcmp(menu_name, "audio") == 0) {
reset_session_defaults(1, "Audio");
append_active_call_entries();
const char *home = getenv("HOME");
if (!home || !home[0])
home = "/home/user";
const char *rdir = getenv("XDG_RUNTIME_DIR");
if (!rdir || !rdir[0])
rdir = "/run/user/10000";
char pbuf[256];
snprintf(label, sizeof(label), "%s Volume & Controls (XDrawer)", ICON_MUS);
add_menu_item(label, "@plugin:xdrawer", 0);
snprintf(pbuf, sizeof(pbuf), "%s/.config/sxmo/.noring", home);
snprintf(label, sizeof(label), "%s Ring %s", ICON_BEL, (access(pbuf, F_OK) == 0) ? ICON_TOF : ICON_TON);
add_menu_item(label, "@toggle:ring", 1);
snprintf(pbuf, sizeof(pbuf), "%s/.config/sxmo/.novibrate", home);
snprintf(label, sizeof(label), "%s Vibrate %s", ICON_BEL, (access(pbuf, F_OK) == 0) ? ICON_TOF : ICON_TON);
add_menu_item(label, "@toggle:vibrate", 1);
snprintf(pbuf, sizeof(pbuf), "%s/sxmo.busy", rdir);
snprintf(label, sizeof(label), "%s Do Not Disturb %s", ICON_ZZZ, (access(pbuf, F_OK) == 0) ? ICON_TON : ICON_TOF);
add_menu_item(label, "@toggle:dnd", 1);
snprintf(label, sizeof(label), "%s Route to Speaker", ICON_MUS);
add_menu_item(label, "amixer -c 0 cset name='SPK DAC Switch' on >/dev/null 2>&1; sxmo_hook_statusbar.sh audio", 1);
snprintf(label, sizeof(label), "%s Route to Earpiece", ICON_PHN);
add_menu_item(label, "amixer -c 0 cset name='SPK DAC Switch' off >/dev/null 2>&1; sxmo_hook_statusbar.sh audio", 1);
snprintf(label, sizeof(label), "%s System Menu", ICON_MNU);
add_menu_item(label, "@menu:sys", 1);
snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS);
add_menu_item(label, "@close", 0);
} else if (strcmp(menu_name, "notifications") == 0) {
reset_session_defaults(1, "Notifs");
populate_notifications_menu();
snprintf(label, sizeof(label), "%s Clear All Notifications", ICON_CLS);
add_menu_item(label, "@toggle:notif_clear_all", 1);
snprintf(label, sizeof(label), "%s System Menu", ICON_MNU);
add_menu_item(label, "@menu:sys", 1);
snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS);
add_menu_item(label, "@close", 0);
} else if (strcmp(menu_name, "wm") == 0) {
reset_session_defaults(1, "WM Menu");
for (int ws = 1; ws <= 4; ws++) {
snprintf(label, sizeof(label), "%d Move to WS %d", ws, ws);
snprintf(cmd, sizeof(cmd), "sxmo_wm.sh moveworkspace %d", ws);
add_menu_item(label, cmd, 1);
}
snprintf(label, sizeof(label), "%s Shift stack", ICON_RLD);
add_menu_item(label, "sxmo_wm.sh switchfocus", 1);
snprintf(label, sizeof(label), "%s Toggle Layout", ICON_GRD);
add_menu_item(label, "sxmo_wm.sh togglelayout", 1);
snprintf(label, sizeof(label), "%s Kill window", ICON_CLS);
add_menu_item(label, "sxmo_killwindow.sh", 0);
snprintf(label, sizeof(label), "%s System Menu", ICON_MNU);
add_menu_item(label, "@menu:sys", 1);
snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS);
add_menu_item(label, "@close", 0);
} else if (strcmp(menu_name, "timezone") == 0) {
reset_session_defaults(1, "Timezone");
snprintf(label, sizeof(label), "%s Back to Config", ICON_CFG);
add_menu_item(label, "@menu:config", 1);
populate_timezone_menu();
} else if (strcmp(menu_name, "config") == 0) {
reset_session_defaults(1, "Config");
append_active_call_entries();
@@ -1814,7 +2140,7 @@ open_builtin_menu(const char *menu_name, int keep_idx)
add_menu_item(label, "@toggle:bluetooth", 1);
snprintf(label, sizeof(label), "%s Change Timezone", ICON_CLK);
add_menu_item(label, "sxmo_timezonechange.sh", 0);
add_menu_item(label, "@menu:timezone", 1);
snprintf(pbuf, sizeof(pbuf), "%s/.cache/sxmo/sxmo.nosuspend", home);
snprintf(label, sizeof(label), "%s Auto-suspend %s", ICON_ZZZ, (access(pbuf, F_OK) == 0) ? ICON_TOF : ICON_TON);
@@ -1967,6 +2293,8 @@ close_other_active_plugins(void)
{ "xfiles.pid", "/tmp/dwm_plugin_files.fifo" },
{ "xcamera.pid", "/tmp/dwm_plugin_camera.fifo" },
{ "xthemes.pid", "/tmp/dwm_plugin_themes.fifo" },
{ "xmodem.pid", "/tmp/dwm_plugin_modem.fifo" },
{ "xnetworks.pid", "/tmp/dwm_plugin_networks.fifo" },
};
const char *rdir = getenv("XDG_RUNTIME_DIR");
const char *base = (rdir && rdir[0]) ? rdir : "/run/user/10000";
@@ -2855,10 +3183,13 @@ dmenu_run_main(int argc, char *argv[])
if (!strcmp(argv[i], "--preload")) {
preload = 1;
} else if (!strcmp(argv[i], "sys") || !strcmp(argv[i], "ctx") ||
!strcmp(argv[i], "applications") || !strcmp(argv[i], "scripts") ||
!strcmp(argv[i], "modem") || !strcmp(argv[i], "config") ||
!strcmp(argv[i], "power") || !strcmp(argv[i], "st") ||
!strcmp(argv[i], "nano") || !strcmp(argv[i], "sthotkeys")) {
!strcmp(argv[i], "applications") || !strcmp(argv[i], "games") ||
!strcmp(argv[i], "scripts") || !strcmp(argv[i], "modem") ||
!strcmp(argv[i], "audio") || !strcmp(argv[i], "notifications") ||
!strcmp(argv[i], "wm") || !strcmp(argv[i], "timezone") ||
!strcmp(argv[i], "config") || !strcmp(argv[i], "power") ||
!strcmp(argv[i], "st") || !strcmp(argv[i], "nano") ||
!strcmp(argv[i], "sthotkeys")) {
preload = 1;
initial_builtin = argv[i];
} else if (!strcmp(argv[i], "-v")) {