3431 lines
99 KiB
C
3431 lines
99 KiB
C
/* 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; /* 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);
|
|
}
|