Preload DWM plugins (dialer, calllog, xdrawer) with resident X11/Pango state and FIFO IPC
This commit is contained in:
+203
-128
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// CallLog DWM Plugin - Keypad-driven X11 call log viewer & dialer launcher (240x320)
|
||||
// CallLog DWM Plugin - Preloaded resident X11 call log viewer & dialer launcher (240x320)
|
||||
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xft/Xft.h>
|
||||
@@ -37,27 +37,23 @@ void handleSignal(int) {
|
||||
gRunning = 0;
|
||||
}
|
||||
|
||||
bool isCallRinging() {
|
||||
std::string ringPidFile = getRuntimeFile("sxmo.ring.pid");
|
||||
int pid = readIntFile(ringPidFile, -1);
|
||||
if (pid > 0 && ::kill(pid, 0) == 0) {
|
||||
return true;
|
||||
}
|
||||
return ::system("pidof -s sxmo_RepeatRing.sh >/dev/null 2>&1") == 0;
|
||||
}
|
||||
|
||||
int calllog_run_main(int, char**) {
|
||||
int calllog_run_main(int argc, char** argv) {
|
||||
gRunning = 1;
|
||||
int64_t startMs = nowMs();
|
||||
|
||||
std::string pidPath = getRuntimeFile("xcalllog.pid");
|
||||
int existingPid = readIntFile(pidPath, -1);
|
||||
if (existingPid > 0 && existingPid != static_cast<int>(::getpid())) {
|
||||
if (::kill(existingPid, 0) == 0) {
|
||||
return 0;
|
||||
bool isPreload = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (argv[i] && std::strcmp(argv[i], "--preload") == 0) {
|
||||
isPreload = true;
|
||||
}
|
||||
}
|
||||
writeIntFile(pidPath, static_cast<int>(::getpid()));
|
||||
|
||||
if (sendPluginCommand("calllog", isPreload ? "NOP" : "OPEN")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string daemonPidPath = getPluginDaemonPidPath("calllog");
|
||||
std::string activePidPath = getRuntimeFile("xcalllog.pid");
|
||||
writeIntFile(daemonPidPath, static_cast<int>(::getpid()));
|
||||
int fifoFd = setupPluginDaemonFifo("calllog");
|
||||
|
||||
struct sigaction sa {};
|
||||
sa.sa_handler = handleSignal;
|
||||
@@ -67,7 +63,8 @@ int calllog_run_main(int, char**) {
|
||||
|
||||
Display* dpy = XOpenDisplay(nullptr);
|
||||
if (!dpy) {
|
||||
::unlink(pidPath.c_str());
|
||||
if (fifoFd >= 0) ::close(fifoFd);
|
||||
::unlink(daemonPidPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -96,17 +93,6 @@ int calllog_run_main(int, char**) {
|
||||
XSetClassHint(dpy, win, &ch);
|
||||
XStoreName(dpy, win, "Call Log");
|
||||
|
||||
XMapRaised(dpy, win);
|
||||
XSync(dpy, False);
|
||||
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
|
||||
GrabSuccess) {
|
||||
break;
|
||||
}
|
||||
usleep(5000);
|
||||
}
|
||||
|
||||
Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen));
|
||||
GC gc = XCreateGC(dpy, pix, 0, nullptr);
|
||||
XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap);
|
||||
@@ -144,12 +130,19 @@ int calllog_run_main(int, char**) {
|
||||
XftColor colRed = allocColor("#e74c3c");
|
||||
|
||||
ContactBook book;
|
||||
book.load();
|
||||
std::vector<CallLogItem> allLogs = loadCallLog(book);
|
||||
std::vector<CallLogItem> allLogs;
|
||||
std::vector<CallLogItem> filtered;
|
||||
FileStamp contactsStamp;
|
||||
FileStamp logStamp;
|
||||
|
||||
int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out
|
||||
int selected = 0;
|
||||
bool visible = false;
|
||||
int64_t openStartMs = 0;
|
||||
int64_t startupIgnoreUntilMs = 0;
|
||||
bool initialSoftkeysReleased = true;
|
||||
int softkeyPending = 0;
|
||||
int64_t softkeyDeadlineMs = 0;
|
||||
|
||||
auto applyFilter = [&]() {
|
||||
filtered.clear();
|
||||
@@ -164,6 +157,21 @@ int calllog_run_main(int, char**) {
|
||||
}
|
||||
};
|
||||
|
||||
bool logsLoadedOnce = false;
|
||||
auto reloadLogsIfNeeded = [&]() -> bool {
|
||||
bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath());
|
||||
bool lChanged = logStamp.checkAndUpdate(getModemLogFilePath());
|
||||
if (cChanged || lChanged || !logsLoadedOnce) {
|
||||
logsLoadedOnce = true;
|
||||
book.load();
|
||||
allLogs = loadCallLog(book);
|
||||
applyFilter();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
reloadLogsIfNeeded();
|
||||
applyFilter();
|
||||
|
||||
auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
|
||||
@@ -194,7 +202,7 @@ int calllog_run_main(int, char**) {
|
||||
constexpr int kHeaderH = 34;
|
||||
constexpr int kSoftbarH = 26;
|
||||
constexpr int kListY = kHeaderH;
|
||||
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH; // 236px
|
||||
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH;
|
||||
constexpr int kVisibleRows = 5;
|
||||
|
||||
const char* kTabLabels[4] = {"All", "Missed", "In", "Out"};
|
||||
@@ -203,7 +211,6 @@ int calllog_run_main(int, char**) {
|
||||
XSetForeground(dpy, gc, 0x222222);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
|
||||
|
||||
// 1. Top Filter Tabs Bar (y = 0..34)
|
||||
XSetForeground(dpy, gc, 0x1c1c1c);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH);
|
||||
|
||||
@@ -226,7 +233,6 @@ int calllog_run_main(int, char**) {
|
||||
XSetForeground(dpy, gc, 0x444444);
|
||||
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
|
||||
|
||||
// 2. Call Log List (y = 34..270, 5 visible rows)
|
||||
if (filtered.empty()) {
|
||||
drawPango(layoutIcon, &colMuted, 112, kListY + 60, 24, kIconPhoneLog);
|
||||
drawPango(layoutMain, &colMuted, 52, kListY + 90, 28, "No call history", 160);
|
||||
@@ -312,7 +318,6 @@ int calllog_run_main(int, char**) {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Bottom Softkey Bar (y = 270..296)
|
||||
int sbY = kWinHeight - kSoftbarH;
|
||||
XSetForeground(dpy, gc, 0x181818);
|
||||
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, kSoftbarH);
|
||||
@@ -329,31 +334,118 @@ int calllog_run_main(int, char**) {
|
||||
drawPango(layoutSmall, &colLight, kWinWidth - rw - 6, sbY + 1,
|
||||
kSoftbarH - 1, rightLabel);
|
||||
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
XFlush(dpy);
|
||||
if (visible) {
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
XFlush(dpy);
|
||||
}
|
||||
};
|
||||
|
||||
// Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup
|
||||
drawPango(layoutIcon, &colGreen, 0, 0, 20,
|
||||
std::string(kIconInCall) + kIconOutCall + kIconMissedCall + kIconPhoneLog);
|
||||
render();
|
||||
XSync(dpy, False);
|
||||
|
||||
auto showWindow = [&]() {
|
||||
if (isPidFileAlive(getRuntimeFile("xdialer.pid"))) {
|
||||
sendPluginCommand("dialer", "CLOSE");
|
||||
}
|
||||
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
|
||||
sendPluginCommand("xdrawer", "CLOSE");
|
||||
}
|
||||
if (!visible) {
|
||||
openStartMs = nowMs();
|
||||
startupIgnoreUntilMs = openStartMs + 90;
|
||||
initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
|
||||
softkeyPending = 0;
|
||||
softkeyDeadlineMs = 0;
|
||||
visible = true;
|
||||
XMapRaised(dpy, win);
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
|
||||
GrabSuccess) {
|
||||
break;
|
||||
}
|
||||
usleep(2000);
|
||||
}
|
||||
if (reloadLogsIfNeeded() || filterTab != 0 || selected != 0) {
|
||||
filterTab = 0;
|
||||
selected = 0;
|
||||
applyFilter();
|
||||
render();
|
||||
}
|
||||
XFlush(dpy);
|
||||
writeIntFile(activePidPath, static_cast<int>(::getpid()));
|
||||
} else {
|
||||
if (reloadLogsIfNeeded()) {
|
||||
render();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto hideWindow = [&](bool drainSoftkeys) {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
int xfd = ConnectionNumber(dpy);
|
||||
if (drainSoftkeys) {
|
||||
int64_t drainDeadline = nowMs() + 180;
|
||||
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
}
|
||||
struct pollfd pfd {};
|
||||
pfd.fd = xfd;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, 10);
|
||||
}
|
||||
writeSoftkeySuppress(nowMs() + 300);
|
||||
}
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
XUnmapWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
::unlink(activePidPath.c_str());
|
||||
visible = false;
|
||||
softkeyPending = 0;
|
||||
softkeyDeadlineMs = 0;
|
||||
if (filterTab != 0 || selected != 0) {
|
||||
filterTab = 0;
|
||||
selected = 0;
|
||||
applyFilter();
|
||||
render();
|
||||
XFlush(dpy);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isPreload) {
|
||||
showWindow();
|
||||
}
|
||||
|
||||
int64_t startupIgnoreUntilMs = startMs + 140;
|
||||
bool initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
|
||||
int softkeyPending = 0;
|
||||
int64_t softkeyDeadlineMs = 0;
|
||||
bool closedByKey = false;
|
||||
std::string numberToCall;
|
||||
std::string dialerInitArg;
|
||||
bool openDialerAfter = false;
|
||||
bool launchXDrawer = false;
|
||||
int xfd = ConnectionNumber(dpy);
|
||||
|
||||
while (gRunning) {
|
||||
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
for (const std::string& cmd : readFifoLines(fifoFd)) {
|
||||
if (cmd == "NOP") {
|
||||
continue;
|
||||
} else if (cmd == "CLOSE") {
|
||||
hideWindow(false);
|
||||
} else if (cmd.rfind("OPEN", 0) == 0) {
|
||||
showWindow();
|
||||
}
|
||||
}
|
||||
|
||||
if (visible && !initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
initialSoftkeysReleased = true;
|
||||
}
|
||||
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
if (!visible) {
|
||||
continue;
|
||||
}
|
||||
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
initialSoftkeysReleased = true;
|
||||
}
|
||||
@@ -391,24 +483,25 @@ int calllog_run_main(int, char**) {
|
||||
softkeyPending |= which | querySoftkeysDown(dpy);
|
||||
if (softkeyPending == 3) {
|
||||
writeSoftkeySuppress(nowMs() + 500);
|
||||
launchXDrawer = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("xdrawer", "TOGGLE")) {
|
||||
runAsyncArgv("xdrawer");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (softkeyDeadlineMs == 0) {
|
||||
softkeyDeadlineMs = nowMs() + 45;
|
||||
softkeyDeadlineMs = nowMs() + 40;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pressing 0-9, *, or # in Call Log switches directly to Dialer with that char
|
||||
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
|
||||
if (dialCh != '\0') {
|
||||
dialerInitArg = std::string(1, dialCh);
|
||||
openDialerAfter = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
std::string arg(1, dialCh);
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("dialer", "OPEN " + arg)) {
|
||||
runAsyncArgv("xdialer", arg.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -416,9 +509,8 @@ int calllog_run_main(int, char**) {
|
||||
case XK_Escape:
|
||||
case XK_BackSpace:
|
||||
case XK_q:
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
writeSoftkeySuppress(nowMs() + 300);
|
||||
hideWindow(true);
|
||||
break;
|
||||
case XK_Left:
|
||||
case XK_KP_Left:
|
||||
@@ -462,14 +554,15 @@ int calllog_run_main(int, char**) {
|
||||
}
|
||||
if (!filtered.empty() && selected >= 0 &&
|
||||
selected < static_cast<int>(filtered.size())) {
|
||||
numberToCall = filtered[selected].number;
|
||||
writeSoftkeySuppress(nowMs() + 400);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
std::string num = filtered[selected].number;
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
hideWindow(true);
|
||||
placePhoneCall(num);
|
||||
} else if (filtered.empty()) {
|
||||
openDialerAfter = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("dialer", "OPEN")) {
|
||||
runAsyncArgv("xdialer");
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -478,66 +571,59 @@ int calllog_run_main(int, char**) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!gRunning) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
|
||||
if (visible && softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
|
||||
int comb = softkeyPending | querySoftkeysDown(dpy);
|
||||
softkeyPending = 0;
|
||||
softkeyDeadlineMs = 0;
|
||||
if (comb == 3) {
|
||||
writeSoftkeySuppress(nowMs() + 500);
|
||||
launchXDrawer = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
break;
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("xdrawer", "TOGGLE")) {
|
||||
runAsyncArgv("xdrawer");
|
||||
}
|
||||
} else if (comb == 1) {
|
||||
// Left Softkey: open Dialer pre-populated with selected number
|
||||
std::string arg;
|
||||
if (!filtered.empty() && selected >= 0 &&
|
||||
selected < static_cast<int>(filtered.size())) {
|
||||
dialerInitArg = filtered[selected].number;
|
||||
arg = filtered[selected].number;
|
||||
}
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("dialer", arg.empty() ? "OPEN" : ("OPEN " + arg))) {
|
||||
if (arg.empty()) {
|
||||
runAsyncArgv("xdialer");
|
||||
} else {
|
||||
runAsyncArgv("xdialer", arg.c_str());
|
||||
}
|
||||
}
|
||||
openDialerAfter = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
break;
|
||||
} else if (comb == 2) {
|
||||
// Right Softkey: close Call Log
|
||||
writeSoftkeySuppress(nowMs() + 400);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
break;
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
hideWindow(true);
|
||||
}
|
||||
}
|
||||
|
||||
int timeoutMs = 200;
|
||||
if (softkeyDeadlineMs > 0) {
|
||||
timeoutMs = static_cast<int>(std::max<int64_t>(5, softkeyDeadlineMs - nowMs()));
|
||||
}
|
||||
|
||||
struct pollfd pfd {};
|
||||
pfd.fd = xfd;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, timeoutMs);
|
||||
}
|
||||
|
||||
if (closedByKey && !launchXDrawer && !openDialerAfter) {
|
||||
int64_t drainDeadline = nowMs() + 200;
|
||||
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
int timeoutMs = -1;
|
||||
if (visible) {
|
||||
timeoutMs = 200;
|
||||
if (softkeyDeadlineMs > 0) {
|
||||
timeoutMs = static_cast<int>(std::max<int64_t>(5, softkeyDeadlineMs - nowMs()));
|
||||
}
|
||||
struct pollfd pfd {};
|
||||
pfd.fd = xfd;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, 15);
|
||||
}
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
|
||||
struct pollfd pfds[2] {};
|
||||
pfds[0].fd = xfd;
|
||||
pfds[0].events = POLLIN;
|
||||
pfds[1].fd = fifoFd;
|
||||
pfds[1].events = POLLIN;
|
||||
::poll(pfds, (fifoFd >= 0) ? 2 : 1, timeoutMs);
|
||||
}
|
||||
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
hideWindow(false);
|
||||
if (fifoFd >= 0) {
|
||||
::close(fifoFd);
|
||||
}
|
||||
::unlink(getPluginFifoPath("calllog").c_str());
|
||||
::unlink(daemonPidPath.c_str());
|
||||
|
||||
pango_font_description_free(descIcon);
|
||||
pango_font_description_free(descSmall);
|
||||
pango_font_description_free(descMain);
|
||||
@@ -550,24 +636,13 @@ int calllog_run_main(int, char**) {
|
||||
XFreePixmap(dpy, pix);
|
||||
XDestroyWindow(dpy, win);
|
||||
XCloseDisplay(dpy);
|
||||
::unlink(pidPath.c_str());
|
||||
|
||||
if (launchXDrawer) {
|
||||
runAsyncArgv("xdrawer");
|
||||
} else if (openDialerAfter) {
|
||||
if (!dialerInitArg.empty()) {
|
||||
runAsyncArgv("xdialer", dialerInitArg.c_str());
|
||||
} else {
|
||||
runAsyncArgv("xdialer");
|
||||
}
|
||||
} else if (!numberToCall.empty()) {
|
||||
placePhoneCall(numberToCall);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int calllog_plugin_init(const DwmHostApi*) {
|
||||
int calllog_plugin_init(const DwmHostApi* host) {
|
||||
if (host && host->spawn_plugin) {
|
||||
host->spawn_plugin("calllog", "--preload");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -578,7 +653,7 @@ int calllog_on_global_key(const DwmHostApi* host, KeySym keysym,
|
||||
return 0;
|
||||
}
|
||||
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
|
||||
if (isCallRinging()) {
|
||||
if (isCallRingingFast()) {
|
||||
const char* const argv[] = {"sxmo_hook_pickup.sh", nullptr};
|
||||
if (host && host->spawn_command) {
|
||||
host->spawn_command(argv);
|
||||
@@ -597,7 +672,7 @@ int calllog_on_idle_key(const DwmHostApi* host, KeySym keysym,
|
||||
return 0;
|
||||
}
|
||||
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
|
||||
if (isCallRinging()) {
|
||||
if (isCallRingingFast()) {
|
||||
const char* const argv[] = {"sxmo_hook_pickup.sh", nullptr};
|
||||
if (host->spawn_command) {
|
||||
host->spawn_command(argv);
|
||||
|
||||
+216
-172
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Dialer DWM Plugin - Keypad-driven X11 dialer with contact & T9 integration (240x320)
|
||||
// Dialer DWM Plugin - Preloaded resident X11 dialer with contact & T9 integration (240x320)
|
||||
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xft/Xft.h>
|
||||
@@ -37,51 +37,28 @@ void handleSignal(int) {
|
||||
gRunning = 0;
|
||||
}
|
||||
|
||||
constexpr const char* kQueuePath = "/tmp/sxmo_dialer_queue";
|
||||
|
||||
void enqueueDialChars(const char* chars) {
|
||||
if (!chars || !chars[0]) {
|
||||
return;
|
||||
}
|
||||
int fd = ::open(kQueuePath, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0644);
|
||||
if (fd >= 0) {
|
||||
(void)::write(fd, chars, std::strlen(chars));
|
||||
::close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
std::string drainDialQueue() {
|
||||
int fd = ::open(kQueuePath, O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
return "";
|
||||
}
|
||||
char buf[128];
|
||||
ssize_t n = ::read(fd, buf, sizeof(buf) - 1);
|
||||
::close(fd);
|
||||
::unlink(kQueuePath);
|
||||
if (n <= 0) {
|
||||
return "";
|
||||
}
|
||||
buf[n] = '\0';
|
||||
return normalizePhone(buf);
|
||||
}
|
||||
|
||||
int dialer_run_main(int argc, char** argv) {
|
||||
gRunning = 1;
|
||||
int64_t startMs = nowMs();
|
||||
bool isPreload = false;
|
||||
std::string initArg;
|
||||
|
||||
if (argc > 1 && argv[1] && argv[1][0]) {
|
||||
enqueueDialChars(argv[1]);
|
||||
}
|
||||
|
||||
std::string pidPath = getRuntimeFile("xdialer.pid");
|
||||
int existingPid = readIntFile(pidPath, -1);
|
||||
if (existingPid > 0 && existingPid != static_cast<int>(::getpid())) {
|
||||
if (::kill(existingPid, 0) == 0) {
|
||||
return 0;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (argv[i] && std::strcmp(argv[i], "--preload") == 0) {
|
||||
isPreload = true;
|
||||
} else if (argv[i] && argv[i][0]) {
|
||||
initArg += argv[i];
|
||||
}
|
||||
}
|
||||
writeIntFile(pidPath, static_cast<int>(::getpid()));
|
||||
|
||||
// If the resident dialer daemon is already running, forward the command via FIFO
|
||||
if (sendPluginCommand("dialer", isPreload ? "NOP" : (initArg.empty() ? "OPEN" : ("OPEN " + initArg)))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string daemonPidPath = getPluginDaemonPidPath("dialer");
|
||||
std::string activePidPath = getRuntimeFile("xdialer.pid");
|
||||
writeIntFile(daemonPidPath, static_cast<int>(::getpid()));
|
||||
int fifoFd = setupPluginDaemonFifo("dialer");
|
||||
|
||||
struct sigaction sa {};
|
||||
sa.sa_handler = handleSignal;
|
||||
@@ -91,7 +68,8 @@ int dialer_run_main(int argc, char** argv) {
|
||||
|
||||
Display* dpy = XOpenDisplay(nullptr);
|
||||
if (!dpy) {
|
||||
::unlink(pidPath.c_str());
|
||||
if (fifoFd >= 0) ::close(fifoFd);
|
||||
::unlink(daemonPidPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -120,17 +98,6 @@ int dialer_run_main(int argc, char** argv) {
|
||||
XSetClassHint(dpy, win, &ch);
|
||||
XStoreName(dpy, win, "Dialer");
|
||||
|
||||
XMapRaised(dpy, win);
|
||||
XSync(dpy, False);
|
||||
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
|
||||
GrabSuccess) {
|
||||
break;
|
||||
}
|
||||
usleep(5000);
|
||||
}
|
||||
|
||||
Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen));
|
||||
GC gc = XCreateGC(dpy, pix, 0, nullptr);
|
||||
XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap);
|
||||
@@ -172,17 +139,36 @@ int dialer_run_main(int argc, char** argv) {
|
||||
XftColor colBlue = allocColor("#5dade2");
|
||||
|
||||
ContactBook book;
|
||||
book.load();
|
||||
std::vector<ContactEntry> allContacts = book.buildDialerList();
|
||||
std::vector<ContactEntry> allContacts;
|
||||
std::vector<ContactEntry> filtered;
|
||||
FileStamp contactsStamp;
|
||||
FileStamp logStamp;
|
||||
bool contactsLoadedOnce = false;
|
||||
|
||||
std::string number = drainDialQueue();
|
||||
int selected = -1; // -1 = top number box focused; 0..N-1 = contact row
|
||||
auto reloadContactsIfNeeded = [&]() -> bool {
|
||||
bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath());
|
||||
bool lChanged = logStamp.checkAndUpdate(getModemLogFilePath());
|
||||
if (cChanged || lChanged || !contactsLoadedOnce) {
|
||||
contactsLoadedOnce = true;
|
||||
book.load();
|
||||
allContacts = book.buildDialerList();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// If launched with '0' from idle desktop and '0' (keycode 19) is still held,
|
||||
// allow long-pressing that initial '0' to turn into '+'.
|
||||
bool zeroHeld = (number == "0" && isKeycodeDown(dpy, 19));
|
||||
int64_t zeroPressMs = zeroHeld ? startMs : 0;
|
||||
reloadContactsIfNeeded();
|
||||
|
||||
std::string number;
|
||||
int selected = -1;
|
||||
bool visible = false;
|
||||
int64_t openStartMs = 0;
|
||||
int64_t startupIgnoreUntilMs = 0;
|
||||
bool initialSoftkeysReleased = true;
|
||||
int softkeyPending = 0;
|
||||
int64_t softkeyDeadlineMs = 0;
|
||||
bool zeroHeld = false;
|
||||
int64_t zeroPressMs = 0;
|
||||
|
||||
auto updateFilter = [&]() {
|
||||
filtered.clear();
|
||||
@@ -223,11 +209,6 @@ int dialer_run_main(int argc, char** argv) {
|
||||
}
|
||||
};
|
||||
|
||||
updateFilter();
|
||||
if (!number.empty()) {
|
||||
selected = -1;
|
||||
}
|
||||
|
||||
auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
|
||||
const std::string& text, int maxW = -1) {
|
||||
pango_layout_set_text(layout, text.c_str(), static_cast<int>(text.size()));
|
||||
@@ -256,14 +237,13 @@ int dialer_run_main(int argc, char** argv) {
|
||||
constexpr int kHeaderH = 56;
|
||||
constexpr int kSoftbarH = 26;
|
||||
constexpr int kListY = kHeaderH;
|
||||
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH; // 214px
|
||||
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH;
|
||||
constexpr int kVisibleRows = 5;
|
||||
|
||||
auto render = [&]() {
|
||||
XSetForeground(dpy, gc, 0x222222);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
|
||||
|
||||
// 1. Top Number Display Box (y = 0..56)
|
||||
bool topSel = (selected < 0);
|
||||
XSetForeground(dpy, gc, topSel ? 0x1b3b4b : 0x1c1c1c);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH);
|
||||
@@ -287,7 +267,6 @@ int dialer_run_main(int argc, char** argv) {
|
||||
drawPango(layoutIcon, &colMuted, 218, 4, 30, kIconBackspace);
|
||||
}
|
||||
|
||||
// Subtitle inside header box (y = 33..54)
|
||||
std::string subText;
|
||||
XftColor* subCol = &colMuted;
|
||||
if (selected >= 0 && selected < static_cast<int>(filtered.size())) {
|
||||
@@ -310,7 +289,6 @@ int dialer_run_main(int argc, char** argv) {
|
||||
}
|
||||
drawPango(layoutSmall, subCol, 32, 33, 20, subText, 202);
|
||||
|
||||
// 2. Contact / Recent Suggestions List (y = 56..270, 5 visible rows)
|
||||
if (filtered.empty()) {
|
||||
drawPango(layoutMain, &colMuted, 16, kListY + 40, 30,
|
||||
number.empty() ? "No saved contacts" : "New number (press Call)",
|
||||
@@ -372,7 +350,6 @@ int dialer_run_main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Bottom Softkey Bar (y = 270..296)
|
||||
int sbY = kWinHeight - kSoftbarH;
|
||||
XSetForeground(dpy, gc, 0x181818);
|
||||
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, kSoftbarH);
|
||||
@@ -391,20 +368,111 @@ int dialer_run_main(int argc, char** argv) {
|
||||
drawPango(layoutSmall, &colLight, kWinWidth - rw - 6, sbY + 1,
|
||||
kSoftbarH - 1, rightLabel);
|
||||
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
XFlush(dpy);
|
||||
if (visible) {
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
XFlush(dpy);
|
||||
}
|
||||
};
|
||||
|
||||
// Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup
|
||||
number = "+0123456789*#";
|
||||
updateFilter();
|
||||
render();
|
||||
drawPango(layoutIcon, &colGreen, 0, 0, 20,
|
||||
std::string(kIconPhone) + kIconBackspace + kIconClock + kIconUser);
|
||||
number.clear();
|
||||
updateFilter();
|
||||
render();
|
||||
XSync(dpy, False);
|
||||
|
||||
auto showWindow = [&](const std::string& arg) {
|
||||
if (isPidFileAlive(getRuntimeFile("xcalllog.pid"))) {
|
||||
sendPluginCommand("calllog", "CLOSE");
|
||||
}
|
||||
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
|
||||
sendPluginCommand("xdrawer", "CLOSE");
|
||||
}
|
||||
std::string cleanArg = normalizePhone(arg);
|
||||
if (!visible) {
|
||||
openStartMs = nowMs();
|
||||
startupIgnoreUntilMs = openStartMs + 70;
|
||||
initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
|
||||
softkeyPending = 0;
|
||||
softkeyDeadlineMs = 0;
|
||||
zeroHeld = (cleanArg == "0" && isKeycodeDown(dpy, 19));
|
||||
zeroPressMs = zeroHeld ? openStartMs : 0;
|
||||
visible = true;
|
||||
XMapRaised(dpy, win);
|
||||
if (cleanArg.empty()) {
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
}
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) ==
|
||||
GrabSuccess) {
|
||||
break;
|
||||
}
|
||||
usleep(2000);
|
||||
}
|
||||
bool reloaded = reloadContactsIfNeeded();
|
||||
if (!cleanArg.empty() || reloaded || number != cleanArg) {
|
||||
number = cleanArg;
|
||||
selected = number.empty() ? (allContacts.empty() ? -1 : 0) : -1;
|
||||
updateFilter();
|
||||
render();
|
||||
}
|
||||
XFlush(dpy);
|
||||
writeIntFile(activePidPath, static_cast<int>(::getpid()));
|
||||
} else if (!cleanArg.empty()) {
|
||||
if (nowMs() - openStartMs < 40 && number == cleanArg) {
|
||||
return;
|
||||
}
|
||||
number += cleanArg;
|
||||
selected = -1;
|
||||
updateFilter();
|
||||
render();
|
||||
}
|
||||
};
|
||||
|
||||
auto hideWindow = [&](bool drainSoftkeys) {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
int xfd = ConnectionNumber(dpy);
|
||||
if (drainSoftkeys) {
|
||||
int64_t drainDeadline = nowMs() + 180;
|
||||
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
}
|
||||
struct pollfd pfd {};
|
||||
pfd.fd = xfd;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, 10);
|
||||
}
|
||||
writeSoftkeySuppress(nowMs() + 300);
|
||||
}
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
XUnmapWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
::unlink(activePidPath.c_str());
|
||||
visible = false;
|
||||
softkeyPending = 0;
|
||||
softkeyDeadlineMs = 0;
|
||||
zeroHeld = false;
|
||||
if (!number.empty() || selected != (allContacts.empty() ? -1 : 0)) {
|
||||
number.clear();
|
||||
selected = allContacts.empty() ? -1 : 0;
|
||||
updateFilter();
|
||||
render();
|
||||
XFlush(dpy);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isPreload) {
|
||||
showWindow(initArg);
|
||||
}
|
||||
|
||||
int64_t startupIgnoreUntilMs = startMs + 90;
|
||||
bool initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
|
||||
int softkeyPending = 0;
|
||||
int64_t softkeyDeadlineMs = 0;
|
||||
bool closedByKey = false;
|
||||
std::string targetToDial;
|
||||
bool switchToCallLog = false;
|
||||
bool launchXDrawer = false;
|
||||
int xfd = ConnectionNumber(dpy);
|
||||
|
||||
auto selectActiveNumber = [&]() -> std::string {
|
||||
@@ -421,15 +489,20 @@ int dialer_run_main(int argc, char** argv) {
|
||||
};
|
||||
|
||||
while (gRunning) {
|
||||
std::string extra = drainDialQueue();
|
||||
if (!extra.empty()) {
|
||||
number += extra;
|
||||
selected = -1;
|
||||
updateFilter();
|
||||
render();
|
||||
// 1. Process any incoming FIFO commands
|
||||
for (const std::string& cmd : readFifoLines(fifoFd)) {
|
||||
if (cmd == "NOP") {
|
||||
continue;
|
||||
} else if (cmd == "CLOSE") {
|
||||
hideWindow(false);
|
||||
} else if (cmd == "OPEN") {
|
||||
showWindow("");
|
||||
} else if (cmd.rfind("OPEN ", 0) == 0) {
|
||||
showWindow(cmd.substr(5));
|
||||
}
|
||||
}
|
||||
|
||||
if (zeroHeld) {
|
||||
if (visible && zeroHeld) {
|
||||
if (!isKeycodeDown(dpy, 19)) {
|
||||
zeroHeld = false;
|
||||
} else if (nowMs() - zeroPressMs >= 420) {
|
||||
@@ -442,13 +515,16 @@ int dialer_run_main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
if (visible && !initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
initialSoftkeysReleased = true;
|
||||
}
|
||||
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
if (!visible) {
|
||||
continue;
|
||||
}
|
||||
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
initialSoftkeysReleased = true;
|
||||
}
|
||||
@@ -489,22 +565,22 @@ int dialer_run_main(int argc, char** argv) {
|
||||
softkeyPending |= which | querySoftkeysDown(dpy);
|
||||
if (softkeyPending == 3) {
|
||||
writeSoftkeySuppress(nowMs() + 500);
|
||||
launchXDrawer = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("xdrawer", "TOGGLE")) {
|
||||
runAsyncArgv("xdrawer");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (softkeyDeadlineMs == 0) {
|
||||
softkeyDeadlineMs = nowMs() + 45;
|
||||
softkeyDeadlineMs = nowMs() + 40;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
|
||||
if (dialCh != '\0') {
|
||||
if (nowMs() - startMs < 45 && number.size() == 1 &&
|
||||
if (nowMs() - openStartMs < 40 && number.size() == 1 &&
|
||||
number[0] == dialCh) {
|
||||
// Ignore duplicate event from the initial key press that opened dialer
|
||||
continue;
|
||||
}
|
||||
number.push_back(dialCh);
|
||||
@@ -522,9 +598,8 @@ int dialer_run_main(int argc, char** argv) {
|
||||
|
||||
switch (sym) {
|
||||
case XK_Escape:
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
writeSoftkeySuppress(nowMs() + 300);
|
||||
hideWindow(true);
|
||||
break;
|
||||
case XK_BackSpace:
|
||||
case XK_Left:
|
||||
@@ -535,8 +610,7 @@ int dialer_run_main(int argc, char** argv) {
|
||||
updateFilter();
|
||||
render();
|
||||
} else if (sym == XK_BackSpace) {
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
hideWindow(true);
|
||||
}
|
||||
break;
|
||||
case XK_Right:
|
||||
@@ -582,10 +656,9 @@ int dialer_run_main(int argc, char** argv) {
|
||||
case XF86XK_PickupPhone: {
|
||||
std::string target = selectActiveNumber();
|
||||
if (!target.empty()) {
|
||||
targetToDial = target;
|
||||
writeSoftkeySuppress(nowMs() + 400);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
hideWindow(true);
|
||||
placePhoneCall(target);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -595,28 +668,23 @@ int dialer_run_main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!gRunning) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
|
||||
if (visible && softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
|
||||
int comb = softkeyPending | querySoftkeysDown(dpy);
|
||||
softkeyPending = 0;
|
||||
softkeyDeadlineMs = 0;
|
||||
if (comb == 3) {
|
||||
writeSoftkeySuppress(nowMs() + 500);
|
||||
launchXDrawer = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
break;
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("xdrawer", "TOGGLE")) {
|
||||
runAsyncArgv("xdrawer");
|
||||
}
|
||||
} else if (comb == 1) {
|
||||
// Left Softkey: if empty, switch to Call Log; if non-empty, toggle leading '+'
|
||||
if (number.empty()) {
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
switchToCallLog = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
break;
|
||||
writeSoftkeySuppress(nowMs() + 250);
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("calllog", "OPEN")) {
|
||||
runAsyncArgv("xcalllog");
|
||||
}
|
||||
} else {
|
||||
if (number[0] == '+') {
|
||||
number.erase(0, 1);
|
||||
@@ -628,50 +696,43 @@ int dialer_run_main(int argc, char** argv) {
|
||||
render();
|
||||
}
|
||||
} else if (comb == 2) {
|
||||
// Right Softkey: delete last character if non-empty, else close Dialer
|
||||
if (!number.empty()) {
|
||||
number.pop_back();
|
||||
selected = -1;
|
||||
updateFilter();
|
||||
render();
|
||||
} else {
|
||||
writeSoftkeySuppress(nowMs() + 400);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
break;
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
hideWindow(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int timeoutMs = 200;
|
||||
if (softkeyDeadlineMs > 0) {
|
||||
timeoutMs = static_cast<int>(std::max<int64_t>(5, softkeyDeadlineMs - nowMs()));
|
||||
} else if (zeroHeld) {
|
||||
timeoutMs = 25;
|
||||
}
|
||||
|
||||
struct pollfd pfd {};
|
||||
pfd.fd = xfd;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, timeoutMs);
|
||||
}
|
||||
|
||||
if (closedByKey && !launchXDrawer) {
|
||||
int64_t drainDeadline = nowMs() + 200;
|
||||
while (nowMs() < drainDeadline && querySoftkeysDown(dpy) != 0) {
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
int timeoutMs = -1;
|
||||
if (visible) {
|
||||
timeoutMs = 200;
|
||||
if (softkeyDeadlineMs > 0) {
|
||||
timeoutMs = static_cast<int>(std::max<int64_t>(5, softkeyDeadlineMs - nowMs()));
|
||||
} else if (zeroHeld) {
|
||||
timeoutMs = 25;
|
||||
}
|
||||
struct pollfd pfd {};
|
||||
pfd.fd = xfd;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, 15);
|
||||
}
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
|
||||
struct pollfd pfds[2] {};
|
||||
pfds[0].fd = xfd;
|
||||
pfds[0].events = POLLIN;
|
||||
pfds[1].fd = fifoFd;
|
||||
pfds[1].events = POLLIN;
|
||||
::poll(pfds, (fifoFd >= 0) ? 2 : 1, timeoutMs);
|
||||
}
|
||||
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
hideWindow(false);
|
||||
if (fifoFd >= 0) {
|
||||
::close(fifoFd);
|
||||
}
|
||||
::unlink(getPluginFifoPath("dialer").c_str());
|
||||
::unlink(daemonPidPath.c_str());
|
||||
|
||||
pango_font_description_free(descIcon);
|
||||
pango_font_description_free(descSmall);
|
||||
pango_font_description_free(descMain);
|
||||
@@ -686,22 +747,13 @@ int dialer_run_main(int argc, char** argv) {
|
||||
XFreePixmap(dpy, pix);
|
||||
XDestroyWindow(dpy, win);
|
||||
XCloseDisplay(dpy);
|
||||
::unlink(pidPath.c_str());
|
||||
|
||||
if (launchXDrawer) {
|
||||
runAsyncArgv("xdrawer");
|
||||
} else if (switchToCallLog) {
|
||||
runAsyncArgv("xcalllog");
|
||||
} else if (!targetToDial.empty()) {
|
||||
placePhoneCall(targetToDial);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t gLastSpawnMs = 0;
|
||||
|
||||
int dialer_plugin_init(const DwmHostApi*) {
|
||||
int dialer_plugin_init(const DwmHostApi* host) {
|
||||
if (host && host->spawn_plugin) {
|
||||
host->spawn_plugin("dialer", "--preload");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -715,16 +767,8 @@ int dialer_on_idle_key(const DwmHostApi* host, KeySym keysym,
|
||||
return 0;
|
||||
}
|
||||
char buf[2] = {ch, '\0'};
|
||||
enqueueDialChars(buf);
|
||||
|
||||
int64_t now = nowMs();
|
||||
std::string pidPath = getRuntimeFile("xdialer.pid");
|
||||
int existingPid = readIntFile(pidPath, -1);
|
||||
bool alreadyRunning = (existingPid > 0 && ::kill(existingPid, 0) == 0);
|
||||
|
||||
if (!alreadyRunning && (now - gLastSpawnMs) > 250 && host->spawn_plugin) {
|
||||
gLastSpawnMs = now;
|
||||
host->spawn_plugin("dialer", nullptr);
|
||||
if (host->spawn_plugin) {
|
||||
host->spawn_plugin("dialer", buf);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <dirent.h>
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -8,6 +10,45 @@
|
||||
|
||||
#include "../sxmo-dwm/dwm_plugin.h"
|
||||
|
||||
static int try_send_plugin_fifo(const char *plugin_name, int sub_argc, char **sub_argv) {
|
||||
if (sub_argc >= 2 && sub_argv[1] && strcmp(sub_argv[1], "--preload") == 0) {
|
||||
return 0;
|
||||
}
|
||||
char pid_path[128];
|
||||
snprintf(pid_path, sizeof(pid_path), "/tmp/dwm_plugin_%s.pid", plugin_name);
|
||||
FILE *pf = fopen(pid_path, "r");
|
||||
if (!pf) {
|
||||
return 0;
|
||||
}
|
||||
int pid = 0;
|
||||
int ok = (fscanf(pf, "%d", &pid) == 1 && pid > 1 && kill((pid_t)pid, 0) == 0);
|
||||
fclose(pf);
|
||||
if (!ok) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char fifo_path[128];
|
||||
snprintf(fifo_path, sizeof(fifo_path), "/tmp/dwm_plugin_%s.fifo", plugin_name);
|
||||
int fd = open(fifo_path, O_WRONLY | O_NONBLOCK | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char cmd[256];
|
||||
int len = 0;
|
||||
if (strcmp(plugin_name, "xdrawer") == 0) {
|
||||
len = snprintf(cmd, sizeof(cmd), "TOGGLE\n");
|
||||
} else if (sub_argc >= 2 && sub_argv[1] && sub_argv[1][0]) {
|
||||
len = snprintf(cmd, sizeof(cmd), "OPEN %s\n", sub_argv[1]);
|
||||
} else {
|
||||
len = snprintf(cmd, sizeof(cmd), "OPEN\n");
|
||||
}
|
||||
|
||||
ssize_t wr = (len > 0 && len < (int)sizeof(cmd)) ? write(fd, cmd, (size_t)len) : -1;
|
||||
close(fd);
|
||||
return wr > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
static const char *const kPluginDirs[] = {
|
||||
"/home/user/.local/lib/dwm/plugins",
|
||||
"/usr/lib/dwm/plugins",
|
||||
@@ -79,6 +120,10 @@ int main(int argc, char **argv) {
|
||||
sub_argv = argv + 1;
|
||||
}
|
||||
|
||||
if (try_send_plugin_fifo(plugin_name, sub_argc, sub_argv)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *env_dir = getenv("DWM_PLUGIN_PATH");
|
||||
char path[512];
|
||||
void *handle = NULL;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <csignal>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -87,6 +88,103 @@ inline bool writeIntFile(const std::string& path, int value) {
|
||||
return w == len;
|
||||
}
|
||||
|
||||
inline bool isPidFileAlive(const std::string& path) {
|
||||
int pid = readIntFile(path, -1);
|
||||
return (pid > 0 && ::kill(pid, 0) == 0);
|
||||
}
|
||||
|
||||
inline std::string getPluginFifoPath(const char* name) {
|
||||
return std::string("/tmp/dwm_plugin_") + name + ".fifo";
|
||||
}
|
||||
|
||||
inline std::string getPluginDaemonPidPath(const char* name) {
|
||||
return std::string("/tmp/dwm_plugin_") + name + ".pid";
|
||||
}
|
||||
|
||||
inline bool sendPluginCommand(const char* name, const std::string& cmdLine) {
|
||||
int pid = readIntFile(getPluginDaemonPidPath(name), -1);
|
||||
if (pid <= 0 || pid == static_cast<int>(::getpid()) || ::kill(pid, 0) != 0) {
|
||||
return false;
|
||||
}
|
||||
std::string fifoPath = getPluginFifoPath(name);
|
||||
int fd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
return false;
|
||||
}
|
||||
std::string msg = cmdLine + "\n";
|
||||
ssize_t w = ::write(fd, msg.data(), msg.size());
|
||||
::close(fd);
|
||||
return w == static_cast<ssize_t>(msg.size());
|
||||
}
|
||||
|
||||
inline int setupPluginDaemonFifo(const char* name) {
|
||||
std::string fifoPath = getPluginFifoPath(name);
|
||||
::mkfifo(fifoPath.c_str(), 0666);
|
||||
::chmod(fifoPath.c_str(), 0666);
|
||||
return ::open(fifoPath.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC);
|
||||
}
|
||||
|
||||
inline std::vector<std::string> readFifoLines(int fifoFd) {
|
||||
std::vector<std::string> lines;
|
||||
if (fifoFd < 0) {
|
||||
return lines;
|
||||
}
|
||||
char buf[512];
|
||||
ssize_t n = ::read(fifoFd, buf, sizeof(buf) - 1);
|
||||
if (n <= 0) {
|
||||
return lines;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
std::stringstream ss(buf);
|
||||
std::string line;
|
||||
while (std::getline(ss, line)) {
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
if (!line.empty()) {
|
||||
lines.push_back(line);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline bool isCallRingingFast() {
|
||||
std::string jobPath = getRuntimeFile("sxmo_jobs/ringing");
|
||||
if (::access(jobPath.c_str(), F_OK) == 0) {
|
||||
return true;
|
||||
}
|
||||
std::string ringPidFile = getRuntimeFile("sxmo.ring.pid");
|
||||
int pid = readIntFile(ringPidFile, -1);
|
||||
return (pid > 0 && ::kill(pid, 0) == 0);
|
||||
}
|
||||
|
||||
struct FileStamp {
|
||||
time_t sec = -1;
|
||||
long nsec = -1;
|
||||
off_t size = -1;
|
||||
|
||||
bool checkAndUpdate(const std::string& path) {
|
||||
struct stat st {};
|
||||
if (::stat(path.c_str(), &st) != 0) {
|
||||
if (sec != 0 || size != 0) {
|
||||
sec = 0;
|
||||
nsec = 0;
|
||||
size = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (st.st_mtim.tv_sec != sec || st.st_mtim.tv_nsec != nsec ||
|
||||
st.st_size != size) {
|
||||
sec = st.st_mtim.tv_sec;
|
||||
nsec = st.st_mtim.tv_nsec;
|
||||
size = st.st_size;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
inline void writeSoftkeySuppress(int64_t untilMs) {
|
||||
int fd = ::open("/tmp/sxmo_softkey_suppress",
|
||||
O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
|
||||
|
||||
+40
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+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