/* See LICENSE file for copyright and license details. */ #include #ifndef _EVDEVK #define _EVDEVK(_v) (0x10081000 + (_v)) #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef XINERAMA #include #endif #include #include #include "drw.h" #include "util.h" #include "../sxmo-dwm/dwm_plugin.h" #define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) #define LENGTH(X) (sizeof X / sizeof X[0]) #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) #define NUMBERSMAXDIGITS 100 #define NUMBERSBUFSIZE (NUMBERSMAXDIGITS * 2) + 1 #define DMENU_PID_PATH "/tmp/dwm_plugin_dmenu.pid" #define DMENU_LOCK_PATH "/tmp/dwm_plugin_dmenu.lock" #define DMENU_FIFO_PATH "/tmp/dwm_plugin_dmenu.fifo" #define DMENU_SOCK_PATH "/tmp/dwm_plugin_dmenu.sock" #define DMENU_ACTIVE_PATH "/tmp/dwm_plugin_dmenu.active" enum { SchemeNorm, SchemeSel, SchemeOut, SchemeNormHighlight, SchemeSelHighlight, SchemeLast }; enum { MODE_NONE = 0, MODE_CLIENT = 1, MODE_BUILTIN = 2 }; struct item { char *text; char *action; int loop; struct item *left, *right; int out; }; static char numbers[NUMBERSBUFSIZE] = ""; static char text[BUFSIZ] = ""; static char *embed; static int bh, item_bh, mw, mh; static int win_x = 0, win_y = 24, win_w = 0, win_h = 0; static int default_lines = 10; static int avail_h_cached = 296; static int inputw = 0, promptw; static int lrpad; static size_t cursor; static struct item *items = NULL; static size_t items_count = 0; static size_t items_cap = 0; static struct item *matches, *matchend; static struct item *prev, *curr, *next, *sel, *nav; static int mon = -1, screen; static int use_text_input = 0; /* selection/output mode: with -t, Shift inverts item vs typed text */ static int engage_ime = 1; /* engage fcitx (XIM) for text entry by default so the T9 input method activates on dmenu prompts */ static int initialindex = 0; static int managed = 0; static int curpos = 0, oldcurlen = 0; static int daemon_mode = 0; static int menu_active = 0; static int menu_mode = MODE_NONE; static int active_client_fd = -1; static char current_builtin_menu[64] = ""; static char *session_prompt = NULL; static int soft_pending = 0; static long long soft_deadline = 0; static long long start_ignore = 0; static int initial_released = 1; static int init_soft_mask = 0; static long long init_soft_ms = 0; static Atom clip, utf8; static Display *dpy; static Window root, parentwin, win; static Window prevfocuswin = None; static XIC xic; static Drw *drw; static Clr *scheme[SchemeLast]; static char *colors[SchemeLast][2]; static const char *fonts[3] = { NULL, NULL, NULL }; static char xres_font_main[256] = ""; static char xres_font_icon[256] = ""; static time_t last_xres_mtime = 0; static long last_xres_nsec = 0; #include "config.h" static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; static char *(*fstrstr)(const char *, const char *) = strstr; /* Sxmo Nerd Font Icons */ static const char *ICON_ARD = "\xe2\x86\x93"; static const char *ICON_ARU = "\xe2\x86\x91"; static const char *ICON_BEL = "\xef\x83\xb3"; static const char *ICON_BTH = "\xef\x8a\x93"; static const char *ICON_CAM = "\xef\x80\xb0"; static const char *ICON_CFG = "\xee\x98\x95"; static const char *ICON_CLK = "\xf3\xb0\xa5\x94"; static const char *ICON_CLS = "\xf3\xb0\x85\x96"; static const char *ICON_DIR = "\xef\x81\xbc"; static const char *ICON_FLL = "\xf3\xb0\x89\x84"; static const char *ICON_GRD = "\xf3\xb0\x80\xbb"; static const char *ICON_INF = "\xef\x84\xa9"; static const char *ICON_LCK = "\xef\x80\xa3"; static const char *ICON_MNU = "\xef\x83\x89"; static const char *ICON_MSG = "\xf3\xb0\x8d\xa6"; static const char *ICON_MUS = "\xf3\xb0\x8e\x88"; static const char *ICON_NET = "\xf3\xb0\x80\x82"; static const char *ICON_OUT = "\xf3\xb0\x8d\x83"; static const char *ICON_PHL = "\xf3\xb0\x8f\xb9"; static const char *ICON_PHN = "\xf3\xb0\x8f\xb2"; static const char *ICON_PLK = "\xf3\xb0\x8f\xb8"; static const char *ICON_PWR = "\xe2\x8f\xbb"; static const char *ICON_RLD = "\xef\x80\xa1"; static const char *ICON_TOF = "\xef\x88\x84"; static const char *ICON_TON = "\xef\x88\x85"; static const char *ICON_TRM = "\xee\x9e\xa2"; static const char *ICON_USR = "\xf3\xb0\x80\x84"; static const char *ICON_WIN = "\xef\x8b\x90"; static const char *ICON_ZZZ = "\xef\x86\x86"; static const char *ICON_SAV = "\xf3\xb0\x86\x93"; static const char *ICON_PST = "\xef\x83\xaa"; static const char *ICON_ITM = "\xef\x83\x9a"; static const char *ICON_CPY = "\xef\x83\x85"; static const char *ICON_ZMI = "\xf3\xb0\x9b\xad"; static const char *ICON_ZMO = "\xf3\xb0\x9b\xac"; static const char *ICON_A2X = "\xe2\x87\x84"; static const char *ICON_KBD = "\xef\x84\x9c"; static const char *ICON_THM = "\xf3\xb0\x8f\x98"; static const char *ICON_GAM = "\xf3\xb0\x8a\xb4"; static void drawmenu(void); static void show_menu(void); static void hide_menu(void); static void open_builtin_menu(const char *menu_name, int keep_idx); static void load_dmenu_xresources(void); static void reload_dmenu_theme(void); static char * cistrstr(const char *s, const char *sub) { size_t len; for (len = strlen(sub); *s; s++) if (!strncasecmp(s, sub, len)) return (char *)s; return NULL; } static void appenditem(struct item *item, struct item **list, struct item **last) { if (*last) (*last)->right = item; else *list = item; item->left = *last; item->right = NULL; *last = item; } static void free_items(void) { if (items) { for (size_t i = 0; i < items_count; i++) { free(items[i].text); free(items[i].action); } free(items); } items = NULL; items_count = 0; items_cap = 0; matches = matchend = prev = curr = next = sel = NULL; } static void add_menu_item(const char *label, const char *action, int loop) { if (items_count + 2 > items_cap) { size_t new_cap = items_cap ? items_cap * 2 : 64; struct item *nitems = realloc(items, new_cap * sizeof(*items)); if (!nitems) return; items = nitems; items_cap = new_cap; } items[items_count].text = strdup(label ? label : ""); items[items_count].action = action ? strdup(action) : NULL; items[items_count].loop = loop; items[items_count].out = 0; items[items_count].left = NULL; items[items_count].right = NULL; items_count++; items[items_count].text = NULL; items[items_count].action = NULL; items[items_count].loop = 0; items[items_count].out = 0; items[items_count].left = NULL; items[items_count].right = NULL; } static void reset_session_defaults(int case_insensitive, const char *new_prompt) { free_items(); topbar = 1; centered = 0; use_text_input = 0; /* reset selection/output mode; fcitx routing (engage_ime) stays on */ initialindex = 0; fstrncmp = case_insensitive ? strncasecmp : strncmp; fstrstr = case_insensitive ? cistrstr : strstr; free(session_prompt); session_prompt = new_prompt ? strdup(new_prompt) : NULL; prompt = session_prompt; text[0] = '\0'; cursor = 0; curpos = 0; oldcurlen = 0; inputw = 0; } static void calcoffsets(void) { int i, n; if (lines > 0) n = lines * bh; else n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); for (i = 0, next = curr; next; next = next->right) if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) break; for (i = 0, prev = curr; prev && prev->left; prev = prev->left) if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) break; } static int ignore_xerror(Display *d, XErrorEvent *ee) { (void)d; (void)ee; return 0; } static void mark_menu_active(int active) { if (active) { int fd = open(DMENU_ACTIVE_PATH, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (fd >= 0) { (void)write(fd, "1\n", 2); close(fd); } } else { unlink(DMENU_ACTIVE_PATH); } } static void cleanup(void) { size_t i; mark_menu_active(0); if (xic) { XUnsetICFocus(xic); XDestroyIC(xic); xic = NULL; } XUngrabKey(dpy, AnyKey, AnyModifier, root); XUngrabKeyboard(dpy, CurrentTime); XSetErrorHandler(ignore_xerror); if (prevfocuswin && prevfocuswin != win && prevfocuswin != PointerRoot) XSetInputFocus(dpy, prevfocuswin, RevertToParent, CurrentTime); free_items(); free(session_prompt); session_prompt = NULL; for (i = 0; i < SchemeLast; i++) free(scheme[i]); drw_free(drw); XSync(dpy, False); XCloseDisplay(dpy); } static void drawhighlights(struct item *item, int x, int y, int maxw) { char restorechar, tokens[sizeof text], *highlight, *token; int indentx, highlightlen; drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : SchemeNormHighlight]); strcpy(tokens, text); for (token = strtok(tokens, " "); token; token = strtok(NULL, " ")) { highlight = fstrstr(item->text, token); while (highlight) { highlightlen = highlight - item->text; restorechar = *highlight; item->text[highlightlen] = '\0'; indentx = TEXTW(item->text); item->text[highlightlen] = restorechar; restorechar = highlight[strlen(token)]; highlight[strlen(token)] = '\0'; drw_text( drw, x + indentx - (lrpad / 2) - 1, y, MIN(maxw - indentx, TEXTW(highlight) - lrpad), (lines > 0 ? item_bh : bh), 0, highlight, 0 ); highlight[strlen(token)] = restorechar; if (strlen(highlight) - strlen(token) < strlen(token)) break; highlight = fstrstr(highlight + strlen(token), token); } } } static int drawitem(struct item *item, int x, int y, int w) { if (item == sel) drw_setscheme(drw, scheme[SchemeSel]); else if (item->out) drw_setscheme(drw, scheme[SchemeOut]); else drw_setscheme(drw, scheme[SchemeNorm]); int r = drw_text(drw, x, y, w, (lines > 0 ? item_bh : bh), lrpad / 2, item->text, 0); drawhighlights(item, x, y, w); return r; } static void recalculatenumbers(void) { unsigned int numer = 0, denom = 0; struct item *item; if (matchend) { numer++; for (item = matchend; item && item->left; item = item->left) numer++; } for (item = items; item && item->text; item++) denom++; if (numer == denom) snprintf(numbers, NUMBERSBUFSIZE, "%d", numer); else snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); } static void drawmenu(void) { struct item *item; int x = 0, y = 0, fh = drw->fonts->h, w; int curlen, rcurlen; drw_setscheme(drw, scheme[SchemeNorm]); drw_rect(drw, 0, 0, mw, mh, 1, 1); drw_setscheme(drw, scheme[SchemeOut]); drw_rect(drw, 0, 0, mw, bh, 1, 1); if (prompt && *prompt) { if (colorprompt) drw_setscheme(drw, scheme[SchemeSel]); x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); } recalculatenumbers(); w = (lines > 0 || !matches) ? mw - x : inputw; w -= lrpad / 2; x += lrpad / 2; rcurlen = drw_fontset_getwidth(drw, text + cursor); curlen = drw_fontset_getwidth(drw, text) - rcurlen; curpos += curlen - oldcurlen; curpos = MIN(w - TEXTW(numbers), MAX(0, curpos)); curpos = MAX(curpos, w - rcurlen - TEXTW(numbers)); curpos = MIN(curpos, curlen); oldcurlen = curlen; drw_setscheme(drw, scheme[SchemeOut]); drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR); drw_text_align(drw, x + curpos, 0, w - curpos - TEXTW(numbers), bh, text + cursor, strlen(text) - cursor, AlignL); drw_rect(drw, x + curpos - 1, 2 + (bh - fh) / 2, 2, fh - 4, 1, 0); if (lines > 0) { x -= lrpad / 2; y = bh; for (item = curr; item != next; item = item->right) { drawitem(item, 0, y, mw); y += item_bh; } } else if (matches) { x += inputw; w = TEXTW("<"); if (curr->left) { drw_setscheme(drw, scheme[SchemeNorm]); drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); } x += w; for (item = curr; item != next; item = item->right) x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">") - TEXTW(numbers))); if (next) { w = TEXTW(">"); drw_setscheme(drw, scheme[SchemeNorm]); drw_text(drw, mw - w - TEXTW(numbers), 0, w, bh, lrpad / 2, ">", 0); } } drw_setscheme(drw, scheme[SchemeOut]); drw_text(drw, mw - TEXTW(numbers), 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0); drw_map(drw, win, 0, 0, mw, mh); } static void grabfocus(void) { struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; Window focuswin; int i, revertwin; for (i = 0; i < 100; ++i) { XGetInputFocus(dpy, &focuswin, &revertwin); if (focuswin == win) return; if (!prevfocuswin && focuswin != None && focuswin != PointerRoot && focuswin != win) prevfocuswin = focuswin; XSetInputFocus(dpy, win, RevertToParent, CurrentTime); XGetInputFocus(dpy, &focuswin, &revertwin); if (focuswin == win) return; nanosleep(&ts, NULL); } } static void grabkeyboard(void) { struct timespec ts = { .tv_sec = 0, .tv_nsec = 2000000 }; Window grabwin; int i; if (embed || managed) return; grabwin = daemon_mode ? win : DefaultRootWindow(dpy); for (i = 0; i < 25; i++) { if (XGrabKeyboard(dpy, grabwin, True, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess) return; nanosleep(&ts, NULL); } } static void match(void) { static char **tokv = NULL; static int tokn = 0; char buf[sizeof text], *s; int i, tokc = 0; size_t len, textsize; struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; strcpy(buf, text); for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) die("cannot realloc %u bytes:", tokn * sizeof *tokv); len = tokc ? strlen(tokv[0]) : 0; matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; textsize = strlen(text) + 1; for (item = items; item && item->text; item++) { for (i = 0; i < tokc; i++) if (!fstrstr(item->text, tokv[i])) break; if (i != tokc) continue; if (!tokc || !fstrncmp(text, item->text, textsize)) appenditem(item, &matches, &matchend); else if (!fstrncmp(tokv[0], item->text, len)) appenditem(item, &lprefix, &prefixend); else appenditem(item, &lsubstr, &substrend); } if (lprefix) { if (matches) { matchend->right = lprefix; lprefix->left = matchend; } else matches = lprefix; matchend = prefixend; } if (lsubstr) { if (matches) { matchend->right = lsubstr; lsubstr->left = matchend; } else matches = lsubstr; matchend = substrend; } curr = sel = matches; calcoffsets(); } static void insert(const char *str, ssize_t n) { if (strlen(text) + n > sizeof text - 1) return; memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); if (n > 0) memcpy(&text[cursor], str, n); cursor += n; match(); } static size_t nextrune(int inc) { ssize_t n; for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) ; return n; } static void movewordedge(int dir) { if (dir < 0) { while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) cursor = nextrune(-1); while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) cursor = nextrune(-1); } else { while (text[cursor] && strchr(worddelimiters, text[cursor])) cursor = nextrune(+1); while (text[cursor] && !strchr(worddelimiters, text[cursor])) cursor = nextrune(+1); } } static int current_sel_index(void) { int idx = 0; for (struct item *it = matches; it; it = it->right, idx++) { if (it == sel) return idx; } return 0; } static int issoftkey(unsigned int keycode) { return keycode == 124 || keycode == 135 || keycode == 166 || keycode == 177; } static long long dmenu_now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (long long)ts.tv_sec * 1000LL + (long long)(ts.tv_nsec / 1000000L); } static void write_softkey_suppress(long long until_ms) { char buf[32]; int fd = open("/tmp/sxmo_softkey_suppress", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (fd >= 0) { int len = snprintf(buf, sizeof(buf), "%lld\n", until_ms); (void)write(fd, buf, len); close(fd); } } static int query_softkeys_down(void) { char km[32]; int mask = 0; if (!XQueryKeymap(dpy, km)) return 0; if (km[135 / 8] & (1 << (135 % 8))) mask |= 1; if (km[166 / 8] & (1 << (166 % 8))) mask |= 2; return mask; } static int query_trigger_keys_down(void) { char km[32]; int mask = 0; if (!XQueryKeymap(dpy, km)) return 0; if (km[135 / 8] & (1 << (135 % 8))) mask |= 1; if (km[166 / 8] & (1 << (166 % 8))) mask |= 2; if (km[36 / 8] & (1 << (36 % 8))) mask |= 4; if (km[104 / 8] & (1 << (104 % 8))) mask |= 8; return mask; } static void read_softkey_state(int *mask_out, long long *ms_out) { char buf[64]; int fd = open("/tmp/sxmo_softkey_state", O_RDONLY | O_CLOEXEC); *mask_out = 0; *ms_out = 0; if (fd >= 0) { ssize_t n = read(fd, buf, sizeof(buf) - 1); close(fd); if (n > 0) { buf[n] = '\0'; sscanf(buf, "%d %lld", mask_out, ms_out); } } } static void clear_softkey_state(void) { int fd = open("/tmp/sxmo_softkey_state", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (fd >= 0) { (void)write(fd, "0 0\n", 4); close(fd); } } static void spawn_detached_sh(const char *cmd) { if (!cmd || !cmd[0]) return; if (fork() == 0) { if (dpy) close(ConnectionNumber(dpy)); int nullfd = open("/dev/null", O_RDWR); if (nullfd >= 0) { dup2(nullfd, 0); dup2(nullfd, 1); dup2(nullfd, 2); if (nullfd > 2) close(nullfd); } setsid(); execl("/bin/sh", "sh", "-c", cmd, (char *)NULL); _exit(0); } } static void 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) { 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[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); } static void send_key_to_window(Window target, KeySym ksym, unsigned int mod_state) { if (!target || target == None || target == PointerRoot) return; KeyCode kc = XKeysymToKeycode(dpy, ksym); if (!kc) return; XKeyEvent kev; memset(&kev, 0, sizeof(kev)); kev.type = KeyPress; kev.display = dpy; kev.window = target; kev.root = root; kev.subwindow = None; kev.time = CurrentTime; kev.state = mod_state; kev.keycode = kc; kev.same_screen = True; XSetErrorHandler(ignore_xerror); XSendEvent(dpy, target, True, KeyPressMask, (XEvent *)&kev); kev.type = KeyRelease; XSendEvent(dpy, target, True, KeyReleaseMask, (XEvent *)&kev); XFlush(dpy); } static void handle_synth_key_action(const char *spec, Window target) { unsigned int mods = 0; const char *keyname = spec; if (strncmp(spec, "ctrl_shift:", 11) == 0) { mods = ControlMask | ShiftMask; keyname = spec + 11; } else if (strncmp(spec, "ctrl:", 5) == 0) { mods = ControlMask; keyname = spec + 5; } else if (strncmp(spec, "none:", 5) == 0) { mods = 0; keyname = spec + 5; } KeySym ks = XStringToKeysym(keyname); if (ks != NoSymbol) send_key_to_window(target, ks, mods); } static int read_first_line(const char *path, char *buf, size_t bufsz) { int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) return 0; ssize_t n = read(fd, buf, bufsz - 1); close(fd); if (n <= 0) return 0; buf[n] = '\0'; char *nl = strpbrk(buf, "\r\n"); if (nl) *nl = '\0'; return 1; } static int write_str_file(const char *path, const char *val) { int fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC); if (fd < 0) return 0; ssize_t n = write(fd, val, strlen(val)); close(fd); return n > 0; } static void handle_builtin_toggle(const char *toggle_name) { if (strcmp(toggle_name, "flashlight") == 0) { char buf[32] = ""; const char *path = "/sys/class/leds/white:torch/brightness"; if (access(path, R_OK) != 0) path = "/sys/class/leds/white:flash/brightness"; if (read_first_line(path, buf, sizeof(buf))) { int cur = atoi(buf); if (!write_str_file(path, cur > 0 ? "0\n" : "255\n")) spawn_detached_sh("sxmo_flashtoggle.sh"); } else { spawn_detached_sh("sxmo_flashtoggle.sh"); } } else if (strcmp(toggle_name, "bright_up") == 0 || strcmp(toggle_name, "bright_down") == 0) { char cur_s[32] = "", max_s[32] = ""; const char *bpath = "/sys/class/backlight/backlight/brightness"; const char *mpath = "/sys/class/backlight/backlight/max_brightness"; if (read_first_line(bpath, cur_s, sizeof(cur_s)) && read_first_line(mpath, max_s, sizeof(max_s))) { int cur = atoi(cur_s); int maxv = atoi(max_s); if (maxv <= 0) maxv = 255; int step = MAX(1, maxv / 10); int nextv = (strcmp(toggle_name, "bright_up") == 0) ? MIN(maxv, cur + step) : MAX(1, cur - step); char out[32]; snprintf(out, sizeof(out), "%d\n", nextv); if (!write_str_file(bpath, out)) spawn_detached_sh(strcmp(toggle_name, "bright_up") == 0 ? "sxmo_brightness.sh up" : "sxmo_brightness.sh down"); } else { spawn_detached_sh(strcmp(toggle_name, "bright_up") == 0 ? "sxmo_brightness.sh up" : "sxmo_brightness.sh down"); } } else if (strcmp(toggle_name, "autosuspend") == 0) { const char *home = getenv("HOME"); if (!home || !home[0]) home = "/home/user"; char path[256]; snprintf(path, sizeof(path), "%s/.cache/sxmo/sxmo.nosuspend", home); if (access(path, F_OK) == 0) { unlink(path); } else { char dir[256]; snprintf(dir, sizeof(dir), "%s/.cache/sxmo", home); mkdir(dir, 0755); int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (fd >= 0) close(fd); } } else if (strcmp(toggle_name, "bluetooth") == 0) { 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"); } } static void finish_client_session(int exit_code, const char *selected_line) { char outbuf[BUFSIZ + 4]; size_t outlen = 0; outbuf[0] = (char)(exit_code & 0xff); outlen = 1; if (exit_code == 0 && selected_line) { size_t slen = strlen(selected_line); if (slen > BUFSIZ) slen = BUFSIZ; memcpy(outbuf + 1, selected_line, slen); outbuf[1 + slen] = '\n'; outlen = 1 + slen + 1; } hide_menu(); if (active_client_fd >= 0) { (void)write(active_client_fd, outbuf, outlen); close(active_client_fd); active_client_fd = -1; } menu_mode = MODE_NONE; } static void activate_selection(unsigned int ev_state) { if (!daemon_mode) { if (use_text_input) puts((sel && (ev_state & ShiftMask)) ? sel->text : text); else puts((sel && !(ev_state & ShiftMask)) ? sel->text : text); if (!(ev_state & ControlMask)) { cleanup(); exit(0); } if (sel) sel->out = 1; return; } if (menu_mode == MODE_CLIENT) { const char *out = use_text_input ? ((sel && (ev_state & ShiftMask)) ? sel->text : text) : ((sel && !(ev_state & ShiftMask)) ? sel->text : text); finish_client_session(0, out ? out : ""); return; } if (menu_mode == MODE_BUILTIN) { if (!sel || !sel->action) { hide_menu(); menu_mode = MODE_NONE; return; } char act[512]; snprintf(act, sizeof(act), "%s", sel->action); int loop = sel->loop; int cur_idx = current_sel_index(); Window target_win = prevfocuswin; if (strcmp(act, "@close") == 0 || strcmp(act, "quit") == 0) { hide_menu(); menu_mode = MODE_NONE; return; } if (strncmp(act, "@menu:", 6) == 0) { open_builtin_menu(act + 6, 0); return; } if (strncmp(act, "@plugin:", 8) == 0) { hide_menu(); menu_mode = MODE_NONE; 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]; snprintf(cur_menu, sizeof(cur_menu), "%s", current_builtin_menu); open_builtin_menu(cur_menu, cur_idx); return; } if (strncmp(act, "@key:", 5) == 0) { if (loop) { handle_synth_key_action(act + 5, target_win); } else { hide_menu(); menu_mode = MODE_NONE; handle_synth_key_action(act + 5, target_win); } return; } if (loop) { spawn_detached_sh(act); char cur_menu[64]; snprintf(cur_menu, sizeof(cur_menu), "%s", current_builtin_menu); open_builtin_menu(cur_menu, cur_idx); } else { hide_menu(); menu_mode = MODE_NONE; spawn_detached_sh(act); } } } static void cancel_session(void) { if (!daemon_mode) { cleanup(); exit(1); } if (menu_mode == MODE_CLIENT) { finish_client_session(1, NULL); } else { hide_menu(); menu_mode = MODE_NONE; } } static void spawn_xdrawer_and_close(void) { clear_softkey_state(); write_softkey_suppress(dmenu_now_ms() + 500); if (!daemon_mode) { cleanup(); trigger_plugin_open("xdrawer"); exit(1); } if (menu_mode == MODE_CLIENT) { finish_client_session(1, NULL); } else { hide_menu(); menu_mode = MODE_NONE; } trigger_plugin_open("xdrawer"); } static void keypress(XKeyEvent *ev) { char buf[32]; int len; KeySym ksym; Status status; ksym = NoSymbol; if (xic && ev->keycode == 0) len = Xutf8LookupString(xic, ev, buf, sizeof buf, &ksym, &status); else { len = XLookupString(ev, buf, sizeof buf, &ksym, NULL); status = (len > 0 && ksym != NoSymbol) ? XLookupBoth : (len > 0) ? XLookupChars : (ksym != NoSymbol) ? XLookupKeySym : XLookupNone; } switch (status) { default: return; case XLookupChars: goto insert; case XLookupKeySym: case XLookupBoth: break; } if (ev->state & ControlMask) { switch(ksym) { case XK_a: ksym = XK_Home; break; case XK_b: ksym = XK_Left; break; case XK_c: ksym = XK_Escape; break; case XK_d: ksym = XK_Delete; break; case XK_e: ksym = XK_End; break; case XK_f: ksym = XK_Right; break; case XK_g: ksym = XK_Escape; break; case XK_h: ksym = XK_BackSpace; break; case XK_i: ksym = XK_Tab; break; case XK_j: case XK_J: case XK_m: case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; case XK_n: ksym = XK_Down; break; case XK_p: ksym = XK_Up; break; case XK_k: text[cursor] = '\0'; match(); break; case XK_u: insert(NULL, 0 - cursor); break; case XK_w: while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) insert(NULL, nextrune(-1) - cursor); while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) insert(NULL, nextrune(-1) - cursor); break; case XK_y: case XK_Y: XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, utf8, utf8, win, CurrentTime); return; case XK_Left: movewordedge(-1); goto draw; case XK_Right: movewordedge(+1); goto draw; case XK_Return: case XK_KP_Enter: break; case XK_bracketleft: cancel_session(); return; default: return; } } else if (ev->state & Mod1Mask) { switch(ksym) { case XK_b: movewordedge(-1); goto draw; case XK_f: movewordedge(+1); goto draw; case XK_g: ksym = XK_Home; break; case XK_G: ksym = XK_End; break; case XK_h: ksym = XK_Up; break; case XK_j: ksym = XK_Next; break; case XK_k: ksym = XK_Prior; break; case XK_l: ksym = XK_Down; break; default: return; } } switch(ksym) { default: insert: if (!iscntrl((unsigned char)*buf)) insert(buf, len); break; case XK_Delete: if (text[cursor] == '\0') return; cursor = nextrune(+1); /* fallthrough */ case XK_BackSpace: if (cursor == 0) return; insert(NULL, nextrune(-1) - cursor); break; case XK_End: if (text[cursor] != '\0') { cursor = strlen(text); break; } if (next) { curr = matchend; calcoffsets(); curr = prev; calcoffsets(); while (next && (curr = curr->right)) calcoffsets(); } sel = matchend; break; case XK_Escape: case XF86XK_PowerOff: cancel_session(); return; case XF86XK_Back: case XF86XK_KbdLcdMenu2: return; case XK_Home: if (sel == matches) { cursor = 0; break; } sel = curr = matches; calcoffsets(); break; case XK_Left: if (cursor > 0 && (!sel || !sel->left || lines > 0)) { cursor = nextrune(-1); break; } if (lines > 0) return; /* fallthrough */ case XF86XK_AudioRaiseVolume: case XK_Up: if (sel && sel->left) { sel = sel->left; if (sel->right == curr) { curr = prev; calcoffsets(); } } else if (wrap_around) { curr = matchend; calcoffsets(); curr = prev; calcoffsets(); while (next && (curr = curr->right)) calcoffsets(); sel = matchend; } break; case XK_Next: if (!next) return; sel = curr = next; calcoffsets(); break; case XK_Prior: if (!prev) return; sel = curr = prev; calcoffsets(); break; case XK_Menu: case XF86XK_KbdLcdMenu1: return; case XF86XK_Phone: case XF86XK_PickupPhone: case XK_Return: case XK_KP_Enter: activate_selection(ev->state); if (!menu_active) return; break; case XK_Right: if (text[cursor] != '\0') { cursor = nextrune(+1); break; } if (lines > 0) return; /* fallthrough */ case XF86XK_AudioLowerVolume: case XK_Down: if (sel && sel->right) { sel = sel->right; if (sel == next) { curr = next; } } else if (wrap_around) { sel = curr = matches; } calcoffsets(); break; case XK_Tab: if (!sel) return; strncpy(text, sel->text, sizeof text - 1); text[sizeof text - 1] = '\0'; cursor = strlen(text); match(); break; } draw: if (menu_active || !daemon_mode) drawmenu(); } static void motionnotify(XEvent *e) { struct item *item; XMotionEvent *ev = &e->xmotion; int y = 0, h = bh; char *pageup = "Page up", *pagedown = "Page down"; char *gotop = "Go to top", *gobottom = "Go to bottom"; char *alreadytop = "Already at top", *alreadybottom = "Already at bottom"; if (ev->window != win) return; if (ev->state & ~Button1Mask) return; if (lines > 0) { y = bh; for (item = curr; item != next; item = item->right) { if (ev->y >= y && ev->y < (y + item_bh)) { sel = item; drawmenu(); return; } y += item_bh; } if (ev->y > mh || ev->y < 0) { if (ev->y >= mh && ev->y <= (mh + bh * scrollregion)) { nav->text = next ? pagedown : alreadybottom; drw_setscheme(drw, scheme[SchemeSel]); y = mh - bh; } else if (ev->y >= (mh + bh * scrollregion) && ev->y < (mh + bh * scrollregion * 2)) { nav->text = next ? gobottom : alreadybottom; drw_setscheme(drw, scheme[SchemeSel]); y = mh - bh; } else if (ev->y >= (0 - h * scrollregion) && ev->y <= 0) { nav->text = curr != matches ? pageup : alreadytop; drw_setscheme(drw, scheme[SchemeSel]); y = 0 + bh; } else if (ev->y >= (0 - h * scrollregion * 2) && ev->y <= (0 - h * scrollregion)) { nav->text = curr != matches ? gotop : alreadytop; drw_setscheme(drw, scheme[SchemeSel]); y = 0 + bh; } else { drawmenu(); return; } drw_setscheme(drw, scheme[SchemeOut]); drw_text(drw, 0, y, mw, bh, lrpad / 2, nav->text, 0); drw_map(drw, win, 0, 0, mw, mh); XFlush(dpy); return; } } } static void buttonpress(XEvent *e) { struct item *item; XButtonPressedEvent *ev = &e->xbutton; int x = 0, y = 0, h = bh, w; if (ev->window != win) return; if (ev->button == Button3) { cancel_session(); return; } if (prompt && *prompt) x += promptw; w = (lines > 0 || !matches) ? mw - x : inputw; if (ev->button == Button1 && ((lines <= 0 && ev->x >= 0 && ev->x <= x + w + ((!prev || !curr->left) ? TEXTW("<") : 0)) || (lines > 0 && ev->y >= y && ev->y <= y + h))) { insert(NULL, -cursor); drawmenu(); return; } if (ev->button == Button2) { XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, utf8, utf8, win, CurrentTime); drawmenu(); return; } if (ev->y >= (0 - h * scrollregion * 2) && ev->y <= (0 - h * scrollregion)) { sel = curr = matches; calcoffsets(); drawmenu(); return; } if ((ev->button == Button4 && prev) || (ev->y >= (0 - h * scrollregion) && ev->y <= 0)) { sel = curr = prev; calcoffsets(); drawmenu(); return; } if ((ev->button == Button5 && next) || (ev->y >= mh && ev->y <= (mh + bh * scrollregion))) { if (next) sel = curr = next; calcoffsets(); drawmenu(); return; } if (ev->y >= (mh + bh * scrollregion) && ev->y < (mh + bh * scrollregion * 2)) { if (next) { curr = matchend; calcoffsets(); curr = prev; calcoffsets(); while (next && (curr = curr->right)) calcoffsets(); } sel = matchend; drawmenu(); return; } if (ev->button != Button1) return; if (ev->state & ~ControlMask & ~Button1Mask) return; if (lines > 0) { y = bh; for (item = curr; item != next; item = item->right) { if (ev->y >= y && ev->y < (y + item_bh)) { sel = item; activate_selection(ev->state); return; } y += item_bh; } } else if (matches) { x += inputw; w = TEXTW("<"); if (prev && curr->left) { if (ev->x >= x && ev->x <= x + w) { sel = curr = prev; calcoffsets(); drawmenu(); return; } } for (item = curr; item != next; item = item->right) { x += w; w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); if (ev->x >= x && ev->x <= x + w) { sel = item; activate_selection(ev->state); return; } } w = TEXTW(">"); x = mw - w; if (next && ev->x >= x && ev->x <= x + w) { sel = curr = next; calcoffsets(); drawmenu(); return; } } } static void paste(void) { char *p, *q; int di; unsigned long dl; Atom da; if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, utf8, &da, &di, &dl, &dl, (unsigned char **)&p) == Success && p) { insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); XFree(p); } drawmenu(); } static void readstdin(void) { char buf[sizeof text], *p; size_t i, imax = 0; unsigned int tmpmax = 0; free_items(); for (i = 0; fgets(buf, sizeof buf, stdin); i++) { if ((p = strchr(buf, '\n'))) *p = '\0'; add_menu_item(buf, NULL, 0); if (lines == 0 && !centered) { drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); if (tmpmax > (unsigned int)inputw) { inputw = tmpmax; imax = i; } } } inputw = (items && items_count > 0 && lines == 0 && !centered) ? TEXTW(items[imax].text) : 0; } /* Built-in Sxmo Menu Builders */ static int bt_is_unblocked_fast(void) { DIR *d = opendir("/sys/class/rfkill"); if (!d) return 0; struct dirent *ent; int unblocked = 0; while ((ent = readdir(d)) != NULL) { if (strncmp(ent->d_name, "rfkill", 6) != 0) continue; char path[256], rtype[32] = "", soft[16] = "0", hard[16] = "0"; snprintf(path, sizeof(path), "/sys/class/rfkill/%s/type", ent->d_name); if (!read_first_line(path, rtype, sizeof(rtype)) || strcmp(rtype, "bluetooth") != 0) continue; snprintf(path, sizeof(path), "/sys/class/rfkill/%s/soft", ent->d_name); read_first_line(path, soft, sizeof(soft)); snprintf(path, sizeof(path), "/sys/class/rfkill/%s/hard", ent->d_name); read_first_line(path, hard, sizeof(hard)); if (strcmp(soft, "0") == 0 && strcmp(hard, "0") == 0) unblocked = 1; break; } closedir(d); return unblocked; } static int count_notifications(void) { const char *ndir = getenv("SXMO_NOTIFDIR"); char default_dir[256]; if (!ndir || !ndir[0]) { const char *home = getenv("HOME"); if (!home || !home[0]) home = "/home/user"; snprintf(default_dir, sizeof(default_dir), "%s/.local/share/sxmo/notifications", home); ndir = default_dir; } DIR *d = opendir(ndir); if (!d) return 0; int n = 0; 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)) n++; } closedir(d); return n; } static int has_tracked_calls(void) { const char *rdir = getenv("XDG_RUNTIME_DIR"); char cdir[256]; snprintf(cdir, sizeof(cdir), "%s/sxmo_calls", (rdir && rdir[0]) ? rdir : "/run/user/10000"); DIR *d = opendir(cdir); if (!d) return 0; struct dirent *ent; int found = 0; while ((ent = readdir(d)) != NULL) { if (ent->d_name[0] != '.') { found = 1; break; } } closedir(d); return found; } static void trim_inplace(char *s) { if (!s) return; char *p = s; while (*p && isspace((unsigned char)*p)) p++; if (p != s) memmove(s, p, strlen(p) + 1); size_t len = strlen(s); while (len > 0 && isspace((unsigned char)s[len - 1])) s[--len] = '\0'; } static void add_parsed_choice_line(const char *line) { if (!line || !line[0]) return; char buf[1024]; snprintf(buf, sizeof(buf), "%s", line); char *c1 = strchr(buf, '^'); if (!c1) return; *c1 = '\0'; char *c2 = strchr(c1 + 1, '^'); if (!c2) return; *c2 = '\0'; char *label = buf; char *loop_str = c1 + 1; char *cmd = c2 + 1; trim_inplace(label); trim_inplace(loop_str); trim_inplace(cmd); if (!label[0] || !cmd[0]) return; for (size_t i = 0; i < items_count; i++) { if (items[i].text && strcmp(items[i].text, label) == 0) return; if (items[i].text && strstr(label, "Themes") && strstr(items[i].text, "Themes")) return; if (items[i].text && strstr(label, "Bantam") && strstr(items[i].text, "Bantam")) return; } int loop = (strchr(loop_str, '1') != NULL) ? 1 : 0; if (strncmp(cmd, "sxmo_appmenu.sh ", 16) == 0) { const char *sub = cmd + 16; while (*sub && isspace((unsigned char)*sub)) sub++; char act[128]; snprintf(act, sizeof(act), "@menu:%s", sub); add_menu_item(label, act, 1); } else if (strcmp(cmd, "quit") == 0) { add_menu_item(label, "@close", 0); } else { add_menu_item(label, cmd, loop); } } static void append_active_call_entries(void) { if (!has_tracked_calls()) return; FILE *fp = popen("sxmo_modemcall.sh list_active_calls 2>/dev/null", "r"); if (!fp) return; char line[512]; int shown_incall = 0; while (fgets(line, sizeof(line), fp)) { char *nl = strpbrk(line, "\r\n"); if (nl) *nl = '\0'; if (!line[0]) continue; if (strstr(line, "(ringing-in)")) { char callid[64] = ""; char *sp = strchr(line, ' '); if (sp) *sp = '\0'; char *slash = strrchr(line, '/'); snprintf(callid, sizeof(callid), "%s", slash ? (slash + 1) : line); char label[256]; snprintf(label, sizeof(label), "%s Incoming call %s", ICON_PHN, callid); add_menu_item(label, "@plugin:callscreen", 0); } else if (!shown_incall) { shown_incall = 1; char label[128]; snprintf(label, sizeof(label), "%s Call Screen", ICON_PHN); add_menu_item(label, "@plugin:callscreen", 0); } } pclose(fp); } static void load_cache_or_hook(const char *cache_name, const char *hook_cmd) { const char *rdir = getenv("XDG_RUNTIME_DIR"); char path[256]; snprintf(path, sizeof(path), "%s/%s", (rdir && rdir[0]) ? rdir : "/run/user/10000", cache_name); FILE *fp = fopen(path, "r"); if (!fp) { snprintf(path, sizeof(path), "/tmp/%s", cache_name); fp = fopen(path, "r"); } if (!fp) { fp = popen(hook_cmd, "r"); if (!fp) return; char line[1024]; while (fgets(line, sizeof(line), fp)) add_parsed_choice_line(line); pclose(fp); return; } char line[1024]; while (fgets(line, sizeof(line), fp)) add_parsed_choice_line(line); 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) { char label[256], cmd[512]; if (!menu_name || !menu_name[0] || strcmp(menu_name, "ctx") == 0) { const char *rdir = getenv("XDG_RUNTIME_DIR"); char fpath[256]; snprintf(fpath, sizeof(fpath), "%s/sxmo.focusedwin", (rdir && rdir[0]) ? rdir : "/run/user/10000"); FILE *fp = fopen(fpath, "r"); char cls[128] = "", ttl[256] = ""; if (fp) { if (fgets(cls, sizeof(cls), fp)) trim_inplace(cls); if (fgets(ttl, sizeof(ttl), fp)) trim_inplace(ttl); fclose(fp); } if (!cls[0] || strcmp(cls, "sys") == 0) { menu_name = "sys"; } else if (strcmp(cls, "st") == 0 || strncmp(cls, "st-", 3) == 0) { /* launched from a terminal: also show the exact same Sys menu */ menu_name = "sys"; } else { /* launched from an app: show the exact same Sys menu as the desktop */ menu_name = "sys"; } } snprintf(current_builtin_menu, sizeof(current_builtin_menu), "%s", menu_name); menu_mode = MODE_BUILTIN; if (strcmp(menu_name, "sys") == 0) { reset_session_defaults(1, "Sys"); append_active_call_entries(); int nnotifs = count_notifications(); if (nnotifs > 0) { snprintf(label, sizeof(label), "%s Notifications (%d)", ICON_BEL, nnotifs); add_menu_item(label, "@menu:notifications", 1); } const char *sh = getenv("SHELL"); if (!sh || !sh[0]) sh = "/bin/sh"; snprintf(label, sizeof(label), "%s Terminal", ICON_TRM); snprintf(cmd, sizeof(cmd), "st -e %s", sh); add_menu_item(label, cmd, 0); snprintf(label, sizeof(label), "%s Apps", ICON_GRD); add_menu_item(label, "@menu:applications", 1); snprintf(label, sizeof(label), "%s Games", ICON_GAM); add_menu_item(label, "@menu:games", 1); snprintf(label, sizeof(label), "%s Scripts", ICON_GRD); add_menu_item(label, "@menu:scripts", 1); snprintf(label, sizeof(label), "%s Files", ICON_DIR); add_menu_item(label, "@plugin:files", 0); snprintf(label, sizeof(label), "%s Dialer", ICON_PHN); add_menu_item(label, "@plugin:dialer", 0); snprintf(label, sizeof(label), "%s Call Log", ICON_PHL); add_menu_item(label, "@plugin:calllog", 0); snprintf(label, sizeof(label), "%s Texts", ICON_MSG); add_menu_item(label, "@plugin:sms", 0); snprintf(label, sizeof(label), "%s Contacts", ICON_USR); add_menu_item(label, "@plugin:contacts", 0); if (bt_is_unblocked_fast()) { snprintf(label, sizeof(label), "%s Bluetooth", ICON_BTH); add_menu_item(label, "@plugin:networks:bluetooth", 0); } snprintf(label, sizeof(label), "%s Camera", ICON_CAM); add_menu_item(label, "@plugin:camera", 0); snprintf(label, sizeof(label), "%s Themes", ICON_THM); add_menu_item(label, "@plugin:themes", 0); snprintf(label, sizeof(label), "%s Wallpapers", ICON_THM); add_menu_item(label, "@plugin:wallpapers", 0); snprintf(label, sizeof(label), "%s Ringtones", ICON_BEL); add_menu_item(label, "@plugin:ringtones", 0); char bl_buf[32] = ""; if (read_first_line("/sys/class/leds/white:torch/brightness", bl_buf, sizeof(bl_buf)) || read_first_line("/sys/class/leds/white:flash/brightness", bl_buf, sizeof(bl_buf))) { int bl = atoi(bl_buf); snprintf(label, sizeof(label), "%s Flashlight %s", ICON_FLL, bl > 0 ? ICON_TON : ICON_TOF); add_menu_item(label, "@toggle:flashlight", 1); } snprintf(label, sizeof(label), "%s Networks", ICON_NET); add_menu_item(label, "@plugin:networks", 0); snprintf(label, sizeof(label), "%s Audio", ICON_MUS); add_menu_item(label, "@menu:audio", 1); snprintf(label, sizeof(label), "%s Modem", ICON_PHN); add_menu_item(label, "@plugin:modem", 0); snprintf(label, sizeof(label), "%s Windows", ICON_WIN); add_menu_item(label, "@menu:wm", 1); snprintf(label, sizeof(label), "%s Config", ICON_CFG); add_menu_item(label, "@menu:config", 1); snprintf(label, sizeof(label), "%s Power", ICON_PWR); add_menu_item(label, "@menu:power", 1); snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS); add_menu_item(label, "@close", 0); } else if (strcmp(menu_name, "applications") == 0) { reset_session_defaults(1, "Apps"); append_active_call_entries(); const char *home = getenv("HOME"); char bpath[256]; snprintf(bpath, sizeof(bpath), "%s/.local/bin/bantam", (home && home[0]) ? home : "/home/user"); if (access("/usr/bin/bantam", X_OK) == 0 || access("/usr/local/bin/bantam", X_OK) == 0 || access(bpath, X_OK) == 0) { snprintf(label, sizeof(label), "%s Bantam", ICON_TRM); add_menu_item(label, "cd \"$HOME\" && sxmo_terminal.sh bantam", 0); } load_cache_or_hook("sxmo_apps.cache", "sxmo_hook_apps.sh"); snprintf(label, sizeof(label), "%s Themes", ICON_THM); int has_themes = 0; for (size_t i = 0; i < items_count; i++) { if (items[i].text && strstr(items[i].text, "Themes")) { has_themes = 1; break; } } if (!has_themes) add_menu_item(label, "@plugin:themes", 0); snprintf(label, sizeof(label), "%s Wallpapers", ICON_THM); add_menu_item(label, "@plugin:wallpapers", 0); snprintf(label, sizeof(label), "%s Ringtones", ICON_BEL); add_menu_item(label, "@plugin:ringtones", 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, "games") == 0) { reset_session_defaults(1, "Games"); append_active_call_entries(); 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, "scripts") == 0) { reset_session_defaults(1, "Scripts"); append_active_call_entries(); load_cache_or_hook("sxmo_scripts.cache", "sxmo_hook_scripts.sh"); 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) { reset_session_defaults(1, "Modem"); append_active_call_entries(); 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 AT Command Console", ICON_TRM); add_menu_item(label, "@plugin:modem:at", 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(label, sizeof(label), "%s Ringtones & Ringer", ICON_BEL); add_menu_item(label, "@plugin:ringtones", 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(); const char *home = getenv("HOME"); if (!home || !home[0]) home = "/home/user"; char pbuf[256]; snprintf(label, sizeof(label), "%s Themes", ICON_THM); add_menu_item(label, "@plugin:themes", 0); snprintf(label, sizeof(label), "%s Wallpapers", ICON_THM); add_menu_item(label, "@plugin:wallpapers", 0); snprintf(label, sizeof(label), "%s Ringtones", ICON_BEL); add_menu_item(label, "@plugin:ringtones", 0); snprintf(label, sizeof(label), "%s Brightness", ICON_ARU); add_menu_item(label, "@toggle:bright_up", 1); snprintf(label, sizeof(label), "%s Brightness", ICON_ARD); add_menu_item(label, "@toggle:bright_down", 1); snprintf(label, sizeof(label), "%s Toggle Bar", ICON_CFG); add_menu_item(label, "sxmo_wm.sh togglebar", 0); snprintf(label, sizeof(label), "%s Bluetooth %s", ICON_BTH, bt_is_unblocked_fast() ? ICON_TON : ICON_TOF); add_menu_item(label, "@toggle:bluetooth", 1); snprintf(label, sizeof(label), "%s Change Timezone", ICON_CLK); 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); add_menu_item(label, "@toggle:autosuspend", 1); snprintf(label, sizeof(label), "%s Hooks", ICON_TRM); add_menu_item(label, "sxmo_hookmenu.sh", 0); snprintf(label, sizeof(label), "%s Suspend Blockers", ICON_CFG); add_menu_item(label, "sxmo_terminal.sh sxmo_debug_suspend.sh", 0); snprintf(label, sizeof(label), "%s Log", ICON_INF); snprintf(cmd, sizeof(cmd), "sxmo_terminal.sh tail -n 100 -f %s/sxmo.log", home); add_menu_item(label, cmd, 0); snprintf(label, sizeof(label), "%s Version", ICON_INF); add_menu_item(label, "sxmo_terminal.sh sxmo_version.sh --block", 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, "power") == 0) { reset_session_defaults(1, "Power"); append_active_call_entries(); snprintf(label, sizeof(label), "%s Lock", ICON_LCK); add_menu_item(label, "sxmo_state.sh set lock", 0); snprintf(label, sizeof(label), "%s Lock (Screen off)", ICON_LCK); add_menu_item(label, "sxmo_state.sh set screenoff", 0); snprintf(label, sizeof(label), "%s Logout", ICON_OUT); add_menu_item(label, "@menu:confirm_logout", 1); snprintf(label, sizeof(label), "%s Reboot", ICON_RLD); add_menu_item(label, "@menu:confirm_reboot", 1); snprintf(label, sizeof(label), "%s Reboot to loader", ICON_RLD); add_menu_item(label, "@menu:confirm_loader", 1); snprintf(label, sizeof(label), "%s Poweroff", ICON_PWR); add_menu_item(label, "@menu:confirm_poweroff", 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, "confirm_logout") == 0) { reset_session_defaults(1, "Confirm Logout"); add_menu_item("Yes", "sxmo_power.sh logout", 0); add_menu_item("No", "@menu:power", 1); } else if (strcmp(menu_name, "confirm_reboot") == 0) { reset_session_defaults(1, "Confirm Reboot"); add_menu_item("Yes", "sxmo_power.sh reboot", 0); add_menu_item("No", "@menu:power", 1); } else if (strcmp(menu_name, "confirm_loader") == 0) { reset_session_defaults(1, "Confirm Loader"); add_menu_item("Yes", "sxmo_power.sh loader", 0); add_menu_item("No", "@menu:power", 1); } else if (strcmp(menu_name, "confirm_poweroff") == 0) { reset_session_defaults(1, "Confirm Poweroff"); add_menu_item("Yes", "sxmo_power.sh poweroff", 0); add_menu_item("No", "@menu:power", 1); } else if (strcmp(menu_name, "st") == 0) { reset_session_defaults(1, "St"); append_active_call_entries(); snprintf(label, sizeof(label), "%s New Terminal", ICON_TRM); add_menu_item(label, "sxmo_terminal.sh", 0); snprintf(label, sizeof(label), "%s Close Terminal", ICON_CLS); add_menu_item(label, "sxmo_killwindow.sh", 0); snprintf(label, sizeof(label), "%s Type complete", ICON_ITM); add_menu_item(label, "@key:ctrl_shift:u", 0); snprintf(label, sizeof(label), "%s Toggle Selmode", ICON_ITM); add_menu_item(label, "@key:ctrl_shift:s", 0); snprintf(label, sizeof(label), "%s Copy complete", ICON_CPY); add_menu_item(label, "@key:ctrl_shift:i", 0); snprintf(label, sizeof(label), "%s Copy selection", ICON_CPY); add_menu_item(label, "@key:ctrl_shift:c", 0); snprintf(label, sizeof(label), "%s Paste", ICON_PST); add_menu_item(label, "@key:ctrl_shift:v", 0); snprintf(label, sizeof(label), "%s Zoom in", ICON_ZMI); add_menu_item(label, "@key:ctrl_shift:Prior", 1); snprintf(label, sizeof(label), "%s Zoom out", ICON_ZMO); add_menu_item(label, "@key:ctrl_shift:Next", 1); snprintf(label, sizeof(label), "%s Scroll up", ICON_ARU); add_menu_item(label, "@key:ctrl_shift:b", 1); snprintf(label, sizeof(label), "%s Scroll down", ICON_ARD); add_menu_item(label, "@key:ctrl_shift:f", 1); snprintf(label, sizeof(label), "%s Invert", ICON_A2X); add_menu_item(label, "@key:ctrl_shift:x", 1); snprintf(label, sizeof(label), "%s Hotkeys", ICON_KBD); add_menu_item(label, "@menu:sthotkeys", 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, "nano") == 0) { reset_session_defaults(1, "Nano"); append_active_call_entries(); snprintf(label, sizeof(label), "%s Scroll up", ICON_ARU); add_menu_item(label, "@key:none:Prior", 1); snprintf(label, sizeof(label), "%s Scroll down", ICON_ARD); add_menu_item(label, "@key:none:Next", 1); snprintf(label, sizeof(label), "%s Save", ICON_SAV); add_menu_item(label, "@key:ctrl:o", 0); snprintf(label, sizeof(label), "%s Quit", ICON_CLS); add_menu_item(label, "@key:ctrl:x", 0); snprintf(label, sizeof(label), "%s Paste", ICON_PST); add_menu_item(label, "@key:ctrl:u", 0); snprintf(label, sizeof(label), "%s Type complete", ICON_ITM); add_menu_item(label, "@key:ctrl_shift:u", 0); snprintf(label, sizeof(label), "%s Copy complete", ICON_CPY); add_menu_item(label, "@key:ctrl_shift:i", 0); snprintf(label, sizeof(label), "%s Zoom in", ICON_ZMI); add_menu_item(label, "@key:none:Prior", 1); snprintf(label, sizeof(label), "%s Zoom out", ICON_ZMO); add_menu_item(label, "@key:none:Next", 1); snprintf(label, sizeof(label), "%s Terminal menu", ICON_MNU); add_menu_item(label, "@menu:st", 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, "sthotkeys") == 0) { reset_session_defaults(1, "St"); append_active_call_entries(); add_menu_item("Send Ctrl-C", "@key:ctrl:c", 0); add_menu_item("Send Ctrl-Z", "@key:ctrl:z", 0); add_menu_item("Send Ctrl-L", "@key:ctrl:l", 0); add_menu_item("Send Ctrl-D", "@key:ctrl:d", 0); add_menu_item("Send Ctrl-A", "@key:ctrl:a", 0); add_menu_item("Send Ctrl-B", "@key:ctrl:b", 0); add_menu_item("Send ESC:w", "sxmo_type.sh -s 100 -k Escape -s 150 -M Shift -k semicolon -m Shift -k w -k Return", 0); add_menu_item("Send ESC:wq", "sxmo_type.sh -s 100 -k Escape -s 150 -M Shift -k semicolon -m Shift -k w -k q -k Return", 0); add_menu_item("Send ESC:wq!", "sxmo_type.sh -s 100 -k Escape -s 150 -M Shift -k semicolon -m Shift -k q -k exclam -k Return", 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 { hide_menu(); menu_mode = MODE_NONE; snprintf(cmd, sizeof(cmd), "sxmo_appmenu.sh --fallback '%s'", menu_name); spawn_detached_sh(cmd); return; } initialindex = keep_idx; show_menu(); } static void close_other_active_plugins(void) { static const struct { const char *pid_file; const char *fifo_path; } plugins[] = { { "xdrawer.pid", "/tmp/dwm_plugin_xdrawer.fifo" }, { "xdialer.pid", "/tmp/dwm_plugin_dialer.fifo" }, { "xcalllog.pid", "/tmp/dwm_plugin_calllog.fifo" }, { "xcallscreen.pid", "/tmp/dwm_plugin_callscreen.fifo" }, { "xcontacts.pid", "/tmp/dwm_plugin_contacts.fifo" }, { "xsms.pid", "/tmp/dwm_plugin_sms.fifo" }, { "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" }, { "xringtones.pid", "/tmp/dwm_plugin_ringtones.fifo" }, { "xwallpapers.pid", "/tmp/dwm_plugin_wallpapers.fifo" }, }; const char *rdir = getenv("XDG_RUNTIME_DIR"); const char *base = (rdir && rdir[0]) ? rdir : "/run/user/10000"; for (size_t i = 0; i < LENGTH(plugins); i++) { char ppath[256]; snprintf(ppath, sizeof(ppath), "%s/%s", base, plugins[i].pid_file); if (access(ppath, F_OK) != 0) snprintf(ppath, sizeof(ppath), "/tmp/%s", plugins[i].pid_file); if (access(ppath, F_OK) == 0) { int fd = open(plugins[i].fifo_path, O_WRONLY | O_NONBLOCK | O_CLOEXEC); if (fd >= 0) { (void)write(fd, "CLOSE\n", 6); close(fd); } } } } static const char * dmenu_xrm_lookup(XrmDatabase xdb, const char *k1, const char *k2, const char *k3) { char *type = NULL; XrmValue xval; if (k1 && XrmGetResource(xdb, k1, "*", &type, &xval) && xval.addr && xval.addr[0]) return xval.addr; if (k2 && XrmGetResource(xdb, k2, "*", &type, &xval) && xval.addr && xval.addr[0]) return xval.addr; if (k3 && XrmGetResource(xdb, k3, "*", &type, &xval) && xval.addr && xval.addr[0]) return xval.addr; return NULL; } static void dmenu_set_color(int scm, int col, const char *val) { if (!val || !val[0]) return; free(colors[scm][col]); colors[scm][col] = strdup(val); } static void load_dmenu_xresources(void) { XrmInitialize(); const char *home = getenv("HOME"); char path[512]; snprintf(path, sizeof(path), "%s/.Xresources", (home && home[0]) ? home : "/home/user"); struct stat st; if (stat(path, &st) == 0) { last_xres_mtime = st.st_mtim.tv_sec; last_xres_nsec = st.st_mtim.tv_nsec; } XrmDatabase xdb = XrmGetFileDatabase(path); Atom actual_type = None; int actual_format = 0; unsigned long nitems = 0, bytes_after = 0; unsigned char *prop = NULL; if (!xdb && XGetWindowProperty(dpy, root, XA_RESOURCE_MANAGER, 0L, 65536L, False, XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after, &prop) == Success && prop) { if (actual_type == XA_STRING && actual_format == 8 && nitems > 0) xdb = XrmGetStringDatabase((const char *)prop); XFree(prop); } if (!xdb) { char *xrm = XResourceManagerString(dpy); if (xrm) xdb = XrmGetStringDatabase(xrm); } if (xdb) { const char *ff = dmenu_xrm_lookup(xdb, "theme.fontFace", NULL, NULL); const char *icf = dmenu_xrm_lookup(xdb, "theme.iconFontFace", NULL, NULL); const char *bsz = dmenu_xrm_lookup(xdb, "theme.baseFontSize", NULL, NULL); const char *dmf = dmenu_xrm_lookup(xdb, "dmenu.font", NULL, NULL); if (ff || bsz) { int base_sz = bsz ? atoi(bsz) : 15; if (base_sz < 8 || base_sz > 32) base_sz = 15; int large_sz = (base_sz * 18 + 7) / 15; snprintf(xres_font_main, sizeof(xres_font_main), "%s:pixelsize=%d:style=Regular:antialias=true:autohint=true", ff ? ff : "Fira Code", large_sz); snprintf(xres_font_icon, sizeof(xres_font_icon), "%s:pixelsize=%d:style=Regular:antialias=true:autohint=true", icf ? icf : "DejaVuSansM Nerd Font", large_sz); fonts[0] = xres_font_main; fonts[1] = xres_font_icon; } else if (dmf) { snprintf(xres_font_main, sizeof(xres_font_main), "%s", dmf); fonts[0] = xres_font_main; fonts[1] = defaultfonts[1]; } dmenu_set_color(SchemeNorm, ColBg, dmenu_xrm_lookup(xdb, "theme.bgHeader", "theme.bgMain", "dmenu.background")); dmenu_set_color(SchemeNorm, ColFg, dmenu_xrm_lookup(xdb, "theme.fgBright", "theme.fgMain", "dmenu.foreground")); dmenu_set_color(SchemeSel, ColBg, dmenu_xrm_lookup(xdb, "theme.selBg", "dmenu.selbackground", NULL)); dmenu_set_color(SchemeSel, ColFg, dmenu_xrm_lookup(xdb, "theme.selFg", "dmenu.selforeground", NULL)); dmenu_set_color(SchemeOut, ColBg, dmenu_xrm_lookup(xdb, "theme.bgSearch", "dmenu.outbackground", "theme.bgInputFocus")); dmenu_set_color(SchemeOut, ColFg, dmenu_xrm_lookup(xdb, "theme.fgBright", "dmenu.outforeground", "theme.fgMain")); dmenu_set_color(SchemeNormHighlight, ColBg, dmenu_xrm_lookup(xdb, "theme.bgHeader", "theme.bgMain", "dmenu.background")); dmenu_set_color(SchemeNormHighlight, ColFg, dmenu_xrm_lookup(xdb, "theme.highlight", "theme.accent", "dmenu.normhlforeground")); dmenu_set_color(SchemeSelHighlight, ColBg, dmenu_xrm_lookup(xdb, "theme.selBg", "dmenu.selbackground", NULL)); dmenu_set_color(SchemeSelHighlight, ColFg, dmenu_xrm_lookup(xdb, "theme.highlight", "theme.accent", "dmenu.selhlforeground")); XrmDestroyDatabase(xdb); } } static void reload_dmenu_theme(void) { int i; load_dmenu_xresources(); for (i = 0; i < SchemeLast; ++i) { if (!colors[i][ColBg]) colors[i][ColBg] = strdup(defaultcolors[i][ColBg]); if (!colors[i][ColFg]) colors[i][ColFg] = strdup(defaultcolors[i][ColFg]); } if (drw->fonts) { drw_fontset_free(drw->fonts); drw->fonts = NULL; } if (!fonts[0]) { fonts[0] = defaultfonts[0]; fonts[1] = defaultfonts[1]; } if (!drw_fontset_create(drw, fonts, 2)) drw_fontset_create(drw, defaultfonts, 2); lrpad = drw->fonts->h / 2; bh = drw->fonts->h + 2; bh = MAX(bh, (int)lineheight); default_lines = MAX(1, (avail_h_cached / (int)bh) - 1); for (i = 0; i < SchemeLast; i++) { free(scheme[i]); scheme[i] = drw_scm_create(drw, (const char **)colors[i], 2); } for (i = 0; i < SchemeLast; ++i) { free(colors[i][ColFg]); free(colors[i][ColBg]); colors[i][ColFg] = NULL; colors[i][ColBg] = NULL; } if (win) { XSetWindowBackground(dpy, win, scheme[SchemeNorm][ColBg].pixel); XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); } if (menu_active) drawmenu(); } static void check_dmenu_xres_changed(void) { const char *home = getenv("HOME"); char path[512]; snprintf(path, sizeof(path), "%s/.Xresources", (home && home[0]) ? home : "/home/user"); struct stat st; if (stat(path, &st) == 0) { if (st.st_mtim.tv_sec != last_xres_mtime || st.st_mtim.tv_nsec != last_xres_nsec) reload_dmenu_theme(); } } static void show_menu(void) { const int bar_h = 24; int target_y; check_dmenu_xres_changed(); promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; if (lines > 0) { lines = default_lines; mh = avail_h_cached; item_bh = (mh - bh) / (int)lines; } else { mh = bh; item_bh = bh; } target_y = topbar ? bar_h : MAX(bar_h, (int)avail_h_cached + bar_h - (int)mh); if (win_w != mw || win_h != mh || win_y != target_y) { drw_resize(drw, mw, mh); XMoveResizeWindow(dpy, win, win_x, target_y, mw, mh); win_y = target_y; win_w = mw; win_h = mh; } inputw = MIN(inputw, mw / 3); if (xic && engage_ime) { char *r = Xutf8ResetIC(xic); if (r) XFree(r); } match(); for (int i = 0; i < initialindex; i++) { if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); } } soft_pending = 0; soft_deadline = 0; if (!menu_active) { close_other_active_plugins(); prevfocuswin = None; Window fw = None; int rev = 0; XGetInputFocus(dpy, &fw, &rev); if (fw != None && fw != PointerRoot && fw != win) prevfocuswin = fw; XEvent ev; while (XPending(dpy)) XNextEvent(dpy, &ev); read_softkey_state(&init_soft_mask, &init_soft_ms); start_ignore = dmenu_now_ms() + 180; initial_released = (query_trigger_keys_down() == 0); menu_active = 1; mark_menu_active(1); drawmenu(); XMapRaised(dpy, win); drawmenu(); grabkeyboard(); if (!daemon_mode) grabfocus(); if (xic && engage_ime) XSetICFocus(xic); XFlush(dpy); } else { init_soft_mask = 0; init_soft_ms = 0; start_ignore = dmenu_now_ms() + 60; initial_released = (query_trigger_keys_down() == 0); XRaiseWindow(dpy, win); drawmenu(); if (xic && engage_ime) XSetICFocus(xic); XFlush(dpy); } } static void hide_menu(void) { if (!menu_active) { free_items(); return; } menu_active = 0; mark_menu_active(0); soft_pending = 0; soft_deadline = 0; if (xic && engage_ime) { char *r = Xutf8ResetIC(xic); if (r) XFree(r); XUnsetICFocus(xic); } XUngrabKeyboard(dpy, CurrentTime); XUnmapWindow(dpy, win); XSetErrorHandler(ignore_xerror); if (!daemon_mode && prevfocuswin && prevfocuswin != win && prevfocuswin != PointerRoot) { XWindowAttributes wa; if (XGetWindowAttributes(dpy, prevfocuswin, &wa) && wa.map_state == IsViewable) XSetInputFocus(dpy, prevfocuswin, RevertToParent, CurrentTime); } prevfocuswin = None; XSync(dpy, False); XEvent ev; while (XPending(dpy)) XNextEvent(dpy, &ev); free_items(); } static void handle_client_socket(int sock_fd) { int cfd = accept(sock_fd, NULL, NULL); if (cfd < 0) return; size_t cap = 8192; size_t len = 0; char *buf = malloc(cap); if (!buf) { close(cfd); return; } for (;;) { struct pollfd pfd = { cfd, POLLIN, 0 }; int pr = poll(&pfd, 1, 5000); if (pr <= 0) break; if (len + 4096 > cap) { size_t ncap = cap * 2; if (ncap > 1024 * 1024) break; char *nbuf = realloc(buf, ncap); if (!nbuf) break; buf = nbuf; cap = ncap; } ssize_t n = read(cfd, buf + len, cap - len - 1); if (n < 0) { if (errno == EINTR || errno == EAGAIN) continue; break; } if (n == 0) break; len += (size_t)n; } buf[len] = '\0'; if (len < sizeof(uint32_t)) { free(buf); close(cfd); return; } if (active_client_fd >= 0) { char cancel_byte = 1; (void)write(active_client_fd, &cancel_byte, 1); close(active_client_fd); active_client_fd = -1; } size_t pos = 0; uint32_t c_argc = 0; memcpy(&c_argc, buf + pos, sizeof(uint32_t)); pos += sizeof(uint32_t); if (c_argc > 128) c_argc = 128; char *c_argv[129]; uint32_t actual_argc = 0; for (uint32_t i = 0; i < c_argc; i++) { if (pos + sizeof(uint32_t) > len) break; uint32_t slen = 0; memcpy(&slen, buf + pos, sizeof(uint32_t)); pos += sizeof(uint32_t); if (pos + slen > len) break; c_argv[actual_argc] = malloc(slen + 1); if (!c_argv[actual_argc]) break; memcpy(c_argv[actual_argc], buf + pos, slen); c_argv[actual_argc][slen] = '\0'; pos += slen; actual_argc++; } reset_session_defaults(0, NULL); for (uint32_t i = 1; i < actual_argc; i++) { if (!strcmp(c_argv[i], "-b")) topbar = 0; else if (!strcmp(c_argv[i], "-f")) ; else if (!strcmp(c_argv[i], "-nc")) centered = 0; else if (!strcmp(c_argv[i], "-c")) centered = 1; else if (!strcmp(c_argv[i], "-i")) { fstrncmp = strncasecmp; fstrstr = cistrstr; } else if (!strcmp(c_argv[i], "-t")) use_text_input = 1; else if (!strcmp(c_argv[i], "-l") && i + 1 < actual_argc) lines = atoi(c_argv[++i]); else if (!strcmp(c_argv[i], "-H") && i + 1 < actual_argc) ++i; else if (!strcmp(c_argv[i], "-m") && i + 1 < actual_argc) ++i; else if (!strcmp(c_argv[i], "-p") && i + 1 < actual_argc) { free(session_prompt); session_prompt = strdup(c_argv[++i]); prompt = session_prompt; } else if ((!strcmp(c_argv[i], "-fn") || !strcmp(c_argv[i], "-nb") || !strcmp(c_argv[i], "-nf") || !strcmp(c_argv[i], "-sb") || !strcmp(c_argv[i], "-sf") || !strcmp(c_argv[i], "-w")) && i + 1 < actual_argc) { ++i; } else if ((!strcmp(c_argv[i], "--index") || !strcmp(c_argv[i], "-I")) && i + 1 < actual_argc) initialindex = atoi(c_argv[++i]); else if (!strcmp(c_argv[i], "-idx") && i + 1 < actual_argc) initialindex = atoi(c_argv[++i]) - 1; } for (uint32_t i = 0; i < actual_argc; i++) free(c_argv[i]); char *stdin_data = buf + pos; size_t imax = 0, idx = 0; unsigned int tmpmax = 0; char *line = stdin_data; while (line && *line) { char *nl = strchr(line, '\n'); if (nl) *nl = '\0'; add_menu_item(line, NULL, 0); if (lines == 0 && !centered) { drw_font_getexts(drw->fonts, line, strlen(line), &tmpmax, NULL); if (tmpmax > (unsigned int)inputw) { inputw = tmpmax; imax = idx; } } idx++; if (!nl) break; line = nl + 1; } inputw = (items && items_count > 0 && lines == 0 && !centered) ? TEXTW(items[imax].text) : 0; free(buf); active_client_fd = cfd; menu_mode = MODE_CLIENT; show_menu(); } static void handle_fifo_commands(int fifo_fd) { char buf[512]; ssize_t n = read(fifo_fd, buf, sizeof(buf) - 1); if (n <= 0) return; buf[n] = '\0'; char *saveptr = NULL; for (char *line = strtok_r(buf, "\r\n", &saveptr); line; line = strtok_r(NULL, "\r\n", &saveptr)) { if (strcmp(line, "CLOSE") == 0) { if (menu_active) cancel_session(); } else if (strcmp(line, "RELOAD_THEME") == 0) { reload_dmenu_theme(); } else if (strcmp(line, "OPEN") == 0) { if (active_client_fd >= 0) finish_client_session(1, NULL); open_builtin_menu("sys", 0); } else if (strncmp(line, "OPEN ", 5) == 0) { const char *arg = line + 5; while (*arg && isspace((unsigned char)*arg)) arg++; if (active_client_fd >= 0) finish_client_session(1, NULL); open_builtin_menu(arg[0] ? arg : "sys", 0); } } } static void process_x11_events(void) { XEvent ev; if (!initial_released && query_trigger_keys_down() == 0) initial_released = 1; while (XPending(dpy)) { if (XNextEvent(dpy, &ev)) return; if (!menu_active && daemon_mode) continue; if (!initial_released && query_trigger_keys_down() == 0) initial_released = 1; if (ev.type == KeyPress || ev.type == KeyRelease) { KeySym ks = XLookupKeysym(&ev.xkey, 0); int is_left = (ks == XK_Menu || ks == XF86XK_KbdLcdMenu1 || ev.xkey.keycode == 135); int is_right = (ks == XF86XK_Back || ks == XF86XK_KbdLcdMenu2 || ev.xkey.keycode == 166); int is_enter = (ks == XK_Return || ks == XK_KP_Enter || ks == XF86XK_Phone || ks == XF86XK_PickupPhone || ev.xkey.keycode == 36 || ev.xkey.keycode == 104); if (is_left || is_right) { long long now = dmenu_now_ms(); int which = (is_left ? 1 : 0) | (is_right ? 2 : 0); if (ev.type == KeyPress && init_soft_mask != 0 && (init_soft_mask | which) == 3 && (now - init_soft_ms) <= 320) { spawn_xdrawer_and_close(); if (!menu_active && daemon_mode) return; } if (ev.type == KeyRelease) { initial_released = 1; continue; } if (!initial_released || now < start_ignore) continue; soft_pending |= which | query_softkeys_down(); if (soft_pending == 3) { spawn_xdrawer_and_close(); if (!menu_active && daemon_mode) return; } if (soft_deadline == 0) soft_deadline = now + 30; continue; } if (is_enter) { if (ev.type == KeyRelease) { initial_released = 1; continue; } if (!initial_released || dmenu_now_ms() < start_ignore) continue; } if (engage_ime && !issoftkey(ev.xkey.keycode) && !(is_enter && items)) { ev.xkey.window = win; if (XFilterEvent(&ev, None)) continue; } } else if (engage_ime && XFilterEvent(&ev, None)) { continue; } switch (ev.type) { case MotionNotify: motionnotify(&ev); break; case ButtonRelease: buttonpress(&ev); break; case DestroyNotify: if (ev.xdestroywindow.window != win) break; cleanup(); exit(1); case Expose: if (ev.xexpose.count == 0 && (menu_active || !daemon_mode)) drw_map(drw, win, 0, 0, mw, mh); break; case FocusIn: if (menu_active || !daemon_mode) { if (!daemon_mode && ev.xfocus.window != win) grabfocus(); if (xic && engage_ime) XSetICFocus(xic); } break; case KeyPress: keypress(&ev.xkey); break; case SelectionNotify: if (ev.xselection.property == utf8) paste(); break; } } } static void check_softkey_timeout(void) { if (!soft_pending || soft_deadline <= 0) return; long long now = dmenu_now_ms(); int hw_down = query_softkeys_down(); soft_pending |= hw_down; if (soft_pending == 3) { spawn_xdrawer_and_close(); return; } long long rem = soft_deadline - now; if (rem <= 0) { int action = soft_pending; soft_pending = 0; soft_deadline = 0; clear_softkey_state(); write_softkey_suppress(now + 250); if (action == 1) { activate_selection(0); } else { cancel_session(); } } } static void run_standalone(void) { int xfd = ConnectionNumber(dpy); soft_pending = 0; soft_deadline = 0; start_ignore = dmenu_now_ms() + 40; initial_released = (query_softkeys_down() == 0); read_softkey_state(&init_soft_mask, &init_soft_ms); for (;;) { process_x11_events(); check_softkey_timeout(); if (soft_pending && soft_deadline > 0) { long long rem = MAX(1LL, soft_deadline - dmenu_now_ms()); struct pollfd pfd = { xfd, POLLIN, 0 }; poll(&pfd, 1, (int)rem); } else { struct pollfd pfd = { xfd, POLLIN, 0 }; poll(&pfd, 1, initial_released ? -1 : 40); } } } static void run_daemon_loop(int fifo_fd, int sock_fd) { int xfd = ConnectionNumber(dpy); for (;;) { process_x11_events(); if (menu_active) check_softkey_timeout(); int timeout_ms = -1; if (menu_active) { if (soft_pending && soft_deadline > 0) timeout_ms = (int)MAX(1LL, soft_deadline - dmenu_now_ms()); else if (!initial_released) timeout_ms = 40; } struct pollfd pfds[3]; pfds[0].fd = xfd; pfds[0].events = POLLIN; pfds[0].revents = 0; pfds[1].fd = fifo_fd; pfds[1].events = POLLIN; pfds[1].revents = 0; pfds[2].fd = sock_fd; pfds[2].events = POLLIN; pfds[2].revents = 0; int pr = poll(pfds, 3, timeout_ms); if (pr < 0) { if (errno == EINTR) continue; break; } if (pfds[1].revents & POLLIN) handle_fifo_commands(fifo_fd); if (pfds[2].revents & POLLIN) handle_client_socket(sock_fd); } } static void setup(int map_immediately) { int x, y, i, j; unsigned int du; XSetWindowAttributes swa; XIM xim; Window w, dw, *dws; XWindowAttributes wa; XClassHint ch = {"dmenu", "dmenu"}; #ifdef XINERAMA XineramaScreenInfo *info; Window pw; int a, di, n, area = 0; #endif for (j = 0; j < SchemeLast; j++) scheme[j] = drw_scm_create(drw, (const char**)colors[j], 2); for (j = 0; j < SchemeLast; ++j) { free(colors[j][ColFg]); free(colors[j][ColBg]); colors[j][ColFg] = NULL; colors[j][ColBg] = NULL; } clip = XInternAtom(dpy, "CLIPBOARD", False); utf8 = XInternAtom(dpy, "UTF8_STRING", False); const int bar_h = 24; bh = drw->fonts->h + 2; bh = MAX(bh, (int)lineheight); item_bh = bh; lines = MAX(lines, 0); mh = (lines + 1) * bh; promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; #ifdef XINERAMA i = 0; if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { int avail_h; XGetInputFocus(dpy, &w, &di); if (mon >= 0 && mon < n) i = mon; else if (w != root && w != PointerRoot && w != None) { do { if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) XFree(dws); } while (w != root && w != pw); if (XGetWindowAttributes(dpy, pw, &wa)) for (j = 0; j < n; j++) if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { area = a; i = j; } } if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) for (i = 0; i < n; i++) if (INTERSECT(x, y, 1, 1, info[i])) break; avail_h = MAX((int)bh * 2, (int)info[i].height - bar_h); avail_h_cached = avail_h; default_lines = MAX(1, (avail_h / (int)bh) - 1); if (daemon_mode || lines > 0) { lines = default_lines; mh = avail_h; item_bh = (mh - bh) / (int)lines; } x = info[i].x_org; mw = info[i].width; y = info[i].y_org + (topbar ? bar_h : MAX(bar_h, (int)info[i].height - (int)mh)); XFree(info); } else #endif { int avail_h; if (!XGetWindowAttributes(dpy, parentwin, &wa)) die("could not get embedding window attributes: 0x%lx", parentwin); avail_h = MAX((int)bh * 2, (int)wa.height - bar_h); avail_h_cached = avail_h; default_lines = MAX(1, (avail_h / (int)bh) - 1); if (daemon_mode || lines > 0) { lines = default_lines; mh = avail_h; item_bh = (mh - bh) / (int)lines; } x = 0; mw = wa.width; y = topbar ? bar_h : MAX(bar_h, (int)wa.height - (int)mh); } win_x = x; win_y = y; win_w = mw; win_h = mh; inputw = MIN(inputw, mw / 3); match(); swa.override_redirect = managed ? False : True; swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask; win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width, CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); XSetClassHint(dpy, win, &ch); drw_resize(drw, mw, mh); calcoffsets(); /* Pre-warm font glyphs into offscreen pixmap */ if (daemon_mode) { drw_setscheme(drw, scheme[SchemeNorm]); drw_rect(drw, 0, 0, mw, mh, 1, 1); drw_text(drw, 0, 0, mw, bh, lrpad / 2, "Sys Apps Terminal", 0); XSync(dpy, False); } for (i = 0; i < initialindex; i++) { if (sel && sel->right && (sel = sel->right) == next) { curr = next; calcoffsets(); } } XSetLocaleModifiers(""); if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) { XSetLocaleModifiers("@im=local"); if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) { XSetLocaleModifiers("@im="); xim = XOpenIM(dpy, NULL, NULL, NULL); } } if (xim) { xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, win, XNFocusWindow, win, NULL); } if (!(nav = malloc(sizeof *nav))) die("cannot malloc %u bytes:", sizeof *nav); nav->out = 1; if (map_immediately) { mark_menu_active(1); XMapRaised(dpy, win); drawmenu(); XFlush(dpy); if (managed) { XTextProperty prop; char *windowtitle = prompt != NULL ? (char *)prompt : "dmenu"; Xutf8TextListToTextProperty(dpy, &windowtitle, 1, XUTF8StringStyle, &prop); XSetWMName(dpy, win, &prop); XSetTextProperty(dpy, win, &prop, XInternAtom(dpy, "_NET_WM_NAME", False)); XFree(prop.value); } else { grabkeyboard(); grabfocus(); } if (xic) XSetICFocus(xic); } } static void usage(void) { fputs("usage: dmenu [-bfiv] [-l lines] [-H height] [-p prompt] [-fn font] [-m monitor]\n" " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); exit(1); } static void prewarm_sxmo_caches(void) { if (fork() == 0) { if (dpy) close(ConnectionNumber(dpy)); int nullfd = open("/dev/null", O_RDWR); if (nullfd >= 0) { dup2(nullfd, 0); dup2(nullfd, 1); dup2(nullfd, 2); if (nullfd > 2) close(nullfd); } setsid(); execl("/bin/sh", "sh", "-c", "sxmo_hook_apps.sh >/dev/null 2>&1; sxmo_hook_scripts.sh >/dev/null 2>&1", (char *)NULL); _exit(0); } } static int dmenu_run_main(int argc, char *argv[]) { XWindowAttributes wa; int i; int preload = 0; const char *initial_builtin = NULL; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--preload")) { preload = 1; } else if (!strcmp(argv[i], "sys") || !strcmp(argv[i], "ctx") || !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")) { puts("dmenu-"VERSION); return 0; } else if (!strcmp(argv[i], "-b")) topbar = 0; else if (!strcmp(argv[i], "-f")) ; else if (!strcmp(argv[i], "-nc")) centered = 0; else if (!strcmp(argv[i], "-c")) centered = 1; else if (!strcmp(argv[i], "-i")) { fstrncmp = strncasecmp; fstrstr = cistrstr; } else if (!strcmp(argv[i], "-t")) use_text_input = 1; else if (!strcmp(argv[i], "-wm")) managed = 1; else if (i + 1 == argc) usage(); else if (!strcmp(argv[i], "-l")) lines = atoi(argv[++i]); else if (!strcmp(argv[i], "-H")) { lineheight = atoi(argv[++i]); lineheight = MAX(lineheight, min_lineheight); } else if (!strcmp(argv[i], "-m")) mon = atoi(argv[++i]); else if (!strcmp(argv[i], "-p")) prompt = argv[++i]; else if (!strcmp(argv[i], "-fn")) fonts[0] = strdup(argv[++i]); else if (!strcmp(argv[i], "-nb")) colors[SchemeNorm][ColBg] = strdup(argv[++i]); else if (!strcmp(argv[i], "-nf")) colors[SchemeNorm][ColFg] = strdup(argv[++i]); else if (!strcmp(argv[i], "-sb")) colors[SchemeSel][ColBg] = strdup(argv[++i]); else if (!strcmp(argv[i], "-sf")) colors[SchemeSel][ColFg] = strdup(argv[++i]); else if (!strcmp(argv[i], "-w")) embed = argv[++i]; else if (!strcmp(argv[i], "--index") || !strcmp(argv[i], "-I")) initialindex = atoi(argv[++i]); else if (!strcmp(argv[i], "-idx")) initialindex = atoi(argv[++i]) - 1; else usage(); } if (getenv("DMENU_WRAP_AROUND")) wrap_around = 1; const char *cur_path = getenv("PATH"); if (!cur_path || !strstr(cur_path, "/home/user/.local/bin")) { char new_path[1024]; snprintf(new_path, sizeof(new_path), "/home/user/.config/sxmo/hooks:/home/user/.local/bin:/usr/share/sxmo/default_hooks:%s", (cur_path && cur_path[0]) ? cur_path : "/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin"); setenv("PATH", new_path, 1); } int lock_fd = -1; int fifo_fd = -1; int sock_fd = -1; if (preload) { lock_fd = open(DMENU_LOCK_PATH, O_RDWR | O_CREAT | O_CLOEXEC, 0666); if (lock_fd < 0 || flock(lock_fd, LOCK_EX | LOCK_NB) != 0) { if (lock_fd >= 0) close(lock_fd); if (initial_builtin) { for (int wait_i = 0; wait_i < 40; wait_i++) { int ffd = open(DMENU_FIFO_PATH, O_WRONLY | O_NONBLOCK | O_CLOEXEC); if (ffd >= 0) { char msg[128]; int mlen = snprintf(msg, sizeof(msg), "OPEN %s\n", initial_builtin); (void)write(ffd, msg, mlen); close(ffd); break; } usleep(10000); } } return 0; } daemon_mode = 1; signal(SIGCHLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); int pfd = open(DMENU_PID_PATH, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); if (pfd >= 0) { char pbuf[32]; int plen = snprintf(pbuf, sizeof(pbuf), "%d\n", (int)getpid()); (void)write(pfd, pbuf, plen); close(pfd); } unlink(DMENU_FIFO_PATH); mkfifo(DMENU_FIFO_PATH, 0666); chmod(DMENU_FIFO_PATH, 0666); fifo_fd = open(DMENU_FIFO_PATH, O_RDWR | O_NONBLOCK | O_CLOEXEC); unlink(DMENU_SOCK_PATH); sock_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); if (sock_fd >= 0) { struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, DMENU_SOCK_PATH, sizeof(addr.sun_path) - 1); if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) { chmod(DMENU_SOCK_PATH, 0666); listen(sock_fd, 8); } } } if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fputs("warning: no locale support\n", stderr); XSetLocaleModifiers(""); if (!(dpy = XOpenDisplay(NULL))) die("cannot open display"); screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); if (!embed || !(parentwin = strtol(embed, NULL, 0))) parentwin = root; if (!XGetWindowAttributes(dpy, parentwin, &wa)) die("could not get embedding window attributes: 0x%lx", parentwin); drw = drw_create(dpy, screen, root, wa.width, wa.height); load_dmenu_xresources(); if (!fonts[0]) { fonts[0] = defaultfonts[0]; fonts[1] = defaultfonts[1]; } else { fonts[1] = defaultfonts[1]; } for (i = 0; i < SchemeLast; ++i) { if (!colors[i][ColBg]) colors[i][ColBg] = strdup(defaultcolors[i][ColBg]); if (!colors[i][ColFg]) colors[i][ColFg] = strdup(defaultcolors[i][ColFg]); } if (!drw_fontset_create(drw, fonts, 2)) die("no fonts could be loaded."); lrpad = drw->fonts->h / 2; if (preload) { setup(0); prewarm_sxmo_caches(); if (initial_builtin) open_builtin_menu(initial_builtin, 0); run_daemon_loop(fifo_fd, sock_fd); cleanup(); return 0; } readstdin(); setup(1); run_standalone(); return 1; } static int dmenu_plugin_init(const DwmHostApi *host) { if (host && host->spawn_plugin) host->spawn_plugin("dmenu", "--preload"); return 0; } static void dmenu_plugin_cleanup(void) { char buf[64] = ""; if (read_first_line(DMENU_PID_PATH, buf, sizeof(buf))) { int pid = atoi(buf); if (pid > 1) kill((pid_t)pid, SIGTERM); } unlink(DMENU_PID_PATH); unlink(DMENU_LOCK_PATH); unlink(DMENU_FIFO_PATH); unlink(DMENU_SOCK_PATH); unlink(DMENU_ACTIVE_PATH); } const DwmPlugin dwm_plugin_entry = { .api_version = DWM_PLUGIN_API_VERSION, .name = "dmenu", .description = "Zero-lag Sxmo & DWM menu plugin", .init = dmenu_plugin_init, .cleanup = dmenu_plugin_cleanup, .on_softkey_chord = NULL, .on_idle_key = NULL, .on_global_key = NULL, .run_main = dmenu_run_main, }; int main(int argc, char *argv[]) { return dmenu_run_main(argc, argv); }