diff --git a/overlay/rootfs/usr/bin/dwm b/overlay/rootfs/usr/bin/dwm index 87e21c5..48c87cd 100755 Binary files a/overlay/rootfs/usr/bin/dwm and b/overlay/rootfs/usr/bin/dwm differ diff --git a/overlay/rootfs/usr/bin/dwm-plugin b/overlay/rootfs/usr/bin/dwm-plugin index 87c34bf..f7f12b8 100755 Binary files a/overlay/rootfs/usr/bin/dwm-plugin and b/overlay/rootfs/usr/bin/dwm-plugin differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/calllog.so b/overlay/rootfs/usr/lib/dwm/plugins/calllog.so index 4f02506..6ec43fa 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/calllog.so and b/overlay/rootfs/usr/lib/dwm/plugins/calllog.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/dialer.so b/overlay/rootfs/usr/lib/dwm/plugins/dialer.so index 185c130..5f27052 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/dialer.so and b/overlay/rootfs/usr/lib/dwm/plugins/dialer.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so b/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so index bc6b025..9a8d355 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so and b/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so differ diff --git a/prebuilt/sxmo-x11/usr/bin/dwm b/prebuilt/sxmo-x11/usr/bin/dwm index 87e21c5..48c87cd 100755 Binary files a/prebuilt/sxmo-x11/usr/bin/dwm and b/prebuilt/sxmo-x11/usr/bin/dwm differ diff --git a/prebuilt/sxmo-x11/usr/bin/dwm-plugin b/prebuilt/sxmo-x11/usr/bin/dwm-plugin index 87c34bf..f7f12b8 100755 Binary files a/prebuilt/sxmo-x11/usr/bin/dwm-plugin and b/prebuilt/sxmo-x11/usr/bin/dwm-plugin differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so index 4f02506..6ec43fa 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so index 185c130..5f27052 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so index bc6b025..9a8d355 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so differ diff --git a/src/dwm-plugins/calllog_plugin.cpp b/src/dwm-plugins/calllog_plugin.cpp index c3154ee..934cb66 100644 --- a/src/dwm-plugins/calllog_plugin.cpp +++ b/src/dwm-plugins/calllog_plugin.cpp @@ -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 #include @@ -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(::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(::getpid())); + + if (sendPluginCommand("calllog", isPreload ? "NOP" : "OPEN")) { + return 0; + } + + std::string daemonPidPath = getPluginDaemonPidPath("calllog"); + std::string activePidPath = getRuntimeFile("xcalllog.pid"); + writeIntFile(daemonPidPath, static_cast(::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 allLogs = loadCallLog(book); + std::vector allLogs; std::vector 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(::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(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(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(std::max(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(std::max(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); diff --git a/src/dwm-plugins/dialer_plugin.cpp b/src/dwm-plugins/dialer_plugin.cpp index c358d40..0e590a9 100644 --- a/src/dwm-plugins/dialer_plugin.cpp +++ b/src/dwm-plugins/dialer_plugin.cpp @@ -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 #include @@ -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(::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(::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(::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 allContacts = book.buildDialerList(); + std::vector allContacts; std::vector 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(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(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(::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(std::max(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(std::max(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; } diff --git a/src/dwm-plugins/dwm_plugin_runner.c b/src/dwm-plugins/dwm_plugin_runner.c index 6f32eb0..898129e 100644 --- a/src/dwm-plugins/dwm_plugin_runner.c +++ b/src/dwm-plugins/dwm_plugin_runner.c @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -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; diff --git a/src/dwm-plugins/phone_data.h b/src/dwm-plugins/phone_data.h index 8606545..8f28515 100644 --- a/src/dwm-plugins/phone_data.h +++ b/src/dwm-plugins/phone_data.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -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(::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(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 readFifoLines(int fifoFd) { + std::vector 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); diff --git a/src/sxmo-dwm/dwm.c b/src/sxmo-dwm/dwm.c index 76393fd..4b2c407 100644 --- a/src/sxmo-dwm/dwm.c +++ b/src/sxmo-dwm/dwm.c @@ -1572,19 +1572,56 @@ host_spawn_command(const char *const *argv) spawn(&a); } +static int +is_plugin_daemon_alive(const char *plugin_name) +{ + char pidpath[128], buf[32]; + snprintf(pidpath, sizeof(pidpath), "/tmp/dwm_plugin_%s.pid", plugin_name); + read_sysfs_str(pidpath, buf, sizeof(buf)); + int pid = atoi(buf); + return (pid > 0 && kill(pid, 0) == 0); +} + static void host_spawn_plugin(const char *plugin_name, const char *arg) { if (!plugin_name || !plugin_name[0]) return; + if (arg && strcmp(arg, "--preload") == 0) { + if (is_plugin_daemon_alive(plugin_name)) + return; + const char *cmd[4] = { "dwm-plugin", plugin_name, "--preload", NULL }; + Arg a = { .v = cmd }; + spawn(&a); + return; + } + XUngrabKeyboard(dpy, CurrentTime); + XFlush(dpy); + if (is_plugin_daemon_alive(plugin_name)) { + char fifopath[128], msg[128]; + snprintf(fifopath, sizeof(fifopath), "/tmp/dwm_plugin_%s.fifo", plugin_name); + int fd = open(fifopath, O_WRONLY | O_NONBLOCK | O_CLOEXEC); + if (fd >= 0) { + int len; + if (strcmp(plugin_name, "xdrawer") == 0) + len = snprintf(msg, sizeof(msg), "TOGGLE\n"); + else if (arg && arg[0]) + len = snprintf(msg, sizeof(msg), "OPEN %s\n", arg); + else + len = snprintf(msg, sizeof(msg), "OPEN\n"); + if (write(fd, msg, len) == len) { + close(fd); + return; + } + close(fd); + } + } const char *cmd[4]; cmd[0] = "dwm-plugin"; cmd[1] = plugin_name; cmd[2] = (arg && arg[0]) ? arg : NULL; cmd[3] = NULL; Arg a = { .v = cmd }; - XUngrabKeyboard(dpy, CurrentTime); - XFlush(dpy); spawn(&a); } @@ -2849,7 +2886,7 @@ unmapnotify(XEvent *e) setclientstate(c, WithdrawnState); else unmanage(c, 0); - } else if (selmon && !selmon->sel) { + } else { focus(NULL); } } diff --git a/src/xdrawer/xdrawer.cpp b/src/xdrawer/xdrawer.cpp index 7706c2c..8a11fe6 100644 --- a/src/xdrawer/xdrawer.cpp +++ b/src/xdrawer/xdrawer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -435,55 +436,47 @@ class PhoneController { } // namespace -static int xdrawer_run_main(int, char**) { +static int xdrawer_run_main(int argc, char** argv) { gRunning = 1; - int64_t startMs = nowMs(); + bool isPreload = false; + for (int i = 1; i < argc; ++i) { + if (argv[i] && std::strcmp(argv[i], "--preload") == 0) { + isPreload = true; + } + } + + std::string daemonPidPath = "/tmp/dwm_plugin_xdrawer.pid"; + std::string fifoPath = "/tmp/dwm_plugin_xdrawer.fifo"; std::string pidPath = getRuntimeFile("xdrawer.pid"); - std::string startPath = getRuntimeFile("xdrawer.start"); - int existingPid = readIntFile(pidPath, -1); - if (existingPid > 0 && existingPid != static_cast(::getpid())) { - if (::kill(existingPid, 0) == 0) { - int fd = ::open(startPath.c_str(), O_RDONLY | O_CLOEXEC); - int64_t prevStartMs = 0; - if (fd >= 0) { - char buf[64]; - ssize_t n = ::read(fd, buf, sizeof(buf) - 1); - ::close(fd); - if (n > 0) { - buf[n] = '\0'; - prevStartMs = std::atoll(buf); - } + + int existingDaemon = readIntFile(daemonPidPath, -1); + if (existingDaemon > 0 && existingDaemon != static_cast(::getpid()) && + ::kill(existingDaemon, 0) == 0) { + if (!isPreload) { + int ffd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC); + if (ffd >= 0) { + (void)::write(ffd, "TOGGLE\n", 7); + ::close(ffd); } - if (startMs - prevStartMs < 480) { - return 0; - } - ::kill(existingPid, SIGTERM); - return 0; - } - } - writeIntFile(pidPath, static_cast(::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(startMs)); - (void)::write(fd, buf, len); - ::close(fd); } + return 0; } + writeIntFile(daemonPidPath, static_cast(::getpid())); + ::mkfifo(fifoPath.c_str(), 0666); + ::chmod(fifoPath.c_str(), 0666); + int fifoFd = ::open(fifoPath.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC); + struct sigaction sa {}; sa.sa_handler = handleSignal; sigemptyset(&sa.sa_mask); sigaction(SIGTERM, &sa, nullptr); sigaction(SIGINT, &sa, nullptr); - runAsyncCmd("pkill -x dmenu >/dev/null 2>&1"); - Display* dpy = XOpenDisplay(nullptr); if (!dpy) { - ::unlink(pidPath.c_str()); + if (fifoFd >= 0) ::close(fifoFd); + ::unlink(daemonPidPath.c_str()); return 1; } @@ -512,22 +505,10 @@ static int xdrawer_run_main(int, char**) { XSetClassHint(dpy, win, &ch); XStoreName(dpy, win, "XDrawer"); - XMapRaised(dpy, win); - XSync(dpy, False); - - for (int i = 0; i < 60; ++i) { - if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) == - GrabSuccess) { - break; - } - usleep(5000); - } - Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen)); GC gc = XCreateGC(dpy, pix, 0, nullptr); XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap); - // Use the exact same Pango+Xft font stack as dwm ("Fira Code, DejaVuSansM Nerd Font 15px") PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen); PangoContext* pangoCtx = pango_font_map_create_context(fontMap); PangoLayout* layoutMain = pango_layout_new(pangoCtx); @@ -559,6 +540,10 @@ static int xdrawer_run_main(int, char**) { PhoneController controller; size_t selected = 0; + bool visible = false; + int64_t openStartMs = 0; + int64_t startupIgnoreUntilMs = 0; + bool initialKeysReleased = true; auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h, const std::string& text, int maxW = -1) { @@ -589,7 +574,6 @@ static int xdrawer_run_main(int, char**) { XSetForeground(dpy, gc, 0x222222); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); - // 7 Control rows filling the full 296px height flush from y=24 to y=320 const auto& items = controller.items(); for (size_t i = 0; i < items.size(); ++i) { int ry = static_cast((i * kWinHeight) / kRowCount); @@ -609,7 +593,6 @@ static int xdrawer_run_main(int, char**) { bool activeState = (it.value > 0); const char* rowIcon = activeState ? it.iconOn : it.iconOff; - // Icon column (x = 8..28) + Label column (x = 30..112 for sliders, 30..166 for toggles) int labelMaxW = it.isSlider ? 82 : 136; drawPango(layoutIcon, isSel ? &colYellow : &colLight, 8, ry, rowH, rowIcon); drawPango(layoutMain, isSel ? &colWhite : &colLight, 30, ry, rowH, @@ -627,7 +610,6 @@ static int xdrawer_run_main(int, char**) { } pct = std::clamp(pct, 0, 100); - // Slider track (x = 114, w = 78, h = 12) + thumb + right-aligned pct (x = 196..234) int barX = 114; int barW = 78; int barH = 12; @@ -644,7 +626,6 @@ static int xdrawer_run_main(int, char**) { XFillRectangle(dpy, pix, gc, barX + 1, barY + 1, fillW, barH - 2); } - // Slider thumb knob int knobX = barX + std::clamp(fillW - 2, 0, barW - 5); XSetForeground(dpy, gc, 0xffffff); XFillRectangle(dpy, pix, gc, knobX, barY - 2, 5, barH + 4); @@ -656,7 +637,6 @@ static int xdrawer_run_main(int, char**) { drawPango(layoutSmall, isSel ? &colWhite : &colLight, kWinWidth - pw - 6, ry, rowH, pctStr); } else { - // Toggle switch icon + ON/OFF label (right-aligned within x = 160..234) bool on = (it.value != 0); std::string stStr = on ? "ON" : "OFF"; int stW = pangoWidth(layoutMain, stStr); @@ -668,11 +648,15 @@ static int xdrawer_run_main(int, char**) { } } - XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); - XFlush(dpy); + if (visible) { + XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); + XFlush(dpy); + } }; + // Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup render(); + XSync(dpy, False); auto writeSoftkeySuppress = [](int64_t untilMs) { int fd = ::open("/tmp/sxmo_softkey_suppress", @@ -701,27 +685,108 @@ static int xdrawer_run_main(int, char**) { return mask; }; - // Drain any stale key events that arrived before initial render finished - while (XPending(dpy)) { - XEvent ev; - XNextEvent(dpy, &ev); - if (ev.type == Expose && ev.xexpose.count == 0) { - render(); - } - } - - int64_t startupIgnoreUntilMs = std::max(startMs + 220, nowMs() + 60); - bool initialKeysReleased = (querySoftkeysDown(dpy) == 0); - bool closedByKey = false; int xfd = ConnectionNumber(dpy); + auto closeOtherPlugin = [](const char* activePidName, const char* fifoPathStr) { + if (readIntFile(getRuntimeFile(activePidName), -1) > 0) { + int ffd = ::open(fifoPathStr, O_WRONLY | O_NONBLOCK | O_CLOEXEC); + if (ffd >= 0) { + (void)::write(ffd, "CLOSE\n", 6); + ::close(ffd); + } + } + }; + + auto showWindow = [&]() { + if (visible) { + return; + } + closeOtherPlugin("xdialer.pid", "/tmp/dwm_plugin_dialer.fifo"); + closeOtherPlugin("xcalllog.pid", "/tmp/dwm_plugin_calllog.fifo"); + openStartMs = nowMs(); + startupIgnoreUntilMs = openStartMs + 180; + initialKeysReleased = (querySoftkeysDown(dpy) == 0); + visible = true; + XMapRaised(dpy, win); + XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); + for (int i = 0; i < 20; ++i) { + if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) == + GrabSuccess) { + break; + } + usleep(2000); + } + XFlush(dpy); + writeIntFile(pidPath, static_cast(::getpid())); + controller.refreshAll(); + render(); + runAsyncCmd("pkill -x dmenu >/dev/null 2>&1"); + }; + + auto hideWindow = [&](bool drainSoftkeys) { + if (!visible) { + return; + } + if (drainSoftkeys) { + int64_t drainDeadline = nowMs() + 220; + while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) { + while (XPending(dpy)) { + XEvent ev; + XNextEvent(dpy, &ev); + } + struct pollfd pfd {}; + pfd.fd = xfd; + pfd.events = POLLIN; + ::poll(&pfd, 1, 10); + } + writeSoftkeySuppress(nowMs() + 350); + } + XUngrabKeyboard(dpy, CurrentTime); + XUnmapWindow(dpy, win); + XFlush(dpy); + ::unlink(pidPath.c_str()); + visible = false; + }; + + if (!isPreload) { + showWindow(); + } + while (gRunning) { - if (!initialKeysReleased && querySoftkeysDown(dpy) == 0) { + if (fifoFd >= 0) { + char fbuf[256]; + ssize_t fn = ::read(fifoFd, fbuf, sizeof(fbuf) - 1); + if (fn > 0) { + fbuf[fn] = '\0'; + std::stringstream ss(fbuf); + std::string cmd; + while (std::getline(ss, cmd)) { + if (!cmd.empty() && cmd.back() == '\r') cmd.pop_back(); + if (cmd == "TOGGLE") { + if (!visible) { + showWindow(); + } else if (nowMs() - openStartMs >= 350) { + hideWindow(true); + } + } else if (cmd == "OPEN") { + showWindow(); + } else if (cmd == "CLOSE") { + hideWindow(false); + } + } + } + } + + if (visible && !initialKeysReleased && querySoftkeysDown(dpy) == 0) { initialKeysReleased = true; } + while (XPending(dpy)) { XEvent ev; XNextEvent(dpy, &ev); + if (!visible) { + continue; + } if (!initialKeysReleased && querySoftkeysDown(dpy) == 0) { initialKeysReleased = true; } @@ -751,9 +816,8 @@ static int xdrawer_run_main(int, char**) { sym == XF86XK_Back || sym == XF86XK_KbdLcdMenu2 || ev.xkey.keycode == 135 || ev.xkey.keycode == 166) { if (initialKeysReleased && nowMs() >= startupIgnoreUntilMs) { - writeSoftkeySuppress(nowMs() + 500); - closedByKey = true; - gRunning = 0; + writeSoftkeySuppress(nowMs() + 450); + hideWindow(true); break; } continue; @@ -763,9 +827,8 @@ static int xdrawer_run_main(int, char**) { case XK_Escape: case XK_BackSpace: case XK_q: - writeSoftkeySuppress(nowMs() + 400); - closedByKey = true; - gRunning = 0; + writeSoftkeySuppress(nowMs() + 350); + hideWindow(true); break; case XK_Up: case XK_KP_Up: @@ -826,38 +889,20 @@ static int xdrawer_run_main(int, char**) { } } - if (!gRunning) { - break; - } - - struct pollfd pfd {}; - pfd.fd = xfd; - pfd.events = POLLIN; - ::poll(&pfd, 1, initialKeysReleased ? 250 : 40); + int timeoutMs = visible ? (initialKeysReleased ? 250 : 40) : -1; + struct pollfd pfds[2] {}; + pfds[0].fd = xfd; + pfds[0].events = POLLIN; + pfds[1].fd = fifoFd; + pfds[1].events = POLLIN; + ::poll(pfds, (fifoFd >= 0) ? 2 : 1, timeoutMs); } - // Absorb any still-held softkeys (or second softkey of a closing chord) - // before releasing XGrabKeyboard so dwm never sees a trailing softkey press. - if (closedByKey) { - int64_t drainDeadline = nowMs() + 260; - while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) { - while (XPending(dpy)) { - XEvent ev; - XNextEvent(dpy, &ev); - } - struct pollfd pfd {}; - pfd.fd = xfd; - pfd.events = POLLIN; - ::poll(&pfd, 1, 15); - } - while (XPending(dpy)) { - XEvent ev; - XNextEvent(dpy, &ev); - } - writeSoftkeySuppress(nowMs() + 400); - } + hideWindow(false); + if (fifoFd >= 0) ::close(fifoFd); + ::unlink(fifoPath.c_str()); + ::unlink(daemonPidPath.c_str()); - XUngrabKeyboard(dpy, CurrentTime); pango_font_description_free(descSmall); pango_font_description_free(descIcon); pango_font_description_free(descMain); @@ -870,11 +915,13 @@ static int xdrawer_run_main(int, char**) { XFreePixmap(dpy, pix); XDestroyWindow(dpy, win); XCloseDisplay(dpy); - ::unlink(pidPath.c_str()); return 0; } -static int xdrawer_plugin_init(const DwmHostApi*) { +static int xdrawer_plugin_init(const DwmHostApi* host) { + if (host && host->spawn_plugin) { + host->spawn_plugin("xdrawer", "--preload"); + } return 0; }