Files
bananarchy/src/sxmo-dmenu/dmenu.c
T

2862 lines
78 KiB
C
Raw Normal View History

/* See LICENSE file for copyright and license details. */
#include <X11/XF86keysym.h>
#ifndef _EVDEVK
#define _EVDEVK(_v) (0x10081000 + (_v))
#endif
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <poll.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#ifdef XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#include <X11/Xft/Xft.h>
#include <X11/Xresource.h>
#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;
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 };
#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 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 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;
lines = default_lines;
use_text_input = 0;
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_name)
{
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";
if (write(fd, msg, strlen(msg)) > 0) {
close(fd);
return;
}
close(fd);
}
char cmd[128];
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, "autoscreenoff") == 0) {
const char *home = getenv("HOME");
if (!home || !home[0])
home = "/home/user";
char path[256];
snprintf(path, sizeof(path), "%s/.cache/sxmo/sxmo.noidle", 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);
}
spawn_detached_sh("sxmo_state.sh set unlock");
} else if (strcmp(toggle_name, "bluetooth") == 0) {
int rc = system("doas sxmo_bluetoothtoggle.sh >/dev/null 2>&1");
(void)rc;
spawn_detached_sh("sxmo_hook_statusbar.sh bluetooth");
}
}
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, "@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;
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], act[256];
snprintf(label, sizeof(label), "%s Incoming call %s", ICON_PHN, callid);
snprintf(act, sizeof(act), "sxmo_jobs.sh start incall_menu sxmo_modemcall.sh incoming_call_menu %s", callid);
add_menu_item(label, act, 0);
} else if (!shown_incall) {
shown_incall = 1;
char label[128];
snprintf(label, sizeof(label), "%s Incall Menu", ICON_PHN);
add_menu_item(label, "sxmo_jobs.sh start incall_menu sxmo_modemcall.sh incall_menu", 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
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) {
if (strstr(ttl, "nano"))
menu_name = "nano";
else
menu_name = "st";
} else {
hide_menu();
menu_mode = MODE_NONE;
snprintf(cmd, sizeof(cmd), "sxmo_appmenu.sh --fallback '%s' '%s'", cls, ttl);
spawn_detached_sh(cmd);
return;
}
}
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, "sxmo_notifs.sh menu", 0);
}
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 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, "sxmo_bluetoothmenu.sh", 0);
}
snprintf(label, sizeof(label), "%s Camera", ICON_CAM);
add_menu_item(label, "@plugin:camera", 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, "sxmo_networks.sh", 0);
snprintf(label, sizeof(label), "%s Audio", ICON_MUS);
add_menu_item(label, "sxmo_audio.sh", 0);
snprintf(label, sizeof(label), "%s Modem", ICON_PHN);
add_menu_item(label, "@menu:modem", 1);
snprintf(label, sizeof(label), "%s Windows", ICON_WIN);
add_menu_item(label, "sxmo_wmmenu.sh", 0);
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();
load_cache_or_hook("sxmo_apps.cache", "sxmo_hook_apps.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, "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 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 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, "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 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, "sxmo_timezonechange.sh", 0);
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(pbuf, sizeof(pbuf), "%s/.cache/sxmo/sxmo.noidle", home);
snprintf(label, sizeof(label), "%s Auto-screen-off %s", ICON_ZZZ, (access(pbuf, F_OK) == 0) ? ICON_TOF : ICON_TON);
add_menu_item(label, "@toggle:autoscreenoff", 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 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_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" },
{ "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" },
};
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 void
show_menu(void)
{
const int bar_h = 24;
int target_y;
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 && use_text_input) {
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 && use_text_input)
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 && use_text_input)
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 && use_text_input) {
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, "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) {
if (query_trigger_keys_down() == 0)
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) {
if (query_trigger_keys_down() == 0)
initial_released = 1;
continue;
}
if (!initial_released || dmenu_now_ms() < start_ignore)
continue;
}
if (use_text_input && !issoftkey(ev.xkey.keycode)) {
ev.xkey.window = win;
if (XFilterEvent(&ev, None))
continue;
}
} else if (use_text_input && 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 && use_text_input)
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]);
}
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], "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")) {
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;
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);
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);
}