// SPDX-License-Identifier: GPL-2.0-only // Bananarchy Networks & Bluetooth Manager Plugin (networks.so / xnetworks) #include "phone_data.h" #include "../sxmo-dwm/dwm_plugin.h" #include #include #include #include #include #include #include #ifdef None #undef None #endif namespace { using namespace bananarchy; volatile sig_atomic_t gRunning = 1; void handleSignal(int) { gRunning = 0; } enum class NetTab { Wifi = 0, Cellular = 1, Bluetooth = 2, }; enum class NetModal { None = 0, WifiActions = 1, WifiPassword = 2, ApnEditor = 3, BtActions = 4, }; enum class TextMode { Lower = 0, // abc Title = 1, // Abc Upper = 2, // ABC Num = 3, // 123 Sym = 4, // Sym }; const char* kMultiTapMap[10] = { " 0+_-", // 0 ".,?!@-_1:/#", // 1 "abc2", // 2 "def3", // 3 "ghi4", // 4 "jkl5", // 5 "mno6", // 6 "pqrs7", // 7 "tuv8", // 8 "wxyz9" // 9 }; const char* kSymTapMap[10] = { " 0+=", // 0 ".,?!@1", // 1 "_-/:;2", // 2 "#*&$%3", // 3 "()[]{}4", // 4 "<>\"'`5", // 5 "\\|^~6", // 6 "+-=*7", // 7 "$,.:8", // 8 "!?;:9" // 9 }; void popUtf8Char(std::string& s) { if (s.empty()) return; while (!s.empty() && (static_cast(s.back()) & 0xC0) == 0x80) { s.pop_back(); } if (!s.empty()) s.pop_back(); } struct WifiApEntry { std::string ssid; int signal = 0; std::string security; bool inUse = false; bool isSaved = false; }; struct CellularNetInfo { bool gsmConfigured = false; bool gsmActive = false; std::string gsmConName; std::string apn; std::string username; std::string ipType = "ipv4v6"; std::string wwanIp; std::string wlanIp; bool hotspotActive = false; std::string hotspotInfo; bool usbShareActive = false; }; struct BtDeviceEntry { std::string mac; std::string name; bool paired = false; bool connected = false; }; bool isWifiEnabledFast() { std::string out = runCaptureCmd("/usr/sbin/rfkill list wifi 2>/dev/null"); if (out.find("Soft blocked: yes") != std::string::npos || out.find("Hard blocked: yes") != std::string::npos) { return false; } std::string nm = trimStr(runCaptureCmd("nmcli radio wifi 2>/dev/null")); if (nm == "disabled") return false; return true; } bool isBtEnabledFast() { std::string out = runCaptureCmd("/usr/sbin/rfkill list bluetooth 2>/dev/null"); if (out.find("Soft blocked: yes") != std::string::npos || out.find("Hard blocked: yes") != std::string::npos) { return false; } return true; } std::vector queryWifiList(bool forceRescan) { std::vector list; std::unordered_set savedSsids; std::string conOut = runCaptureCmd( "nmcli -t -f NAME,TYPE connection show 2>/dev/null"); { std::istringstream ss(conOut); std::string line; while (std::getline(ss, line)) { size_t col = line.rfind(':'); if (col == std::string::npos) continue; std::string nm = line.substr(0, col); std::string tp = line.substr(col + 1); if (tp.find("wireless") != std::string::npos && !nm.empty()) { savedSsids.insert(nm); } } } std::string cmd = std::string("nmcli -t -f IN-USE,SSID,SIGNAL,SECURITY device wifi list --rescan ") + (forceRescan ? "yes" : "no") + " 2>/dev/null"; std::string out = runCaptureCmd(cmd); std::istringstream ss(out); std::string line; std::unordered_map seenIdx; while (std::getline(ss, line)) { if (line.empty()) continue; // Parse IN-USE:SSID:SIGNAL:SECURITY (note SSID can contain escaped colons \:) size_t c1 = line.find(':'); if (c1 == std::string::npos) continue; size_t c3 = line.rfind(':'); if (c3 == std::string::npos || c3 <= c1) continue; size_t c2 = line.rfind(':', c3 - 1); if (c2 == std::string::npos || c2 <= c1) continue; std::string inUseStr = line.substr(0, c1); std::string rawSsid = line.substr(c1 + 1, c2 - c1 - 1); std::string sigStr = line.substr(c2 + 1, c3 - c2 - 1); std::string secStr = line.substr(c3 + 1); std::string ssid; for (size_t i = 0; i < rawSsid.size(); ++i) { if (rawSsid[i] == '\\' && i + 1 < rawSsid.size()) { ssid.push_back(rawSsid[++i]); } else { ssid.push_back(rawSsid[i]); } } ssid = trimStr(ssid); if (ssid.empty()) continue; WifiApEntry ap; ap.ssid = ssid; ap.signal = std::clamp(std::atoi(sigStr.c_str()), 0, 100); ap.security = (secStr == "--") ? "" : trimStr(secStr); ap.inUse = (inUseStr == "*"); ap.isSaved = (savedSsids.count(ssid) > 0); auto it = seenIdx.find(ssid); if (it == seenIdx.end()) { seenIdx[ssid] = list.size(); list.push_back(ap); } else { auto& existing = list[it->second]; if (ap.inUse) existing.inUse = true; if (ap.signal > existing.signal) existing.signal = ap.signal; if (existing.security.empty() && !ap.security.empty()) { existing.security = ap.security; } } } std::sort(list.begin(), list.end(), [](const WifiApEntry& a, const WifiApEntry& b) { if (a.inUse != b.inUse) return a.inUse > b.inUse; if (a.isSaved != b.isSaved) return a.isSaved > b.isSaved; return a.signal > b.signal; }); return list; } CellularNetInfo queryCellularInfo() { CellularNetInfo info; std::string allCons = runCaptureCmd( "nmcli -t -f NAME,TYPE,ACTIVE connection show 2>/dev/null"); std::istringstream ss(allCons); std::string line; while (std::getline(ss, line)) { size_t c2 = line.rfind(':'); if (c2 == std::string::npos) continue; size_t c1 = line.rfind(':', c2 - 1); if (c1 == std::string::npos) continue; std::string nm = line.substr(0, c1); std::string tp = line.substr(c1 + 1, c2 - c1 - 1); std::string act = line.substr(c2 + 1); if (tp == "gsm" && info.gsmConName.empty()) { info.gsmConfigured = true; info.gsmConName = nm; info.gsmActive = (act == "yes"); } if (nm == "Hotspot" && act == "yes") { info.hotspotActive = true; } if (nm == "USBNetworkShare" && act == "yes") { info.usbShareActive = true; } } if (!info.usbShareActive) { struct stat st {}; if (::stat("/tmp/bananarchy_usb_tether.active", &st) == 0) { info.usbShareActive = true; } } if (info.gsmConfigured && !info.gsmConName.empty()) { std::string det = runCaptureCmd( "nmcli -t -f gsm.apn,gsm.username connection show " + shellQuote(info.gsmConName) + " 2>/dev/null"); std::istringstream ds(det); std::string dline; while (std::getline(ds, dline)) { if (dline.rfind("gsm.apn:", 0) == 0) { info.apn = trimStr(dline.substr(8)); if (info.apn == "--") info.apn.clear(); } else if (dline.rfind("gsm.username:", 0) == 0) { info.username = trimStr(dline.substr(13)); if (info.username == "--") info.username.clear(); } } if (info.apn.empty()) info.apn = info.gsmConName; } std::string ipOut = runCaptureCmd("ip -4 -o addr show 2>/dev/null"); std::istringstream ips(ipOut); while (std::getline(ips, line)) { size_t inetPos = line.find("inet "); if (inetPos == std::string::npos) continue; std::string rest = line.substr(inetPos + 5); size_t sp = rest.find(' '); std::string cidr = (sp == std::string::npos) ? rest : rest.substr(0, sp); if (line.find("wwan") != std::string::npos || line.find("rmnet") != std::string::npos) { info.wwanIp = cidr; } else if (line.find("wlan") != std::string::npos) { info.wlanIp = cidr; } } return info; } std::vector queryBtDevices() { std::vector out; std::unordered_set pairedMacs; std::unordered_set connectedMacs; auto parseMacSet = [](const std::string& raw, std::unordered_set& dst) { std::istringstream ss(raw); std::string line; while (std::getline(ss, line)) { // Format: "Device XX:XX:XX:XX:XX:XX Name..." if (line.rfind("Device ", 0) == 0 && line.size() >= 24) { dst.insert(line.substr(7, 17)); } } }; parseMacSet(runCaptureCmd("bluetoothctl devices Paired 2>/dev/null || " "bluetoothctl paired-devices 2>/dev/null"), pairedMacs); parseMacSet(runCaptureCmd("bluetoothctl devices Connected 2>/dev/null"), connectedMacs); std::string allRaw = runCaptureCmd("bluetoothctl devices 2>/dev/null"); std::istringstream ss(allRaw); std::string line; while (std::getline(ss, line)) { if (line.rfind("Device ", 0) != 0 || line.size() < 24) continue; BtDeviceEntry d; d.mac = line.substr(7, 17); d.name = (line.size() > 25) ? trimStr(line.substr(25)) : d.mac; d.paired = (pairedMacs.count(d.mac) > 0); d.connected = (connectedMacs.count(d.mac) > 0); out.push_back(d); } std::sort(out.begin(), out.end(), [](const BtDeviceEntry& a, const BtDeviceEntry& b) { if (a.connected != b.connected) return a.connected > b.connected; if (a.paired != b.paired) return a.paired > b.paired; return a.name < b.name; }); return out; } int runNetworksApp(int argc, char** argv) { gRunning = 1; std::setlocale(LC_ALL, ""); XSetLocaleModifiers(""); bool isPreload = false; std::string openArg; const char* base0 = std::strrchr(argv[0], '/'); base0 = base0 ? (base0 + 1) : argv[0]; if (base0 && (strcasecmp(base0, "xbluetooth") == 0 || strcasecmp(base0, "bluetooth") == 0)) { openArg = "bluetooth"; } for (int i = 1; i < argc; ++i) { if (!argv[i]) continue; if (std::strcmp(argv[i], "--preload") == 0) { isPreload = true; } else if (argv[i][0] != '-') { openArg = argv[i]; } } std::string daemonPidPath = getPluginDaemonPidPath("networks"); std::string activePidPath = getRuntimeFile("xnetworks.pid"); if (isPluginDaemonAlive("networks")) { if (!isPreload) { sendPluginCommand("networks", openArg.empty() ? "OPEN" : ("OPEN " + openArg)); } return 0; } writeIntFile(daemonPidPath, static_cast(::getpid())); int fifoFd = setupPluginDaemonFifo("networks"); struct sigaction sa {}; sa.sa_handler = handleSignal; sigemptyset(&sa.sa_mask); sigaction(SIGINT, &sa, nullptr); sigaction(SIGTERM, &sa, nullptr); Display* dpy = XOpenDisplay(nullptr); if (!dpy) { if (fifoFd >= 0) ::close(fifoFd); ::unlink(daemonPidPath.c_str()); return 1; } int screen = DefaultScreen(dpy); Window root = RootWindow(dpy, screen); Visual* visual = DefaultVisual(dpy, screen); Colormap cmap = DefaultColormap(dpy, screen); ThemeSpec initSpec; initSpec.loadActive(dpy, screen); XSetWindowAttributes swa {}; swa.override_redirect = True; swa.background_pixel = initSpec.bgMain; swa.border_pixel = initSpec.selBg; swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | VisibilityChangeMask | StructureNotifyMask; Window win = XCreateWindow( dpy, root, kWinX, kWinY, kWinWidth, kWinHeight, 0, DefaultDepth(dpy, screen), CopyFromParent, visual, CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask, &swa); XClassHint ch {}; char resName[] = "xnetworks"; char resClass[] = "XNetworks"; ch.res_name = resName; ch.res_class = resClass; XSetClassHint(dpy, win, &ch); XStoreName(dpy, win, "Networks & Bluetooth"); Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, DefaultDepth(dpy, screen)); GC gc = XCreateGC(dpy, pix, 0, nullptr); XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap); PluginTheme pt; pt.init(dpy, screen, visual, cmap, win); auto& layoutMain = pt.layoutMain; auto& layoutSmall = pt.layoutSmall; auto& layoutIcon = pt.layoutIcon; auto& colWhite = pt.colWhite; auto& colLight = pt.colLight; auto& colMuted = pt.colMuted; auto& colYellow = pt.colYellow; auto& colGreen = pt.colGreen; auto& colBlue = pt.colBlue; auto& colOrange = pt.colOrange; NetTab activeTab = NetTab::Wifi; NetModal modal = NetModal::None; bool wifiOn = true; std::vector wifiList; int wifiRowSel = 0; // 0 = Wi-Fi Toggle, 1..N = AP entries CellularNetInfo cellInfo; int cellRowSel = 0; // 0 = Mobile Data, 1 = Configure APN, 2 = Wi-Fi Hotspot, 3 = USB Share bool btOn = false; std::vector btList; int btRowSel = 0; // 0 = BT Power Toggle, 1 = Scan Devices, 2..N = BT devices int actionSel = 0; // Wi-Fi Password modal state WifiApEntry targetAp; std::string wifiPassword; bool showPassword = false; // APN Editor modal state std::string apnName; std::string apnUser; std::string apnPass; int apnFieldSel = 0; // 0 = APN, 1 = Username, 2 = Password, 3 = Save // Bluetooth target device BtDeviceEntry targetBt; // Multi-tap state for text inputs TextMode textMode = TextMode::Lower; int lastTapDigit = -1; int lastTapCycle = 0; int64_t lastTapMs = 0; std::string statusBanner; int64_t statusBannerUntilMs = 0; bool visible = false; int64_t openStartMs = 0; int64_t startupIgnoreUntilMs = 0; bool initialSoftkeysReleased = true; int softkeyPending = 0; int64_t softkeyDeadlineMs = 0; auto setBanner = [&](const std::string& msg, int durationMs = 2300) { statusBanner = msg; statusBannerUntilMs = nowMs() + durationMs; }; auto refreshCurrentTab = [&](bool forceScan = false) { if (activeTab == NetTab::Wifi) { wifiOn = isWifiEnabledFast(); if (wifiOn) { wifiList = queryWifiList(forceScan); } else { wifiList.clear(); } int maxRow = static_cast(wifiList.size()); if (wifiRowSel > maxRow) wifiRowSel = maxRow; } else if (activeTab == NetTab::Cellular) { cellInfo = queryCellularInfo(); } else if (activeTab == NetTab::Bluetooth) { btOn = isBtEnabledFast(); if (btOn) { if (forceScan) { runCaptureCmd("bluetoothctl --timeout 5 scan on >/dev/null 2>&1"); } btList = queryBtDevices(); } else { btList.clear(); } int maxRow = 1 + static_cast(btList.size()); if (btRowSel > maxRow) btRowSel = maxRow; } }; auto applyOpenArg = [&](const std::string& arg) { std::string a = toLowerStr(trimStr(arg)); modal = NetModal::None; if (a == "bluetooth" || a == "bt") { activeTab = NetTab::Bluetooth; } else if (a == "cell" || a == "cellular" || a == "apn" || a == "hotspot") { activeTab = NetTab::Cellular; } else { activeTab = NetTab::Wifi; } refreshCurrentTab(false); }; auto textModeLabel = [&]() -> const char* { switch (textMode) { case TextMode::Lower: return "abc"; case TextMode::Title: return "Abc"; case TextMode::Upper: return "ABC"; case TextMode::Num: return "123"; case TextMode::Sym: return "Sym"; } return "abc"; }; auto handleMultiTapKey = [&](std::string& targetStr, KeySym sym, unsigned int kc, unsigned int state) -> bool { char dch = mapDialKey(sym, kc, state); if (dch == '#') { textMode = static_cast((static_cast(textMode) + 1) % 5); lastTapDigit = -1; return true; } if (dch >= '0' && dch <= '9') { int digit = dch - '0'; int64_t now = nowMs(); if (textMode == TextMode::Num) { targetStr.push_back(dch); lastTapDigit = -1; return true; } const char* seq = (textMode == TextMode::Sym) ? kSymTapMap[digit] : kMultiTapMap[digit]; int seqLen = static_cast(std::strlen(seq)); if (seqLen <= 0) return false; if (digit == lastTapDigit && (now - lastTapMs) < 750 && !targetStr.empty()) { popUtf8Char(targetStr); lastTapCycle = (lastTapCycle + 1) % seqLen; } else { lastTapDigit = digit; lastTapCycle = 0; } lastTapMs = now; char outCh = seq[lastTapCycle]; if (textMode == TextMode::Upper) { outCh = static_cast(std::toupper(static_cast(outCh))); } else if (textMode == TextMode::Title && targetStr.empty()) { outCh = static_cast(std::toupper(static_cast(outCh))); } targetStr.push_back(outCh); return true; } if (sym == XK_BackSpace || sym == XK_Delete) { if (!targetStr.empty()) { popUtf8Char(targetStr); lastTapDigit = -1; return true; } } // Also accept direct ASCII keysyms from XIM / hardware keyboards if (sym >= 0x20 && sym <= 0x7e && !(state & (ControlMask | Mod1Mask))) { targetStr.push_back(static_cast(sym)); lastTapDigit = -1; return true; } return false; }; auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h, const std::string& text, int maxW = -1) -> int { pango_layout_set_text(layout, text.c_str(), static_cast(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); } int tw = 0, th = 0; pango_layout_get_pixel_size(layout, &tw, &th); int dy = y + std::max(0, (h - th) / 2); pango_xft_render_layout(xftDraw, color, layout, x * PANGO_SCALE, dy * PANGO_SCALE); return tw; }; auto pangoTextWidth = [&](PangoLayout* layout, const std::string& text) -> int { pango_layout_set_width(layout, -1); pango_layout_set_text(layout, text.c_str(), static_cast(text.size())); int tw = 0, th = 0; pango_layout_get_pixel_size(layout, &tw, &th); return tw; }; auto drawSoftkeyBar = [&](const std::string& leftLabel, const std::string& centerLabel, const std::string& rightLabel) { const int barY = kWinHeight - 26; XSetForeground(dpy, gc, pt.spec.bgFooter); XFillRectangle(dpy, pix, gc, 0, barY, kWinWidth, 26); XSetForeground(dpy, gc, pt.spec.divider); XDrawLine(dpy, pix, gc, 0, barY, kWinWidth, barY); drawPango(layoutMain, &colBlue, 6, barY, 26, leftLabel); int cw = pangoTextWidth(layoutMain, centerLabel); drawPango(layoutMain, &colWhite, (kWinWidth - cw) / 2, barY, 26, centerLabel); int rw = pangoTextWidth(layoutMain, rightLabel); drawPango(layoutMain, &colLight, kWinWidth - rw - 6, barY, 26, rightLabel); }; auto render = [&]() { if (!visible) return; pt.checkAndReload(); XSetForeground(dpy, gc, pt.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); // Top Tab Bar (Y=0..30) XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 30); XSetForeground(dpy, gc, pt.spec.border); XDrawLine(dpy, pix, gc, 0, 29, kWinWidth, 29); const char* tabLabels[3] = {"1:Wi-Fi", "2:Cell/APN", "3:Bluetooth"}; int tabW = kWinWidth / 3; for (int t = 0; t < 3; ++t) { bool isSel = (static_cast(activeTab) == t); if (isSel) { XSetForeground(dpy, gc, pt.spec.selBg); XFillRectangle(dpy, pix, gc, t * tabW, 0, tabW, 29); XSetForeground(dpy, gc, pt.spec.accent); XFillRectangle(dpy, pix, gc, t * tabW, 27, tabW, 2); } int tw = pangoTextWidth(layoutSmall, tabLabels[t]); drawPango(layoutSmall, isSel ? &colWhite : &colMuted, t * tabW + (tabW - tw) / 2, 0, 28, tabLabels[t]); } const int contentY = 32; const int contentH = kWinHeight - 32 - 26; if (activeTab == NetTab::Wifi) { // Row 0: Wi-Fi Power Toggle (height 32) bool sel0 = (wifiRowSel == 0); XSetForeground(dpy, gc, sel0 ? pt.spec.selBg : pt.spec.bgAltEven); XFillRectangle(dpy, pix, gc, 4, contentY, kWinWidth - 8, 30); drawPango(layoutIcon, wifiOn ? &colGreen : &colMuted, 10, contentY, 30, kIconWifi); drawPango(layoutMain, sel0 ? &colWhite : &colLight, 32, contentY, 30, std::string("Wi-Fi Radio: ") + (wifiOn ? "ON" : "OFF")); drawPango(layoutIcon, wifiOn ? &colGreen : &colMuted, kWinWidth - 28, contentY, 30, wifiOn ? kIconToggleOn : kIconToggleOff); int listY = contentY + 34; int listH = contentH - 34; if (!wifiOn) { drawPango(layoutMain, &colMuted, 18, listY + 50, 24, "Wi-Fi is turned off."); } else if (wifiList.empty()) { drawPango(layoutMain, &colMuted, 18, listY + 50, 24, "No networks found. Press Scan."); } else { const int rowH = 38; const int maxVis = listH / rowH; // 5 rows int apSel = std::max(0, wifiRowSel - 1); int startIdx = std::clamp(apSel - maxVis / 2, 0, std::max(0, static_cast(wifiList.size()) - maxVis)); for (int i = 0; i < maxVis && (startIdx + i) < static_cast(wifiList.size()); ++i) { int idx = startIdx + i; const auto& ap = wifiList[idx]; int ry = listY + i * rowH; bool isSel = (wifiRowSel == (idx + 1)); XSetForeground(dpy, gc, isSel ? pt.spec.selBg : ((i % 2 == 0) ? pt.spec.bgAltEven : pt.spec.bgAltOdd)); XFillRectangle(dpy, pix, gc, 4, ry, kWinWidth - 8, rowH - 2); drawPango(layoutIcon, ap.inUse ? &colGreen : (ap.isSaved ? &colBlue : &colLight), 10, ry + 1, 20, ap.security.empty() ? kIconWifi : kIconLock); drawPango(layoutMain, isSel ? &colWhite : &colLight, 30, ry + 1, 20, ap.ssid, kWinWidth - 42); std::string sub = std::to_string(ap.signal) + "% "; if (ap.inUse) sub += "[Connected] "; else if (ap.isSaved) sub += "[Saved] "; sub += ap.security.empty() ? "Open" : ap.security; drawPango(layoutSmall, ap.inUse ? &colGreen : (isSel ? &colLight : &colMuted), 30, ry + 19, 17, sub, kWinWidth - 42); } } drawSoftkeyBar("Scan", "Select", "Close"); } else if (activeTab == NetTab::Cellular) { // Status Card (height 86) XSetForeground(dpy, gc, pt.spec.bgAltEven); XFillRectangle(dpy, pix, gc, 4, contentY, kWinWidth - 8, 86); XSetForeground(dpy, gc, pt.spec.border); XDrawRectangle(dpy, pix, gc, 4, contentY, kWinWidth - 9, 85); std::string cellSt = cellInfo.gsmActive ? "Connected" : (cellInfo.gsmConfigured ? "Disconnected" : "Unconfigured"); drawPango(layoutMain, cellInfo.gsmActive ? &colGreen : &colOrange, 10, contentY + 2, 22, "Cellular: " + cellSt, 220); drawPango(layoutSmall, &colLight, 10, contentY + 24, 18, "APN: " + (cellInfo.apn.empty() ? "(not set)" : cellInfo.apn), 220); drawPango(layoutSmall, &colMuted, 10, contentY + 43, 18, "WWAN IP: " + (cellInfo.wwanIp.empty() ? "--" : cellInfo.wwanIp), 220); drawPango(layoutSmall, &colMuted, 10, contentY + 62, 18, "WLAN IP: " + (cellInfo.wlanIp.empty() ? "--" : cellInfo.wlanIp), 220); // 4 Interactive Rows std::string rows[4] = { std::string(kIconGlobe) + " Mobile Data: " + (cellInfo.gsmActive ? "ON" : "OFF"), std::string(kIconEdit) + " Configure APN (" + (cellInfo.apn.empty() ? "Set" : cellInfo.apn) + ")", std::string(kIconWifi) + " Wi-Fi Hotspot: " + (cellInfo.hotspotActive ? "ON" : "OFF"), std::string(kIconFlash) + " USB Tethering: " + (cellInfo.usbShareActive ? "ON" : "OFF"), }; int baseY = contentY + 92; for (int r = 0; r < 4; ++r) { int ry = baseY + r * 32; bool isSel = (cellRowSel == r); XSetForeground(dpy, gc, isSel ? pt.spec.selBg : ((r % 2 == 0) ? pt.spec.bgAltEven : pt.spec.bgAltOdd)); XFillRectangle(dpy, pix, gc, 4, ry, kWinWidth - 8, 30); drawPango(layoutSmall, isSel ? &colWhite : &colLight, 10, ry, 30, rows[r], kWinWidth - 20); } drawSoftkeyBar("Refresh", "Select", "Close"); } else if (activeTab == NetTab::Bluetooth) { // Row 0: BT Power Toggle (height 28) bool sel0 = (btRowSel == 0); XSetForeground(dpy, gc, sel0 ? pt.spec.selBg : pt.spec.bgAltEven); XFillRectangle(dpy, pix, gc, 4, contentY, kWinWidth - 8, 28); drawPango(layoutIcon, btOn ? &colBlue : &colMuted, 10, contentY, 28, kIconBluetooth); drawPango(layoutMain, sel0 ? &colWhite : &colLight, 30, contentY, 28, std::string("Bluetooth: ") + (btOn ? "ON" : "OFF")); drawPango(layoutIcon, btOn ? &colGreen : &colMuted, kWinWidth - 28, contentY, 28, btOn ? kIconToggleOn : kIconToggleOff); // Row 1: Scan for Devices (height 26) bool sel1 = (btRowSel == 1); XSetForeground(dpy, gc, sel1 ? pt.spec.selBg : pt.spec.bgAltOdd); XFillRectangle(dpy, pix, gc, 4, contentY + 30, kWinWidth - 8, 26); drawPango(layoutIcon, &colYellow, 10, contentY + 30, 26, kIconSearch); drawPango(layoutSmall, sel1 ? &colWhite : &colLight, 30, contentY + 30, 26, "Scan for Bluetooth Devices"); int listY = contentY + 58; int listH = contentH - 58; if (!btOn) { drawPango(layoutMain, &colMuted, 18, listY + 40, 24, "Bluetooth is turned off."); } else if (btList.empty()) { drawPango(layoutMain, &colMuted, 18, listY + 40, 24, "No devices. Select Scan."); } else { const int rowH = 38; const int maxVis = listH / rowH; // 4 rows int devSel = std::max(0, btRowSel - 2); int startIdx = std::clamp(devSel - maxVis / 2, 0, std::max(0, static_cast(btList.size()) - maxVis)); for (int i = 0; i < maxVis && (startIdx + i) < static_cast(btList.size()); ++i) { int idx = startIdx + i; const auto& d = btList[idx]; int ry = listY + i * rowH; bool isSel = (btRowSel == (idx + 2)); XSetForeground(dpy, gc, isSel ? pt.spec.selBg : ((i % 2 == 0) ? pt.spec.bgAltEven : pt.spec.bgAltOdd)); XFillRectangle(dpy, pix, gc, 4, ry, kWinWidth - 8, rowH - 2); drawPango(layoutIcon, d.connected ? &colGreen : (d.paired ? &colBlue : &colMuted), 10, ry + 1, 20, kIconBluetooth); drawPango(layoutMain, isSel ? &colWhite : &colLight, 30, ry + 1, 20, d.name, kWinWidth - 42); std::string st = d.connected ? "[Connected] " : (d.paired ? "[Paired] " : "[Available] "); st += d.mac; drawPango(layoutSmall, d.connected ? &colGreen : (isSel ? &colLight : &colMuted), 30, ry + 19, 17, st, kWinWidth - 42); } } drawSoftkeyBar("Scan", "Select", "Close"); } // Modal Overlays if (modal == NetModal::WifiActions) { XSetForeground(dpy, gc, pt.spec.bgOverlay); XFillRectangle(dpy, pix, gc, 12, 48, kWinWidth - 24, 174); XSetForeground(dpy, gc, pt.spec.accent); XDrawRectangle(dpy, pix, gc, 12, 48, kWinWidth - 25, 173); drawPango(layoutMain, &colYellow, 20, 52, 24, targetAp.ssid, kWinWidth - 40); const char* acts[4] = { targetAp.inUse ? "Reconnect Network" : "Connect to Network", "Enter / Update Password", "Disconnect Network", "Forget Saved Network", }; for (int i = 0; i < 4; ++i) { int ry = 82 + i * 32; bool isSel = (actionSel == i); XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 18, ry, kWinWidth - 36, 28); drawPango(layoutSmall, isSel ? &colWhite : &colLight, 24, ry, 28, acts[i], kWinWidth - 48); } drawSoftkeyBar("Select", "OK", "Back"); } else if (modal == NetModal::WifiPassword) { XSetForeground(dpy, gc, pt.spec.bgOverlay); XFillRectangle(dpy, pix, gc, 10, 42, kWinWidth - 20, 200); XSetForeground(dpy, gc, pt.spec.accent); XDrawRectangle(dpy, pix, gc, 10, 42, kWinWidth - 21, 199); drawPango(layoutMain, &colYellow, 18, 48, 22, "Wi-Fi: " + targetAp.ssid, kWinWidth - 80); drawPango(layoutSmall, &colBlue, kWinWidth - 58, 48, 22, std::string("[") + textModeLabel() + "]"); drawPango(layoutSmall, &colLight, 18, 76, 20, "Enter WPA/WEP Passphrase:"); XSetForeground(dpy, gc, pt.spec.bgInputFocus); XFillRectangle(dpy, pix, gc, 18, 100, kWinWidth - 36, 34); XSetForeground(dpy, gc, pt.spec.accent); XDrawRectangle(dpy, pix, gc, 18, 100, kWinWidth - 37, 33); std::string disp = showPassword ? wifiPassword : std::string(wifiPassword.size(), '*'); drawPango(layoutMain, &colWhite, 24, 100, 34, disp + "_", kWinWidth - 48); drawPango(layoutSmall, &colMuted, 18, 144, 18, "[#] Cycle input mode (abc/Abc/ABC/123/Sym)"); drawPango(layoutSmall, &colMuted, 18, 164, 18, "[*] Toggle Show/Hide password"); drawPango(layoutSmall, &colMuted, 18, 184, 18, "[BackSpace] Delete character"); drawSoftkeyBar("Connect", "Connect", "Cancel"); } else if (modal == NetModal::ApnEditor) { XSetForeground(dpy, gc, pt.spec.bgOverlay); XFillRectangle(dpy, pix, gc, 10, 36, kWinWidth - 20, 220); XSetForeground(dpy, gc, pt.spec.accent); XDrawRectangle(dpy, pix, gc, 10, 36, kWinWidth - 21, 219); drawPango(layoutMain, &colYellow, 18, 40, 22, "Cellular APN Setup"); drawPango(layoutSmall, &colBlue, kWinWidth - 58, 40, 22, std::string("[") + textModeLabel() + "]"); const char* labels[3] = {"APN Name:", "Username (optional):", "Password (optional):"}; std::string vals[3] = {apnName, apnUser, apnPass}; for (int f = 0; f < 3; ++f) { int fy = 66 + f * 44; bool isF = (apnFieldSel == f); drawPango(layoutSmall, isF ? &colYellow : &colMuted, 18, fy, 16, labels[f]); XSetForeground(dpy, gc, isF ? pt.spec.bgInputFocus : pt.spec.bgInput); XFillRectangle(dpy, pix, gc, 18, fy + 16, kWinWidth - 36, 24); XSetForeground(dpy, gc, isF ? pt.spec.accent : pt.spec.border); XDrawRectangle(dpy, pix, gc, 18, fy + 16, kWinWidth - 37, 23); drawPango(layoutSmall, &colWhite, 22, fy + 16, 24, vals[f] + (isF ? "_" : ""), kWinWidth - 44); } bool saveSel = (apnFieldSel == 3); XSetForeground(dpy, gc, saveSel ? pt.spec.selBg : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 18, 202, kWinWidth - 36, 26); drawPango(layoutMain, saveSel ? &colWhite : &colGreen, 50, 202, 26, "Save & Activate APN"); drawSoftkeyBar("Save", "Select", "Cancel"); } else if (modal == NetModal::BtActions) { XSetForeground(dpy, gc, pt.spec.bgOverlay); XFillRectangle(dpy, pix, gc, 12, 48, kWinWidth - 24, 174); XSetForeground(dpy, gc, pt.spec.accent); XDrawRectangle(dpy, pix, gc, 12, 48, kWinWidth - 25, 173); drawPango(layoutMain, &colYellow, 20, 52, 24, targetBt.name, kWinWidth - 40); const char* acts[4] = { "Connect Device", "Disconnect Device", "Pair & Trust Device", "Forget / Remove Device", }; for (int i = 0; i < 4; ++i) { int ry = 82 + i * 32; bool isSel = (actionSel == i); XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 18, ry, kWinWidth - 36, 28); drawPango(layoutSmall, isSel ? &colWhite : &colLight, 24, ry, 28, acts[i], kWinWidth - 48); } drawSoftkeyBar("Select", "OK", "Back"); } if (!statusBanner.empty() && nowMs() < statusBannerUntilMs) { XSetForeground(dpy, gc, pt.spec.bgToast); XFillRectangle(dpy, pix, gc, 8, kWinHeight - 56, kWinWidth - 16, 26); XSetForeground(dpy, gc, pt.spec.accent); XDrawRectangle(dpy, pix, gc, 8, kWinHeight - 56, kWinWidth - 17, 25); drawPango(layoutSmall, &colWhite, 14, kWinHeight - 56, 26, statusBanner, kWinWidth - 28); } XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); XFlush(dpy); }; auto grabKeys = [&]() { for (int i = 0; i < 6; ++i) { if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess) { break; } usleep(15000); } }; auto showWindow = [&](const std::string& arg = "") { closeOtherPlugins("networks"); applyOpenArg(arg); writeIntFile(activePidPath, static_cast(::getpid())); if (!visible) { XMapRaised(dpy, win); visible = true; } else { XRaiseWindow(dpy, win); } openStartMs = nowMs(); startupIgnoreUntilMs = openStartMs + 180; initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0); softkeyPending = 0; grabKeys(); render(); }; auto hideWindow = [&]() { if (!visible) return; XUngrabKeyboard(dpy, CurrentTime); XUnmapWindow(dpy, win); XFlush(dpy); visible = false; modal = NetModal::None; ::unlink(activePidPath.c_str()); }; auto connectWifiNow = [&](const std::string& ssid, const std::string& pass) { modal = NetModal::None; setBanner("Connecting to " + ssid + "..."); render(); int rc = 0; std::string cmd = "nmcli device wifi connect " + shellQuote(ssid); if (!pass.empty()) { cmd += " password " + shellQuote(pass); } cmd += " >/dev/null 2>&1"; runCaptureCmd(cmd, &rc); refreshCurrentTab(false); runAsyncCmd("sxmo_hook_statusbar.sh wifi >/dev/null 2>&1"); setBanner(rc == 0 ? ("Connected to " + ssid) : ("Failed to connect to " + ssid)); }; auto saveAndConnectApn = [&]() { std::string cleanApn = trimStr(apnName); if (cleanApn.empty()) { setBanner("APN name cannot be empty"); return; } modal = NetModal::None; setBanner("Saving APN " + cleanApn + "..."); render(); if (cellInfo.gsmConfigured && !cellInfo.gsmConName.empty()) { runCaptureCmd("nmcli connection delete " + shellQuote(cellInfo.gsmConName) + " >/dev/null 2>&1"); } std::string cmd = "nmcli connection add type gsm con-name " + shellQuote(cleanApn) + " ifname '*' apn " + shellQuote(cleanApn) + " connection.autoconnect yes"; if (!trimStr(apnUser).empty()) { cmd += " gsm.username " + shellQuote(trimStr(apnUser)); } if (!trimStr(apnPass).empty()) { cmd += " gsm.password " + shellQuote(trimStr(apnPass)); } cmd += " >/dev/null 2>&1 && nmcli connection up " + shellQuote(cleanApn) + " >/dev/null 2>&1"; int rc = 0; runCaptureCmd(cmd, &rc); refreshCurrentTab(false); runAsyncCmd("sxmo_hook_statusbar.sh modem >/dev/null 2>&1"); setBanner(rc == 0 ? ("Connected APN: " + cleanApn) : ("Saved APN: " + cleanApn)); }; auto executeWifiAction = [&](int act) { modal = NetModal::None; if (act == 0) { if (!targetAp.security.empty() && !targetAp.isSaved && !targetAp.inUse) { wifiPassword.clear(); showPassword = false; modal = NetModal::WifiPassword; } else { setBanner("Connecting to " + targetAp.ssid + "..."); render(); int rc = 0; if (targetAp.isSaved) { runCaptureCmd("nmcli connection up " + shellQuote(targetAp.ssid) + " >/dev/null 2>&1", &rc); } else { runCaptureCmd("nmcli device wifi connect " + shellQuote(targetAp.ssid) + " >/dev/null 2>&1", &rc); } refreshCurrentTab(false); runAsyncCmd("sxmo_hook_statusbar.sh wifi >/dev/null 2>&1"); setBanner(rc == 0 ? ("Connected: " + targetAp.ssid) : "Connection failed"); } } else if (act == 1) { wifiPassword.clear(); showPassword = false; modal = NetModal::WifiPassword; } else if (act == 2) { runCaptureCmd("nmcli connection down " + shellQuote(targetAp.ssid) + " >/dev/null 2>&1"); refreshCurrentTab(false); runAsyncCmd("sxmo_hook_statusbar.sh wifi >/dev/null 2>&1"); setBanner("Disconnected " + targetAp.ssid); } else if (act == 3) { runCaptureCmd("nmcli connection delete " + shellQuote(targetAp.ssid) + " >/dev/null 2>&1"); refreshCurrentTab(false); runAsyncCmd("sxmo_hook_statusbar.sh wifi >/dev/null 2>&1"); setBanner("Forgot " + targetAp.ssid); } }; auto executeBtAction = [&](int act) { modal = NetModal::None; int rc = 0; if (act == 0) { setBanner("Connecting " + targetBt.name + "..."); render(); runCaptureCmd("bluetoothctl connect " + shellQuote(targetBt.mac) + " >/dev/null 2>&1", &rc); refreshCurrentTab(false); setBanner(rc == 0 ? ("Connected: " + targetBt.name) : "Connect failed"); } else if (act == 1) { setBanner("Disconnecting " + targetBt.name + "..."); render(); runCaptureCmd("bluetoothctl disconnect " + shellQuote(targetBt.mac) + " >/dev/null 2>&1", &rc); refreshCurrentTab(false); setBanner("Disconnected " + targetBt.name); } else if (act == 2) { setBanner("Pairing " + targetBt.name + "..."); render(); runCaptureCmd("bluetoothctl --timeout 8 pair " + shellQuote(targetBt.mac) + " >/dev/null 2>&1; bluetoothctl trust " + shellQuote(targetBt.mac) + " >/dev/null 2>&1; " "bluetoothctl connect " + shellQuote(targetBt.mac) + " >/dev/null 2>&1", &rc); refreshCurrentTab(false); setBanner("Paired & trusted " + targetBt.name); } else if (act == 3) { runCaptureCmd("bluetoothctl remove " + shellQuote(targetBt.mac) + " >/dev/null 2>&1", &rc); refreshCurrentTab(false); setBanner("Removed " + targetBt.name); } }; auto handleEnterKey = [&]() { if (modal == NetModal::WifiActions) { executeWifiAction(actionSel); return; } if (modal == NetModal::WifiPassword) { connectWifiNow(targetAp.ssid, wifiPassword); return; } if (modal == NetModal::ApnEditor) { if (apnFieldSel == 3) { saveAndConnectApn(); } else { apnFieldSel = (apnFieldSel + 1) % 4; } return; } if (modal == NetModal::BtActions) { executeBtAction(actionSel); return; } if (activeTab == NetTab::Wifi) { if (wifiRowSel == 0) { setBanner(wifiOn ? "Turning Wi-Fi OFF..." : "Turning Wi-Fi ON..."); render(); runCaptureCmd("doas -n sxmo_wifitoggle.sh " + std::string(wifiOn ? "off" : "on") + " >/dev/null 2>&1"); refreshCurrentTab(false); runAsyncCmd("sxmo_hook_statusbar.sh wifi >/dev/null 2>&1"); } else if (wifiRowSel - 1 < static_cast(wifiList.size())) { targetAp = wifiList[wifiRowSel - 1]; if (!targetAp.inUse && !targetAp.isSaved && !targetAp.security.empty()) { wifiPassword.clear(); showPassword = false; modal = NetModal::WifiPassword; } else { actionSel = 0; modal = NetModal::WifiActions; } } } else if (activeTab == NetTab::Cellular) { if (cellRowSel == 0) { if (!cellInfo.gsmConfigured) { apnName = cellInfo.apn; apnUser = cellInfo.username; apnPass.clear(); apnFieldSel = 0; modal = NetModal::ApnEditor; } else { bool turnOn = !cellInfo.gsmActive; setBanner(turnOn ? "Enabling Mobile Data..." : "Disabling Mobile Data..."); render(); runCaptureCmd("nmcli connection " + std::string(turnOn ? "up " : "down ") + shellQuote(cellInfo.gsmConName) + " >/dev/null 2>&1"); refreshCurrentTab(false); } } else if (cellRowSel == 1) { apnName = cellInfo.apn; apnUser = cellInfo.username; apnPass.clear(); apnFieldSel = 0; modal = NetModal::ApnEditor; } else if (cellRowSel == 2) { if (cellInfo.hotspotActive) { setBanner("Stopping Wi-Fi Hotspot..."); render(); runCaptureCmd("nmcli connection delete Hotspot >/dev/null 2>&1"); refreshCurrentTab(false); setBanner("Hotspot stopped"); } else { setBanner("Starting Wi-Fi Hotspot..."); render(); std::string hsOut = trimStr(runCaptureCmd( "nmcli device wifi hotspot 2>&1")); refreshCurrentTab(false); setBanner(hsOut.empty() ? "Hotspot active" : hsOut, 4000); } runAsyncCmd("sxmo_hook_statusbar.sh wifi >/dev/null 2>&1"); } else if (cellRowSel == 3) { if (cellInfo.usbShareActive) { setBanner("Stopping USB Tethering..."); render(); runCaptureCmd("doas -n /usr/libexec/msm-modem-uim-selection usb-tether off >/dev/null 2>&1"); refreshCurrentTab(false); setBanner("USB Tethering stopped"); } else { setBanner("Starting USB Tethering..."); render(); runCaptureCmd("doas -n /usr/libexec/msm-modem-uim-selection usb-tether on >/dev/null 2>&1"); refreshCurrentTab(false); setBanner("USB Tethering active"); } } } else if (activeTab == NetTab::Bluetooth) { if (btRowSel == 0) { setBanner(btOn ? "Turning Bluetooth OFF..." : "Turning Bluetooth ON..."); render(); runCaptureCmd("SXMO_OS=alpine doas -n sxmo_bluetoothtoggle.sh " + std::string(btOn ? "off" : "on") + " >/dev/null 2>&1"); refreshCurrentTab(false); } else if (btRowSel == 1) { setBanner("Scanning Bluetooth devices (5s)..."); render(); refreshCurrentTab(true); setBanner("Found " + std::to_string(btList.size()) + " device(s)"); } else if (btRowSel - 2 < static_cast(btList.size())) { targetBt = btList[btRowSel - 2]; actionSel = 0; modal = NetModal::BtActions; } } }; auto handleLeftSoftkey = [&]() { if (modal == NetModal::ApnEditor) { saveAndConnectApn(); return; } if (modal != NetModal::None) { handleEnterKey(); return; } if (activeTab == NetTab::Wifi) { setBanner("Scanning Wi-Fi networks..."); render(); refreshCurrentTab(true); setBanner("Found " + std::to_string(wifiList.size()) + " network(s)"); } else if (activeTab == NetTab::Cellular) { refreshCurrentTab(false); setBanner("Refreshed cellular state"); } else if (activeTab == NetTab::Bluetooth) { setBanner("Scanning Bluetooth (5s)..."); render(); refreshCurrentTab(true); setBanner("Found " + std::to_string(btList.size()) + " device(s)"); } }; auto handleRightSoftkey = [&]() { if (modal != NetModal::None) { modal = NetModal::None; return; } hideWindow(); }; if (!isPreload) { showWindow(openArg); } int xfd = ConnectionNumber(dpy); while (gRunning) { while (XPending(dpy) > 0) { XEvent ev; XNextEvent(dpy, &ev); if (ev.type == Expose && ev.xexpose.count == 0) { render(); } else if (ev.type == VisibilityNotify && visible && ev.xvisibility.state != VisibilityUnobscured) { XRaiseWindow(dpy, win); } else if (ev.type == KeyRelease) { if (!initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) { initialSoftkeysReleased = true; } } else if (ev.type == KeyPress && visible) { int64_t now = nowMs(); if (now < startupIgnoreUntilMs) continue; KeySym sym = XLookupKeysym(&ev.xkey, 0); unsigned int kc = ev.xkey.keycode; bool isLSK = (kc == 135 || sym == XK_F1 || sym == XK_Menu || sym == XF86XK_TaskPane); bool isRSK = (kc == 166 || sym == XK_F2 || sym == XF86XK_Back || sym == XK_Escape || sym == static_cast(XF86XK_KbdLcdMenu2)); if (isLSK || isRSK) { if (!initialSoftkeysReleased) continue; int mask = querySoftkeysDown(dpy); if (mask == 3 || (softkeyPending != 0 && ((isLSK && softkeyPending == 2) || (isRSK && softkeyPending == 1)))) { softkeyPending = 0; writeSoftkeySuppress(now + 400); hideWindow(); sendPluginCommand("xdrawer", "TOGGLE"); continue; } softkeyPending = isLSK ? 1 : 2; softkeyDeadlineMs = now + 90; continue; } if (sym == XF86XK_HangupPhone || sym == XK_Cancel) { hideWindow(); continue; } if (modal == NetModal::WifiPassword) { char dch = mapDialKey(sym, kc, ev.xkey.state); if (dch == '*') { showPassword = !showPassword; render(); continue; } if (sym == XK_Return || sym == XK_KP_Enter) { connectWifiNow(targetAp.ssid, wifiPassword); render(); continue; } if (handleMultiTapKey(wifiPassword, sym, kc, ev.xkey.state)) { render(); } continue; } if (modal == NetModal::ApnEditor) { if (sym == XK_Up || sym == XK_KP_Up) { apnFieldSel = (apnFieldSel + 3) % 4; lastTapDigit = -1; render(); continue; } if (sym == XK_Down || sym == XK_KP_Down) { apnFieldSel = (apnFieldSel + 1) % 4; lastTapDigit = -1; render(); continue; } if (sym == XK_Return || sym == XK_KP_Enter) { handleEnterKey(); render(); continue; } if (apnFieldSel < 3) { std::string* dst = (apnFieldSel == 0) ? &apnName : ((apnFieldSel == 1) ? &apnUser : &apnPass); if (handleMultiTapKey(*dst, sym, kc, ev.xkey.state)) { render(); } } continue; } if (modal == NetModal::WifiActions || modal == NetModal::BtActions) { if (sym == XK_Up || sym == XK_KP_Up) { actionSel = (actionSel + 3) % 4; render(); } else if (sym == XK_Down || sym == XK_KP_Down) { actionSel = (actionSel + 1) % 4; render(); } else if (sym == XK_Return || sym == XK_KP_Enter) { handleEnterKey(); render(); } continue; } // Main View Tab & Row Navigation char dch = mapDialKey(sym, kc, ev.xkey.state); if (dch == '1') { activeTab = NetTab::Wifi; refreshCurrentTab(false); render(); continue; } else if (dch == '2') { activeTab = NetTab::Cellular; refreshCurrentTab(false); render(); continue; } else if (dch == '3') { activeTab = NetTab::Bluetooth; refreshCurrentTab(false); render(); continue; } if (sym == XK_Left || sym == XK_KP_Left) { activeTab = static_cast((static_cast(activeTab) + 2) % 3); refreshCurrentTab(false); render(); } else if (sym == XK_Right || sym == XK_KP_Right) { activeTab = static_cast((static_cast(activeTab) + 1) % 3); refreshCurrentTab(false); render(); } else if (sym == XK_Up || sym == XK_KP_Up) { if (activeTab == NetTab::Wifi) { int total = 1 + static_cast(wifiList.size()); wifiRowSel = (wifiRowSel + total - 1) % total; } else if (activeTab == NetTab::Cellular) { cellRowSel = (cellRowSel + 3) % 4; } else if (activeTab == NetTab::Bluetooth) { int total = 2 + static_cast(btList.size()); btRowSel = (btRowSel + total - 1) % total; } render(); } else if (sym == XK_Down || sym == XK_KP_Down) { if (activeTab == NetTab::Wifi) { int total = 1 + static_cast(wifiList.size()); wifiRowSel = (wifiRowSel + 1) % total; } else if (activeTab == NetTab::Cellular) { cellRowSel = (cellRowSel + 1) % 4; } else if (activeTab == NetTab::Bluetooth) { int total = 2 + static_cast(btList.size()); btRowSel = (btRowSel + 1) % total; } render(); } else if (sym == XK_Return || sym == XK_KP_Enter) { handleEnterKey(); render(); } } } int timeoutMs = -1; int64_t now = nowMs(); if (visible) { if (softkeyPending != 0) { int64_t rem = softkeyDeadlineMs - now; if (rem <= 0) { int pend = softkeyPending; softkeyPending = 0; if (querySoftkeysDown(dpy) == 3) { writeSoftkeySuppress(now + 400); hideWindow(); sendPluginCommand("xdrawer", "TOGGLE"); } else if (pend == 1) { handleLeftSoftkey(); render(); } else if (pend == 2) { handleRightSoftkey(); render(); } } else { timeoutMs = static_cast(rem); } } if (!statusBanner.empty()) { int64_t bRem = statusBannerUntilMs - now; if (bRem <= 0) { statusBanner.clear(); render(); } else if (timeoutMs < 0 || bRem < timeoutMs) { timeoutMs = static_cast(bRem); } } } struct pollfd pfds[2]; pfds[0].fd = xfd; pfds[0].events = POLLIN; pfds[1].fd = fifoFd; pfds[1].events = POLLIN; int pr = ::poll(pfds, (fifoFd >= 0) ? 2 : 1, timeoutMs); if (pr > 0 && fifoFd >= 0 && (pfds[1].revents & POLLIN)) { for (const auto& cmd : readFifoLines(fifoFd)) { if (cmd == "CLOSE") { hideWindow(); } else if (cmd == "RELOAD_THEME") { pt.reload(); if (visible) render(); } else if (cmd == "TOGGLE") { if (visible) hideWindow(); else showWindow(); } else if (cmd.rfind("OPEN", 0) == 0) { std::string arg = (cmd.size() > 5) ? trimStr(cmd.substr(5)) : ""; showWindow(arg); } } } } hideWindow(); if (fifoFd >= 0) ::close(fifoFd); ::unlink(daemonPidPath.c_str()); XftDrawDestroy(xftDraw); XFreePixmap(dpy, pix); XFreeGC(dpy, gc); XDestroyWindow(dpy, win); XCloseDisplay(dpy); return 0; } int pluginInit(const DwmHostApi* host) { if (host && host->spawn_plugin) { host->spawn_plugin("networks", "--preload"); } return 0; } void pluginCleanup(void) {} } // namespace extern "C" __attribute__((visibility("default"))) const DwmPlugin dwm_plugin_entry = { DWM_PLUGIN_API_VERSION, "networks", "Bananarchy Wi-Fi, Cellular APN, Hotspot & Bluetooth Manager (240x320)", pluginInit, pluginCleanup, nullptr, nullptr, nullptr, runNetworksApp };