feat(dwm): add dynamic plugin system with xdrawer, dialer, and calllog plugins
This commit is contained in:
@@ -0,0 +1,627 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// CallLog DWM Plugin - Keypad-driven X11 call log viewer & dialer launcher (240x320)
|
||||
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xft/Xft.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/XF86keysym.h>
|
||||
#ifndef _EVDEVK
|
||||
#define _EVDEVK(_v) (0x10081000 + (_v))
|
||||
#endif
|
||||
#include <pango/pango.h>
|
||||
#include <pango/pangoxft.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <csignal>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../sxmo-dwm/dwm_plugin.h"
|
||||
#include "phone_data.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace bananarchy;
|
||||
|
||||
volatile sig_atomic_t gRunning = 1;
|
||||
|
||||
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**) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
writeIntFile(pidPath, static_cast<int>(::getpid()));
|
||||
|
||||
struct sigaction sa {};
|
||||
sa.sa_handler = handleSignal;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
|
||||
Display* dpy = XOpenDisplay(nullptr);
|
||||
if (!dpy) {
|
||||
::unlink(pidPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int screen = DefaultScreen(dpy);
|
||||
Window root = RootWindow(dpy, screen);
|
||||
Visual* visual = DefaultVisual(dpy, screen);
|
||||
Colormap cmap = DefaultColormap(dpy, screen);
|
||||
|
||||
XSetWindowAttributes swa{};
|
||||
swa.override_redirect = True;
|
||||
swa.background_pixel = 0x222222;
|
||||
swa.border_pixel = 0x005577;
|
||||
swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask |
|
||||
StructureNotifyMask | VisibilityChangeMask;
|
||||
|
||||
Window win = XCreateWindow(
|
||||
dpy, root, kWinX, kWinY, kWinWidth, kWinHeight, 0,
|
||||
DefaultDepth(dpy, screen), CopyFromParent, visual,
|
||||
CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask, &swa);
|
||||
|
||||
XClassHint ch{};
|
||||
char resName[] = "xcalllog";
|
||||
char resClass[] = "XCallLog";
|
||||
ch.res_name = resName;
|
||||
ch.res_class = resClass;
|
||||
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);
|
||||
|
||||
PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen);
|
||||
PangoContext* pangoCtx = pango_font_map_create_context(fontMap);
|
||||
|
||||
PangoLayout* layoutMain = pango_layout_new(pangoCtx);
|
||||
PangoFontDescription* descMain =
|
||||
pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 14px");
|
||||
pango_layout_set_font_description(layoutMain, descMain);
|
||||
|
||||
PangoLayout* layoutSmall = pango_layout_new(pangoCtx);
|
||||
PangoFontDescription* descSmall =
|
||||
pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 11px");
|
||||
pango_layout_set_font_description(layoutSmall, descSmall);
|
||||
|
||||
PangoLayout* layoutIcon = pango_layout_new(pangoCtx);
|
||||
PangoFontDescription* descIcon =
|
||||
pango_font_description_from_string("DejaVuSansM Nerd Font, Fira Code 15px");
|
||||
pango_layout_set_font_description(layoutIcon, descIcon);
|
||||
|
||||
auto allocColor = [&](const char* hex) {
|
||||
XftColor col{};
|
||||
XftColorAllocName(dpy, visual, cmap, hex, &col);
|
||||
return col;
|
||||
};
|
||||
|
||||
XftColor colWhite = allocColor("#ffffff");
|
||||
XftColor colLight = allocColor("#dddddd");
|
||||
XftColor colMuted = allocColor("#999999");
|
||||
XftColor colYellow = allocColor("#f4d03f");
|
||||
XftColor colGreen = allocColor("#2ecc71");
|
||||
XftColor colBlue = allocColor("#5dade2");
|
||||
XftColor colRed = allocColor("#e74c3c");
|
||||
|
||||
ContactBook book;
|
||||
book.load();
|
||||
std::vector<CallLogItem> allLogs = loadCallLog(book);
|
||||
std::vector<CallLogItem> filtered;
|
||||
|
||||
int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out
|
||||
int selected = 0;
|
||||
|
||||
auto applyFilter = [&]() {
|
||||
filtered.clear();
|
||||
for (const auto& item : allLogs) {
|
||||
if (filterTab == 1 && item.direction != CallDirection::Missed) continue;
|
||||
if (filterTab == 2 && item.direction != CallDirection::Incoming) continue;
|
||||
if (filterTab == 3 && item.direction != CallDirection::Outgoing) continue;
|
||||
filtered.push_back(item);
|
||||
}
|
||||
if (selected >= static_cast<int>(filtered.size())) {
|
||||
selected = std::max(0, static_cast<int>(filtered.size()) - 1);
|
||||
}
|
||||
};
|
||||
|
||||
applyFilter();
|
||||
|
||||
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()));
|
||||
if (maxW > 0) {
|
||||
pango_layout_set_width(layout, maxW * PANGO_SCALE);
|
||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
||||
} else {
|
||||
pango_layout_set_width(layout, -1);
|
||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
|
||||
}
|
||||
PangoRectangle ink{}, logical{};
|
||||
pango_layout_get_pixel_extents(layout, &ink, &logical);
|
||||
int ty = y + (h - logical.height) / 2;
|
||||
pango_xft_render_layout(xftDraw, color, layout, x * PANGO_SCALE,
|
||||
ty * PANGO_SCALE);
|
||||
};
|
||||
|
||||
auto pangoWidth = [&](PangoLayout* layout, const std::string& text) -> int {
|
||||
pango_layout_set_width(layout, -1);
|
||||
pango_layout_set_text(layout, text.c_str(), static_cast<int>(text.size()));
|
||||
PangoRectangle ink{}, logical{};
|
||||
pango_layout_get_pixel_extents(layout, &ink, &logical);
|
||||
return logical.width;
|
||||
};
|
||||
|
||||
constexpr int kHeaderH = 34;
|
||||
constexpr int kSoftbarH = 26;
|
||||
constexpr int kListY = kHeaderH;
|
||||
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH; // 236px
|
||||
constexpr int kVisibleRows = 5;
|
||||
|
||||
const char* kTabLabels[4] = {"All", "Missed", "In", "Out"};
|
||||
|
||||
auto render = [&]() {
|
||||
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);
|
||||
|
||||
for (int t = 0; t < 4; ++t) {
|
||||
int tx = (t * kWinWidth) / 4;
|
||||
int tw = ((t + 1) * kWinWidth) / 4 - tx;
|
||||
bool active = (t == filterTab);
|
||||
if (active) {
|
||||
XSetForeground(dpy, gc, 0x005577);
|
||||
XFillRectangle(dpy, pix, gc, tx, 0, tw, kHeaderH - 2);
|
||||
XSetForeground(dpy, gc, 0xf4d03f);
|
||||
XFillRectangle(dpy, pix, gc, tx, kHeaderH - 3, tw, 3);
|
||||
}
|
||||
std::string lbl(kTabLabels[t]);
|
||||
int lw = pangoWidth(layoutSmall, lbl);
|
||||
drawPango(layoutSmall, active ? &colWhite : &colMuted,
|
||||
tx + (tw - lw) / 2, 0, kHeaderH - 2, lbl);
|
||||
}
|
||||
|
||||
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);
|
||||
drawPango(layoutSmall, &colMuted, 24, kListY + 120, 20,
|
||||
"Press 0-9 or Left Softkey to dial", 200);
|
||||
} else {
|
||||
int total = static_cast<int>(filtered.size());
|
||||
int scrollOffset = 0;
|
||||
if (selected >= kVisibleRows) {
|
||||
scrollOffset = selected - kVisibleRows + 1;
|
||||
}
|
||||
if (scrollOffset > std::max(0, total - kVisibleRows)) {
|
||||
scrollOffset = std::max(0, total - kVisibleRows);
|
||||
}
|
||||
|
||||
for (int i = 0; i < kVisibleRows; ++i) {
|
||||
int idx = scrollOffset + i;
|
||||
if (idx >= total) {
|
||||
break;
|
||||
}
|
||||
int ry = kListY + (i * kListH) / kVisibleRows;
|
||||
int nextRy = kListY + ((i + 1) * kListH) / kVisibleRows;
|
||||
int rowH = nextRy - ry;
|
||||
bool isSel = (idx == selected);
|
||||
|
||||
XSetForeground(dpy, gc,
|
||||
isSel ? 0x005577 : ((i % 2 == 0) ? 0x282828 : 0x2e2e2e));
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH);
|
||||
|
||||
if (isSel) {
|
||||
XSetForeground(dpy, gc, 0xf4d03f);
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
|
||||
}
|
||||
|
||||
const CallLogItem& item = filtered[idx];
|
||||
const char* dirIcon = kIconInCall;
|
||||
XftColor* dirCol = &colGreen;
|
||||
const char* dirName = "In";
|
||||
if (item.direction == CallDirection::Outgoing) {
|
||||
dirIcon = kIconOutCall;
|
||||
dirCol = &colBlue;
|
||||
dirName = "Out";
|
||||
} else if (item.direction == CallDirection::Missed) {
|
||||
dirIcon = kIconMissedCall;
|
||||
dirCol = &colRed;
|
||||
dirName = "Missed";
|
||||
}
|
||||
|
||||
drawPango(layoutIcon, dirCol, 8, ry, rowH, dirIcon);
|
||||
|
||||
std::string dur = formatDuration(item.durationSec);
|
||||
int durW = 0;
|
||||
if (!dur.empty()) {
|
||||
durW = pangoWidth(layoutSmall, dur);
|
||||
drawPango(layoutSmall, isSel ? &colYellow : &colGreen,
|
||||
kWinWidth - durW - 6, ry + 3, 22, dur);
|
||||
}
|
||||
|
||||
std::string primary = item.contactName.empty() ? item.number : item.contactName;
|
||||
XftColor* priCol = (item.direction == CallDirection::Missed && !isSel)
|
||||
? &colRed
|
||||
: (isSel ? &colWhite : &colLight);
|
||||
int priMaxW = (durW > 0) ? (kWinWidth - 36 - durW - 6) : 200;
|
||||
drawPango(layoutMain, priCol, 30, ry + 3, 22, primary, priMaxW);
|
||||
|
||||
int timeW = pangoWidth(layoutSmall, item.displayTime);
|
||||
drawPango(layoutSmall, isSel ? &colWhite : &colMuted,
|
||||
kWinWidth - timeW - 6, ry + 24, 19, item.displayTime);
|
||||
|
||||
std::string secondary =
|
||||
!item.contactName.empty() ? item.number : std::string(dirName);
|
||||
int secMaxW = std::max(60, kWinWidth - 36 - timeW - 8);
|
||||
drawPango(layoutSmall, isSel ? &colYellow : &colMuted, 30, ry + 24, 19,
|
||||
secondary, secMaxW);
|
||||
}
|
||||
|
||||
if (total > kVisibleRows) {
|
||||
int sbH = std::max(16, (kListH * kVisibleRows) / total);
|
||||
int sbY = kListY +
|
||||
((kListH - sbH) * scrollOffset) / (total - kVisibleRows);
|
||||
XSetForeground(dpy, gc, 0x666666);
|
||||
XFillRectangle(dpy, pix, gc, kWinWidth - 3, sbY, 3, sbH);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Bottom Softkey Bar (y = 270..296)
|
||||
int sbY = kWinHeight - kSoftbarH;
|
||||
XSetForeground(dpy, gc, 0x181818);
|
||||
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, kSoftbarH);
|
||||
XSetForeground(dpy, gc, 0x444444);
|
||||
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, 1);
|
||||
|
||||
drawPango(layoutSmall, &colLight, 6, sbY + 1, kSoftbarH - 1, "Dialer");
|
||||
std::string centerLabel = "CALL";
|
||||
int cw = pangoWidth(layoutMain, centerLabel);
|
||||
drawPango(layoutMain, filtered.empty() ? &colMuted : &colGreen,
|
||||
(kWinWidth - cw) / 2, sbY + 1, kSoftbarH - 1, centerLabel);
|
||||
std::string rightLabel = "Close";
|
||||
int rw = pangoWidth(layoutSmall, rightLabel);
|
||||
drawPango(layoutSmall, &colLight, kWinWidth - rw - 6, sbY + 1,
|
||||
kSoftbarH - 1, rightLabel);
|
||||
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
XFlush(dpy);
|
||||
};
|
||||
|
||||
render();
|
||||
|
||||
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) {
|
||||
initialSoftkeysReleased = true;
|
||||
}
|
||||
|
||||
while (XPending(dpy)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dpy, &ev);
|
||||
if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
initialSoftkeysReleased = true;
|
||||
}
|
||||
|
||||
if (ev.type == Expose) {
|
||||
if (ev.xexpose.count == 0) {
|
||||
render();
|
||||
}
|
||||
} else if (ev.type == VisibilityNotify) {
|
||||
if (ev.xvisibility.state != VisibilityUnobscured) {
|
||||
XRaiseWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
}
|
||||
} else if (ev.type == KeyRelease) {
|
||||
KeySym sym = XLookupKeysym(&ev.xkey, 0);
|
||||
if (sym == XK_Menu || sym == XF86XK_KbdLcdMenu1 ||
|
||||
sym == XF86XK_Back || sym == XF86XK_KbdLcdMenu2 ||
|
||||
ev.xkey.keycode == 135 || ev.xkey.keycode == 166) {
|
||||
if (querySoftkeysDown(dpy) == 0) {
|
||||
initialSoftkeysReleased = true;
|
||||
}
|
||||
}
|
||||
} else if (ev.type == KeyPress) {
|
||||
KeySym sym = XLookupKeysym(&ev.xkey, 0);
|
||||
bool isLeftSoft = (sym == XK_Menu || sym == XF86XK_KbdLcdMenu1 ||
|
||||
ev.xkey.keycode == 135);
|
||||
bool isRightSoft = (sym == XF86XK_Back || sym == XF86XK_KbdLcdMenu2 ||
|
||||
ev.xkey.keycode == 166);
|
||||
|
||||
if (isLeftSoft || isRightSoft) {
|
||||
if (!initialSoftkeysReleased || nowMs() < startupIgnoreUntilMs) {
|
||||
continue;
|
||||
}
|
||||
int which = (isLeftSoft ? 1 : 0) | (isRightSoft ? 2 : 0);
|
||||
softkeyPending |= which | querySoftkeysDown(dpy);
|
||||
if (softkeyPending == 3) {
|
||||
writeSoftkeySuppress(nowMs() + 500);
|
||||
launchXDrawer = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
break;
|
||||
}
|
||||
if (softkeyDeadlineMs == 0) {
|
||||
softkeyDeadlineMs = nowMs() + 45;
|
||||
}
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sym) {
|
||||
case XK_Escape:
|
||||
case XK_BackSpace:
|
||||
case XK_q:
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
break;
|
||||
case XK_Left:
|
||||
case XK_KP_Left:
|
||||
case XK_h:
|
||||
filterTab = (filterTab + 3) % 4;
|
||||
selected = 0;
|
||||
applyFilter();
|
||||
render();
|
||||
break;
|
||||
case XK_Right:
|
||||
case XK_KP_Right:
|
||||
case XK_l:
|
||||
filterTab = (filterTab + 1) % 4;
|
||||
selected = 0;
|
||||
applyFilter();
|
||||
render();
|
||||
break;
|
||||
case XK_Up:
|
||||
case XK_KP_Up:
|
||||
case XK_k:
|
||||
if (!filtered.empty()) {
|
||||
selected = (selected + static_cast<int>(filtered.size()) - 1) %
|
||||
static_cast<int>(filtered.size());
|
||||
render();
|
||||
}
|
||||
break;
|
||||
case XK_Down:
|
||||
case XK_KP_Down:
|
||||
case XK_j:
|
||||
if (!filtered.empty()) {
|
||||
selected = (selected + 1) % static_cast<int>(filtered.size());
|
||||
render();
|
||||
}
|
||||
break;
|
||||
case XK_Return:
|
||||
case XK_KP_Enter:
|
||||
case XF86XK_Phone:
|
||||
case XF86XK_PickupPhone:
|
||||
if (nowMs() < startupIgnoreUntilMs) {
|
||||
break;
|
||||
}
|
||||
if (!filtered.empty() && selected >= 0 &&
|
||||
selected < static_cast<int>(filtered.size())) {
|
||||
numberToCall = filtered[selected].number;
|
||||
writeSoftkeySuppress(nowMs() + 400);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
} else if (filtered.empty()) {
|
||||
openDialerAfter = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gRunning) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (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;
|
||||
} else if (comb == 1) {
|
||||
// Left Softkey: open Dialer pre-populated with selected number
|
||||
if (!filtered.empty() && selected >= 0 &&
|
||||
selected < static_cast<int>(filtered.size())) {
|
||||
dialerInitArg = filtered[selected].number;
|
||||
}
|
||||
openDialerAfter = true;
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
break;
|
||||
} else if (comb == 2) {
|
||||
// Right Softkey: close Call Log
|
||||
writeSoftkeySuppress(nowMs() + 400);
|
||||
closedByKey = true;
|
||||
gRunning = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
struct pollfd pfd {};
|
||||
pfd.fd = xfd;
|
||||
pfd.events = POLLIN;
|
||||
::poll(&pfd, 1, 15);
|
||||
}
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
}
|
||||
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
pango_font_description_free(descIcon);
|
||||
pango_font_description_free(descSmall);
|
||||
pango_font_description_free(descMain);
|
||||
g_object_unref(layoutIcon);
|
||||
g_object_unref(layoutSmall);
|
||||
g_object_unref(layoutMain);
|
||||
g_object_unref(pangoCtx);
|
||||
XftDrawDestroy(xftDraw);
|
||||
XFreeGC(dpy, gc);
|
||||
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*) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int calllog_on_global_key(const DwmHostApi* host, KeySym keysym,
|
||||
unsigned int keycode, unsigned int state,
|
||||
int is_press) {
|
||||
if (!is_press || state != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
|
||||
if (isCallRinging()) {
|
||||
const char* const argv[] = {"sxmo_hook_pickup.sh", nullptr};
|
||||
if (host && host->spawn_command) {
|
||||
host->spawn_command(argv);
|
||||
} else {
|
||||
runAsyncArgv("sxmo_hook_pickup.sh");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int calllog_on_idle_key(const DwmHostApi* host, KeySym keysym,
|
||||
unsigned int keycode, unsigned int state) {
|
||||
if (!host || !host->is_idle_desktop || !host->is_idle_desktop() || state != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
|
||||
if (isCallRinging()) {
|
||||
const char* const argv[] = {"sxmo_hook_pickup.sh", nullptr};
|
||||
if (host->spawn_command) {
|
||||
host->spawn_command(argv);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (host->spawn_plugin) {
|
||||
host->spawn_plugin("calllog", nullptr);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) const DwmPlugin dwm_plugin_entry = {
|
||||
DWM_PLUGIN_API_VERSION,
|
||||
"calllog",
|
||||
"Keypad-driven call log viewer & dialer launcher (opens on idle Call key)",
|
||||
calllog_plugin_init,
|
||||
nullptr,
|
||||
nullptr,
|
||||
calllog_on_idle_key,
|
||||
calllog_on_global_key,
|
||||
calllog_run_main
|
||||
};
|
||||
Reference in New Issue
Block a user