Preload DWM plugins (dialer, calllog, xdrawer) with resident X11/Pango state and FIFO IPC
This commit is contained in:
+151
-104
@@ -26,6 +26,7 @@
|
||||
#include <ctime>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
@@ -435,55 +436,47 @@ class PhoneController {
|
||||
|
||||
} // namespace
|
||||
|
||||
static int xdrawer_run_main(int, char**) {
|
||||
static int xdrawer_run_main(int argc, char** argv) {
|
||||
gRunning = 1;
|
||||
int64_t startMs = nowMs();
|
||||
bool isPreload = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (argv[i] && std::strcmp(argv[i], "--preload") == 0) {
|
||||
isPreload = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string daemonPidPath = "/tmp/dwm_plugin_xdrawer.pid";
|
||||
std::string fifoPath = "/tmp/dwm_plugin_xdrawer.fifo";
|
||||
std::string pidPath = getRuntimeFile("xdrawer.pid");
|
||||
std::string startPath = getRuntimeFile("xdrawer.start");
|
||||
int existingPid = readIntFile(pidPath, -1);
|
||||
if (existingPid > 0 && existingPid != static_cast<int>(::getpid())) {
|
||||
if (::kill(existingPid, 0) == 0) {
|
||||
int fd = ::open(startPath.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
int64_t prevStartMs = 0;
|
||||
if (fd >= 0) {
|
||||
char buf[64];
|
||||
ssize_t n = ::read(fd, buf, sizeof(buf) - 1);
|
||||
::close(fd);
|
||||
if (n > 0) {
|
||||
buf[n] = '\0';
|
||||
prevStartMs = std::atoll(buf);
|
||||
}
|
||||
|
||||
int existingDaemon = readIntFile(daemonPidPath, -1);
|
||||
if (existingDaemon > 0 && existingDaemon != static_cast<int>(::getpid()) &&
|
||||
::kill(existingDaemon, 0) == 0) {
|
||||
if (!isPreload) {
|
||||
int ffd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC);
|
||||
if (ffd >= 0) {
|
||||
(void)::write(ffd, "TOGGLE\n", 7);
|
||||
::close(ffd);
|
||||
}
|
||||
if (startMs - prevStartMs < 480) {
|
||||
return 0;
|
||||
}
|
||||
::kill(existingPid, SIGTERM);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
writeIntFile(pidPath, static_cast<int>(::getpid()));
|
||||
{
|
||||
int fd = ::open(startPath.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, 0644);
|
||||
if (fd >= 0) {
|
||||
char buf[32];
|
||||
int len = std::snprintf(buf, sizeof(buf), "%lld\n",
|
||||
static_cast<long long>(startMs));
|
||||
(void)::write(fd, buf, len);
|
||||
::close(fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
writeIntFile(daemonPidPath, static_cast<int>(::getpid()));
|
||||
::mkfifo(fifoPath.c_str(), 0666);
|
||||
::chmod(fifoPath.c_str(), 0666);
|
||||
int fifoFd = ::open(fifoPath.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC);
|
||||
|
||||
struct sigaction sa {};
|
||||
sa.sa_handler = handleSignal;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
|
||||
runAsyncCmd("pkill -x dmenu >/dev/null 2>&1");
|
||||
|
||||
Display* dpy = XOpenDisplay(nullptr);
|
||||
if (!dpy) {
|
||||
::unlink(pidPath.c_str());
|
||||
if (fifoFd >= 0) ::close(fifoFd);
|
||||
::unlink(daemonPidPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -512,22 +505,10 @@ static int xdrawer_run_main(int, char**) {
|
||||
XSetClassHint(dpy, win, &ch);
|
||||
XStoreName(dpy, win, "XDrawer");
|
||||
|
||||
XMapRaised(dpy, win);
|
||||
XSync(dpy, False);
|
||||
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
|
||||
GrabSuccess) {
|
||||
break;
|
||||
}
|
||||
usleep(5000);
|
||||
}
|
||||
|
||||
Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen));
|
||||
GC gc = XCreateGC(dpy, pix, 0, nullptr);
|
||||
XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap);
|
||||
|
||||
// Use the exact same Pango+Xft font stack as dwm ("Fira Code, DejaVuSansM Nerd Font 15px")
|
||||
PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen);
|
||||
PangoContext* pangoCtx = pango_font_map_create_context(fontMap);
|
||||
PangoLayout* layoutMain = pango_layout_new(pangoCtx);
|
||||
@@ -559,6 +540,10 @@ static int xdrawer_run_main(int, char**) {
|
||||
|
||||
PhoneController controller;
|
||||
size_t selected = 0;
|
||||
bool visible = false;
|
||||
int64_t openStartMs = 0;
|
||||
int64_t startupIgnoreUntilMs = 0;
|
||||
bool initialKeysReleased = true;
|
||||
|
||||
auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
|
||||
const std::string& text, int maxW = -1) {
|
||||
@@ -589,7 +574,6 @@ static int xdrawer_run_main(int, char**) {
|
||||
XSetForeground(dpy, gc, 0x222222);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
|
||||
|
||||
// 7 Control rows filling the full 296px height flush from y=24 to y=320
|
||||
const auto& items = controller.items();
|
||||
for (size_t i = 0; i < items.size(); ++i) {
|
||||
int ry = static_cast<int>((i * kWinHeight) / kRowCount);
|
||||
@@ -609,7 +593,6 @@ static int xdrawer_run_main(int, char**) {
|
||||
bool activeState = (it.value > 0);
|
||||
const char* rowIcon = activeState ? it.iconOn : it.iconOff;
|
||||
|
||||
// Icon column (x = 8..28) + Label column (x = 30..112 for sliders, 30..166 for toggles)
|
||||
int labelMaxW = it.isSlider ? 82 : 136;
|
||||
drawPango(layoutIcon, isSel ? &colYellow : &colLight, 8, ry, rowH, rowIcon);
|
||||
drawPango(layoutMain, isSel ? &colWhite : &colLight, 30, ry, rowH,
|
||||
@@ -627,7 +610,6 @@ static int xdrawer_run_main(int, char**) {
|
||||
}
|
||||
pct = std::clamp(pct, 0, 100);
|
||||
|
||||
// Slider track (x = 114, w = 78, h = 12) + thumb + right-aligned pct (x = 196..234)
|
||||
int barX = 114;
|
||||
int barW = 78;
|
||||
int barH = 12;
|
||||
@@ -644,7 +626,6 @@ static int xdrawer_run_main(int, char**) {
|
||||
XFillRectangle(dpy, pix, gc, barX + 1, barY + 1, fillW, barH - 2);
|
||||
}
|
||||
|
||||
// Slider thumb knob
|
||||
int knobX = barX + std::clamp(fillW - 2, 0, barW - 5);
|
||||
XSetForeground(dpy, gc, 0xffffff);
|
||||
XFillRectangle(dpy, pix, gc, knobX, barY - 2, 5, barH + 4);
|
||||
@@ -656,7 +637,6 @@ static int xdrawer_run_main(int, char**) {
|
||||
drawPango(layoutSmall, isSel ? &colWhite : &colLight, kWinWidth - pw - 6,
|
||||
ry, rowH, pctStr);
|
||||
} else {
|
||||
// Toggle switch icon + ON/OFF label (right-aligned within x = 160..234)
|
||||
bool on = (it.value != 0);
|
||||
std::string stStr = on ? "ON" : "OFF";
|
||||
int stW = pangoWidth(layoutMain, stStr);
|
||||
@@ -668,11 +648,15 @@ static int xdrawer_run_main(int, char**) {
|
||||
}
|
||||
}
|
||||
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
XFlush(dpy);
|
||||
if (visible) {
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
XFlush(dpy);
|
||||
}
|
||||
};
|
||||
|
||||
// Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup
|
||||
render();
|
||||
XSync(dpy, False);
|
||||
|
||||
auto writeSoftkeySuppress = [](int64_t untilMs) {
|
||||
int fd = ::open("/tmp/sxmo_softkey_suppress",
|
||||
@@ -701,27 +685,108 @@ static int xdrawer_run_main(int, char**) {
|
||||
return mask;
|
||||
};
|
||||
|
||||
// Drain any stale key events that arrived before initial render finished
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
if (ev.type == Expose && ev.xexpose.count == 0) {
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t startupIgnoreUntilMs = std::max(startMs + 220, nowMs() + 60);
|
||||
bool initialKeysReleased = (querySoftkeysDown(dpy) == 0);
|
||||
bool closedByKey = false;
|
||||
int xfd = ConnectionNumber(dpy);
|
||||
|
||||
auto closeOtherPlugin = [](const char* activePidName, const char* fifoPathStr) {
|
||||
if (readIntFile(getRuntimeFile(activePidName), -1) > 0) {
|
||||
int ffd = ::open(fifoPathStr, O_WRONLY | O_NONBLOCK | O_CLOEXEC);
|
||||
if (ffd >= 0) {
|
||||
(void)::write(ffd, "CLOSE\n", 6);
|
||||
::close(ffd);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto showWindow = [&]() {
|
||||
if (visible) {
|
||||
return;
|
||||
}
|
||||
closeOtherPlugin("xdialer.pid", "/tmp/dwm_plugin_dialer.fifo");
|
||||
closeOtherPlugin("xcalllog.pid", "/tmp/dwm_plugin_calllog.fifo");
|
||||
openStartMs = nowMs();
|
||||
startupIgnoreUntilMs = openStartMs + 180;
|
||||
initialKeysReleased = (querySoftkeysDown(dpy) == 0);
|
||||
visible = true;
|
||||
XMapRaised(dpy, win);
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
|
||||
GrabSuccess) {
|
||||
break;
|
||||
}
|
||||
usleep(2000);
|
||||
}
|
||||
XFlush(dpy);
|
||||
writeIntFile(pidPath, static_cast<int>(::getpid()));
|
||||
controller.refreshAll();
|
||||
render();
|
||||
runAsyncCmd("pkill -x dmenu >/dev/null 2>&1");
|
||||
};
|
||||
|
||||
auto hideWindow = [&](bool drainSoftkeys) {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
if (drainSoftkeys) {
|
||||
int64_t drainDeadline = nowMs() + 220;
|
||||
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
}
|
||||
struct pollfd pfd {};
|
||||
pfd.fd = xfd;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, 10);
|
||||
}
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
}
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
XUnmapWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
::unlink(pidPath.c_str());
|
||||
visible = false;
|
||||
};
|
||||
|
||||
if (!isPreload) {
|
||||
showWindow();
|
||||
}
|
||||
|
||||
while (gRunning) {
|
||||
if (!initialKeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
if (fifoFd >= 0) {
|
||||
char fbuf[256];
|
||||
ssize_t fn = ::read(fifoFd, fbuf, sizeof(fbuf) - 1);
|
||||
if (fn > 0) {
|
||||
fbuf[fn] = '\0';
|
||||
std::stringstream ss(fbuf);
|
||||
std::string cmd;
|
||||
while (std::getline(ss, cmd)) {
|
||||
if (!cmd.empty() && cmd.back() == '\r') cmd.pop_back();
|
||||
if (cmd == "TOGGLE") {
|
||||
if (!visible) {
|
||||
showWindow();
|
||||
} else if (nowMs() - openStartMs >= 350) {
|
||||
hideWindow(true);
|
||||
}
|
||||
} else if (cmd == "OPEN") {
|
||||
showWindow();
|
||||
} else if (cmd == "CLOSE") {
|
||||
hideWindow(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (visible && !initialKeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
initialKeysReleased = true;
|
||||
}
|
||||
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
if (!visible) {
|
||||
continue;
|
||||
}
|
||||
if (!initialKeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
initialKeysReleased = true;
|
||||
}
|
||||
@@ -751,9 +816,8 @@ static int xdrawer_run_main(int, char**) {
|
||||
sym == XF86XK_Back || sym == XF86XK_KbdLcdMenu2 ||
|
||||
ev.xkey.keycode == 135 || ev.xkey.keycode == 166) {
|
||||
if (initialKeysReleased && nowMs() >= startupIgnoreUntilMs) {
|
||||
writeSoftkeySuppress(nowMs() + 500);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
writeSoftkeySuppress(nowMs() + 450);
|
||||
hideWindow(true);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
@@ -763,9 +827,8 @@ static int xdrawer_run_main(int, char**) {
|
||||
case XK_Escape:
|
||||
case XK_BackSpace:
|
||||
case XK_q:
|
||||
writeSoftkeySuppress(nowMs() + 400);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
hideWindow(true);
|
||||
break;
|
||||
case XK_Up:
|
||||
case XK_KP_Up:
|
||||
@@ -826,38 +889,20 @@ static int xdrawer_run_main(int, char**) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!gRunning) {
|
||||
break;
|
||||
}
|
||||
|
||||
struct pollfd pfd {};
|
||||
pfd.fd = xfd;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, initialKeysReleased ? 250 : 40);
|
||||
int timeoutMs = visible ? (initialKeysReleased ? 250 : 40) : -1;
|
||||
struct pollfd pfds[2] {};
|
||||
pfds[0].fd = xfd;
|
||||
pfds[0].events = POLLIN;
|
||||
pfds[1].fd = fifoFd;
|
||||
pfds[1].events = POLLIN;
|
||||
::poll(pfds, (fifoFd >= 0) ? 2 : 1, timeoutMs);
|
||||
}
|
||||
|
||||
// Absorb any still-held softkeys (or second softkey of a closing chord)
|
||||
// before releasing XGrabKeyboard so dwm never sees a trailing softkey press.
|
||||
if (closedByKey) {
|
||||
int64_t drainDeadline = nowMs() + 260;
|
||||
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
}
|
||||
struct pollfd pfd {};
|
||||
pfd.fd = xfd;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, 15);
|
||||
}
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
}
|
||||
writeSoftkeySuppress(nowMs() + 400);
|
||||
}
|
||||
hideWindow(false);
|
||||
if (fifoFd >= 0) ::close(fifoFd);
|
||||
::unlink(fifoPath.c_str());
|
||||
::unlink(daemonPidPath.c_str());
|
||||
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
pango_font_description_free(descSmall);
|
||||
pango_font_description_free(descIcon);
|
||||
pango_font_description_free(descMain);
|
||||
@@ -870,11 +915,13 @@ static int xdrawer_run_main(int, char**) {
|
||||
XFreePixmap(dpy, pix);
|
||||
XDestroyWindow(dpy, win);
|
||||
XCloseDisplay(dpy);
|
||||
::unlink(pidPath.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xdrawer_plugin_init(const DwmHostApi*) {
|
||||
static int xdrawer_plugin_init(const DwmHostApi* host) {
|
||||
if (host && host->spawn_plugin) {
|
||||
host->spawn_plugin("xdrawer", "--preload");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user