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