Preload DWM plugins (dialer, calllog, xdrawer) with resident X11/Pango state and FIFO IPC
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+203
-128
@@ -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