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 // 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/Xatom.h>
#include <X11/Xft/Xft.h> #include <X11/Xft/Xft.h>
@@ -37,27 +37,23 @@ void handleSignal(int) {
gRunning = 0; gRunning = 0;
} }
bool isCallRinging() { int calllog_run_main(int argc, char** argv) {
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**) {
gRunning = 1; gRunning = 1;
int64_t startMs = nowMs(); bool isPreload = false;
for (int i = 1; i < argc; ++i) {
std::string pidPath = getRuntimeFile("xcalllog.pid"); if (argv[i] && std::strcmp(argv[i], "--preload") == 0) {
int existingPid = readIntFile(pidPath, -1); isPreload = true;
if (existingPid > 0 && existingPid != static_cast<int>(::getpid())) {
if (::kill(existingPid, 0) == 0) {
return 0;
} }
} }
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 {}; struct sigaction sa {};
sa.sa_handler = handleSignal; sa.sa_handler = handleSignal;
@@ -67,7 +63,8 @@ int calllog_run_main(int, char**) {
Display* dpy = XOpenDisplay(nullptr); Display* dpy = XOpenDisplay(nullptr);
if (!dpy) { if (!dpy) {
::unlink(pidPath.c_str()); if (fifoFd >= 0) ::close(fifoFd);
::unlink(daemonPidPath.c_str());
return 1; return 1;
} }
@@ -96,17 +93,6 @@ int calllog_run_main(int, char**) {
XSetClassHint(dpy, win, &ch); XSetClassHint(dpy, win, &ch);
XStoreName(dpy, win, "Call Log"); 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)); Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen));
GC gc = XCreateGC(dpy, pix, 0, nullptr); GC gc = XCreateGC(dpy, pix, 0, nullptr);
XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap); XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap);
@@ -144,12 +130,19 @@ int calllog_run_main(int, char**) {
XftColor colRed = allocColor("#e74c3c"); XftColor colRed = allocColor("#e74c3c");
ContactBook book; ContactBook book;
book.load(); std::vector<CallLogItem> allLogs;
std::vector<CallLogItem> allLogs = loadCallLog(book);
std::vector<CallLogItem> filtered; std::vector<CallLogItem> filtered;
FileStamp contactsStamp;
FileStamp logStamp;
int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out
int selected = 0; 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 = [&]() { auto applyFilter = [&]() {
filtered.clear(); 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(); applyFilter();
auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h, 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 kHeaderH = 34;
constexpr int kSoftbarH = 26; constexpr int kSoftbarH = 26;
constexpr int kListY = kHeaderH; constexpr int kListY = kHeaderH;
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH; // 236px constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH;
constexpr int kVisibleRows = 5; constexpr int kVisibleRows = 5;
const char* kTabLabels[4] = {"All", "Missed", "In", "Out"}; const char* kTabLabels[4] = {"All", "Missed", "In", "Out"};
@@ -203,7 +211,6 @@ int calllog_run_main(int, char**) {
XSetForeground(dpy, gc, 0x222222); XSetForeground(dpy, gc, 0x222222);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
// 1. Top Filter Tabs Bar (y = 0..34)
XSetForeground(dpy, gc, 0x1c1c1c); XSetForeground(dpy, gc, 0x1c1c1c);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH);
@@ -226,7 +233,6 @@ int calllog_run_main(int, char**) {
XSetForeground(dpy, gc, 0x444444); XSetForeground(dpy, gc, 0x444444);
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1); XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
// 2. Call Log List (y = 34..270, 5 visible rows)
if (filtered.empty()) { if (filtered.empty()) {
drawPango(layoutIcon, &colMuted, 112, kListY + 60, 24, kIconPhoneLog); drawPango(layoutIcon, &colMuted, 112, kListY + 60, 24, kIconPhoneLog);
drawPango(layoutMain, &colMuted, 52, kListY + 90, 28, "No call history", 160); 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; int sbY = kWinHeight - kSoftbarH;
XSetForeground(dpy, gc, 0x181818); XSetForeground(dpy, gc, 0x181818);
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, kSoftbarH); 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, drawPango(layoutSmall, &colLight, kWinWidth - rw - 6, sbY + 1,
kSoftbarH - 1, rightLabel); kSoftbarH - 1, rightLabel);
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); if (visible) {
XFlush(dpy); 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(); 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); int xfd = ConnectionNumber(dpy);
while (gRunning) { 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; initialSoftkeysReleased = true;
} }
while (XPending(dpy)) { while (XPending(dpy)) {
XEvent ev; XEvent ev;
XNextEvent(dpy, &ev); XNextEvent(dpy, &ev);
if (!visible) {
continue;
}
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) { if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
initialSoftkeysReleased = true; initialSoftkeysReleased = true;
} }
@@ -391,24 +483,25 @@ int calllog_run_main(int, char**) {
softkeyPending |= which | querySoftkeysDown(dpy); softkeyPending |= which | querySoftkeysDown(dpy);
if (softkeyPending == 3) { if (softkeyPending == 3) {
writeSoftkeySuppress(nowMs() + 500); writeSoftkeySuppress(nowMs() + 500);
launchXDrawer = true; hideWindow(false);
closedByKey = true; if (!sendPluginCommand("xdrawer", "TOGGLE")) {
gRunning = 0; runAsyncArgv("xdrawer");
}
break; break;
} }
if (softkeyDeadlineMs == 0) { if (softkeyDeadlineMs == 0) {
softkeyDeadlineMs = nowMs() + 45; softkeyDeadlineMs = nowMs() + 40;
} }
continue; 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); char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
if (dialCh != '\0') { if (dialCh != '\0') {
dialerInitArg = std::string(1, dialCh); std::string arg(1, dialCh);
openDialerAfter = true; hideWindow(false);
closedByKey = true; if (!sendPluginCommand("dialer", "OPEN " + arg)) {
gRunning = 0; runAsyncArgv("xdialer", arg.c_str());
}
break; break;
} }
@@ -416,9 +509,8 @@ int calllog_run_main(int, char**) {
case XK_Escape: case XK_Escape:
case XK_BackSpace: case XK_BackSpace:
case XK_q: case XK_q:
writeSoftkeySuppress(nowMs() + 350); writeSoftkeySuppress(nowMs() + 300);
closedByKey = true; hideWindow(true);
gRunning = 0;
break; break;
case XK_Left: case XK_Left:
case XK_KP_Left: case XK_KP_Left:
@@ -462,14 +554,15 @@ int calllog_run_main(int, char**) {
} }
if (!filtered.empty() && selected >= 0 && if (!filtered.empty() && selected >= 0 &&
selected < static_cast<int>(filtered.size())) { selected < static_cast<int>(filtered.size())) {
numberToCall = filtered[selected].number; std::string num = filtered[selected].number;
writeSoftkeySuppress(nowMs() + 400); writeSoftkeySuppress(nowMs() + 350);
closedByKey = true; hideWindow(true);
gRunning = 0; placePhoneCall(num);
} else if (filtered.empty()) { } else if (filtered.empty()) {
openDialerAfter = true; hideWindow(false);
closedByKey = true; if (!sendPluginCommand("dialer", "OPEN")) {
gRunning = 0; runAsyncArgv("xdialer");
}
} }
break; break;
default: default:
@@ -478,66 +571,59 @@ int calllog_run_main(int, char**) {
} }
} }
if (!gRunning) { if (visible && softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
break;
}
if (softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
int comb = softkeyPending | querySoftkeysDown(dpy); int comb = softkeyPending | querySoftkeysDown(dpy);
softkeyPending = 0; softkeyPending = 0;
softkeyDeadlineMs = 0; softkeyDeadlineMs = 0;
if (comb == 3) { if (comb == 3) {
writeSoftkeySuppress(nowMs() + 500); writeSoftkeySuppress(nowMs() + 500);
launchXDrawer = true; hideWindow(false);
closedByKey = true; if (!sendPluginCommand("xdrawer", "TOGGLE")) {
gRunning = 0; runAsyncArgv("xdrawer");
break; }
} else if (comb == 1) { } else if (comb == 1) {
// Left Softkey: open Dialer pre-populated with selected number std::string arg;
if (!filtered.empty() && selected >= 0 && if (!filtered.empty() && selected >= 0 &&
selected < static_cast<int>(filtered.size())) { 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) { } else if (comb == 2) {
// Right Softkey: close Call Log writeSoftkeySuppress(nowMs() + 350);
writeSoftkeySuppress(nowMs() + 400); hideWindow(true);
closedByKey = true;
gRunning = 0;
break;
} }
} }
int timeoutMs = 200; int timeoutMs = -1;
if (softkeyDeadlineMs > 0) { if (visible) {
timeoutMs = static_cast<int>(std::max<int64_t>(5, softkeyDeadlineMs - nowMs())); 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);
} }
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(descIcon);
pango_font_description_free(descSmall); pango_font_description_free(descSmall);
pango_font_description_free(descMain); pango_font_description_free(descMain);
@@ -550,24 +636,13 @@ int calllog_run_main(int, char**) {
XFreePixmap(dpy, pix); XFreePixmap(dpy, pix);
XDestroyWindow(dpy, win); XDestroyWindow(dpy, win);
XCloseDisplay(dpy); 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; 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; return 0;
} }
@@ -578,7 +653,7 @@ int calllog_on_global_key(const DwmHostApi* host, KeySym keysym,
return 0; return 0;
} }
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) { if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
if (isCallRinging()) { if (isCallRingingFast()) {
const char* const argv[] = {"sxmo_hook_pickup.sh", nullptr}; const char* const argv[] = {"sxmo_hook_pickup.sh", nullptr};
if (host && host->spawn_command) { if (host && host->spawn_command) {
host->spawn_command(argv); host->spawn_command(argv);
@@ -597,7 +672,7 @@ int calllog_on_idle_key(const DwmHostApi* host, KeySym keysym,
return 0; return 0;
} }
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) { if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
if (isCallRinging()) { if (isCallRingingFast()) {
const char* const argv[] = {"sxmo_hook_pickup.sh", nullptr}; const char* const argv[] = {"sxmo_hook_pickup.sh", nullptr};
if (host->spawn_command) { if (host->spawn_command) {
host->spawn_command(argv); host->spawn_command(argv);
+216 -172
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only // 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/Xatom.h>
#include <X11/Xft/Xft.h> #include <X11/Xft/Xft.h>
@@ -37,51 +37,28 @@ void handleSignal(int) {
gRunning = 0; 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) { int dialer_run_main(int argc, char** argv) {
gRunning = 1; gRunning = 1;
int64_t startMs = nowMs(); bool isPreload = false;
std::string initArg;
if (argc > 1 && argv[1] && argv[1][0]) { for (int i = 1; i < argc; ++i) {
enqueueDialChars(argv[1]); if (argv[i] && std::strcmp(argv[i], "--preload") == 0) {
} isPreload = true;
} else if (argv[i] && argv[i][0]) {
std::string pidPath = getRuntimeFile("xdialer.pid"); initArg += argv[i];
int existingPid = readIntFile(pidPath, -1);
if (existingPid > 0 && existingPid != static_cast<int>(::getpid())) {
if (::kill(existingPid, 0) == 0) {
return 0;
} }
} }
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 {}; struct sigaction sa {};
sa.sa_handler = handleSignal; sa.sa_handler = handleSignal;
@@ -91,7 +68,8 @@ int dialer_run_main(int argc, char** argv) {
Display* dpy = XOpenDisplay(nullptr); Display* dpy = XOpenDisplay(nullptr);
if (!dpy) { if (!dpy) {
::unlink(pidPath.c_str()); if (fifoFd >= 0) ::close(fifoFd);
::unlink(daemonPidPath.c_str());
return 1; return 1;
} }
@@ -120,17 +98,6 @@ int dialer_run_main(int argc, char** argv) {
XSetClassHint(dpy, win, &ch); XSetClassHint(dpy, win, &ch);
XStoreName(dpy, win, "Dialer"); 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)); Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen));
GC gc = XCreateGC(dpy, pix, 0, nullptr); GC gc = XCreateGC(dpy, pix, 0, nullptr);
XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap); XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap);
@@ -172,17 +139,36 @@ int dialer_run_main(int argc, char** argv) {
XftColor colBlue = allocColor("#5dade2"); XftColor colBlue = allocColor("#5dade2");
ContactBook book; ContactBook book;
book.load(); std::vector<ContactEntry> allContacts;
std::vector<ContactEntry> allContacts = book.buildDialerList();
std::vector<ContactEntry> filtered; std::vector<ContactEntry> filtered;
FileStamp contactsStamp;
FileStamp logStamp;
bool contactsLoadedOnce = false;
std::string number = drainDialQueue(); auto reloadContactsIfNeeded = [&]() -> bool {
int selected = -1; // -1 = top number box focused; 0..N-1 = contact row 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, reloadContactsIfNeeded();
// allow long-pressing that initial '0' to turn into '+'.
bool zeroHeld = (number == "0" && isKeycodeDown(dpy, 19)); std::string number;
int64_t zeroPressMs = zeroHeld ? startMs : 0; 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 = [&]() { auto updateFilter = [&]() {
filtered.clear(); 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, auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
const std::string& text, int maxW = -1) { const std::string& text, int maxW = -1) {
pango_layout_set_text(layout, text.c_str(), static_cast<int>(text.size())); 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 kHeaderH = 56;
constexpr int kSoftbarH = 26; constexpr int kSoftbarH = 26;
constexpr int kListY = kHeaderH; constexpr int kListY = kHeaderH;
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH; // 214px constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH;
constexpr int kVisibleRows = 5; constexpr int kVisibleRows = 5;
auto render = [&]() { auto render = [&]() {
XSetForeground(dpy, gc, 0x222222); XSetForeground(dpy, gc, 0x222222);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
// 1. Top Number Display Box (y = 0..56)
bool topSel = (selected < 0); bool topSel = (selected < 0);
XSetForeground(dpy, gc, topSel ? 0x1b3b4b : 0x1c1c1c); XSetForeground(dpy, gc, topSel ? 0x1b3b4b : 0x1c1c1c);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH); 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); drawPango(layoutIcon, &colMuted, 218, 4, 30, kIconBackspace);
} }
// Subtitle inside header box (y = 33..54)
std::string subText; std::string subText;
XftColor* subCol = &colMuted; XftColor* subCol = &colMuted;
if (selected >= 0 && selected < static_cast<int>(filtered.size())) { 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); drawPango(layoutSmall, subCol, 32, 33, 20, subText, 202);
// 2. Contact / Recent Suggestions List (y = 56..270, 5 visible rows)
if (filtered.empty()) { if (filtered.empty()) {
drawPango(layoutMain, &colMuted, 16, kListY + 40, 30, drawPango(layoutMain, &colMuted, 16, kListY + 40, 30,
number.empty() ? "No saved contacts" : "New number (press Call)", 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; int sbY = kWinHeight - kSoftbarH;
XSetForeground(dpy, gc, 0x181818); XSetForeground(dpy, gc, 0x181818);
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, kSoftbarH); 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, drawPango(layoutSmall, &colLight, kWinWidth - rw - 6, sbY + 1,
kSoftbarH - 1, rightLabel); kSoftbarH - 1, rightLabel);
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); if (visible) {
XFlush(dpy); 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(); 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); int xfd = ConnectionNumber(dpy);
auto selectActiveNumber = [&]() -> std::string { auto selectActiveNumber = [&]() -> std::string {
@@ -421,15 +489,20 @@ int dialer_run_main(int argc, char** argv) {
}; };
while (gRunning) { while (gRunning) {
std::string extra = drainDialQueue(); // 1. Process any incoming FIFO commands
if (!extra.empty()) { for (const std::string& cmd : readFifoLines(fifoFd)) {
number += extra; if (cmd == "NOP") {
selected = -1; continue;
updateFilter(); } else if (cmd == "CLOSE") {
render(); 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)) { if (!isKeycodeDown(dpy, 19)) {
zeroHeld = false; zeroHeld = false;
} else if (nowMs() - zeroPressMs >= 420) { } 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; initialSoftkeysReleased = true;
} }
while (XPending(dpy)) { while (XPending(dpy)) {
XEvent ev; XEvent ev;
XNextEvent(dpy, &ev); XNextEvent(dpy, &ev);
if (!visible) {
continue;
}
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) { if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
initialSoftkeysReleased = true; initialSoftkeysReleased = true;
} }
@@ -489,22 +565,22 @@ int dialer_run_main(int argc, char** argv) {
softkeyPending |= which | querySoftkeysDown(dpy); softkeyPending |= which | querySoftkeysDown(dpy);
if (softkeyPending == 3) { if (softkeyPending == 3) {
writeSoftkeySuppress(nowMs() + 500); writeSoftkeySuppress(nowMs() + 500);
launchXDrawer = true; hideWindow(false);
closedByKey = true; if (!sendPluginCommand("xdrawer", "TOGGLE")) {
gRunning = 0; runAsyncArgv("xdrawer");
}
break; break;
} }
if (softkeyDeadlineMs == 0) { if (softkeyDeadlineMs == 0) {
softkeyDeadlineMs = nowMs() + 45; softkeyDeadlineMs = nowMs() + 40;
} }
continue; continue;
} }
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state); char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
if (dialCh != '\0') { if (dialCh != '\0') {
if (nowMs() - startMs < 45 && number.size() == 1 && if (nowMs() - openStartMs < 40 && number.size() == 1 &&
number[0] == dialCh) { number[0] == dialCh) {
// Ignore duplicate event from the initial key press that opened dialer
continue; continue;
} }
number.push_back(dialCh); number.push_back(dialCh);
@@ -522,9 +598,8 @@ int dialer_run_main(int argc, char** argv) {
switch (sym) { switch (sym) {
case XK_Escape: case XK_Escape:
writeSoftkeySuppress(nowMs() + 350); writeSoftkeySuppress(nowMs() + 300);
closedByKey = true; hideWindow(true);
gRunning = 0;
break; break;
case XK_BackSpace: case XK_BackSpace:
case XK_Left: case XK_Left:
@@ -535,8 +610,7 @@ int dialer_run_main(int argc, char** argv) {
updateFilter(); updateFilter();
render(); render();
} else if (sym == XK_BackSpace) { } else if (sym == XK_BackSpace) {
closedByKey = true; hideWindow(true);
gRunning = 0;
} }
break; break;
case XK_Right: case XK_Right:
@@ -582,10 +656,9 @@ int dialer_run_main(int argc, char** argv) {
case XF86XK_PickupPhone: { case XF86XK_PickupPhone: {
std::string target = selectActiveNumber(); std::string target = selectActiveNumber();
if (!target.empty()) { if (!target.empty()) {
targetToDial = target; writeSoftkeySuppress(nowMs() + 350);
writeSoftkeySuppress(nowMs() + 400); hideWindow(true);
closedByKey = true; placePhoneCall(target);
gRunning = 0;
} }
break; break;
} }
@@ -595,28 +668,23 @@ int dialer_run_main(int argc, char** argv) {
} }
} }
if (!gRunning) { if (visible && softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
break;
}
if (softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
int comb = softkeyPending | querySoftkeysDown(dpy); int comb = softkeyPending | querySoftkeysDown(dpy);
softkeyPending = 0; softkeyPending = 0;
softkeyDeadlineMs = 0; softkeyDeadlineMs = 0;
if (comb == 3) { if (comb == 3) {
writeSoftkeySuppress(nowMs() + 500); writeSoftkeySuppress(nowMs() + 500);
launchXDrawer = true; hideWindow(false);
closedByKey = true; if (!sendPluginCommand("xdrawer", "TOGGLE")) {
gRunning = 0; runAsyncArgv("xdrawer");
break; }
} else if (comb == 1) { } else if (comb == 1) {
// Left Softkey: if empty, switch to Call Log; if non-empty, toggle leading '+'
if (number.empty()) { if (number.empty()) {
writeSoftkeySuppress(nowMs() + 350); writeSoftkeySuppress(nowMs() + 250);
switchToCallLog = true; hideWindow(false);
closedByKey = true; if (!sendPluginCommand("calllog", "OPEN")) {
gRunning = 0; runAsyncArgv("xcalllog");
break; }
} else { } else {
if (number[0] == '+') { if (number[0] == '+') {
number.erase(0, 1); number.erase(0, 1);
@@ -628,50 +696,43 @@ int dialer_run_main(int argc, char** argv) {
render(); render();
} }
} else if (comb == 2) { } else if (comb == 2) {
// Right Softkey: delete last character if non-empty, else close Dialer
if (!number.empty()) { if (!number.empty()) {
number.pop_back(); number.pop_back();
selected = -1; selected = -1;
updateFilter(); updateFilter();
render(); render();
} else { } else {
writeSoftkeySuppress(nowMs() + 400); writeSoftkeySuppress(nowMs() + 350);
closedByKey = true; hideWindow(true);
gRunning = 0;
break;
} }
} }
} }
int timeoutMs = 200; int timeoutMs = -1;
if (softkeyDeadlineMs > 0) { if (visible) {
timeoutMs = static_cast<int>(std::max<int64_t>(5, softkeyDeadlineMs - nowMs())); timeoutMs = 200;
} else if (zeroHeld) { if (softkeyDeadlineMs > 0) {
timeoutMs = 25; 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);
} }
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(descIcon);
pango_font_description_free(descSmall); pango_font_description_free(descSmall);
pango_font_description_free(descMain); pango_font_description_free(descMain);
@@ -686,22 +747,13 @@ int dialer_run_main(int argc, char** argv) {
XFreePixmap(dpy, pix); XFreePixmap(dpy, pix);
XDestroyWindow(dpy, win); XDestroyWindow(dpy, win);
XCloseDisplay(dpy); XCloseDisplay(dpy);
::unlink(pidPath.c_str());
if (launchXDrawer) {
runAsyncArgv("xdrawer");
} else if (switchToCallLog) {
runAsyncArgv("xcalllog");
} else if (!targetToDial.empty()) {
placePhoneCall(targetToDial);
}
return 0; return 0;
} }
int64_t gLastSpawnMs = 0; int dialer_plugin_init(const DwmHostApi* host) {
if (host && host->spawn_plugin) {
int dialer_plugin_init(const DwmHostApi*) { host->spawn_plugin("dialer", "--preload");
}
return 0; return 0;
} }
@@ -715,16 +767,8 @@ int dialer_on_idle_key(const DwmHostApi* host, KeySym keysym,
return 0; return 0;
} }
char buf[2] = {ch, '\0'}; char buf[2] = {ch, '\0'};
enqueueDialChars(buf); if (host->spawn_plugin) {
host->spawn_plugin("dialer", 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);
} }
return 1; return 1;
} }
+45
View File
@@ -1,5 +1,7 @@
#include <dirent.h> #include <dirent.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -8,6 +10,45 @@
#include "../sxmo-dwm/dwm_plugin.h" #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[] = { static const char *const kPluginDirs[] = {
"/home/user/.local/lib/dwm/plugins", "/home/user/.local/lib/dwm/plugins",
"/usr/lib/dwm/plugins", "/usr/lib/dwm/plugins",
@@ -79,6 +120,10 @@ int main(int argc, char **argv) {
sub_argv = argv + 1; 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"); const char *env_dir = getenv("DWM_PLUGIN_PATH");
char path[512]; char path[512];
void *handle = NULL; void *handle = NULL;
+98
View File
@@ -10,6 +10,7 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <csignal>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
@@ -87,6 +88,103 @@ inline bool writeIntFile(const std::string& path, int value) {
return w == len; 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) { inline void writeSoftkeySuppress(int64_t untilMs) {
int fd = ::open("/tmp/sxmo_softkey_suppress", int fd = ::open("/tmp/sxmo_softkey_suppress",
O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); 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); 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 static void
host_spawn_plugin(const char *plugin_name, const char *arg) host_spawn_plugin(const char *plugin_name, const char *arg)
{ {
if (!plugin_name || !plugin_name[0]) if (!plugin_name || !plugin_name[0])
return; 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]; const char *cmd[4];
cmd[0] = "dwm-plugin"; cmd[0] = "dwm-plugin";
cmd[1] = plugin_name; cmd[1] = plugin_name;
cmd[2] = (arg && arg[0]) ? arg : NULL; cmd[2] = (arg && arg[0]) ? arg : NULL;
cmd[3] = NULL; cmd[3] = NULL;
Arg a = { .v = cmd }; Arg a = { .v = cmd };
XUngrabKeyboard(dpy, CurrentTime);
XFlush(dpy);
spawn(&a); spawn(&a);
} }
@@ -2849,7 +2886,7 @@ unmapnotify(XEvent *e)
setclientstate(c, WithdrawnState); setclientstate(c, WithdrawnState);
else else
unmanage(c, 0); unmanage(c, 0);
} else if (selmon && !selmon->sel) { } else {
focus(NULL); focus(NULL);
} }
} }
+151 -104
View File
@@ -26,6 +26,7 @@
#include <ctime> #include <ctime>
#include <fcntl.h> #include <fcntl.h>
#include <poll.h> #include <poll.h>
#include <sstream>
#include <string> #include <string>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
@@ -435,55 +436,47 @@ class PhoneController {
} // namespace } // namespace
static int xdrawer_run_main(int, char**) { static int xdrawer_run_main(int argc, char** argv) {
gRunning = 1; 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 pidPath = getRuntimeFile("xdrawer.pid");
std::string startPath = getRuntimeFile("xdrawer.start");
int existingPid = readIntFile(pidPath, -1); int existingDaemon = readIntFile(daemonPidPath, -1);
if (existingPid > 0 && existingPid != static_cast<int>(::getpid())) { if (existingDaemon > 0 && existingDaemon != static_cast<int>(::getpid()) &&
if (::kill(existingPid, 0) == 0) { ::kill(existingDaemon, 0) == 0) {
int fd = ::open(startPath.c_str(), O_RDONLY | O_CLOEXEC); if (!isPreload) {
int64_t prevStartMs = 0; int ffd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC);
if (fd >= 0) { if (ffd >= 0) {
char buf[64]; (void)::write(ffd, "TOGGLE\n", 7);
ssize_t n = ::read(fd, buf, sizeof(buf) - 1); ::close(ffd);
::close(fd);
if (n > 0) {
buf[n] = '\0';
prevStartMs = std::atoll(buf);
}
} }
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 {}; struct sigaction sa {};
sa.sa_handler = handleSignal; sa.sa_handler = handleSignal;
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
sigaction(SIGTERM, &sa, nullptr); sigaction(SIGTERM, &sa, nullptr);
sigaction(SIGINT, &sa, nullptr); sigaction(SIGINT, &sa, nullptr);
runAsyncCmd("pkill -x dmenu >/dev/null 2>&1");
Display* dpy = XOpenDisplay(nullptr); Display* dpy = XOpenDisplay(nullptr);
if (!dpy) { if (!dpy) {
::unlink(pidPath.c_str()); if (fifoFd >= 0) ::close(fifoFd);
::unlink(daemonPidPath.c_str());
return 1; return 1;
} }
@@ -512,22 +505,10 @@ static int xdrawer_run_main(int, char**) {
XSetClassHint(dpy, win, &ch); XSetClassHint(dpy, win, &ch);
XStoreName(dpy, win, "XDrawer"); 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)); Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen));
GC gc = XCreateGC(dpy, pix, 0, nullptr); GC gc = XCreateGC(dpy, pix, 0, nullptr);
XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap); 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); PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen);
PangoContext* pangoCtx = pango_font_map_create_context(fontMap); PangoContext* pangoCtx = pango_font_map_create_context(fontMap);
PangoLayout* layoutMain = pango_layout_new(pangoCtx); PangoLayout* layoutMain = pango_layout_new(pangoCtx);
@@ -559,6 +540,10 @@ static int xdrawer_run_main(int, char**) {
PhoneController controller; PhoneController controller;
size_t selected = 0; 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, auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
const std::string& text, int maxW = -1) { const std::string& text, int maxW = -1) {
@@ -589,7 +574,6 @@ static int xdrawer_run_main(int, char**) {
XSetForeground(dpy, gc, 0x222222); XSetForeground(dpy, gc, 0x222222);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); 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(); const auto& items = controller.items();
for (size_t i = 0; i < items.size(); ++i) { for (size_t i = 0; i < items.size(); ++i) {
int ry = static_cast<int>((i * kWinHeight) / kRowCount); int ry = static_cast<int>((i * kWinHeight) / kRowCount);
@@ -609,7 +593,6 @@ static int xdrawer_run_main(int, char**) {
bool activeState = (it.value > 0); bool activeState = (it.value > 0);
const char* rowIcon = activeState ? it.iconOn : it.iconOff; 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; int labelMaxW = it.isSlider ? 82 : 136;
drawPango(layoutIcon, isSel ? &colYellow : &colLight, 8, ry, rowH, rowIcon); drawPango(layoutIcon, isSel ? &colYellow : &colLight, 8, ry, rowH, rowIcon);
drawPango(layoutMain, isSel ? &colWhite : &colLight, 30, ry, rowH, 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); 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 barX = 114;
int barW = 78; int barW = 78;
int barH = 12; 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); XFillRectangle(dpy, pix, gc, barX + 1, barY + 1, fillW, barH - 2);
} }
// Slider thumb knob
int knobX = barX + std::clamp(fillW - 2, 0, barW - 5); int knobX = barX + std::clamp(fillW - 2, 0, barW - 5);
XSetForeground(dpy, gc, 0xffffff); XSetForeground(dpy, gc, 0xffffff);
XFillRectangle(dpy, pix, gc, knobX, barY - 2, 5, barH + 4); 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, drawPango(layoutSmall, isSel ? &colWhite : &colLight, kWinWidth - pw - 6,
ry, rowH, pctStr); ry, rowH, pctStr);
} else { } else {
// Toggle switch icon + ON/OFF label (right-aligned within x = 160..234)
bool on = (it.value != 0); bool on = (it.value != 0);
std::string stStr = on ? "ON" : "OFF"; std::string stStr = on ? "ON" : "OFF";
int stW = pangoWidth(layoutMain, stStr); 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); if (visible) {
XFlush(dpy); 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(); render();
XSync(dpy, False);
auto writeSoftkeySuppress = [](int64_t untilMs) { auto writeSoftkeySuppress = [](int64_t untilMs) {
int fd = ::open("/tmp/sxmo_softkey_suppress", int fd = ::open("/tmp/sxmo_softkey_suppress",
@@ -701,27 +685,108 @@ static int xdrawer_run_main(int, char**) {
return mask; 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); 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) { 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; initialKeysReleased = true;
} }
while (XPending(dpy)) { while (XPending(dpy)) {
XEvent ev; XEvent ev;
XNextEvent(dpy, &ev); XNextEvent(dpy, &ev);
if (!visible) {
continue;
}
if (!initialKeysReleased && querySoftkeysDown(dpy) == 0) { if (!initialKeysReleased && querySoftkeysDown(dpy) == 0) {
initialKeysReleased = true; initialKeysReleased = true;
} }
@@ -751,9 +816,8 @@ static int xdrawer_run_main(int, char**) {
sym == XF86XK_Back || sym == XF86XK_KbdLcdMenu2 || sym == XF86XK_Back || sym == XF86XK_KbdLcdMenu2 ||
ev.xkey.keycode == 135 || ev.xkey.keycode == 166) { ev.xkey.keycode == 135 || ev.xkey.keycode == 166) {
if (initialKeysReleased && nowMs() >= startupIgnoreUntilMs) { if (initialKeysReleased && nowMs() >= startupIgnoreUntilMs) {
writeSoftkeySuppress(nowMs() + 500); writeSoftkeySuppress(nowMs() + 450);
closedByKey = true; hideWindow(true);
gRunning = 0;
break; break;
} }
continue; continue;
@@ -763,9 +827,8 @@ static int xdrawer_run_main(int, char**) {
case XK_Escape: case XK_Escape:
case XK_BackSpace: case XK_BackSpace:
case XK_q: case XK_q:
writeSoftkeySuppress(nowMs() + 400); writeSoftkeySuppress(nowMs() + 350);
closedByKey = true; hideWindow(true);
gRunning = 0;
break; break;
case XK_Up: case XK_Up:
case XK_KP_Up: case XK_KP_Up:
@@ -826,38 +889,20 @@ static int xdrawer_run_main(int, char**) {
} }
} }
if (!gRunning) { int timeoutMs = visible ? (initialKeysReleased ? 250 : 40) : -1;
break; struct pollfd pfds[2] {};
} pfds[0].fd = xfd;
pfds[0].events = POLLIN;
struct pollfd pfd {}; pfds[1].fd = fifoFd;
pfd.fd = xfd; pfds[1].events = POLLIN;
pfd.events = POLLIN; ::poll(pfds, (fifoFd >= 0) ? 2 : 1, timeoutMs);
::poll(&pfd, 1, initialKeysReleased ? 250 : 40);
} }
// Absorb any still-held softkeys (or second softkey of a closing chord) hideWindow(false);
// before releasing XGrabKeyboard so dwm never sees a trailing softkey press. if (fifoFd >= 0) ::close(fifoFd);
if (closedByKey) { ::unlink(fifoPath.c_str());
int64_t drainDeadline = nowMs() + 260; ::unlink(daemonPidPath.c_str());
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);
}
XUngrabKeyboard(dpy, CurrentTime);
pango_font_description_free(descSmall); pango_font_description_free(descSmall);
pango_font_description_free(descIcon); pango_font_description_free(descIcon);
pango_font_description_free(descMain); pango_font_description_free(descMain);
@@ -870,11 +915,13 @@ static int xdrawer_run_main(int, char**) {
XFreePixmap(dpy, pix); XFreePixmap(dpy, pix);
XDestroyWindow(dpy, win); XDestroyWindow(dpy, win);
XCloseDisplay(dpy); XCloseDisplay(dpy);
::unlink(pidPath.c_str());
return 0; 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; return 0;
} }