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
+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;
}