Preload DWM plugins (dialer, calllog, xdrawer) with resident X11/Pango state and FIFO IPC

This commit is contained in:
Luxferre
2026-10-21 11:57:23 +03:00
parent 0a02400505
commit 996e80b095
16 changed files with 753 additions and 407 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+203 -128
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
// CallLog DWM Plugin - Keypad-driven X11 call log viewer & dialer launcher (240x320)
// CallLog DWM Plugin - Preloaded resident X11 call log viewer & dialer launcher (240x320)
#include <X11/Xatom.h>
#include <X11/Xft/Xft.h>
@@ -37,27 +37,23 @@ void handleSignal(int) {
gRunning = 0;
}
bool isCallRinging() {
std::string ringPidFile = getRuntimeFile("sxmo.ring.pid");
int pid = readIntFile(ringPidFile, -1);
if (pid > 0 && ::kill(pid, 0) == 0) {
return true;
}
return ::system("pidof -s sxmo_RepeatRing.sh >/dev/null 2>&1") == 0;
}
int calllog_run_main(int, char**) {
int calllog_run_main(int argc, char** argv) {
gRunning = 1;
int64_t startMs = nowMs();
std::string pidPath = getRuntimeFile("xcalllog.pid");
int existingPid = readIntFile(pidPath, -1);
if (existingPid > 0 && existingPid != static_cast<int>(::getpid())) {
if (::kill(existingPid, 0) == 0) {
return 0;
bool isPreload = false;
for (int i = 1; i < argc; ++i) {
if (argv[i] && std::strcmp(argv[i], "--preload") == 0) {
isPreload = true;
}
}
writeIntFile(pidPath, static_cast<int>(::getpid()));
if (sendPluginCommand("calllog", isPreload ? "NOP" : "OPEN")) {
return 0;
}
std::string daemonPidPath = getPluginDaemonPidPath("calllog");
std::string activePidPath = getRuntimeFile("xcalllog.pid");
writeIntFile(daemonPidPath, static_cast<int>(::getpid()));
int fifoFd = setupPluginDaemonFifo("calllog");
struct sigaction sa {};
sa.sa_handler = handleSignal;
@@ -67,7 +63,8 @@ int calllog_run_main(int, char**) {
Display* dpy = XOpenDisplay(nullptr);
if (!dpy) {
::unlink(pidPath.c_str());
if (fifoFd >= 0) ::close(fifoFd);
::unlink(daemonPidPath.c_str());
return 1;
}
@@ -96,17 +93,6 @@ int calllog_run_main(int, char**) {
XSetClassHint(dpy, win, &ch);
XStoreName(dpy, win, "Call Log");
XMapRaised(dpy, win);
XSync(dpy, False);
for (int i = 0; i < 60; ++i) {
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
GrabSuccess) {
break;
}
usleep(5000);
}
Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen));
GC gc = XCreateGC(dpy, pix, 0, nullptr);
XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap);
@@ -144,12 +130,19 @@ int calllog_run_main(int, char**) {
XftColor colRed = allocColor("#e74c3c");
ContactBook book;
book.load();
std::vector<CallLogItem> allLogs = loadCallLog(book);
std::vector<CallLogItem> allLogs;
std::vector<CallLogItem> filtered;
FileStamp contactsStamp;
FileStamp logStamp;
int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out
int selected = 0;
bool visible = false;
int64_t openStartMs = 0;
int64_t startupIgnoreUntilMs = 0;
bool initialSoftkeysReleased = true;
int softkeyPending = 0;
int64_t softkeyDeadlineMs = 0;
auto applyFilter = [&]() {
filtered.clear();
@@ -164,6 +157,21 @@ int calllog_run_main(int, char**) {
}
};
bool logsLoadedOnce = false;
auto reloadLogsIfNeeded = [&]() -> bool {
bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath());
bool lChanged = logStamp.checkAndUpdate(getModemLogFilePath());
if (cChanged || lChanged || !logsLoadedOnce) {
logsLoadedOnce = true;
book.load();
allLogs = loadCallLog(book);
applyFilter();
return true;
}
return false;
};
reloadLogsIfNeeded();
applyFilter();
auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
@@ -194,7 +202,7 @@ int calllog_run_main(int, char**) {
constexpr int kHeaderH = 34;
constexpr int kSoftbarH = 26;
constexpr int kListY = kHeaderH;
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH; // 236px
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH;
constexpr int kVisibleRows = 5;
const char* kTabLabels[4] = {"All", "Missed", "In", "Out"};
@@ -203,7 +211,6 @@ int calllog_run_main(int, char**) {
XSetForeground(dpy, gc, 0x222222);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
// 1. Top Filter Tabs Bar (y = 0..34)
XSetForeground(dpy, gc, 0x1c1c1c);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH);
@@ -226,7 +233,6 @@ int calllog_run_main(int, char**) {
XSetForeground(dpy, gc, 0x444444);
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
// 2. Call Log List (y = 34..270, 5 visible rows)
if (filtered.empty()) {
drawPango(layoutIcon, &colMuted, 112, kListY + 60, 24, kIconPhoneLog);
drawPango(layoutMain, &colMuted, 52, kListY + 90, 28, "No call history", 160);
@@ -312,7 +318,6 @@ int calllog_run_main(int, char**) {
}
}
// 3. Bottom Softkey Bar (y = 270..296)
int sbY = kWinHeight - kSoftbarH;
XSetForeground(dpy, gc, 0x181818);
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, kSoftbarH);
@@ -329,31 +334,118 @@ int calllog_run_main(int, char**) {
drawPango(layoutSmall, &colLight, kWinWidth - rw - 6, sbY + 1,
kSoftbarH - 1, rightLabel);
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
XFlush(dpy);
if (visible) {
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
XFlush(dpy);
}
};
// Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup
drawPango(layoutIcon, &colGreen, 0, 0, 20,
std::string(kIconInCall) + kIconOutCall + kIconMissedCall + kIconPhoneLog);
render();
XSync(dpy, False);
auto showWindow = [&]() {
if (isPidFileAlive(getRuntimeFile("xdialer.pid"))) {
sendPluginCommand("dialer", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
sendPluginCommand("xdrawer", "CLOSE");
}
if (!visible) {
openStartMs = nowMs();
startupIgnoreUntilMs = openStartMs + 90;
initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
softkeyPending = 0;
softkeyDeadlineMs = 0;
visible = true;
XMapRaised(dpy, win);
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
for (int i = 0; i < 20; ++i) {
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
GrabSuccess) {
break;
}
usleep(2000);
}
if (reloadLogsIfNeeded() || filterTab != 0 || selected != 0) {
filterTab = 0;
selected = 0;
applyFilter();
render();
}
XFlush(dpy);
writeIntFile(activePidPath, static_cast<int>(::getpid()));
} else {
if (reloadLogsIfNeeded()) {
render();
}
}
};
auto hideWindow = [&](bool drainSoftkeys) {
if (!visible) {
return;
}
int xfd = ConnectionNumber(dpy);
if (drainSoftkeys) {
int64_t drainDeadline = nowMs() + 180;
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
}
struct pollfd pfd {};
pfd.fd = xfd;
pfd.events = POLLIN;
::poll(&pfd, 1, 10);
}
writeSoftkeySuppress(nowMs() + 300);
}
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(activePidPath.c_str());
visible = false;
softkeyPending = 0;
softkeyDeadlineMs = 0;
if (filterTab != 0 || selected != 0) {
filterTab = 0;
selected = 0;
applyFilter();
render();
XFlush(dpy);
}
};
if (!isPreload) {
showWindow();
}
int64_t startupIgnoreUntilMs = startMs + 140;
bool initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
int softkeyPending = 0;
int64_t softkeyDeadlineMs = 0;
bool closedByKey = false;
std::string numberToCall;
std::string dialerInitArg;
bool openDialerAfter = false;
bool launchXDrawer = false;
int xfd = ConnectionNumber(dpy);
while (gRunning) {
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
for (const std::string& cmd : readFifoLines(fifoFd)) {
if (cmd == "NOP") {
continue;
} else if (cmd == "CLOSE") {
hideWindow(false);
} else if (cmd.rfind("OPEN", 0) == 0) {
showWindow();
}
}
if (visible && !initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
initialSoftkeysReleased = true;
}
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
if (!visible) {
continue;
}
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
initialSoftkeysReleased = true;
}
@@ -391,24 +483,25 @@ int calllog_run_main(int, char**) {
softkeyPending |= which | querySoftkeysDown(dpy);
if (softkeyPending == 3) {
writeSoftkeySuppress(nowMs() + 500);
launchXDrawer = true;
closedByKey = true;
gRunning = 0;
hideWindow(false);
if (!sendPluginCommand("xdrawer", "TOGGLE")) {
runAsyncArgv("xdrawer");
}
break;
}
if (softkeyDeadlineMs == 0) {
softkeyDeadlineMs = nowMs() + 45;
softkeyDeadlineMs = nowMs() + 40;
}
continue;
}
// Pressing 0-9, *, or # in Call Log switches directly to Dialer with that char
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
if (dialCh != '\0') {
dialerInitArg = std::string(1, dialCh);
openDialerAfter = true;
closedByKey = true;
gRunning = 0;
std::string arg(1, dialCh);
hideWindow(false);
if (!sendPluginCommand("dialer", "OPEN " + arg)) {
runAsyncArgv("xdialer", arg.c_str());
}
break;
}
@@ -416,9 +509,8 @@ int calllog_run_main(int, char**) {
case XK_Escape:
case XK_BackSpace:
case XK_q:
writeSoftkeySuppress(nowMs() + 350);
closedByKey = true;
gRunning = 0;
writeSoftkeySuppress(nowMs() + 300);
hideWindow(true);
break;
case XK_Left:
case XK_KP_Left:
@@ -462,14 +554,15 @@ int calllog_run_main(int, char**) {
}
if (!filtered.empty() && selected >= 0 &&
selected < static_cast<int>(filtered.size())) {
numberToCall = filtered[selected].number;
writeSoftkeySuppress(nowMs() + 400);
closedByKey = true;
gRunning = 0;
std::string num = filtered[selected].number;
writeSoftkeySuppress(nowMs() + 350);
hideWindow(true);
placePhoneCall(num);
} else if (filtered.empty()) {
openDialerAfter = true;
closedByKey = true;
gRunning = 0;
hideWindow(false);
if (!sendPluginCommand("dialer", "OPEN")) {
runAsyncArgv("xdialer");
}
}
break;
default:
@@ -478,66 +571,59 @@ int calllog_run_main(int, char**) {
}
}
if (!gRunning) {
break;
}
if (softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
if (visible && softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
int comb = softkeyPending | querySoftkeysDown(dpy);
softkeyPending = 0;
softkeyDeadlineMs = 0;
if (comb == 3) {
writeSoftkeySuppress(nowMs() + 500);
launchXDrawer = true;
closedByKey = true;
gRunning = 0;
break;
hideWindow(false);
if (!sendPluginCommand("xdrawer", "TOGGLE")) {
runAsyncArgv("xdrawer");
}
} else if (comb == 1) {
// Left Softkey: open Dialer pre-populated with selected number
std::string arg;
if (!filtered.empty() && selected >= 0 &&
selected < static_cast<int>(filtered.size())) {
dialerInitArg = filtered[selected].number;
arg = filtered[selected].number;
}
hideWindow(false);
if (!sendPluginCommand("dialer", arg.empty() ? "OPEN" : ("OPEN " + arg))) {
if (arg.empty()) {
runAsyncArgv("xdialer");
} else {
runAsyncArgv("xdialer", arg.c_str());
}
}
openDialerAfter = true;
closedByKey = true;
gRunning = 0;
break;
} else if (comb == 2) {
// Right Softkey: close Call Log
writeSoftkeySuppress(nowMs() + 400);
closedByKey = true;
gRunning = 0;
break;
writeSoftkeySuppress(nowMs() + 350);
hideWindow(true);
}
}
int timeoutMs = 200;
if (softkeyDeadlineMs > 0) {
timeoutMs = static_cast<int>(std::max<int64_t>(5, softkeyDeadlineMs - nowMs()));
}
struct pollfd pfd {};
pfd.fd = xfd;
pfd.events = POLLIN;
::poll(&pfd, 1, timeoutMs);
}
if (closedByKey && !launchXDrawer && !openDialerAfter) {
int64_t drainDeadline = nowMs() + 200;
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
int timeoutMs = -1;
if (visible) {
timeoutMs = 200;
if (softkeyDeadlineMs > 0) {
timeoutMs = static_cast<int>(std::max<int64_t>(5, softkeyDeadlineMs - nowMs()));
}
struct pollfd pfd {};
pfd.fd = xfd;
pfd.events = POLLIN;
::poll(&pfd, 1, 15);
}
writeSoftkeySuppress(nowMs() + 350);
struct pollfd pfds[2] {};
pfds[0].fd = xfd;
pfds[0].events = POLLIN;
pfds[1].fd = fifoFd;
pfds[1].events = POLLIN;
::poll(pfds, (fifoFd >= 0) ? 2 : 1, timeoutMs);
}
XUngrabKeyboard(dpy, CurrentTime);
hideWindow(false);
if (fifoFd >= 0) {
::close(fifoFd);
}
::unlink(getPluginFifoPath("calllog").c_str());
::unlink(daemonPidPath.c_str());
pango_font_description_free(descIcon);
pango_font_description_free(descSmall);
pango_font_description_free(descMain);
@@ -550,24 +636,13 @@ int calllog_run_main(int, char**) {
XFreePixmap(dpy, pix);
XDestroyWindow(dpy, win);
XCloseDisplay(dpy);
::unlink(pidPath.c_str());
if (launchXDrawer) {
runAsyncArgv("xdrawer");
} else if (openDialerAfter) {
if (!dialerInitArg.empty()) {
runAsyncArgv("xdialer", dialerInitArg.c_str());
} else {
runAsyncArgv("xdialer");
}
} else if (!numberToCall.empty()) {
placePhoneCall(numberToCall);
}
return 0;
}
int calllog_plugin_init(const DwmHostApi*) {
int calllog_plugin_init(const DwmHostApi* host) {
if (host && host->spawn_plugin) {
host->spawn_plugin("calllog", "--preload");
}
return 0;
}
@@ -578,7 +653,7 @@ int calllog_on_global_key(const DwmHostApi* host, KeySym keysym,
return 0;
}
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
if (isCallRinging()) {
if (isCallRingingFast()) {
const char* const argv[] = {"sxmo_hook_pickup.sh", nullptr};
if (host && host->spawn_command) {
host->spawn_command(argv);
@@ -597,7 +672,7 @@ int calllog_on_idle_key(const DwmHostApi* host, KeySym keysym,
return 0;
}
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
if (isCallRinging()) {
if (isCallRingingFast()) {
const char* const argv[] = {"sxmo_hook_pickup.sh", nullptr};
if (host->spawn_command) {
host->spawn_command(argv);
+216 -172
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
// Dialer DWM Plugin - Keypad-driven X11 dialer with contact & T9 integration (240x320)
// Dialer DWM Plugin - Preloaded resident X11 dialer with contact & T9 integration (240x320)
#include <X11/Xatom.h>
#include <X11/Xft/Xft.h>
@@ -37,51 +37,28 @@ void handleSignal(int) {
gRunning = 0;
}
constexpr const char* kQueuePath = "/tmp/sxmo_dialer_queue";
void enqueueDialChars(const char* chars) {
if (!chars || !chars[0]) {
return;
}
int fd = ::open(kQueuePath, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0644);
if (fd >= 0) {
(void)::write(fd, chars, std::strlen(chars));
::close(fd);
}
}
std::string drainDialQueue() {
int fd = ::open(kQueuePath, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return "";
}
char buf[128];
ssize_t n = ::read(fd, buf, sizeof(buf) - 1);
::close(fd);
::unlink(kQueuePath);
if (n <= 0) {
return "";
}
buf[n] = '\0';
return normalizePhone(buf);
}
int dialer_run_main(int argc, char** argv) {
gRunning = 1;
int64_t startMs = nowMs();
bool isPreload = false;
std::string initArg;
if (argc > 1 && argv[1] && argv[1][0]) {
enqueueDialChars(argv[1]);
}
std::string pidPath = getRuntimeFile("xdialer.pid");
int existingPid = readIntFile(pidPath, -1);
if (existingPid > 0 && existingPid != static_cast<int>(::getpid())) {
if (::kill(existingPid, 0) == 0) {
return 0;
for (int i = 1; i < argc; ++i) {
if (argv[i] && std::strcmp(argv[i], "--preload") == 0) {
isPreload = true;
} else if (argv[i] && argv[i][0]) {
initArg += argv[i];
}
}
writeIntFile(pidPath, static_cast<int>(::getpid()));
// If the resident dialer daemon is already running, forward the command via FIFO
if (sendPluginCommand("dialer", isPreload ? "NOP" : (initArg.empty() ? "OPEN" : ("OPEN " + initArg)))) {
return 0;
}
std::string daemonPidPath = getPluginDaemonPidPath("dialer");
std::string activePidPath = getRuntimeFile("xdialer.pid");
writeIntFile(daemonPidPath, static_cast<int>(::getpid()));
int fifoFd = setupPluginDaemonFifo("dialer");
struct sigaction sa {};
sa.sa_handler = handleSignal;
@@ -91,7 +68,8 @@ int dialer_run_main(int argc, char** argv) {
Display* dpy = XOpenDisplay(nullptr);
if (!dpy) {
::unlink(pidPath.c_str());
if (fifoFd >= 0) ::close(fifoFd);
::unlink(daemonPidPath.c_str());
return 1;
}
@@ -120,17 +98,6 @@ int dialer_run_main(int argc, char** argv) {
XSetClassHint(dpy, win, &ch);
XStoreName(dpy, win, "Dialer");
XMapRaised(dpy, win);
XSync(dpy, False);
for (int i = 0; i < 60; ++i) {
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
GrabSuccess) {
break;
}
usleep(5000);
}
Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen));
GC gc = XCreateGC(dpy, pix, 0, nullptr);
XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap);
@@ -172,17 +139,36 @@ int dialer_run_main(int argc, char** argv) {
XftColor colBlue = allocColor("#5dade2");
ContactBook book;
book.load();
std::vector<ContactEntry> allContacts = book.buildDialerList();
std::vector<ContactEntry> allContacts;
std::vector<ContactEntry> filtered;
FileStamp contactsStamp;
FileStamp logStamp;
bool contactsLoadedOnce = false;
std::string number = drainDialQueue();
int selected = -1; // -1 = top number box focused; 0..N-1 = contact row
auto reloadContactsIfNeeded = [&]() -> bool {
bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath());
bool lChanged = logStamp.checkAndUpdate(getModemLogFilePath());
if (cChanged || lChanged || !contactsLoadedOnce) {
contactsLoadedOnce = true;
book.load();
allContacts = book.buildDialerList();
return true;
}
return false;
};
// If launched with '0' from idle desktop and '0' (keycode 19) is still held,
// allow long-pressing that initial '0' to turn into '+'.
bool zeroHeld = (number == "0" && isKeycodeDown(dpy, 19));
int64_t zeroPressMs = zeroHeld ? startMs : 0;
reloadContactsIfNeeded();
std::string number;
int selected = -1;
bool visible = false;
int64_t openStartMs = 0;
int64_t startupIgnoreUntilMs = 0;
bool initialSoftkeysReleased = true;
int softkeyPending = 0;
int64_t softkeyDeadlineMs = 0;
bool zeroHeld = false;
int64_t zeroPressMs = 0;
auto updateFilter = [&]() {
filtered.clear();
@@ -223,11 +209,6 @@ int dialer_run_main(int argc, char** argv) {
}
};
updateFilter();
if (!number.empty()) {
selected = -1;
}
auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
const std::string& text, int maxW = -1) {
pango_layout_set_text(layout, text.c_str(), static_cast<int>(text.size()));
@@ -256,14 +237,13 @@ int dialer_run_main(int argc, char** argv) {
constexpr int kHeaderH = 56;
constexpr int kSoftbarH = 26;
constexpr int kListY = kHeaderH;
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH; // 214px
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH;
constexpr int kVisibleRows = 5;
auto render = [&]() {
XSetForeground(dpy, gc, 0x222222);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
// 1. Top Number Display Box (y = 0..56)
bool topSel = (selected < 0);
XSetForeground(dpy, gc, topSel ? 0x1b3b4b : 0x1c1c1c);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH);
@@ -287,7 +267,6 @@ int dialer_run_main(int argc, char** argv) {
drawPango(layoutIcon, &colMuted, 218, 4, 30, kIconBackspace);
}
// Subtitle inside header box (y = 33..54)
std::string subText;
XftColor* subCol = &colMuted;
if (selected >= 0 && selected < static_cast<int>(filtered.size())) {
@@ -310,7 +289,6 @@ int dialer_run_main(int argc, char** argv) {
}
drawPango(layoutSmall, subCol, 32, 33, 20, subText, 202);
// 2. Contact / Recent Suggestions List (y = 56..270, 5 visible rows)
if (filtered.empty()) {
drawPango(layoutMain, &colMuted, 16, kListY + 40, 30,
number.empty() ? "No saved contacts" : "New number (press Call)",
@@ -372,7 +350,6 @@ int dialer_run_main(int argc, char** argv) {
}
}
// 3. Bottom Softkey Bar (y = 270..296)
int sbY = kWinHeight - kSoftbarH;
XSetForeground(dpy, gc, 0x181818);
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, kSoftbarH);
@@ -391,20 +368,111 @@ int dialer_run_main(int argc, char** argv) {
drawPango(layoutSmall, &colLight, kWinWidth - rw - 6, sbY + 1,
kSoftbarH - 1, rightLabel);
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
XFlush(dpy);
if (visible) {
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
XFlush(dpy);
}
};
// Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup
number = "+0123456789*#";
updateFilter();
render();
drawPango(layoutIcon, &colGreen, 0, 0, 20,
std::string(kIconPhone) + kIconBackspace + kIconClock + kIconUser);
number.clear();
updateFilter();
render();
XSync(dpy, False);
auto showWindow = [&](const std::string& arg) {
if (isPidFileAlive(getRuntimeFile("xcalllog.pid"))) {
sendPluginCommand("calllog", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
sendPluginCommand("xdrawer", "CLOSE");
}
std::string cleanArg = normalizePhone(arg);
if (!visible) {
openStartMs = nowMs();
startupIgnoreUntilMs = openStartMs + 70;
initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
softkeyPending = 0;
softkeyDeadlineMs = 0;
zeroHeld = (cleanArg == "0" && isKeycodeDown(dpy, 19));
zeroPressMs = zeroHeld ? openStartMs : 0;
visible = true;
XMapRaised(dpy, win);
if (cleanArg.empty()) {
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
}
for (int i = 0; i < 20; ++i) {
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
GrabSuccess) {
break;
}
usleep(2000);
}
bool reloaded = reloadContactsIfNeeded();
if (!cleanArg.empty() || reloaded || number != cleanArg) {
number = cleanArg;
selected = number.empty() ? (allContacts.empty() ? -1 : 0) : -1;
updateFilter();
render();
}
XFlush(dpy);
writeIntFile(activePidPath, static_cast<int>(::getpid()));
} else if (!cleanArg.empty()) {
if (nowMs() - openStartMs < 40 && number == cleanArg) {
return;
}
number += cleanArg;
selected = -1;
updateFilter();
render();
}
};
auto hideWindow = [&](bool drainSoftkeys) {
if (!visible) {
return;
}
int xfd = ConnectionNumber(dpy);
if (drainSoftkeys) {
int64_t drainDeadline = nowMs() + 180;
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
}
struct pollfd pfd {};
pfd.fd = xfd;
pfd.events = POLLIN;
::poll(&pfd, 1, 10);
}
writeSoftkeySuppress(nowMs() + 300);
}
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(activePidPath.c_str());
visible = false;
softkeyPending = 0;
softkeyDeadlineMs = 0;
zeroHeld = false;
if (!number.empty() || selected != (allContacts.empty() ? -1 : 0)) {
number.clear();
selected = allContacts.empty() ? -1 : 0;
updateFilter();
render();
XFlush(dpy);
}
};
if (!isPreload) {
showWindow(initArg);
}
int64_t startupIgnoreUntilMs = startMs + 90;
bool initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
int softkeyPending = 0;
int64_t softkeyDeadlineMs = 0;
bool closedByKey = false;
std::string targetToDial;
bool switchToCallLog = false;
bool launchXDrawer = false;
int xfd = ConnectionNumber(dpy);
auto selectActiveNumber = [&]() -> std::string {
@@ -421,15 +489,20 @@ int dialer_run_main(int argc, char** argv) {
};
while (gRunning) {
std::string extra = drainDialQueue();
if (!extra.empty()) {
number += extra;
selected = -1;
updateFilter();
render();
// 1. Process any incoming FIFO commands
for (const std::string& cmd : readFifoLines(fifoFd)) {
if (cmd == "NOP") {
continue;
} else if (cmd == "CLOSE") {
hideWindow(false);
} else if (cmd == "OPEN") {
showWindow("");
} else if (cmd.rfind("OPEN ", 0) == 0) {
showWindow(cmd.substr(5));
}
}
if (zeroHeld) {
if (visible && zeroHeld) {
if (!isKeycodeDown(dpy, 19)) {
zeroHeld = false;
} else if (nowMs() - zeroPressMs >= 420) {
@@ -442,13 +515,16 @@ int dialer_run_main(int argc, char** argv) {
}
}
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
if (visible && !initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
initialSoftkeysReleased = true;
}
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
if (!visible) {
continue;
}
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
initialSoftkeysReleased = true;
}
@@ -489,22 +565,22 @@ int dialer_run_main(int argc, char** argv) {
softkeyPending |= which | querySoftkeysDown(dpy);
if (softkeyPending == 3) {
writeSoftkeySuppress(nowMs() + 500);
launchXDrawer = true;
closedByKey = true;
gRunning = 0;
hideWindow(false);
if (!sendPluginCommand("xdrawer", "TOGGLE")) {
runAsyncArgv("xdrawer");
}
break;
}
if (softkeyDeadlineMs == 0) {
softkeyDeadlineMs = nowMs() + 45;
softkeyDeadlineMs = nowMs() + 40;
}
continue;
}
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
if (dialCh != '\0') {
if (nowMs() - startMs < 45 && number.size() == 1 &&
if (nowMs() - openStartMs < 40 && number.size() == 1 &&
number[0] == dialCh) {
// Ignore duplicate event from the initial key press that opened dialer
continue;
}
number.push_back(dialCh);
@@ -522,9 +598,8 @@ int dialer_run_main(int argc, char** argv) {
switch (sym) {
case XK_Escape:
writeSoftkeySuppress(nowMs() + 350);
closedByKey = true;
gRunning = 0;
writeSoftkeySuppress(nowMs() + 300);
hideWindow(true);
break;
case XK_BackSpace:
case XK_Left:
@@ -535,8 +610,7 @@ int dialer_run_main(int argc, char** argv) {
updateFilter();
render();
} else if (sym == XK_BackSpace) {
closedByKey = true;
gRunning = 0;
hideWindow(true);
}
break;
case XK_Right:
@@ -582,10 +656,9 @@ int dialer_run_main(int argc, char** argv) {
case XF86XK_PickupPhone: {
std::string target = selectActiveNumber();
if (!target.empty()) {
targetToDial = target;
writeSoftkeySuppress(nowMs() + 400);
closedByKey = true;
gRunning = 0;
writeSoftkeySuppress(nowMs() + 350);
hideWindow(true);
placePhoneCall(target);
}
break;
}
@@ -595,28 +668,23 @@ int dialer_run_main(int argc, char** argv) {
}
}
if (!gRunning) {
break;
}
if (softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
if (visible && softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
int comb = softkeyPending | querySoftkeysDown(dpy);
softkeyPending = 0;
softkeyDeadlineMs = 0;
if (comb == 3) {
writeSoftkeySuppress(nowMs() + 500);
launchXDrawer = true;
closedByKey = true;
gRunning = 0;
break;
hideWindow(false);
if (!sendPluginCommand("xdrawer", "TOGGLE")) {
runAsyncArgv("xdrawer");
}
} else if (comb == 1) {
// Left Softkey: if empty, switch to Call Log; if non-empty, toggle leading '+'
if (number.empty()) {
writeSoftkeySuppress(nowMs() + 350);
switchToCallLog = true;
closedByKey = true;
gRunning = 0;
break;
writeSoftkeySuppress(nowMs() + 250);
hideWindow(false);
if (!sendPluginCommand("calllog", "OPEN")) {
runAsyncArgv("xcalllog");
}
} else {
if (number[0] == '+') {
number.erase(0, 1);
@@ -628,50 +696,43 @@ int dialer_run_main(int argc, char** argv) {
render();
}
} else if (comb == 2) {
// Right Softkey: delete last character if non-empty, else close Dialer
if (!number.empty()) {
number.pop_back();
selected = -1;
updateFilter();
render();
} else {
writeSoftkeySuppress(nowMs() + 400);
closedByKey = true;
gRunning = 0;
break;
writeSoftkeySuppress(nowMs() + 350);
hideWindow(true);
}
}
}
int timeoutMs = 200;
if (softkeyDeadlineMs > 0) {
timeoutMs = static_cast<int>(std::max<int64_t>(5, softkeyDeadlineMs - nowMs()));
} else if (zeroHeld) {
timeoutMs = 25;
}
struct pollfd pfd {};
pfd.fd = xfd;
pfd.events = POLLIN;
::poll(&pfd, 1, timeoutMs);
}
if (closedByKey && !launchXDrawer) {
int64_t drainDeadline = nowMs() + 200;
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
int timeoutMs = -1;
if (visible) {
timeoutMs = 200;
if (softkeyDeadlineMs > 0) {
timeoutMs = static_cast<int>(std::max<int64_t>(5, softkeyDeadlineMs - nowMs()));
} else if (zeroHeld) {
timeoutMs = 25;
}
struct pollfd pfd {};
pfd.fd = xfd;
pfd.events = POLLIN;
::poll(&pfd, 1, 15);
}
writeSoftkeySuppress(nowMs() + 350);
struct pollfd pfds[2] {};
pfds[0].fd = xfd;
pfds[0].events = POLLIN;
pfds[1].fd = fifoFd;
pfds[1].events = POLLIN;
::poll(pfds, (fifoFd >= 0) ? 2 : 1, timeoutMs);
}
XUngrabKeyboard(dpy, CurrentTime);
hideWindow(false);
if (fifoFd >= 0) {
::close(fifoFd);
}
::unlink(getPluginFifoPath("dialer").c_str());
::unlink(daemonPidPath.c_str());
pango_font_description_free(descIcon);
pango_font_description_free(descSmall);
pango_font_description_free(descMain);
@@ -686,22 +747,13 @@ int dialer_run_main(int argc, char** argv) {
XFreePixmap(dpy, pix);
XDestroyWindow(dpy, win);
XCloseDisplay(dpy);
::unlink(pidPath.c_str());
if (launchXDrawer) {
runAsyncArgv("xdrawer");
} else if (switchToCallLog) {
runAsyncArgv("xcalllog");
} else if (!targetToDial.empty()) {
placePhoneCall(targetToDial);
}
return 0;
}
int64_t gLastSpawnMs = 0;
int dialer_plugin_init(const DwmHostApi*) {
int dialer_plugin_init(const DwmHostApi* host) {
if (host && host->spawn_plugin) {
host->spawn_plugin("dialer", "--preload");
}
return 0;
}
@@ -715,16 +767,8 @@ int dialer_on_idle_key(const DwmHostApi* host, KeySym keysym,
return 0;
}
char buf[2] = {ch, '\0'};
enqueueDialChars(buf);
int64_t now = nowMs();
std::string pidPath = getRuntimeFile("xdialer.pid");
int existingPid = readIntFile(pidPath, -1);
bool alreadyRunning = (existingPid > 0 && ::kill(existingPid, 0) == 0);
if (!alreadyRunning && (now - gLastSpawnMs) > 250 && host->spawn_plugin) {
gLastSpawnMs = now;
host->spawn_plugin("dialer", nullptr);
if (host->spawn_plugin) {
host->spawn_plugin("dialer", buf);
}
return 1;
}
+45
View File
@@ -1,5 +1,7 @@
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -8,6 +10,45 @@
#include "../sxmo-dwm/dwm_plugin.h"
static int try_send_plugin_fifo(const char *plugin_name, int sub_argc, char **sub_argv) {
if (sub_argc >= 2 && sub_argv[1] && strcmp(sub_argv[1], "--preload") == 0) {
return 0;
}
char pid_path[128];
snprintf(pid_path, sizeof(pid_path), "/tmp/dwm_plugin_%s.pid", plugin_name);
FILE *pf = fopen(pid_path, "r");
if (!pf) {
return 0;
}
int pid = 0;
int ok = (fscanf(pf, "%d", &pid) == 1 && pid > 1 && kill((pid_t)pid, 0) == 0);
fclose(pf);
if (!ok) {
return 0;
}
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) {
return 0;
}
char cmd[256];
int len = 0;
if (strcmp(plugin_name, "xdrawer") == 0) {
len = snprintf(cmd, sizeof(cmd), "TOGGLE\n");
} else if (sub_argc >= 2 && sub_argv[1] && sub_argv[1][0]) {
len = snprintf(cmd, sizeof(cmd), "OPEN %s\n", sub_argv[1]);
} else {
len = snprintf(cmd, sizeof(cmd), "OPEN\n");
}
ssize_t wr = (len > 0 && len < (int)sizeof(cmd)) ? write(fd, cmd, (size_t)len) : -1;
close(fd);
return wr > 0 ? 1 : 0;
}
static const char *const kPluginDirs[] = {
"/home/user/.local/lib/dwm/plugins",
"/usr/lib/dwm/plugins",
@@ -79,6 +120,10 @@ int main(int argc, char **argv) {
sub_argv = argv + 1;
}
if (try_send_plugin_fifo(plugin_name, sub_argc, sub_argv)) {
return 0;
}
const char *env_dir = getenv("DWM_PLUGIN_PATH");
char path[512];
void *handle = NULL;
+98
View File
@@ -10,6 +10,7 @@
#include <algorithm>
#include <cctype>
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
@@ -87,6 +88,103 @@ inline bool writeIntFile(const std::string& path, int value) {
return w == len;
}
inline bool isPidFileAlive(const std::string& path) {
int pid = readIntFile(path, -1);
return (pid > 0 && ::kill(pid, 0) == 0);
}
inline std::string getPluginFifoPath(const char* name) {
return std::string("/tmp/dwm_plugin_") + name + ".fifo";
}
inline std::string getPluginDaemonPidPath(const char* name) {
return std::string("/tmp/dwm_plugin_") + name + ".pid";
}
inline bool sendPluginCommand(const char* name, const std::string& cmdLine) {
int pid = readIntFile(getPluginDaemonPidPath(name), -1);
if (pid <= 0 || pid == static_cast<int>(::getpid()) || ::kill(pid, 0) != 0) {
return false;
}
std::string fifoPath = getPluginFifoPath(name);
int fd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC);
if (fd < 0) {
return false;
}
std::string msg = cmdLine + "\n";
ssize_t w = ::write(fd, msg.data(), msg.size());
::close(fd);
return w == static_cast<ssize_t>(msg.size());
}
inline int setupPluginDaemonFifo(const char* name) {
std::string fifoPath = getPluginFifoPath(name);
::mkfifo(fifoPath.c_str(), 0666);
::chmod(fifoPath.c_str(), 0666);
return ::open(fifoPath.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC);
}
inline std::vector<std::string> readFifoLines(int fifoFd) {
std::vector<std::string> lines;
if (fifoFd < 0) {
return lines;
}
char buf[512];
ssize_t n = ::read(fifoFd, buf, sizeof(buf) - 1);
if (n <= 0) {
return lines;
}
buf[n] = '\0';
std::stringstream ss(buf);
std::string line;
while (std::getline(ss, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (!line.empty()) {
lines.push_back(line);
}
}
return lines;
}
inline bool isCallRingingFast() {
std::string jobPath = getRuntimeFile("sxmo_jobs/ringing");
if (::access(jobPath.c_str(), F_OK) == 0) {
return true;
}
std::string ringPidFile = getRuntimeFile("sxmo.ring.pid");
int pid = readIntFile(ringPidFile, -1);
return (pid > 0 && ::kill(pid, 0) == 0);
}
struct FileStamp {
time_t sec = -1;
long nsec = -1;
off_t size = -1;
bool checkAndUpdate(const std::string& path) {
struct stat st {};
if (::stat(path.c_str(), &st) != 0) {
if (sec != 0 || size != 0) {
sec = 0;
nsec = 0;
size = 0;
return true;
}
return false;
}
if (st.st_mtim.tv_sec != sec || st.st_mtim.tv_nsec != nsec ||
st.st_size != size) {
sec = st.st_mtim.tv_sec;
nsec = st.st_mtim.tv_nsec;
size = st.st_size;
return true;
}
return false;
}
};
inline void writeSoftkeySuppress(int64_t untilMs) {
int fd = ::open("/tmp/sxmo_softkey_suppress",
O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
+40 -3
View File
@@ -1572,19 +1572,56 @@ host_spawn_command(const char *const *argv)
spawn(&a);
}
static int
is_plugin_daemon_alive(const char *plugin_name)
{
char pidpath[128], buf[32];
snprintf(pidpath, sizeof(pidpath), "/tmp/dwm_plugin_%s.pid", plugin_name);
read_sysfs_str(pidpath, buf, sizeof(buf));
int pid = atoi(buf);
return (pid > 0 && kill(pid, 0) == 0);
}
static void
host_spawn_plugin(const char *plugin_name, const char *arg)
{
if (!plugin_name || !plugin_name[0])
return;
if (arg && strcmp(arg, "--preload") == 0) {
if (is_plugin_daemon_alive(plugin_name))
return;
const char *cmd[4] = { "dwm-plugin", plugin_name, "--preload", NULL };
Arg a = { .v = cmd };
spawn(&a);
return;
}
XUngrabKeyboard(dpy, CurrentTime);
XFlush(dpy);
if (is_plugin_daemon_alive(plugin_name)) {
char fifopath[128], msg[128];
snprintf(fifopath, sizeof(fifopath), "/tmp/dwm_plugin_%s.fifo", plugin_name);
int fd = open(fifopath, O_WRONLY | O_NONBLOCK | O_CLOEXEC);
if (fd >= 0) {
int len;
if (strcmp(plugin_name, "xdrawer") == 0)
len = snprintf(msg, sizeof(msg), "TOGGLE\n");
else if (arg && arg[0])
len = snprintf(msg, sizeof(msg), "OPEN %s\n", arg);
else
len = snprintf(msg, sizeof(msg), "OPEN\n");
if (write(fd, msg, len) == len) {
close(fd);
return;
}
close(fd);
}
}
const char *cmd[4];
cmd[0] = "dwm-plugin";
cmd[1] = plugin_name;
cmd[2] = (arg && arg[0]) ? arg : NULL;
cmd[3] = NULL;
Arg a = { .v = cmd };
XUngrabKeyboard(dpy, CurrentTime);
XFlush(dpy);
spawn(&a);
}
@@ -2849,7 +2886,7 @@ unmapnotify(XEvent *e)
setclientstate(c, WithdrawnState);
else
unmanage(c, 0);
} else if (selmon && !selmon->sel) {
} else {
focus(NULL);
}
}
+151 -104
View File
@@ -26,6 +26,7 @@
#include <ctime>
#include <fcntl.h>
#include <poll.h>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <unistd.h>
@@ -435,55 +436,47 @@ class PhoneController {
} // namespace
static int xdrawer_run_main(int, char**) {
static int xdrawer_run_main(int argc, char** argv) {
gRunning = 1;
int64_t startMs = nowMs();
bool isPreload = false;
for (int i = 1; i < argc; ++i) {
if (argv[i] && std::strcmp(argv[i], "--preload") == 0) {
isPreload = true;
}
}
std::string daemonPidPath = "/tmp/dwm_plugin_xdrawer.pid";
std::string fifoPath = "/tmp/dwm_plugin_xdrawer.fifo";
std::string pidPath = getRuntimeFile("xdrawer.pid");
std::string startPath = getRuntimeFile("xdrawer.start");
int existingPid = readIntFile(pidPath, -1);
if (existingPid > 0 && existingPid != static_cast<int>(::getpid())) {
if (::kill(existingPid, 0) == 0) {
int fd = ::open(startPath.c_str(), O_RDONLY | O_CLOEXEC);
int64_t prevStartMs = 0;
if (fd >= 0) {
char buf[64];
ssize_t n = ::read(fd, buf, sizeof(buf) - 1);
::close(fd);
if (n > 0) {
buf[n] = '\0';
prevStartMs = std::atoll(buf);
}
int existingDaemon = readIntFile(daemonPidPath, -1);
if (existingDaemon > 0 && existingDaemon != static_cast<int>(::getpid()) &&
::kill(existingDaemon, 0) == 0) {
if (!isPreload) {
int ffd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC);
if (ffd >= 0) {
(void)::write(ffd, "TOGGLE\n", 7);
::close(ffd);
}
if (startMs - prevStartMs < 480) {
return 0;
}
::kill(existingPid, SIGTERM);
return 0;
}
}
writeIntFile(pidPath, static_cast<int>(::getpid()));
{
int fd = ::open(startPath.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, 0644);
if (fd >= 0) {
char buf[32];
int len = std::snprintf(buf, sizeof(buf), "%lld\n",
static_cast<long long>(startMs));
(void)::write(fd, buf, len);
::close(fd);
}
return 0;
}
writeIntFile(daemonPidPath, static_cast<int>(::getpid()));
::mkfifo(fifoPath.c_str(), 0666);
::chmod(fifoPath.c_str(), 0666);
int fifoFd = ::open(fifoPath.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC);
struct sigaction sa {};
sa.sa_handler = handleSignal;
sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, nullptr);
sigaction(SIGINT, &sa, nullptr);
runAsyncCmd("pkill -x dmenu >/dev/null 2>&1");
Display* dpy = XOpenDisplay(nullptr);
if (!dpy) {
::unlink(pidPath.c_str());
if (fifoFd >= 0) ::close(fifoFd);
::unlink(daemonPidPath.c_str());
return 1;
}
@@ -512,22 +505,10 @@ static int xdrawer_run_main(int, char**) {
XSetClassHint(dpy, win, &ch);
XStoreName(dpy, win, "XDrawer");
XMapRaised(dpy, win);
XSync(dpy, False);
for (int i = 0; i < 60; ++i) {
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
GrabSuccess) {
break;
}
usleep(5000);
}
Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen));
GC gc = XCreateGC(dpy, pix, 0, nullptr);
XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap);
// Use the exact same Pango+Xft font stack as dwm ("Fira Code, DejaVuSansM Nerd Font 15px")
PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen);
PangoContext* pangoCtx = pango_font_map_create_context(fontMap);
PangoLayout* layoutMain = pango_layout_new(pangoCtx);
@@ -559,6 +540,10 @@ static int xdrawer_run_main(int, char**) {
PhoneController controller;
size_t selected = 0;
bool visible = false;
int64_t openStartMs = 0;
int64_t startupIgnoreUntilMs = 0;
bool initialKeysReleased = true;
auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
const std::string& text, int maxW = -1) {
@@ -589,7 +574,6 @@ static int xdrawer_run_main(int, char**) {
XSetForeground(dpy, gc, 0x222222);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
// 7 Control rows filling the full 296px height flush from y=24 to y=320
const auto& items = controller.items();
for (size_t i = 0; i < items.size(); ++i) {
int ry = static_cast<int>((i * kWinHeight) / kRowCount);
@@ -609,7 +593,6 @@ static int xdrawer_run_main(int, char**) {
bool activeState = (it.value > 0);
const char* rowIcon = activeState ? it.iconOn : it.iconOff;
// Icon column (x = 8..28) + Label column (x = 30..112 for sliders, 30..166 for toggles)
int labelMaxW = it.isSlider ? 82 : 136;
drawPango(layoutIcon, isSel ? &colYellow : &colLight, 8, ry, rowH, rowIcon);
drawPango(layoutMain, isSel ? &colWhite : &colLight, 30, ry, rowH,
@@ -627,7 +610,6 @@ static int xdrawer_run_main(int, char**) {
}
pct = std::clamp(pct, 0, 100);
// Slider track (x = 114, w = 78, h = 12) + thumb + right-aligned pct (x = 196..234)
int barX = 114;
int barW = 78;
int barH = 12;
@@ -644,7 +626,6 @@ static int xdrawer_run_main(int, char**) {
XFillRectangle(dpy, pix, gc, barX + 1, barY + 1, fillW, barH - 2);
}
// Slider thumb knob
int knobX = barX + std::clamp(fillW - 2, 0, barW - 5);
XSetForeground(dpy, gc, 0xffffff);
XFillRectangle(dpy, pix, gc, knobX, barY - 2, 5, barH + 4);
@@ -656,7 +637,6 @@ static int xdrawer_run_main(int, char**) {
drawPango(layoutSmall, isSel ? &colWhite : &colLight, kWinWidth - pw - 6,
ry, rowH, pctStr);
} else {
// Toggle switch icon + ON/OFF label (right-aligned within x = 160..234)
bool on = (it.value != 0);
std::string stStr = on ? "ON" : "OFF";
int stW = pangoWidth(layoutMain, stStr);
@@ -668,11 +648,15 @@ static int xdrawer_run_main(int, char**) {
}
}
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
XFlush(dpy);
if (visible) {
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
XFlush(dpy);
}
};
// Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup
render();
XSync(dpy, False);
auto writeSoftkeySuppress = [](int64_t untilMs) {
int fd = ::open("/tmp/sxmo_softkey_suppress",
@@ -701,27 +685,108 @@ static int xdrawer_run_main(int, char**) {
return mask;
};
// Drain any stale key events that arrived before initial render finished
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
if (ev.type == Expose && ev.xexpose.count == 0) {
render();
}
}
int64_t startupIgnoreUntilMs = std::max(startMs + 220, nowMs() + 60);
bool initialKeysReleased = (querySoftkeysDown(dpy) == 0);
bool closedByKey = false;
int xfd = ConnectionNumber(dpy);
auto closeOtherPlugin = [](const char* activePidName, const char* fifoPathStr) {
if (readIntFile(getRuntimeFile(activePidName), -1) > 0) {
int ffd = ::open(fifoPathStr, O_WRONLY | O_NONBLOCK | O_CLOEXEC);
if (ffd >= 0) {
(void)::write(ffd, "CLOSE\n", 6);
::close(ffd);
}
}
};
auto showWindow = [&]() {
if (visible) {
return;
}
closeOtherPlugin("xdialer.pid", "/tmp/dwm_plugin_dialer.fifo");
closeOtherPlugin("xcalllog.pid", "/tmp/dwm_plugin_calllog.fifo");
openStartMs = nowMs();
startupIgnoreUntilMs = openStartMs + 180;
initialKeysReleased = (querySoftkeysDown(dpy) == 0);
visible = true;
XMapRaised(dpy, win);
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
for (int i = 0; i < 20; ++i) {
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
GrabSuccess) {
break;
}
usleep(2000);
}
XFlush(dpy);
writeIntFile(pidPath, static_cast<int>(::getpid()));
controller.refreshAll();
render();
runAsyncCmd("pkill -x dmenu >/dev/null 2>&1");
};
auto hideWindow = [&](bool drainSoftkeys) {
if (!visible) {
return;
}
if (drainSoftkeys) {
int64_t drainDeadline = nowMs() + 220;
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
}
struct pollfd pfd {};
pfd.fd = xfd;
pfd.events = POLLIN;
::poll(&pfd, 1, 10);
}
writeSoftkeySuppress(nowMs() + 350);
}
XUngrabKeyboard(dpy, CurrentTime);
XUnmapWindow(dpy, win);
XFlush(dpy);
::unlink(pidPath.c_str());
visible = false;
};
if (!isPreload) {
showWindow();
}
while (gRunning) {
if (!initialKeysReleased && querySoftkeysDown(dpy) == 0) {
if (fifoFd >= 0) {
char fbuf[256];
ssize_t fn = ::read(fifoFd, fbuf, sizeof(fbuf) - 1);
if (fn > 0) {
fbuf[fn] = '\0';
std::stringstream ss(fbuf);
std::string cmd;
while (std::getline(ss, cmd)) {
if (!cmd.empty() && cmd.back() == '\r') cmd.pop_back();
if (cmd == "TOGGLE") {
if (!visible) {
showWindow();
} else if (nowMs() - openStartMs >= 350) {
hideWindow(true);
}
} else if (cmd == "OPEN") {
showWindow();
} else if (cmd == "CLOSE") {
hideWindow(false);
}
}
}
}
if (visible && !initialKeysReleased && querySoftkeysDown(dpy) == 0) {
initialKeysReleased = true;
}
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
if (!visible) {
continue;
}
if (!initialKeysReleased && querySoftkeysDown(dpy) == 0) {
initialKeysReleased = true;
}
@@ -751,9 +816,8 @@ static int xdrawer_run_main(int, char**) {
sym == XF86XK_Back || sym == XF86XK_KbdLcdMenu2 ||
ev.xkey.keycode == 135 || ev.xkey.keycode == 166) {
if (initialKeysReleased && nowMs() >= startupIgnoreUntilMs) {
writeSoftkeySuppress(nowMs() + 500);
closedByKey = true;
gRunning = 0;
writeSoftkeySuppress(nowMs() + 450);
hideWindow(true);
break;
}
continue;
@@ -763,9 +827,8 @@ static int xdrawer_run_main(int, char**) {
case XK_Escape:
case XK_BackSpace:
case XK_q:
writeSoftkeySuppress(nowMs() + 400);
closedByKey = true;
gRunning = 0;
writeSoftkeySuppress(nowMs() + 350);
hideWindow(true);
break;
case XK_Up:
case XK_KP_Up:
@@ -826,38 +889,20 @@ static int xdrawer_run_main(int, char**) {
}
}
if (!gRunning) {
break;
}
struct pollfd pfd {};
pfd.fd = xfd;
pfd.events = POLLIN;
::poll(&pfd, 1, initialKeysReleased ? 250 : 40);
int timeoutMs = visible ? (initialKeysReleased ? 250 : 40) : -1;
struct pollfd pfds[2] {};
pfds[0].fd = xfd;
pfds[0].events = POLLIN;
pfds[1].fd = fifoFd;
pfds[1].events = POLLIN;
::poll(pfds, (fifoFd >= 0) ? 2 : 1, timeoutMs);
}
// Absorb any still-held softkeys (or second softkey of a closing chord)
// before releasing XGrabKeyboard so dwm never sees a trailing softkey press.
if (closedByKey) {
int64_t drainDeadline = nowMs() + 260;
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
}
struct pollfd pfd {};
pfd.fd = xfd;
pfd.events = POLLIN;
::poll(&pfd, 1, 15);
}
while (XPending(dpy)) {
XEvent ev;
XNextEvent(dpy, &ev);
}
writeSoftkeySuppress(nowMs() + 400);
}
hideWindow(false);
if (fifoFd >= 0) ::close(fifoFd);
::unlink(fifoPath.c_str());
::unlink(daemonPidPath.c_str());
XUngrabKeyboard(dpy, CurrentTime);
pango_font_description_free(descSmall);
pango_font_description_free(descIcon);
pango_font_description_free(descMain);
@@ -870,11 +915,13 @@ static int xdrawer_run_main(int, char**) {
XFreePixmap(dpy, pix);
XDestroyWindow(dpy, win);
XCloseDisplay(dpy);
::unlink(pidPath.c_str());
return 0;
}
static int xdrawer_plugin_init(const DwmHostApi*) {
static int xdrawer_plugin_init(const DwmHostApi* host) {
if (host && host->spawn_plugin) {
host->spawn_plugin("xdrawer", "--preload");
}
return 0;
}