Add Contacts DWM plugin, desktop Menu/Names softkey labels, and 15px plugin label fonts

This commit is contained in:
Luxferre
2026-10-21 12:32:21 +03:00
parent 996e80b095
commit d27ca9f224
25 changed files with 1789 additions and 22 deletions
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -154,10 +154,10 @@ fast_sys_choices() {
$icon_grd Apps^0^sxmo_appmenu.sh applications
$icon_grd Scripts^0^sxmo_appmenu.sh scripts
$icon_dir Files^0^sxmo_files.sh
$icon_phn Dialer^0^sxmo_modemdial.sh
$icon_phn Dialer^0^xdialer
$icon_phl Call Log^0^xcalllog
$icon_msg Texts^0^sxmo_modemtext.sh
$icon_usr Contacts^0^sxmo_contactmenu.sh
$icon_usr Contacts^0^xcontacts
${_bt_line}$icon_cam Camera^0^bananarchy-photo --menu
${_flash_line}$icon_net Networks^0^sxmo_networks.sh
$icon_mus Audio^0^sxmo_audio.sh
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
if command -v xcontacts >/dev/null 2>&1; then
exec xcontacts "$@"
fi
exec /usr/bin/dwm-plugin contacts "$@"
+1
View File
@@ -0,0 +1 @@
dwm-plugin
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -767,9 +767,10 @@ case "$WMCLASS" in
$icon_grd Apps ^ 0 ^ sxmo_appmenu.sh applications
$icon_grd Scripts ^ 0 ^ sxmo_appmenu.sh scripts
$icon_dir Files ^ 0 ^ sxmo_files.sh
$icon_phn Dialer ^ 0 ^ sxmo_modemdial.sh
$icon_phn Dialer ^ 0 ^ xdialer
$icon_phl Call Log ^ 0 ^ xcalllog
$icon_msg Texts ^ 0 ^ sxmo_modemtext.sh
$icon_usr Contacts ^ 0 ^ sxmo_contactmenu.sh
$icon_usr Contacts ^ 0 ^ xcontacts
$(
bt_is_unblocked &&
printf %b "$icon_bth Bluetooth ^ 1 ^ sxmo_bluetoothmenu.sh"
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
dwm-plugin
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8 -2
View File
@@ -97,7 +97,7 @@ EOF
dwm.c drw.c util.c $DWM_LIBS -o /bananarchy/prebuilt/sxmo-x11/usr/bin/dwm
$STRIP --strip-unneeded /bananarchy/prebuilt/sxmo-x11/usr/bin/dwm
echo "[6/6] Compiling DWM Plugins (xdrawer.so, dialer.so, calllog.so) and dwm-plugin runner..."
echo "[6/6] Compiling DWM Plugins (xdrawer.so, dialer.so, calllog.so, contacts.so) and dwm-plugin runner..."
mkdir -p /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins
PLUG_INCS="$(pkgconf --cflags x11 xft fontconfig freetype2 pango pangoxft alsa)"
PLUG_LIBS="$(pkgconf --libs x11 xft fontconfig freetype2 pango pangoxft alsa)"
@@ -114,10 +114,15 @@ EOF
/bananarchy/src/dwm-plugins/calllog_plugin.cpp $PLUG_LIBS \
-o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so
$CXX $OPT_CFLAGS -fPIC -shared -std=c++17 -Wall -Wextra $PLUG_INCS \
/bananarchy/src/dwm-plugins/contacts_plugin.cpp $PLUG_LIBS \
-o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so
$STRIP --strip-unneeded \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so
$CC $OPT_CFLAGS -std=c99 -D_DEFAULT_SOURCE -Wall -Wextra \
/bananarchy/src/dwm-plugins/dwm_plugin_runner.c -ldl \
@@ -128,6 +133,7 @@ EOF
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/XDrawer
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xdialer
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xcalllog
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xcontacts
mkdir -p /bananarchy/overlay/rootfs/usr/bin /bananarchy/overlay/rootfs/usr/lib/dwm/plugins
cp -a /bananarchy/prebuilt/sxmo-x11/usr/bin/. /bananarchy/overlay/rootfs/usr/bin/
+9 -6
View File
@@ -102,17 +102,17 @@ int calllog_run_main(int argc, char** argv) {
PangoLayout* layoutMain = pango_layout_new(pangoCtx);
PangoFontDescription* descMain =
pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 14px");
pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 15px");
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_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 12px");
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_font_description_from_string("DejaVuSansM Nerd Font, Fira Code 16px");
pango_layout_set_font_description(layoutIcon, descIcon);
auto allocColor = [&](const char* hex) {
@@ -324,14 +324,14 @@ int calllog_run_main(int argc, char** argv) {
XSetForeground(dpy, gc, 0x444444);
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, 1);
drawPango(layoutSmall, &colLight, 6, sbY + 1, kSoftbarH - 1, "Dialer");
drawPango(layoutMain, &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,
int rw = pangoWidth(layoutMain, rightLabel);
drawPango(layoutMain, &colLight, kWinWidth - rw - 6, sbY + 1,
kSoftbarH - 1, rightLabel);
if (visible) {
@@ -350,6 +350,9 @@ int calllog_run_main(int argc, char** argv) {
if (isPidFileAlive(getRuntimeFile("xdialer.pid"))) {
sendPluginCommand("dialer", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xcontacts.pid"))) {
sendPluginCommand("contacts", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
sendPluginCommand("xdrawer", "CLOSE");
}
File diff suppressed because it is too large Load Diff
+9 -6
View File
@@ -112,17 +112,17 @@ int dialer_run_main(int argc, char** argv) {
PangoLayout* layoutMain = pango_layout_new(pangoCtx);
PangoFontDescription* descMain =
pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 14px");
pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 15px");
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_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 12px");
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_font_description_from_string("DejaVuSansM Nerd Font, Fira Code 16px");
pango_layout_set_font_description(layoutIcon, descIcon);
auto allocColor = [&](const char* hex) {
@@ -360,12 +360,12 @@ int dialer_run_main(int argc, char** argv) {
std::string centerLabel = "CALL";
std::string rightLabel = number.empty() ? "Close" : "Clear";
drawPango(layoutSmall, &colLight, 6, sbY + 1, kSoftbarH - 1, leftLabel);
drawPango(layoutMain, &colLight, 6, sbY + 1, kSoftbarH - 1, leftLabel);
int cw = pangoWidth(layoutMain, centerLabel);
drawPango(layoutMain, &colGreen, (kWinWidth - cw) / 2, sbY + 1,
kSoftbarH - 1, centerLabel);
int rw = pangoWidth(layoutSmall, rightLabel);
drawPango(layoutSmall, &colLight, kWinWidth - rw - 6, sbY + 1,
int rw = pangoWidth(layoutMain, rightLabel);
drawPango(layoutMain, &colLight, kWinWidth - rw - 6, sbY + 1,
kSoftbarH - 1, rightLabel);
if (visible) {
@@ -389,6 +389,9 @@ int dialer_run_main(int argc, char** argv) {
if (isPidFileAlive(getRuntimeFile("xcalllog.pid"))) {
sendPluginCommand("calllog", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xcontacts.pid"))) {
sendPluginCommand("contacts", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
sendPluginCommand("xdrawer", "CLOSE");
}
+6
View File
@@ -38,6 +38,9 @@ static int try_send_plugin_fifo(const char *plugin_name, int sub_argc, char **su
int len = 0;
if (strcmp(plugin_name, "xdrawer") == 0) {
len = snprintf(cmd, sizeof(cmd), "TOGGLE\n");
} else if (sub_argc >= 2 && sub_argv[1] && strcmp(sub_argv[1], "newcontact") == 0) {
const char *num = (sub_argc >= 3 && sub_argv[2]) ? sub_argv[2] : "";
len = snprintf(cmd, sizeof(cmd), "OPEN new:%s\n", num);
} else if (sub_argc >= 2 && sub_argv[1] && sub_argv[1][0]) {
len = snprintf(cmd, sizeof(cmd), "OPEN %s\n", sub_argv[1]);
} else {
@@ -67,6 +70,9 @@ static const char *resolve_plugin_name_from_argv0(const char *argv0) {
if (strcasecmp(base, "xcalllog") == 0 || strcasecmp(base, "calllog") == 0) {
return "calllog";
}
if (strcasecmp(base, "xcontacts") == 0 || strcasecmp(base, "contacts") == 0) {
return "contacts";
}
return NULL;
}
+178
View File
@@ -40,11 +40,19 @@ constexpr int kWinHeight = kScreenHeight - kTopBarHeight; // 296px flush below 2
constexpr const char* kIconPhone = "\xef\x82\x95"; // U+F095 phone handset
constexpr const char* kIconPhoneLog = "\xef\x82\x98"; // U+F098 phone square
constexpr const char* kIconUser = "\xef\x80\x87"; // U+F007 user contact
constexpr const char* kIconBook = "\xef\x83\x80"; // U+F0C0 address book / users
constexpr const char* kIconClock = "\xef\x80\x97"; // U+F017 recent clock
constexpr const char* kIconOutCall = "\xef\x85\xb8"; // U+F178 outgoing arrow
constexpr const char* kIconInCall = "\xef\x85\xb5"; // U+F175 incoming arrow
constexpr const char* kIconMissedCall = "\xef\x81\xb1"; // U+F071 missed/alert
constexpr const char* kIconBackspace = "\xef\x86\x90"; // U+F190 left circle arrow
constexpr const char* kIconSearch = "\xef\x80\x82"; // U+F002 search
constexpr const char* kIconPlus = "\xef\x81\xa7"; // U+F067 plus/add
constexpr const char* kIconEdit = "\xef\x81\x84"; // U+F044 edit
constexpr const char* kIconTrash = "\xef\x87\xb8"; // U+F1F8 delete/trash
constexpr const char* kIconSms = "\xef\x83\xa0"; // U+F0E0 envelope/SMS
constexpr const char* kIconList = "\xef\x80\xba"; // U+F03A list
constexpr const char* kIconCheck = "\xef\x80\x8c"; // U+F00C check
inline int64_t nowMs() {
struct timespec ts {};
@@ -525,6 +533,176 @@ class ContactBook {
return out;
}
const std::vector<ContactEntry>& savedContacts() const { return saved_; }
std::vector<std::string> unknownRecentNumbers() const {
std::vector<std::string> out;
std::unordered_set<std::string> seen;
std::ifstream lf(getModemLogFilePath());
std::vector<std::string> logLines;
std::string line;
while (std::getline(lf, line)) {
if (!line.empty()) {
logLines.push_back(line);
}
}
for (auto it = logLines.rbegin(); it != logLines.rend(); ++it) {
size_t t1 = it->find('\t');
if (t1 == std::string::npos) continue;
size_t t2 = it->find('\t', t1 + 1);
if (t2 == std::string::npos) continue;
size_t t3 = it->find('\t', t2 + 1);
std::string num = trimStr(
t3 == std::string::npos ? it->substr(t2 + 1)
: it->substr(t2 + 1, t3 - t2 - 1));
if (num.empty() || num == "NULL" || num.find('+', 1) != std::string::npos) {
continue;
}
std::string norm = normalizePhone(num);
if (norm.empty() || !resolveName(num).empty()) {
continue;
}
std::string key = phoneSuffixKey(norm);
if (seen.count(key) || seen.count(norm)) {
continue;
}
seen.insert(key);
seen.insert(norm);
out.push_back(num);
}
return out;
}
static void ensureParentDir(const std::string& filePath) {
size_t slash = filePath.rfind('/');
if (slash == std::string::npos || slash == 0) {
return;
}
std::string dir = filePath.substr(0, slash);
std::string cur;
for (size_t i = 0; i < dir.size(); ++i) {
cur.push_back(dir[i]);
if (dir[i] == '/' && cur.size() > 1) {
::mkdir(cur.c_str(), 0755);
}
}
::mkdir(dir.c_str(), 0755);
}
bool addOrUpdateContact(const std::string& oldNumber,
const std::string& oldName,
const std::string& newNumberRaw,
const std::string& newNameRaw) {
std::string newNumber = trimStr(newNumberRaw);
std::string newName = trimStr(newNameRaw);
for (char& ch : newName) {
if (ch == '\t' || ch == '\r' || ch == '\n') {
ch = ' ';
}
}
if (newNumber.empty() || newName.empty()) {
return false;
}
std::string path = getContactsFilePath();
ensureParentDir(path);
std::vector<std::pair<std::string, std::string>> rows;
{
std::ifstream in(path);
std::string line;
while (std::getline(in, line)) {
if (line.empty()) continue;
size_t tab = line.find('\t');
if (tab == std::string::npos) continue;
std::string num = trimStr(line.substr(0, tab));
std::string name = trimStr(line.substr(tab + 1));
if (!num.empty() && !name.empty()) {
rows.push_back({num, name});
}
}
}
bool replaced = false;
std::string oldNorm = normalizePhone(oldNumber);
std::string newNorm = normalizePhone(newNumber);
for (auto& r : rows) {
if (!oldNumber.empty() &&
((r.first == oldNumber && r.second == oldName) ||
(!oldNorm.empty() && normalizePhone(r.first) == oldNorm &&
r.second == oldName))) {
r.first = newNumber;
r.second = newName;
replaced = true;
break;
}
}
if (!replaced && oldNumber.empty()) {
for (auto& r : rows) {
if (r.first == newNumber ||
(!newNorm.empty() && normalizePhone(r.first) == newNorm)) {
r.first = newNumber;
r.second = newName;
replaced = true;
break;
}
}
}
if (!replaced) {
rows.push_back({newNumber, newName});
}
std::ofstream out(path, std::ios::trunc);
if (!out.is_open()) {
return false;
}
for (const auto& r : rows) {
out << r.first << "\t" << r.second << "\n";
}
out.close();
load();
return true;
}
bool deleteContact(const std::string& number, const std::string& name) {
std::string path = getContactsFilePath();
std::vector<std::pair<std::string, std::string>> rows;
std::string targetNorm = normalizePhone(number);
bool removed = false;
{
std::ifstream in(path);
std::string line;
while (std::getline(in, line)) {
if (line.empty()) continue;
size_t tab = line.find('\t');
if (tab == std::string::npos) continue;
std::string num = trimStr(line.substr(0, tab));
std::string nm = trimStr(line.substr(tab + 1));
if (num.empty() || nm.empty()) continue;
if (!removed &&
((num == number && nm == name) ||
(!targetNorm.empty() && normalizePhone(num) == targetNorm &&
nm == name))) {
removed = true;
continue;
}
rows.push_back({num, nm});
}
}
if (!removed) {
return false;
}
std::ofstream out(path, std::ios::trunc);
if (!out.is_open()) {
return false;
}
for (const auto& r : rows) {
out << r.first << "\t" << r.second << "\n";
}
out.close();
load();
return true;
}
private:
std::vector<ContactEntry> saved_;
std::unordered_map<std::string, std::string> exactMap_;
+61 -4
View File
@@ -49,6 +49,7 @@
#include <X11/Xlib-xcb.h>
#include <xcb/res.h>
#include <pango/pango.h>
#include <pango/pangoxft.h>
#include "drw.h"
#include "util.h"
@@ -147,6 +148,7 @@ struct Monitor {
Client *stack;
Monitor *next;
Window barwin;
Window softwin;
const Layout *lt[2];
};
@@ -769,6 +771,10 @@ cleanupmon(Monitor *mon)
}
XUnmapWindow(dpy, mon->barwin);
XDestroyWindow(dpy, mon->barwin);
if (mon->softwin) {
XUnmapWindow(dpy, mon->softwin);
XDestroyWindow(dpy, mon->softwin);
}
free(mon);
}
@@ -1021,6 +1027,35 @@ drawbar(Monitor *m)
drw_text(drw, MAX(x, m->ww - sw), 0, sw, bh, 0, s, 0, True);
}
drw_map(drw, m->barwin, 0, 0, m->ww, bh);
if (m->softwin && drw && drw->font) {
XLowerWindow(dpy, m->softwin);
XClearWindow(dpy, m->softwin);
if (!m->sel) {
XftDraw *d = XftDrawCreate(dpy, m->softwin,
DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));
if (d) {
unsigned int ew = 0, eh = 0;
const char *left_txt = "Menu";
const char *right_txt = "Names";
int sh_bar = 26;
drw_font_getexts(drw->font, left_txt, strlen(left_txt), &ew, &eh, False);
int ty = (sh_bar - (int)eh) / 2;
pango_layout_set_text(drw->font->layout, left_txt, strlen(left_txt));
pango_xft_render_layout(d, &scheme[SchemeSel][ColFg],
drw->font->layout, 6 * PANGO_SCALE, ty * PANGO_SCALE);
drw_font_getexts(drw->font, right_txt, strlen(right_txt), &ew, &eh, False);
ty = (sh_bar - (int)eh) / 2;
int rx = m->ww - (int)ew - 6;
pango_layout_set_text(drw->font->layout, right_txt, strlen(right_txt));
pango_xft_render_layout(d, &scheme[SchemeSel][ColFg],
drw->font->layout, rx * PANGO_SCALE, ty * PANGO_SCALE);
XftDrawDestroy(d);
}
}
}
}
void
@@ -1806,6 +1841,10 @@ flush_pending_softkey(void)
const Arg syscmd = { .v = (const char*[]){ "sxmo_appmenu.sh", "sys", NULL } };
last_menu_pgid = spawn_pgid(&syscmd);
} else if (pending == SOFTKEY_RIGHT) {
if (host_is_idle_desktop() &&
dispatch_plugin_idle_key(XF86XK_Back, 166, 0)) {
return;
}
const Arg ctxcmd = { .v = (const char*[]){ "sxmo_appmenu.sh", NULL } };
last_menu_pgid = spawn_pgid(&ctxcmd);
}
@@ -2170,9 +2209,12 @@ propertynotify(XEvent *e)
Window trans;
XPropertyEvent *ev = &e->xproperty;
if ((ev->window == root) && (ev->atom == XA_WM_NAME))
updatestatus();
else if (ev->state == PropertyDelete)
if (ev->window == root) {
if (ev->atom == XA_WM_NAME)
updatestatus();
else
drawbars();
} else if (ev->state == PropertyDelete)
return; /* ignore */
else if ((c = wintoclient(ev->window))) {
switch(ev->atom) {
@@ -2902,6 +2944,21 @@ updatebars(void)
};
XClassHint ch = {"dwm", "dwm"};
for (m = mons; m; m = m->next) {
if (!m->softwin) {
XSetWindowAttributes softwa = {
.override_redirect = True,
.background_pixmap = ParentRelative,
.event_mask = ExposureMask
};
int sh_bar = 26;
m->softwin = XCreateWindow(dpy, root, m->wx, m->my + m->mh - sh_bar, m->ww, sh_bar, 0,
DefaultDepth(dpy, screen), CopyFromParent, DefaultVisual(dpy, screen),
CWOverrideRedirect|CWBackPixmap|CWEventMask, &softwa);
XDefineCursor(dpy, m->softwin, cursor[CurNormal]->cursor);
XMapWindow(dpy, m->softwin);
XLowerWindow(dpy, m->softwin);
XSetClassHint(dpy, m->softwin, &ch);
}
if (m->barwin)
continue;
m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen),
@@ -3266,7 +3323,7 @@ wintomon(Window w)
if (w == root && getrootptr(&x, &y))
return recttomon(x, y, 1, 1);
for (m = mons; m; m = m->next)
if (w == m->barwin)
if (w == m->barwin || w == m->softwin)
return m;
if ((c = wintoclient(w)))
return c->mon;
+1
View File
@@ -703,6 +703,7 @@ static int xdrawer_run_main(int argc, char** argv) {
}
closeOtherPlugin("xdialer.pid", "/tmp/dwm_plugin_dialer.fifo");
closeOtherPlugin("xcalllog.pid", "/tmp/dwm_plugin_calllog.fifo");
closeOtherPlugin("xcontacts.pid", "/tmp/dwm_plugin_contacts.fifo");
openStartMs = nowMs();
startupIgnoreUntilMs = openStartMs + 180;
initialKeysReleased = (querySoftkeysDown(dpy) == 0);