Fix SIM2 QMI UIM provisioning, auto-enable ModemManager, PIN auto-prompt, call audio, and Dunst clearing

This commit is contained in:
Luxferre
2026-10-25 11:31:41 +02:00
parent 9b712dcbca
commit b56f310f21
41 changed files with 1193 additions and 352 deletions
+190 -26
View File
@@ -62,12 +62,15 @@ struct SimSlotInfo {
bool needsPin() const {
return state == "present" &&
(pin1State == "enabled-not-verified" || appState == "pin1");
(pin1State == "enabled-not-verified" ||
appState.rfind("pin1", 0) == 0);
}
bool needsPuk() const {
return state == "present" &&
(pin1State == "blocked" || appState == "puk1");
(pin1State == "blocked" ||
appState.rfind("puk1", 0) == 0 ||
appState == "pin1-blocked");
}
bool isPinEnabled() const {
@@ -88,6 +91,13 @@ struct ModemStatusData {
std::string mmFailedReason;
std::string mmPowerState = "off";
std::string mmUnlockRequired;
std::string mmRegState;
std::string nasRegState;
std::string nasOperator;
std::string nasMcc;
std::string nasMnc;
std::string nasRoaming;
std::string nasRadio;
int mmPinRetries = -1;
int mmPukRetries = -1;
std::string operatorName;
@@ -103,6 +113,56 @@ struct ModemStatusData {
std::string simOperatorName;
std::string currentModes;
int modePresetIdx = 0;
bool isNetworkRegistered() const {
return mmRegState == "home" || mmRegState == "roaming" ||
nasRegState == "registered" ||
mmState == "registered" || mmState == "connected";
}
bool isNetworkSearching() const {
return mmRegState == "searching" || nasRegState == "searching" ||
mmState == "searching" || mmState == "enabling" ||
mmState == "connecting";
}
std::string regStatusLabel() const {
if (slots[0].needsPuk() || slots[1].needsPuk() ||
mmUnlockRequired == "sim-puk") {
return "SIM PUK Required";
}
if (slots[0].needsPin() || slots[1].needsPin() ||
mmUnlockRequired == "sim-pin" || mmState == "locked") {
return "SIM PIN Required";
}
if (mmRegState == "home" ||
(nasRegState == "registered" && nasRoaming != "on")) {
return "Registered (Home)";
}
if (mmRegState == "roaming" ||
(nasRegState == "registered" && nasRoaming == "on")) {
return "Registered (Roaming)";
}
if (mmState == "connected" || mmState == "registered") {
return "Registered";
}
if (isNetworkSearching()) {
return "Searching...";
}
if (mmRegState == "denied" || nasRegState == "registration-denied") {
return "Registration Denied";
}
if (slots[0].state != "present" && slots[1].state != "present") {
return "No SIM Inserted";
}
if (primaryGwSlot == 0) {
return "SIM Not Activated";
}
if (mmState == "disabled") {
return "Modem Disabled";
}
return "Not Registered";
}
};
struct RadioModePreset {
@@ -201,6 +261,12 @@ ModemStatusData queryModemAndSimStatus() {
if (!uimKv["PRIMARY_GW_SLOT"].empty()) {
d.primaryGwSlot = std::atoi(uimKv["PRIMARY_GW_SLOT"].c_str());
}
d.nasRegState = uimKv["NAS_REG_STATE"];
d.nasOperator = uimKv["NAS_OPERATOR"];
d.nasMcc = uimKv["NAS_MCC"];
d.nasMnc = uimKv["NAS_MNC"];
d.nasRoaming = uimKv["NAS_ROAMING"];
d.nasRadio = uimKv["NAS_RADIO"];
for (int i = 0; i < 2; ++i) {
std::string pfx = "SLOT" + std::to_string(i + 1) + "_";
@@ -244,6 +310,7 @@ ModemStatusData queryModemAndSimStatus() {
d.mmFailedReason = cleanMmVal(mmKv["modem.generic.state-failed-reason"]);
d.mmPowerState = cleanMmVal(mmKv["modem.generic.power-state"]);
d.mmUnlockRequired = cleanMmVal(mmKv["modem.generic.unlock-required"]);
d.mmRegState = cleanMmVal(mmKv["modem.3gpp.registration-state"]);
d.operatorName = cleanMmVal(mmKv["modem.3gpp.operator-name"]);
d.operatorCode = cleanMmVal(mmKv["modem.3gpp.operator-code"]);
d.accessTech = cleanMmVal(mmKv["modem.generic.access-technologies.value[1]"]);
@@ -309,6 +376,16 @@ ModemStatusData queryModemAndSimStatus() {
}
}
if (d.operatorName.empty() && !d.nasOperator.empty()) {
d.operatorName = d.nasOperator;
}
if (d.operatorCode.empty() && !d.nasMcc.empty() && !d.nasMnc.empty()) {
d.operatorCode = d.nasMcc + d.nasMnc;
}
if (d.accessTech.empty() && !d.nasRadio.empty() && d.nasRadio != "none") {
d.accessTech = d.nasRadio;
}
// Sync ModemManager lock info into active slot if QMI didn't report retries
int actIdx = (d.primaryGwSlot >= 1 && d.primaryGwSlot <= 2)
? (d.primaryGwSlot - 1)
@@ -395,8 +472,8 @@ int runModemApp(int argc, char** argv) {
return 0;
}
writeIntFile(daemonPidPath, static_cast<int>(::getpid()));
int fifoFd = setupPluginDaemonFifo("modem");
writeIntFile(daemonPidPath, static_cast<int>(::getpid()));
struct sigaction sa {};
sa.sa_handler = handleSignal;
@@ -702,10 +779,24 @@ int runModemApp(int argc, char** argv) {
line2 = "LOCKED (Enter PIN)";
line2Col = &colOrange;
} else if (s.isActiveProvisioned) {
std::string op = status.operatorName.empty() ? "Ready" : status.operatorName;
if (!status.ownNumber.empty()) op += " " + status.ownNumber;
line2 = op;
line2Col = &colGreen;
std::string reg = status.regStatusLabel();
if (!status.operatorName.empty()) {
reg += " - " + status.operatorName;
}
if (!status.accessTech.empty()) {
reg += " [" + status.accessTech + "]";
}
line2 = reg;
if (status.isNetworkRegistered()) {
line2Col = &colGreen;
} else if (status.isNetworkSearching()) {
line2Col = &colOrange;
} else {
line2Col = &colRed;
}
} else if (status.primaryGwSlot == 0) {
line2 = "Present (Unactivated - Press Select)";
line2Col = &colOrange;
} else {
line2 = "Present (Standby " + s.appType + ")";
line2Col = &colBlue;
@@ -726,6 +817,9 @@ int runModemApp(int argc, char** argv) {
else if (pState == "blocked") pState = "PUK Req";
line3 = pState + " (PIN:" + std::to_string(s.pin1Retries) +
" PUK:" + std::to_string(s.puk1Retries) + ")";
if (s.isActiveProvisioned && !status.ownNumber.empty()) {
line3 += " " + status.ownNumber;
}
} else {
line3 = "Insert nano-SIM & restart/switch";
}
@@ -736,8 +830,18 @@ int runModemApp(int argc, char** argv) {
// Quick Action Rows below the 2 SIM cards (4 rows, 24px each)
int actSlot = (simRowSel == 1) ? 2 : 1;
int otherSlot = (status.preferredSlot == 1) ? 2 : 1;
if (status.primaryGwSlot == 0) {
if (status.slots[1].state == "present" && status.slots[0].state != "present") {
otherSlot = 2;
} else if (status.slots[0].state == "present" && status.slots[1].state != "present") {
otherSlot = 1;
}
}
std::string quickRows[4] = {
std::string(kIconRefresh) + " Switch Active to SIM " + std::to_string(otherSlot),
std::string(kIconRefresh) +
(status.primaryGwSlot == 0
? (" Activate SIM " + std::to_string(otherSlot))
: (" Switch Active to SIM " + std::to_string(otherSlot))),
std::string(kIconUnlock) + " Unlock SIM (PIN / PUK)",
std::string(kIconLock) + " Enable / Disable SIM PIN",
std::string(kIconKey) + " Change SIM PIN Code",
@@ -762,29 +866,31 @@ int runModemApp(int argc, char** argv) {
XSetForeground(dpy, gc, pt.spec.border);
XDrawRectangle(dpy, pix, gc, 4, contentY, kWinWidth - 9, 101);
std::string stStr = status.mmPresent ? status.mmState : "nomodem";
if (!status.mmFailedReason.empty()) {
stStr += " (" + status.mmFailedReason + ")";
}
std::string regStr = status.regStatusLabel();
XftColor* stCol = &colRed;
if (status.mmState == "connected" || status.mmState == "registered") {
if (status.isNetworkRegistered()) {
stCol = &colGreen;
} else if (status.mmState == "searching" || status.mmState == "connecting") {
} else if (status.isNetworkSearching()) {
stCol = &colOrange;
}
drawPango(layoutMain, stCol, 10, contentY + 2, 22,
"State: " + stStr, 185);
"Net: " + regStr, 185);
drawSignalBars(kWinWidth - 32, contentY + 6, status.signalQuality);
std::string opLine = "Operator: " +
(status.operatorName.empty() ? "--" : status.operatorName);
if (!status.operatorCode.empty() &&
status.operatorName != status.operatorCode) {
opLine += " (" + status.operatorCode + ")";
}
if (!status.accessTech.empty()) {
opLine += " [" + status.accessTech + "]";
}
drawPango(layoutSmall, &colLight, 10, contentY + 24, 18, opLine, 220);
std::string sigLine = "Signal: " + std::to_string(status.signalQuality) +
"% | Slot: SIM " +
std::string stStr = status.mmPresent ? status.mmState : "nomodem";
std::string sigLine = "Modem: " + stStr + " | Sig: " +
std::to_string(status.signalQuality) + "% | SIM " +
(status.primaryGwSlot > 0 ? std::to_string(status.primaryGwSlot) : "--");
drawPango(layoutSmall, &colLight, 10, contentY + 42, 18, sigLine, 220);
@@ -1090,6 +1196,15 @@ int runModemApp(int argc, char** argv) {
};
auto showWindow = [&](const std::string& arg = "") {
std::string a = toLowerStr(trimStr(arg));
if (visible &&
(modal == ModalMode::VerifyPin || modal == ModalMode::UnblockPuk) &&
(a == "unlock" || a == "pin" || a == "puk")) {
XRaiseWindow(dpy, win);
grabKeys();
render();
return;
}
closeOtherPlugins("modem");
refreshData(true);
applyOpenArg(arg);
@@ -1308,16 +1423,36 @@ int runModemApp(int argc, char** argv) {
auto triggerSimRowAction = [&](int row) {
if (row == 0 || row == 1) {
targetSlot = row + 1;
actionSel = 0;
modal = ModalMode::SimActions;
int sNum = row + 1;
const auto& s = status.slots[row];
if (s.needsPin() || s.needsPuk()) {
openPinOrPukModalForSlot(sNum);
} else if (s.state == "present" && !s.isActiveProvisioned) {
switchSimSlotNow(sNum);
} else {
targetSlot = sNum;
actionSel = 0;
modal = ModalMode::SimActions;
}
} else if (row == 2) {
int otherSlot = (status.preferredSlot == 1) ? 2 : 1;
if (status.primaryGwSlot == 0) {
if (status.slots[1].state == "present" && status.slots[0].state != "present") {
otherSlot = 2;
} else if (status.slots[0].state == "present" && status.slots[1].state != "present") {
otherSlot = 1;
}
}
switchSimSlotNow(otherSlot);
} else if (row == 3) {
int slot = (status.primaryGwSlot >= 1 && status.primaryGwSlot <= 2)
? status.primaryGwSlot
: status.preferredSlot;
if (status.slots[0].needsPin() || status.slots[0].needsPuk()) {
slot = 1;
} else if (status.slots[1].needsPin() || status.slots[1].needsPuk()) {
slot = 2;
}
openPinOrPukModalForSlot(slot);
} else if (row == 4) {
targetSlot = (status.primaryGwSlot >= 1 && status.primaryGwSlot <= 2)
@@ -1446,14 +1581,18 @@ int runModemApp(int argc, char** argv) {
ussdResponse.clear();
ussdWaitingReply = false;
} else if (radioRowSel == 2) {
bool powerOn = (status.mmPowerState == "on" ||
status.mmState == "registered" ||
status.mmState == "connected" ||
status.mmState == "searching");
bool powerOn = (status.mmPowerState == "on" &&
status.mmState != "disabled" &&
status.mmState != "failed");
setBanner(powerOn ? "Disabling modem..." : "Enabling modem...");
render();
runCaptureCmd(powerOn ? "mmcli -m any -d >/dev/null 2>&1"
: "mmcli -m any -e >/dev/null 2>&1");
if (powerOn) {
runCaptureCmd("mmcli -m any -d >/dev/null 2>&1");
} else {
runCaptureCmd(
"doas -n /usr/libexec/msm-modem-uim-selection boot >/dev/null 2>&1 "
"|| mmcli -m any -e >/dev/null 2>&1");
}
refreshData(false);
runAsyncCmd("sxmo_hook_statusbar.sh modem >/dev/null 2>&1");
} else if (radioRowSel == 3) {
@@ -1494,6 +1633,9 @@ int runModemApp(int argc, char** argv) {
}
};
int autoCheckStage = isPreload ? 0 : 2;
int64_t nextAutoCheckMs = nowMs() + 2500;
if (!isPreload) {
showWindow(openArg);
}
@@ -1841,6 +1983,28 @@ int runModemApp(int argc, char** argv) {
timeoutMs = static_cast<int>(rRem);
}
}
} else if (autoCheckStage < 2) {
int64_t aRem = nextAutoCheckMs - now;
if (aRem <= 0) {
++autoCheckStage;
nextAutoCheckMs = now + 4500;
refreshData(false);
if (status.slots[0].needsPin() || status.slots[0].needsPuk() ||
status.slots[1].needsPin() || status.slots[1].needsPuk() ||
status.mmState == "locked") {
autoCheckStage = 2;
showWindow("unlock");
} else if ((status.slots[0].state == "present" ||
status.slots[1].state == "present") &&
(status.primaryGwSlot == 0 ||
status.mmState == "disabled")) {
runAsyncCmd(
"doas -n /usr/libexec/msm-modem-uim-selection boot >/dev/null "
"2>&1");
}
} else if (timeoutMs < 0 || aRem < timeoutMs) {
timeoutMs = static_cast<int>(aRem);
}
}
struct pollfd pfds[2];
+15 -14
View File
@@ -183,7 +183,14 @@ inline bool sendPluginCommand(const char* name, const std::string& cmdLine) {
return false;
}
std::string fifoPath = getPluginFifoPath(name);
int fd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC);
int fd = -1;
for (int attempt = 0; attempt < 6; ++attempt) {
fd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC);
if (fd >= 0) {
break;
}
::usleep(25000);
}
if (fd < 0) {
return false;
}
@@ -1651,20 +1658,12 @@ inline std::string sendModemUssd(const std::string& input,
if (clean.empty()) return "Empty USSD request.";
int rc = 0;
std::string cmd;
if (isReply && !isUssdCode(clean)) {
cmd = "mmcli -m any --3gpp-ussd-respond=" + shellQuote(clean) + " 2>&1";
} else {
cmd = "mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1; "
"mmcli -m any --3gpp-ussd-initiate=" + shellQuote(clean) + " 2>&1";
}
std::string mode = (isReply && !isUssdCode(clean)) ? "respond" : "initiate";
std::string cmd =
"doas -n /usr/libexec/msm-modem-uim-selection ussd " + mode + " " +
shellQuote(clean) + " 2>&1";
std::string out = trimStr(runCaptureCmd(cmd, &rc));
size_t q1 = out.find('\'');
size_t q2 = out.rfind('\'');
if (q1 != std::string::npos && q2 != std::string::npos && q2 > q1) {
out = out.substr(q1 + 1, q2 - q1 - 1);
}
if (out.empty()) {
out = (rc == 0) ? "USSD request completed." : "USSD request failed.";
}
@@ -1698,7 +1697,9 @@ inline std::string sendModemUssd(const std::string& input,
}
inline void cancelModemUssd() {
runAsyncCmd("mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1");
runAsyncCmd(
"doas -n /usr/libexec/msm-modem-uim-selection ussd cancel >/dev/null 2>&1 "
"|| mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1");
}
} // namespace bananarchy
+107 -24
View File
@@ -816,6 +816,97 @@ write_str_file(const char *path, const char *val)
return n > 0;
}
static int
dunst_window_visible(void)
{
if (!dpy)
return 0;
Window r_ret, p_ret, *children = NULL;
unsigned int nchildren = 0;
int found = 0;
if (!XQueryTree(dpy, root, &r_ret, &p_ret, &children, &nchildren) || !children)
return 0;
for (unsigned int i = 0; i < nchildren; i++) {
XWindowAttributes wa;
if (!XGetWindowAttributes(dpy, children[i], &wa) || wa.map_state != IsViewable)
continue;
XClassHint ch = { NULL, NULL };
if (XGetClassHint(dpy, children[i], &ch)) {
if ((ch.res_class && strcasecmp(ch.res_class, "Dunst") == 0) ||
(ch.res_name && strcasecmp(ch.res_name, "dunst") == 0))
found = 1;
if (ch.res_name)
XFree(ch.res_name);
if (ch.res_class)
XFree(ch.res_class);
if (found)
break;
}
}
XFree(children);
return found;
}
static void
unmap_dunst_windows(void)
{
if (!dpy)
return;
Window r_ret, p_ret, *children = NULL;
unsigned int nchildren = 0;
int unmapped = 0;
if (!XQueryTree(dpy, root, &r_ret, &p_ret, &children, &nchildren) || !children)
return;
for (unsigned int i = 0; i < nchildren; i++) {
XClassHint ch = { NULL, NULL };
if (XGetClassHint(dpy, children[i], &ch)) {
if ((ch.res_class && strcasecmp(ch.res_class, "Dunst") == 0) ||
(ch.res_name && strcasecmp(ch.res_name, "dunst") == 0)) {
XUnmapWindow(dpy, children[i]);
unmapped = 1;
}
if (ch.res_name)
XFree(ch.res_name);
if (ch.res_class)
XFree(ch.res_class);
}
}
XFree(children);
if (unmapped)
XFlush(dpy);
}
static void
clear_all_notifications_now(void)
{
const char *ndir = getenv("SXMO_NOTIFDIR");
char default_dir[256];
if (!ndir || !ndir[0]) {
const char *home = getenv("HOME");
snprintf(default_dir, sizeof(default_dir), "%s/.local/share/sxmo/notifications",
(home && home[0]) ? home : "/home/user");
ndir = default_dir;
}
DIR *d = opendir(ndir);
if (d) {
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
if (ent->d_name[0] == '.')
continue;
char fpath[512];
snprintf(fpath, sizeof(fpath), "%s/%s", ndir, ent->d_name);
unlink(fpath);
}
closedir(d);
}
unmap_dunst_windows();
spawn_detached_sh(
"dunstctl close-all >/dev/null 2>&1; "
"dunstctl history-clear >/dev/null 2>&1; "
"pkill -x dunstify >/dev/null 2>&1; "
"sxmo_hook_statusbar.sh notifications >/dev/null 2>&1");
}
static void
handle_builtin_toggle(const char *toggle_name)
{
@@ -914,27 +1005,7 @@ handle_builtin_toggle(const char *toggle_name)
}
spawn_detached_sh("sxmo_hook_statusbar.sh audio");
} else if (strcmp(toggle_name, "notif_clear_all") == 0) {
const char *ndir = getenv("SXMO_NOTIFDIR");
char default_dir[256];
if (!ndir || !ndir[0]) {
const char *home = getenv("HOME");
snprintf(default_dir, sizeof(default_dir), "%s/.local/share/sxmo/notifications",
(home && home[0]) ? home : "/home/user");
ndir = default_dir;
}
DIR *d = opendir(ndir);
if (d) {
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
if (ent->d_name[0] == '.')
continue;
char fpath[512];
snprintf(fpath, sizeof(fpath), "%s/%s", ndir, ent->d_name);
unlink(fpath);
}
closedir(d);
}
spawn_detached_sh("sxmo_hook_statusbar.sh notifs");
clear_all_notifications_now();
}
}
@@ -1031,7 +1102,10 @@ activate_selection(unsigned int ev_state)
fclose(fp);
}
unlink(fpath);
spawn_detached_sh("sxmo_hook_statusbar.sh notifs");
unmap_dunst_windows();
spawn_detached_sh(
"dunstctl close-all >/dev/null 2>&1; "
"sxmo_hook_statusbar.sh notifications >/dev/null 2>&1");
hide_menu();
menu_mode = MODE_NONE;
if (action_cmd[0] && strcmp(action_cmd, "none") != 0)
@@ -1040,6 +1114,11 @@ activate_selection(unsigned int ev_state)
}
if (strncmp(act, "@toggle:", 8) == 0) {
handle_builtin_toggle(act + 8);
if (strcmp(act + 8, "notif_clear_all") == 0) {
hide_menu();
menu_mode = MODE_NONE;
return;
}
char cur_menu[64];
snprintf(cur_menu, sizeof(cur_menu), "%s", current_builtin_menu);
open_builtin_menu(cur_menu, cur_idx);
@@ -1906,9 +1985,13 @@ open_builtin_menu(const char *menu_name, int keep_idx)
reset_session_defaults(1, "Sys");
append_active_call_entries();
int nnotifs = count_notifications();
int dunst_vis = dunst_window_visible();
if (nnotifs > 0) {
snprintf(label, sizeof(label), "%s Notifications (%d)", ICON_BEL, nnotifs);
add_menu_item(label, "@menu:notifications", 1);
} else if (dunst_vis) {
snprintf(label, sizeof(label), "%s Clear Notification", ICON_BEL);
add_menu_item(label, "@toggle:notif_clear_all", 0);
}
const char *sh = getenv("SHELL");
if (!sh || !sh[0])
@@ -2105,9 +2188,9 @@ open_builtin_menu(const char *menu_name, int keep_idx)
add_menu_item(label, "@close", 0);
} else if (strcmp(menu_name, "notifications") == 0) {
reset_session_defaults(1, "Notifs");
populate_notifications_menu();
snprintf(label, sizeof(label), "%s Clear All Notifications", ICON_CLS);
add_menu_item(label, "@toggle:notif_clear_all", 1);
add_menu_item(label, "@toggle:notif_clear_all", 0);
populate_notifications_menu();
snprintf(label, sizeof(label), "%s System Menu", ICON_MNU);
add_menu_item(label, "@menu:sys", 1);
snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS);