diff --git a/MANUAL.md b/MANUAL.md index e1dd8fc..dabf82c 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -52,6 +52,7 @@ sudo ./flash-edl.sh The script will: 1. Temporarily pause host `ModemManager` so it does not interfere with `05c6:9008`. 2. Wait for the phone to appear in Qualcomm EDL mode (`05c6:9008`): + - **From Bananarchy Linux**: open the **Power** menu (`End / Power` key or `System menu -> Power`) and select **Reboot to EDL** (or run `sxmo_power.sh edl`). - **Button combo**: power off the phone, hold **D-Pad up + D-Pad down**, and plug in the USB cable. - **EDL cable**: hold the EDL cable button while plugging in USB. - **From ADB / Fastboot**: the script automatically sends `adb reboot edl` or `fastboot oem edl` if detected. @@ -63,7 +64,7 @@ The script will: ### Method B: Fastboot flashing (once `lk2nd` is installed) -1. Hold **D-Pad up** while powering on the phone to enter `lk2nd` Fastboot mode. +1. Enter `lk2nd` Fastboot mode either by selecting **Reboot to Fastboot** from the **Power** menu (`sxmo_power.sh fastboot`) or by holding **D-Pad up** while powering on the phone. 2. Flash the system image to `userdata`: ```bash fastboot flash userdata ./images/bananarchy-nokia-argon.img @@ -128,7 +129,7 @@ All fourteen core phone overlays are compiled as shared-object plugins (`/usr/li 1. **`DMenu` (`dmenu.so` / `dmenu` / `xdmenu`) — Zero-Lag System & Context Menus**: - **Open / Close**: Press **Left softkey (`Menu`)** for the **System menu** (`sys`) or **Right softkey (`Back`)** inside an application for its **Context menu** (`ctx`); press the same softkey or `Escape` to close. - - **Built-in Native Menus & `< 0.5 ms` In-Place Transitions**: Renders `sys`, `ctx`, `applications` (including **Nano**, **Bantam**, **Files**, **Camera**, **Dialer**, **Contacts**), `scripts`, `modem`, `config`, `power` (with Polkit + `elogind` multi-session **Reboot** and **Poweroff** via `sxmo_power.sh`), `audio`, `notifications`, `wm`, `timezone`, `games`, `st`, `nano`, `sthotkeys`, and confirmation prompts directly inside the resident `240x296` plugin window without spawning shell scripts or new X11 connections. Submenu transitions (`@menu:...`) and hardware toggles (`@toggle:flashlight`, `@toggle:brightness`, `@toggle:autosuspend`, `@toggle:autocallscreenoff`, `@toggle:bluetooth`, `@toggle:dnd`) update in place in `< 0.5 ms`. + - **Built-in Native Menus & `< 0.5 ms` In-Place Transitions**: Renders `sys`, `ctx`, `applications` (including **Nano**, **Bantam**, **Files**, **Camera**, **Dialer**, **Contacts**), `scripts`, `modem`, `config`, `power` (with **Reboot**, **Reboot to Fastboot**, **Reboot to EDL**, and **Poweroff** via `sxmo_power.sh`), `audio`, `notifications`, `wm`, `timezone`, `games`, `st`, `nano`, `sthotkeys`, and confirmation prompts directly inside the resident `240x296` plugin window without spawning shell scripts or new X11 connections. Submenu transitions (`@menu:...`) and hardware toggles (`@toggle:flashlight`, `@toggle:brightness`, `@toggle:autosuspend`, `@toggle:autocallscreenoff`, `@toggle:bluetooth`, `@toggle:dnd`) update in place in `< 0.5 ms`. - **Client Socket Mode (`/tmp/dwm_plugin_dmenu.sock`)**: Any script piping items into `/usr/bin/dmenu` or `sxmo_dmenu.sh` streams its item list over the Unix domain socket to the resident `dmenu.so` daemon and receives the user's selection instantaneously, while `/tmp/dwm_plugin_dmenu.active` provides zero-overhead `sxmo_dmenu.sh isopen` checks. 2. **`XDrawer` (`xdrawer.so` / `xdrawer`) — Global Phone Controls**: @@ -160,8 +161,9 @@ All fourteen core phone overlays are compiled as shared-object plugins (`/usr/li 6. **`Contacts` (`contacts.so` / `xcontacts` / `sxmo_contactmenu.sh`) — Full-Featured Phonebook**: - **Open**: On the empty desktop, press the **Right softkey (`Names`)**, or select **Contacts** from the Sxmo menu. - - **Phonebook List & Live Search**: Typing `0`–`9` filters contacts in real time by T9 name or phone number; `Up`/`Down` scrolls; **Call (green)** immediately calls the selected contact; **Center `OK` (`Return`)** opens the contact's details & actions card; **Left softkey (`Options`)** opens the options menu (**New Contact**, **Add from Recent**, **Call Contact**, **Send SMS**, **Edit Contact**, **Delete Contact**). - - **Contact Details View**: Provides one-click actions for **Call Contact**, **Send SMS**, **Read Messages**, **Open in Dialer**, **Edit Contact**, and **Delete Contact**. + - **Phonebook List & Live Search**: Typing `0`–`9` filters contacts in real time by T9 name or phone number; `Up`/`Down` scrolls; **Call (green)** immediately calls the selected contact; **Center `OK` (`Return`)** opens the contact's details & actions card; **Left softkey (`Options`)** opens the options menu (**New Contact**, **Add from Recent**, **Import from SIM**, **Export to SIM**, **Call Contact**, **Send SMS**, **Edit Contact**, **Delete Contact**). + - **SIM Card Import & Export (Dual-SIM Aware)**: Select **Import from SIM** or **Export to SIM** in the **Options** menu (or **Copy to SIM** inside a contact's detail view) to transfer contacts between `~/.config/sxmo/contacts.tsv` and the SIM card phonebook (`EF_ADN` / `SM`). If both **SIM 1** and **SIM 2** are installed, Contacts prompts you to choose between **SIM 1** and **SIM 2**; if only one SIM is installed, it uses that slot automatically. + - **Contact Details View**: Provides one-click actions for **Call Contact**, **Send SMS**, **Read Messages**, **Open in Dialer**, **Copy to SIM**, **Edit Contact**, and **Delete Contact**. - **Built-in Contact Editor**: Supports adding new contacts or editing existing contacts directly in the `240x320` UI. In the **Name** field, keypad keys `0`–`9` provide multi-tap character entry (`#` cycles case mode `[Abc]` -> `[ABC]` -> `[abc]` -> `[123]`, `*` inserts space). In the **Number** field, hold `0` or press `Right` to insert `+`, or select **Use Recent Number** to pick from recent unknown callers in `modemlog.tsv`. Press **Left softkey (`Save`)** or select **Save Contact** to write directly to `~/.config/sxmo/contacts.tsv`. 7. **`SMS Manager / Editor` (`sms.so` / `xsms` / `sxmo_modemtext.sh`) — Conversations & Composer**: diff --git a/README.md b/README.md index c475ed8..53d20c5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Instead of spawning slow shell scripts for everyday phone tasks, Bananarchy runs - **System & context menus (`dmenu`)**: Instant full-screen 10-line vertical menus with live T9 filtering and built-in submenus. - **Global controls drawer (`xdrawer`)**: Press **both softkeys together** from anywhere to adjust speaker/earpiece volume, ringer mode, flashlight, mic/sound mute, screen brightness, and keypad backlight. - **Phone dialer (`xdialer`), call log (`xcalllog`) & call screen (`xcallscreen`)**: Press any digit on the home screen to start dialing with live T9 contact matching, run carrier USSD codes (`*100#`, etc.) interactively, or type `*#06#` to inspect both IMEIs. Incoming and active calls open a dedicated call screen with earpiece/speaker routing, microphone mute, DTMF keypad, and one-key call recording (`~/Recordings/`). -- **Contacts (`xcontacts`) & SMS manager (`xsms`)**: Full phonebook editor and threaded conversation/draft SMS manager with multi-tap keypad text entry. +- **Contacts (`xcontacts`) & SMS manager (`xsms`)**: Full phonebook editor with SIM card contact import/export (supporting SIM 1 / SIM 2 selection on dual-SIM setups) and threaded conversation/draft SMS manager with multi-tap keypad text entry. - **Modem & Dual-SIM manager (`xmodem`)**: 4-tab cellular control center (`1:SIMs`, `2:Radio`, `3:AT`, `4:Log`) supporting physical SIM 1 / SIM 2 active slot switching, SIM PIN unlock and PUK unblock wizards, 2G/3G/4G mode selection, interactive USSD sessions, and a live AT command console. - **Networks & Bluetooth manager (`xnetworks`)**: 3-tab connectivity manager (`1:Wi-Fi`, `2:Cell/APN`, `3:Bluetooth`) for Wi-Fi networks, WPA2 password entry, hotspot/USB tethering, mobile data & APN editing, and Bluetooth pairing. - **File manager (`xfiles`) & camera (`xcamera`)**: Graphical directory browser with built-in text and image viewers, plus a live V4L2 camera viewfinder with digital zoom, flash/torch control, self-timer, and photo gallery. diff --git a/overlay/rootfs/etc/doas.d/50-sxmo.conf b/overlay/rootfs/etc/doas.d/50-sxmo.conf index 417ce70..02b7839 100644 --- a/overlay/rootfs/etc/doas.d/50-sxmo.conf +++ b/overlay/rootfs/etc/doas.d/50-sxmo.conf @@ -30,5 +30,8 @@ permit nopass :wheel as root cmd rtcwake permit nopass :wheel as root cmd tee args -a /sys/power/wake_lock permit nopass :wheel as root cmd tee args -a /sys/power/wake_unlock permit nopass :wheel as root cmd tee args /sys/power/wakeup_count +permit nopass :wheel as root cmd tee args /sys/module/leds_msm8905_torch/parameters/next_reboot_mode +permit nopass :wheel as root cmd /usr/bin/sxmo_power.sh +permit nopass :wheel as root cmd sxmo_power.sh permit nopass :wheel as root cmd rc-service args hexagonrpcd-adsp-sensorspd stop permit nopass :wheel as root cmd systemctl args stop hexagonrpcd-adsp-sensorspd diff --git a/overlay/rootfs/usr/bin/sxmo_appmenu.sh b/overlay/rootfs/usr/bin/sxmo_appmenu.sh index abadf47..ea0e584 100755 --- a/overlay/rootfs/usr/bin/sxmo_appmenu.sh +++ b/overlay/rootfs/usr/bin/sxmo_appmenu.sh @@ -210,6 +210,8 @@ getprogchoices() { $icon_lck Lock (Screen off)^0^sxmo_state.sh set screenoff $icon_out Logout^0^confirm Logout && sxmo_power.sh logout $icon_rld Reboot^0^confirm Reboot && sxmo_power.sh reboot +$icon_rld Reboot to Fastboot^0^confirm Fastboot && sxmo_power.sh fastboot +$icon_rld Reboot to EDL^0^confirm EDL && sxmo_power.sh edl $icon_pwr Poweroff^0^confirm Poweroff && sxmo_power.sh poweroff" ;; modem) diff --git a/overlay/rootfs/usr/bin/sxmo_power.sh b/overlay/rootfs/usr/bin/sxmo_power.sh index 38e71e9..94731ee 100755 --- a/overlay/rootfs/usr/bin/sxmo_power.sh +++ b/overlay/rootfs/usr/bin/sxmo_power.sh @@ -8,7 +8,7 @@ export PATH="/sbin:/usr/sbin:$PATH" . sxmo_common.sh usage() { - printf "usage: %s [reboot|poweroff|logout|togglewm]\n" "$(basename "$0")" + printf "usage: %s [reboot|fastboot|bootloader|edl|poweroff|logout|togglewm]\n" "$(basename "$0")" } run_polkit_login1() { @@ -89,9 +89,29 @@ run_priv_power() { return 1 } +set_kernel_reboot_mode() { + rmode="$1" + param_path="/sys/module/leds_msm8905_torch/parameters/next_reboot_mode" + if [ -e "$param_path" ]; then + printf "%s\n" "$rmode" | /usr/bin/doas tee "$param_path" >/dev/null 2>&1 || \ + printf "%s\n" "$rmode" > "$param_path" 2>/dev/null || true + fi +} + +force_reboot_cmd() { + rmode="$1" + /usr/bin/doas python3 -c " +import ctypes, os +os.sync() +libc = ctypes.CDLL(None) +libc.syscall(88, 0xfee1dead, 672274793, 0xa1b2c3d4, b'$rmode') +" >/dev/null 2>&1 || /usr/bin/doas /sbin/reboot -f >/dev/null 2>&1 || true +} + case "$1" in reboot) sync + set_kernel_reboot_mode "" sxmo_hook_power.sh reboot || true (sleep 4; [ -u /usr/bin/doas ] && /usr/bin/doas /sbin/reboot -f >/dev/null 2>&1 || [ -u /usr/bin/sudo ] && /usr/bin/sudo -n /sbin/reboot -f >/dev/null 2>&1) & if ! run_priv_power reboot; then @@ -102,6 +122,26 @@ case "$1" in fi sxmo_jobs.sh stop all >/dev/null 2>&1 || true ;; + fastboot|bootloader) + sync + set_kernel_reboot_mode "bootloader" + sxmo_hook_power.sh reboot || true + (sleep 4; force_reboot_cmd "bootloader") & + if ! run_priv_power reboot; then + force_reboot_cmd "bootloader" + fi + sxmo_jobs.sh stop all >/dev/null 2>&1 || true + ;; + edl) + sync + set_kernel_reboot_mode "edl" + sxmo_hook_power.sh reboot || true + (sleep 4; force_reboot_cmd "edl") & + if ! run_priv_power reboot; then + force_reboot_cmd "edl" + fi + sxmo_jobs.sh stop all >/dev/null 2>&1 || true + ;; poweroff) sync sxmo_hook_power.sh poweroff || true diff --git a/overlay/rootfs/usr/lib/dwm/plugins/contacts.so b/overlay/rootfs/usr/lib/dwm/plugins/contacts.so index 799f9b4..b0ca5a8 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/contacts.so and b/overlay/rootfs/usr/lib/dwm/plugins/contacts.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so b/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so index 7c9d4fd..3901e1b 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so and b/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so differ diff --git a/overlay/rootfs/usr/libexec/msm-modem-uim-selection b/overlay/rootfs/usr/libexec/msm-modem-uim-selection index 17dd0cd..f943191 100755 --- a/overlay/rootfs/usr/libexec/msm-modem-uim-selection +++ b/overlay/rootfs/usr/libexec/msm-modem-uim-selection @@ -558,8 +558,331 @@ case "$cmd" in exit 1 ;; + sim-contacts-import) + req_slot="${2:-auto}" + if ! wait_for_qmicli_modem 3; then + echo "ERROR: No modem available" + exit 1 + fi + QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)" + if [ -z "$QMI_CARDS" ]; then + echo "ERROR: Cannot read SIM card status" + exit 1 + fi + eval "$(parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT")" + + target_slot="" + target_aid="" + if [ "$req_slot" = "1" ]; then + if [ "$SLOT1_STATE" != "present" ]; then + echo "ERROR: SIM 1 is not inserted" + exit 1 + fi + target_slot="1" + target_aid="$SLOT1_AID" + elif [ "$req_slot" = "2" ]; then + if [ "$SLOT2_STATE" != "present" ]; then + echo "ERROR: SIM 2 is not inserted" + exit 1 + fi + target_slot="2" + target_aid="$SLOT2_AID" + else + if [ "$PRIMARY_GW_SLOT" = "1" ] && [ "$SLOT1_STATE" = "present" ]; then + target_slot="1" + target_aid="$SLOT1_AID" + elif [ "$PRIMARY_GW_SLOT" = "2" ] && [ "$SLOT2_STATE" = "present" ]; then + target_slot="2" + target_aid="$SLOT2_AID" + elif [ "$SLOT1_STATE" = "present" ]; then + target_slot="1" + target_aid="$SLOT1_AID" + elif [ "$SLOT2_STATE" = "present" ]; then + target_slot="2" + target_aid="$SLOT2_AID" + else + echo "ERROR: No SIM card detected" + exit 1 + fi + fi + + orig_gw_slot="$PRIMARY_GW_SLOT" + orig_gw_aid="" + if [ "$orig_gw_slot" = "1" ]; then + orig_gw_aid="$SLOT1_AID" + elif [ "$orig_gw_slot" = "2" ]; then + orig_gw_aid="$SLOT2_AID" + fi + switched_gw=0 + restore_gw_slot() { + if [ "$switched_gw" = "1" ]; then + $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true + if [ -n "$orig_gw_slot" ] && [ "$orig_gw_slot" != "0" ] && [ -n "$orig_gw_aid" ]; then + $QMICLI_MODEM --uim-change-provisioning-session="slot=$orig_gw_slot,activate=yes,session-type=primary-gw-provisioning,aid=$orig_gw_aid" >/dev/null 2>&1 || true + fi + switched_gw=0 + fi + } + trap restore_gw_slot EXIT INT TERM + + if [ "$PRIMARY_GW_SLOT" != "$target_slot" ] && [ -n "$target_aid" ]; then + if [ "$PRIMARY_GW_SLOT" != "0" ]; then + $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true + fi + if $QMICLI_MODEM --uim-change-provisioning-session="slot=$target_slot,activate=yes,session-type=primary-gw-provisioning,aid=$target_aid" >/dev/null 2>&1; then + switched_gw=1 + sleep 1 + fi + fi + + tmp_contacts="/tmp/sim_import_$$.tsv" + rm -f "$tmp_contacts" + : > "$tmp_contacts" + + # Method 1: Standard 3GPP AT+CPBS="SM" / AT+CPBR + at_info="$("$0" at auto 'AT+CMEE=2;+CSCS="GSM";+CPBS="SM";+CPBR=?' 2>/dev/null || true)" + max_idx="$(printf "%s\n" "$at_info" | sed -n 's/.*+CPBR:[[:space:]]*([0-9]*-\([0-9]*\)).*/\1/p' | head -n1)" + if [ -n "$max_idx" ] && [ "$max_idx" -gt 0 ] 2>/dev/null; then + if [ "$max_idx" -gt 500 ]; then + max_idx=500 + fi + start_idx=1 + while [ "$start_idx" -le "$max_idx" ]; do + end_idx=$((start_idx + 49)) + if [ "$end_idx" -gt "$max_idx" ]; then + end_idx="$max_idx" + fi + chunk="$("$0" at auto "AT+CPBR=${start_idx},${end_idx}" 2>/dev/null || true)" + if [ -n "$chunk" ]; then + printf "%s\n" "$chunk" | awk ' + /^\+CPBR:[[:space:]]*[0-9]+,/ { + line = $0 + sub(/^\+CPBR:[[:space:]]*[0-9]+,[[:space:]]*"/, "", line) + q1 = index(line, "\"") + if (q1 <= 0) next + num = substr(line, 1, q1 - 1) + rest = substr(line, q1 + 1) + sub(/^[[:space:]]*,[[:space:]]*/, "", rest) + c2 = index(rest, ",") + if (c2 <= 0) next + ton = substr(rest, 1, c2 - 1) + 0 + name = substr(rest, c2 + 1) + sub(/^[[:space:]]*"/, "", name) + sub(/"[[:space:]]*$/, "", name) + gsub(/[\t\r\n]/, " ", name) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", name) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", num) + if (num == "") next + if (ton == 145 && substr(num, 1, 1) != "+") { + num = "+" num + } + if (name == "") name = num + printf "%s\t%s\n", num, name + } + ' >> "$tmp_contacts" + fi + start_idx=$((end_idx + 1)) + done + fi + + # Method 2: Direct QMI UIM EF_ADN (3F00:7F10:6F3A) record fallback + if [ ! -s "$tmp_contacts" ]; then + adn_attr="$($QMICLI_MODEM --uim-get-file-attributes=0x3F00,0x7F10,0x6F3A 2>/dev/null || true)" + reclen="$(printf "%s\n" "$adn_attr" | awk -F"'" '/Record length:/ {print $2 + 0; exit}')" + reccount="$(printf "%s\n" "$adn_attr" | awk -F"'" '/Record count:/ {print $2 + 0; exit}')" + if [ -n "$reclen" ] && [ -n "$reccount" ] && [ "$reclen" -ge 14 ] 2>/dev/null && [ "$reccount" -ge 1 ] 2>/dev/null; then + if [ "$reccount" -gt 250 ]; then + reccount=250 + fi + rec=1 + while [ "$rec" -le "$reccount" ]; do + raw_rec="$($QMICLI_MODEM --uim-read-record="file=0x3F00,0x7F10,0x6F3A,record-number=${rec},record-length=${reclen}" 2>/dev/null || true)" + if [ -n "$raw_rec" ]; then + printf "%s\n" "$raw_rec" | awk -v rlen="$reclen" ' + function hex2val(h, i, c, v, res) { + res = 0 + h = toupper(h) + for (i = 1; i <= length(h); i++) { + c = substr(h, i, 1) + v = index("0123456789ABCDEF", c) - 1 + if (v < 0) return -1 + res = res * 16 + v + } + return res + } + function bcd_digit(n) { + if (n >= 0 && n <= 9) return "" n + if (n == 10) return "*" + if (n == 11) return "#" + return "" + } + /^[[:space:]]*([0-9A-Fa-f]{2}:)+[0-9A-Fa-f]{2}[[:space:]]*$/ { + line = $0 + gsub(/^[[:space:]]+|[[:space:]]+$/, "", line) + n = split(line, hex, ":") + if (n < 14) next + alpha_len = n - 14 + bcd_len = hex2val(hex[alpha_len + 1]) + ton_npi = hex2val(hex[alpha_len + 2]) + if (bcd_len <= 0 || bcd_len > 11 || bcd_len == 255) next + num = "" + if (int(ton_npi / 16) % 8 == 1) { + num = "+" + } + bcd_bytes = bcd_len - 1 + if (bcd_bytes > 10) bcd_bytes = 10 + for (i = 1; i <= bcd_bytes; i++) { + b = hex2val(hex[alpha_len + 2 + i]) + if (b < 0 || b == 255) break + lo = b % 16 + hi = int(b / 16) + d1 = bcd_digit(lo) + if (d1 == "") break + num = num d1 + d2 = bcd_digit(hi) + if (d2 == "") break + num = num d2 + } + if (num == "" || num == "+") next + name = "" + for (i = 1; i <= alpha_len; i++) { + b = hex2val(hex[i]) + if (b < 0 || b == 255) break + if (b >= 32 && b < 127 && b != 9) { + name = name sprintf("%c", b) + } + } + gsub(/^[[:space:]]+|[[:space:]]+$/, "", name) + if (name == "") name = num + printf "%s\t%s\n", num, name + } + ' >> "$tmp_contacts" + fi + rec=$((rec + 1)) + done + fi + fi + + cat "$tmp_contacts" + rm -f "$tmp_contacts" + exit 0 + ;; + + sim-contacts-export) + req_slot="${2:-auto}" + src_tsv="${3:-/home/user/.config/sxmo/contacts.tsv}" + if [ ! -r "$src_tsv" ]; then + echo "ERROR: Contacts file not found" + exit 1 + fi + if ! wait_for_qmicli_modem 3; then + echo "ERROR: No modem available" + exit 1 + fi + QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)" + if [ -z "$QMI_CARDS" ]; then + echo "ERROR: Cannot read SIM card status" + exit 1 + fi + eval "$(parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT")" + + target_slot="" + target_aid="" + if [ "$req_slot" = "1" ]; then + if [ "$SLOT1_STATE" != "present" ]; then + echo "ERROR: SIM 1 is not inserted" + exit 1 + fi + target_slot="1" + target_aid="$SLOT1_AID" + elif [ "$req_slot" = "2" ]; then + if [ "$SLOT2_STATE" != "present" ]; then + echo "ERROR: SIM 2 is not inserted" + exit 1 + fi + target_slot="2" + target_aid="$SLOT2_AID" + else + if [ "$PRIMARY_GW_SLOT" = "1" ] && [ "$SLOT1_STATE" = "present" ]; then + target_slot="1" + target_aid="$SLOT1_AID" + elif [ "$PRIMARY_GW_SLOT" = "2" ] && [ "$SLOT2_STATE" = "present" ]; then + target_slot="2" + target_aid="$SLOT2_AID" + elif [ "$SLOT1_STATE" = "present" ]; then + target_slot="1" + target_aid="$SLOT1_AID" + elif [ "$SLOT2_STATE" = "present" ]; then + target_slot="2" + target_aid="$SLOT2_AID" + else + echo "ERROR: No SIM card detected" + exit 1 + fi + fi + + orig_gw_slot="$PRIMARY_GW_SLOT" + orig_gw_aid="" + if [ "$orig_gw_slot" = "1" ]; then + orig_gw_aid="$SLOT1_AID" + elif [ "$orig_gw_slot" = "2" ]; then + orig_gw_aid="$SLOT2_AID" + fi + switched_gw=0 + restore_gw_slot_exp() { + if [ "$switched_gw" = "1" ]; then + $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true + if [ -n "$orig_gw_slot" ] && [ "$orig_gw_slot" != "0" ] && [ -n "$orig_gw_aid" ]; then + $QMICLI_MODEM --uim-change-provisioning-session="slot=$orig_gw_slot,activate=yes,session-type=primary-gw-provisioning,aid=$orig_gw_aid" >/dev/null 2>&1 || true + fi + switched_gw=0 + fi + } + trap restore_gw_slot_exp EXIT INT TERM + + if [ "$PRIMARY_GW_SLOT" != "$target_slot" ] && [ -n "$target_aid" ]; then + if [ "$PRIMARY_GW_SLOT" != "0" ]; then + $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true + fi + if $QMICLI_MODEM --uim-change-provisioning-session="slot=$target_slot,activate=yes,session-type=primary-gw-provisioning,aid=$target_aid" >/dev/null 2>&1; then + switched_gw=1 + sleep 1 + fi + fi + + at_info="$("$0" at auto 'AT+CMEE=2;+CSCS="GSM";+CPBS="SM";+CPBR=?' 2>/dev/null || true)" + if printf "%s\n" "$at_info" | grep -qE "ERROR"; then + echo "ERROR: SIM $target_slot phonebook not ready" + exit 1 + fi + tlen="$(printf "%s\n" "$at_info" | sed -n 's/.*+CPBR:[[:space:]]*([0-9]*-[0-9]*),[0-9]*,\([0-9]*\).*/\1/p' | head -n1)" + if [ -z "$tlen" ] || [ "$tlen" -le 0 ] 2>/dev/null; then + tlen=14 + fi + + exported=0 + while IFS="$(printf '\t')" read -r raw_num raw_name; do + num="$(printf "%s" "$raw_num" | tr -cd '+0-9*#')" + name="$(printf "%s" "$raw_name" | tr -d '"\r\n\t\\' | cut -c1-"$tlen")" + [ -n "$num" ] || continue + [ -n "$name" ] || name="$(printf "%s" "$num" | cut -c1-"$tlen")" + ton=129 + case "$num" in + +*) ton=145 ;; + esac + res="$("$0" at auto "AT+CPBW=,\"${num}\",${ton},\"${name}\"" 2>/dev/null || true)" + if printf "%s\n" "$res" | grep -q "OK"; then + exported=$((exported + 1)) + fi + done < "$src_tsv" + + echo "EXPORTED=${exported}" + echo "SLOT=${target_slot}" + exit 0 + ;; + *) - echo "Usage: $0 [boot|status|switch <1|2>|verify-pin |unblock-puk |set-pin-lock |change-pin |imei|usb-tether |at ]" >&2 + echo "Usage: $0 [boot|status|switch <1|2>|verify-pin |unblock-puk |set-pin-lock |change-pin |imei|usb-tether |at |sim-contacts-import [1|2|auto]|sim-contacts-export [1|2|auto] [tsv_file]]" >&2 exit 1 ;; esac diff --git a/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_contextmenu.sh b/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_contextmenu.sh index df4c218..01e9388 100755 --- a/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_contextmenu.sh +++ b/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_contextmenu.sh @@ -91,6 +91,8 @@ case "$WMCLASS" in $icon_lck Lock (Screen off) ^ 0 ^ sxmo_state.sh set screenoff $icon_out Logout ^ 0 ^ confirm Logout && sxmo_power.sh logout $icon_rld Reboot ^ 0 ^ confirm Reboot && sxmo_power.sh reboot + $icon_rld Reboot to Fastboot ^ 0 ^ confirm Fastboot && sxmo_power.sh fastboot + $icon_rld Reboot to EDL ^ 0 ^ confirm EDL && sxmo_power.sh edl $icon_pwr Poweroff ^ 0 ^ confirm Poweroff && sxmo_power.sh poweroff " WINNAME=Power diff --git a/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko b/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko index a023978..9438f0f 100644 Binary files a/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko and b/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko differ diff --git a/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko.zst b/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko.zst index 08775e5..8fd736e 100644 Binary files a/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko.zst and b/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko.zst differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so index 799f9b4..b0ca5a8 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so index 7c9d4fd..3901e1b 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so differ diff --git a/src/dwm-plugins/contacts_plugin.cpp b/src/dwm-plugins/contacts_plugin.cpp index 6a76e9f..7a03f6f 100644 --- a/src/dwm-plugins/contacts_plugin.cpp +++ b/src/dwm-plugins/contacts_plugin.cpp @@ -25,6 +25,13 @@ enum class ViewMode { Edit = 3, PickRecent = 4, ConfirmDelete = 5, + SelectSim = 6, +}; + +enum class SimOp { + ImportAll = 0, + ExportAll = 1, + ExportSingle = 2, }; enum class TextCaseMode { @@ -153,12 +160,19 @@ int runContactsApp(int argc, char** argv) { ViewMode mode = ViewMode::List; ViewMode prevModeBeforeEdit = ViewMode::List; + ViewMode prevModeBeforeSim = ViewMode::List; + SimOp pendingSimOp = SimOp::ImportAll; std::string searchQuery; int selected = 0; int detailSelected = 0; int optionSelected = 0; int confirmSelected = 0; int recentSelected = 0; + int simSelected = 0; + int activeGwSlot = 1; + std::string statusMsg; + bool statusIsError = false; + int64_t statusUntilMs = 0; // Editor state bool editIsNew = true; @@ -360,44 +374,66 @@ int runContactsApp(int argc, char** argv) { if (mode == ViewMode::Options) { // Popup options menu overlay - const char* optLabels[6] = { + const char* optLabels[8] = { "New Contact", "Add from Recent", + "Import from SIM", + "Export to SIM", "Call Contact", "Send SMS", "Edit Contact", "Delete Contact" }; - const char* optIcons[6] = { + const char* optIcons[8] = { kIconPlus, kIconClock, + kIconSim, + kIconSim, kIconPhone, kIconSms, kIconEdit, kIconTrash }; - int optCount = filtered.empty() ? 2 : 6; + int optCount = filtered.empty() ? (book.savedContacts().empty() ? 3 : 4) : 8; + constexpr int kMaxOptVis = 6; + int visCount = std::min(optCount, kMaxOptVis); + int optScroll = 0; + if (optionSelected >= visCount) { + optScroll = optionSelected - visCount + 1; + } + if (optScroll > std::max(0, optCount - visCount)) { + optScroll = std::max(0, optCount - visCount); + } int menuRowH = 34; - int menuH = optCount * menuRowH + 4; + int menuH = visCount * menuRowH + 4; int menuY = kWinHeight - kSoftbarH - menuH; XSetForeground(dpy, gc, theme.spec.bgFooter); XFillRectangle(dpy, pix, gc, 8, menuY, kWinWidth - 16, menuH); XSetForeground(dpy, gc, theme.spec.selBg); XDrawRectangle(dpy, pix, gc, 8, menuY, kWinWidth - 17, menuH - 1); - for (int i = 0; i < optCount; ++i) { + for (int i = 0; i < visCount; ++i) { + int idx = optScroll + i; + if (idx >= optCount) break; int ry = menuY + 2 + i * menuRowH; - bool isSel = (i == optionSelected); + bool isSel = (idx == optionSelected); if (isSel) { XSetForeground(dpy, gc, theme.spec.selBg); XFillRectangle(dpy, pix, gc, 10, ry, kWinWidth - 20, menuRowH); XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 10, ry, 4, menuRowH); } - XftColor* icCol = (i == 5) ? &colRed : (isSel ? &colYellow : &colGreen); - drawPango(layoutIcon, icCol, 18, ry, menuRowH, optIcons[i]); + XftColor* icCol = (idx == 7) ? &colRed : (isSel ? &colYellow : &colGreen); + drawPango(layoutIcon, icCol, 18, ry, menuRowH, optIcons[idx]); drawPango(layoutMain, isSel ? &colWhite : &colLight, 42, ry, menuRowH, - optLabels[i], 180); + optLabels[idx], 180); + } + if (optCount > visCount) { + int sbH = std::max(14, ((menuH - 4) * visCount) / optCount); + int sbY = menuY + 2 + + (((menuH - 4) - sbH) * optScroll) / (optCount - visCount); + XSetForeground(dpy, gc, theme.spec.divider); + XFillRectangle(dpy, pix, gc, kWinWidth - 12, sbY, 2, sbH); } drawSoftbar("Select", "OK", "Back", &colYellow); } else { @@ -421,23 +457,25 @@ int runContactsApp(int argc, char** argv) { drawPango(layoutMain, &colWhite, 34, 4, 26, c.name, 198); drawPango(layoutSmall, &colYellow, 34, 30, 20, c.number, 198); - const char* actLabels[6] = { + const char* actLabels[7] = { "Call Contact", "Send SMS", "Read Messages", "Open in Dialer", + "Copy to SIM", "Edit Contact", "Delete Contact" }; - const char* actIcons[6] = { + const char* actIcons[7] = { kIconPhone, kIconSms, kIconList, kIconPhoneLog, + kIconSim, kIconEdit, kIconTrash }; - constexpr int kActCount = 6; + constexpr int kActCount = 7; constexpr int kActVis = 5; int actListY = kCardH; int actListH = kWinHeight - kCardH - kSoftbarH; @@ -462,10 +500,10 @@ int runContactsApp(int argc, char** argv) { XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH); } XftColor* icCol = - (idx == 5) ? &colRed : (isSel ? &colYellow : &colGreen); + (idx == 6) ? &colRed : (isSel ? &colYellow : &colGreen); drawPango(layoutIcon, icCol, 10, ry, rowH, actIcons[idx]); drawPango(layoutMain, - (idx == 5 && !isSel) ? &colRed : (isSel ? &colWhite : &colLight), + (idx == 6 && !isSel) ? &colRed : (isSel ? &colWhite : &colLight), 34, ry, rowH, actLabels[idx], 196); } @@ -608,6 +646,57 @@ int runContactsApp(int argc, char** argv) { } drawSoftbar("Delete", "Select", "Cancel", &colRed); + } else if (mode == ViewMode::SelectSim) { + XSetForeground(dpy, gc, theme.spec.bgHeader); + XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH); + XSetForeground(dpy, gc, theme.spec.border); + XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1); + drawPango(layoutIcon, &colYellow, 8, 0, kHeaderH, kIconSim); + const char* simTitle = + (pendingSimOp == SimOp::ImportAll) + ? "Import from SIM" + : ((pendingSimOp == SimOp::ExportSingle) ? "Copy to SIM" + : "Export to SIM"); + drawPango(layoutMain, &colWhite, 32, 0, kHeaderH, simTitle, 200); + + drawPango(layoutSmall, &colMuted, 12, 48, 22, "Select SIM card slot:", + 216); + + for (int i = 0; i < 2; ++i) { + int slotNum = i + 1; + int ry = 78 + i * 54; + bool isSel = (i == simSelected); + XSetForeground(dpy, gc, isSel ? theme.spec.selBg : theme.spec.bgButton); + XFillRectangle(dpy, pix, gc, 8, ry, kWinWidth - 16, 46); + if (isSel) { + XSetForeground(dpy, gc, theme.spec.accent); + XFillRectangle(dpy, pix, gc, 8, ry, 4, 46); + } + drawPango(layoutIcon, isSel ? &colYellow : &colGreen, 16, ry, 46, + kIconSim); + std::string label = + std::string("SIM ") + std::to_string(slotNum) + + ((activeGwSlot == slotNum) ? " [Active]" : ""); + drawPango(layoutMain, isSel ? &colWhite : &colLight, 42, ry, 46, label, + 182); + } + + drawSoftbar("Select", "OK", "Cancel", &colGreen); + } + + if (!statusMsg.empty() && + (mode == ViewMode::List || mode == ViewMode::Detail)) { + int bannerH = 26; + int bannerY = kWinHeight - kSoftbarH - bannerH; + XSetForeground(dpy, gc, theme.spec.bgFooter); + XFillRectangle(dpy, pix, gc, 0, bannerY, kWinWidth, bannerH); + XSetForeground(dpy, gc, + statusIsError ? colRed.pixel : theme.spec.accent); + XFillRectangle(dpy, pix, gc, 0, bannerY, kWinWidth, 1); + drawPango(layoutIcon, statusIsError ? &colRed : &colYellow, 8, bannerY, + bannerH, kIconSim); + drawPango(layoutSmall, statusIsError ? &colRed : &colWhite, 30, bannerY, + bannerH, statusMsg, 204); } if (visible) { @@ -620,7 +709,7 @@ int runContactsApp(int argc, char** argv) { drawPango(layoutIcon, &colGreen, 0, 0, 20, std::string(kIconBook) + kIconUser + kIconSearch + kIconPlus + kIconEdit + kIconTrash + kIconSms + kIconList + kIconCheck + - kIconPhone + kIconClock); + kIconPhone + kIconClock + kIconSim); render(); XSync(dpy, False); @@ -810,6 +899,159 @@ int runContactsApp(int argc, char** argv) { lastTapMs = now; }; + auto setStatus = [&](const std::string& msg, bool isErr, int durationMs = 3500) { + statusMsg = msg; + statusIsError = isErr; + statusUntilMs = (durationMs > 0) ? (nowMs() + durationMs) : 0; + }; + + auto performSimOperation = [&](int slotNum) { + mode = (pendingSimOp == SimOp::ExportSingle) ? ViewMode::Detail + : ViewMode::List; + if (pendingSimOp == SimOp::ImportAll) { + setStatus("Reading SIM " + std::to_string(slotNum) + " contacts...", + false, 0); + render(); + int rc = 0; + std::string out = runCaptureCmd( + "doas /usr/libexec/msm-modem-uim-selection sim-contacts-import " + + std::to_string(slotNum) + " 2>&1", + &rc); + if (out.rfind("ERROR:", 0) == 0) { + std::string err = trimStr(out.substr(6)); + setStatus(err.empty() ? "SIM import failed" : err, true); + render(); + return; + } + std::vector> rows; + std::stringstream ss(out); + std::string line; + while (std::getline(ss, line)) { + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + 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()) { + rows.push_back({num, name.empty() ? num : name}); + } + } + if (rows.empty()) { + setStatus("No contacts on SIM " + std::to_string(slotNum), false); + } else { + int imported = book.importContactsBatch(rows); + contactsStamp.checkAndUpdate(getContactsFilePath()); + updateFilter(); + if (imported < 0) { + setStatus("Failed to save imported contacts", true); + } else { + setStatus("Imported " + std::to_string(imported) + " from SIM " + + std::to_string(slotNum), + false); + } + } + render(); + } else { + std::string srcFile = getContactsFilePath(); + std::string tmpSingle; + if (pendingSimOp == SimOp::ExportSingle) { + if (selected < 0 || selected >= static_cast(filtered.size())) { + return; + } + tmpSingle = "/tmp/sim_single_" + std::to_string(::getpid()) + ".tsv"; + std::ofstream tf(tmpSingle, std::ios::trunc); + tf << filtered[selected].number << "\t" << filtered[selected].name + << "\n"; + tf.close(); + srcFile = tmpSingle; + } else if (book.savedContacts().empty()) { + setStatus("Phonebook is empty", true); + render(); + return; + } + setStatus("Writing to SIM " + std::to_string(slotNum) + "...", false, 0); + render(); + int rc = 0; + std::string out = runCaptureCmd( + "doas /usr/libexec/msm-modem-uim-selection sim-contacts-export " + + std::to_string(slotNum) + " " + shellQuote(srcFile) + " 2>&1", + &rc); + if (!tmpSingle.empty()) { + ::unlink(tmpSingle.c_str()); + } + if (out.rfind("ERROR:", 0) == 0) { + std::string err = trimStr(out.substr(6)); + setStatus(err.empty() ? "SIM export failed" : err, true); + render(); + return; + } + int exported = 0; + std::stringstream ss(out); + std::string line; + while (std::getline(ss, line)) { + if (line.rfind("EXPORTED=", 0) == 0) { + exported = std::atoi(line.c_str() + 9); + } + } + if (exported > 0) { + if (pendingSimOp == SimOp::ExportSingle) { + setStatus("Copied contact to SIM " + std::to_string(slotNum), false); + } else { + setStatus("Exported " + std::to_string(exported) + " to SIM " + + std::to_string(slotNum), + false); + } + } else { + setStatus("SIM " + std::to_string(slotNum) + " write failed", true); + } + render(); + } + }; + + auto beginSimOperation = [&](SimOp op) { + pendingSimOp = op; + prevModeBeforeSim = + (op == SimOp::ExportSingle) ? ViewMode::Detail : ViewMode::List; + std::string st = runCaptureCmd( + "doas /usr/libexec/msm-modem-uim-selection status 2>/dev/null"); + bool s1Present = false; + bool s2Present = false; + int gwSlot = 1; + std::stringstream ss(st); + std::string line; + while (std::getline(ss, line)) { + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + if (line == "SLOT1_STATE=present") { + s1Present = true; + } else if (line == "SLOT2_STATE=present") { + s2Present = true; + } else if (line.rfind("PRIMARY_GW_SLOT=", 0) == 0) { + int v = std::atoi(line.c_str() + 16); + if (v == 1 || v == 2) { + gwSlot = v; + } + } + } + activeGwSlot = gwSlot; + if (s1Present && s2Present) { + simSelected = (gwSlot == 2) ? 1 : 0; + mode = ViewMode::SelectSim; + render(); + } else if (s1Present) { + performSimOperation(1); + } else if (s2Present) { + performSimOperation(2); + } else { + mode = prevModeBeforeSim; + setStatus("No SIM card detected", true); + render(); + } + }; + auto executeDetailAction = [&](int actIdx) { if (selected < 0 || selected >= static_cast(filtered.size())) { return; @@ -842,11 +1084,14 @@ int runContactsApp(int argc, char** argv) { runAsyncArgv("xdialer", c.number.c_str()); } break; - case 4: // Edit Contact + case 4: // Copy to SIM + beginSimOperation(SimOp::ExportSingle); + break; + case 5: // Edit Contact beginEditContact(c); render(); break; - case 5: // Delete Contact + case 6: // Delete Contact confirmSelected = 0; mode = ViewMode::ConfirmDelete; render(); @@ -865,6 +1110,10 @@ int runContactsApp(int argc, char** argv) { beginNewContact(""); mode = ViewMode::PickRecent; render(); + } else if (optIdx == 2) { + beginSimOperation(SimOp::ImportAll); + } else if (optIdx == 3) { + beginSimOperation(SimOp::ExportAll); } return; } @@ -880,18 +1129,24 @@ int runContactsApp(int argc, char** argv) { mode = ViewMode::PickRecent; render(); break; - case 2: // Call + case 2: // Import from SIM + beginSimOperation(SimOp::ImportAll); + break; + case 3: // Export to SIM + beginSimOperation(SimOp::ExportAll); + break; + case 4: // Call executeDetailAction(0); break; - case 3: // Send SMS + case 5: // Send SMS executeDetailAction(1); break; - case 4: // Edit - executeDetailAction(4); - break; - case 5: // Delete + case 6: // Edit executeDetailAction(5); break; + case 7: // Delete + executeDetailAction(6); + break; } }; @@ -1072,7 +1327,8 @@ int runContactsApp(int argc, char** argv) { break; } } else if (mode == ViewMode::Options) { - int optCount = filtered.empty() ? 2 : 6; + int optCount = + filtered.empty() ? (book.savedContacts().empty() ? 3 : 4) : 8; switch (sym) { case XK_Up: case XK_KP_Up: @@ -1098,7 +1354,7 @@ int runContactsApp(int argc, char** argv) { break; } } else if (mode == ViewMode::Detail) { - constexpr int kActCount = 6; + constexpr int kActCount = 7; switch (sym) { case XK_Up: case XK_KP_Up: @@ -1317,10 +1573,49 @@ int runContactsApp(int argc, char** argv) { default: break; } + } else if (mode == ViewMode::SelectSim) { + switch (sym) { + case XK_Up: + case XK_KP_Up: + case XK_Down: + case XK_KP_Down: + simSelected = 1 - simSelected; + render(); + break; + case XK_1: + case XK_KP_1: + simSelected = 0; + performSimOperation(1); + break; + case XK_2: + case XK_KP_2: + simSelected = 1; + performSimOperation(2); + break; + case XK_Return: + case XK_KP_Enter: + performSimOperation(simSelected + 1); + break; + case XK_BackSpace: + case XK_Left: + case XK_KP_Left: + mode = prevModeBeforeSim; + render(); + break; + default: + break; + } } } } + if (visible && !statusMsg.empty() && statusUntilMs > 0 && + nowMs() >= statusUntilMs) { + statusMsg.clear(); + statusUntilMs = 0; + render(); + } + if (visible && softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) { int comb = softkeyPending | querySoftkeysDown(dpy); softkeyPending = 0; @@ -1366,6 +1661,8 @@ int runContactsApp(int argc, char** argv) { } mode = ViewMode::List; render(); + } else if (mode == ViewMode::SelectSim) { + performSimOperation(simSelected + 1); } } else if (comb == 2) { // Right softkey @@ -1404,6 +1701,9 @@ int runContactsApp(int argc, char** argv) { } else if (mode == ViewMode::ConfirmDelete) { mode = ViewMode::Detail; render(); + } else if (mode == ViewMode::SelectSim) { + mode = prevModeBeforeSim; + render(); } } } diff --git a/src/dwm-plugins/phone_data.h b/src/dwm-plugins/phone_data.h index bd7859e..f7813c2 100644 --- a/src/dwm-plugins/phone_data.h +++ b/src/dwm-plugins/phone_data.h @@ -809,6 +809,78 @@ class ContactBook { return true; } + int importContactsBatch( + const std::vector>& items) { + if (items.empty()) { + return 0; + } + std::string path = getContactsFilePath(); + ensureParentDir(path); + + std::vector> 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}); + } + } + } + + int count = 0; + for (const auto& item : items) { + std::string newNumber = trimStr(item.first); + std::string newName = trimStr(item.second); + for (char& ch : newName) { + if (ch == '\t' || ch == '\r' || ch == '\n') { + ch = ' '; + } + } + std::string newNorm = normalizePhone(newNumber); + if (newNorm.empty()) { + continue; + } + if (newName.empty()) { + newName = newNumber; + } + bool replaced = false; + 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}); + } + ++count; + } + + if (count <= 0) { + return 0; + } + + std::ofstream out(path, std::ios::trunc); + if (!out.is_open()) { + return -1; + } + for (const auto& r : rows) { + out << r.first << "\t" << r.second << "\n"; + } + out.close(); + load(); + return count; + } + bool deleteContact(const std::string& number, const std::string& name) { std::string path = getContactsFilePath(); std::vector> rows; diff --git a/src/leds-msm8905-torch/leds-msm8905-torch.c b/src/leds-msm8905-torch/leds-msm8905-torch.c index 95e2ee6..a9ab1ad 100644 --- a/src/leds-msm8905-torch/leds-msm8905-torch.c +++ b/src/leds-msm8905-torch/leds-msm8905-torch.c @@ -8,10 +8,12 @@ */ #include +#include #include #include #include #include +#include #include #include #include @@ -19,6 +21,7 @@ #include #include #include +#include #define MSM8905_TLMM_GPIO32_PHYS 0x01020000UL #define MSM8905_TLMM_GPIO32_SIZE 0x1000UL @@ -42,16 +45,25 @@ #define RCGR_D_TORCH_50PCT 0xff7fU #define RCGR_CMD_ENABLE_UPDATE 0x0003U /* ROOT_EN | CMD_UPDATE */ -/* PM8909 PON registers for PS_HOLD shutdown vs hard reset configuration */ +/* PM8909 PON registers for PS_HOLD shutdown vs hard/warm reset configuration */ #define QPNP_PON_REVISION2 0x0801U #define QPNP_PON_PS_HOLD_RST_CTL 0x085aU #define QPNP_PON_PS_HOLD_RST_CTL2 0x085bU +#define QPNP_PON_SOFT_RB_SPARE 0x088fU #define QPNP_PON_S2_CNTL_EN 0x80U #define QPNP_PON_RST_TYPE_MASK 0x0fU #define PON_POWER_OFF_WARM_RESET 0x01U #define PON_POWER_OFF_SHUTDOWN 0x04U #define PON_POWER_OFF_HARD_RESET 0x07U +/* MSM8905/MSM8909 TCSR Boot Misc Detect register for EDL (05c6:9008) */ +#define MSM8905_TCSR_BOOT_MISC_DETECT 0x0193d100U +#define MSM8905_DLOAD_EDL 0x00000001U + +static char next_reboot_mode[32]; +module_param_string(next_reboot_mode, next_reboot_mode, sizeof(next_reboot_mode), 0644); +MODULE_PARM_DESC(next_reboot_mode, "Next reboot target mode (bootloader, fastboot, edl)"); + static void __iomem *tlmm_gpio32_base; static void __iomem *gcc_camss_base; static struct regmap *pon_regmap; @@ -164,7 +176,63 @@ static void msm8905_pon_config_reset(unsigned int rst_type) udelay(350); pr_info("leds-msm8905-torch: configured PM8909 PON PS_HOLD_RST_CTL=0x%02x (%s)\n", - rst_type, (rst_type == PON_POWER_OFF_SHUTDOWN) ? "shutdown" : "hard-reset"); + rst_type, + (rst_type == PON_POWER_OFF_SHUTDOWN) ? "shutdown" : + (rst_type == PON_POWER_OFF_WARM_RESET) ? "warm-reset" : "hard-reset"); +} + +static unsigned int msm8905_pon_prepare_restart_mode(const char *cmd) +{ + const char *mode = next_reboot_mode; + char trimmed[32]; + size_t len; + int ret; + void __iomem *tcsr_dload; + + if ((!mode || !mode[0]) && cmd && cmd[0]) + mode = cmd; + + if (!mode || !mode[0]) + return PON_POWER_OFF_HARD_RESET; + + strscpy(trimmed, mode, sizeof(trimmed)); + len = strlen(trimmed); + while (len > 0 && (trimmed[len - 1] == '\n' || trimmed[len - 1] == '\r' || + trimmed[len - 1] == ' ' || trimmed[len - 1] == '\t')) { + trimmed[--len] = '\0'; + } + + if (strcmp(trimmed, "bootloader") == 0 || strcmp(trimmed, "fastboot") == 0) { + if (pon_regmap) { + /* + * Write 0x04 (1 << 2) into PON_SOFT_RB_SPARE (0x088f) matching + * mode-bootloader = <0x02> so lk2nd enters Fastboot mode on reboot. + */ + regmap_update_bits(pon_regmap, QPNP_PON_SOFT_RB_SPARE, 0xfcU, 0x04U); + } + pr_info("leds-msm8905-torch: armed PM8909 PON_SOFT_RB_SPARE for lk2nd Fastboot\n"); + return PON_POWER_OFF_HARD_RESET; + } + + if (strcmp(trimmed, "edl") == 0) { + if (pon_regmap) + regmap_update_bits(pon_regmap, QPNP_PON_SOFT_RB_SPARE, 0xfcU, 0x00U); + + ret = qcom_scm_io_writel(MSM8905_TCSR_BOOT_MISC_DETECT, MSM8905_DLOAD_EDL); + if (ret) { + tcsr_dload = ioremap(MSM8905_TCSR_BOOT_MISC_DETECT, 4); + if (tcsr_dload) { + writel_relaxed(MSM8905_DLOAD_EDL, tcsr_dload); + wmb(); + iounmap(tcsr_dload); + } + } + pr_info("leds-msm8905-torch: armed TCSR_BOOT_MISC_DETECT (0x0193d100) for EDL (scm_ret=%d)\n", + ret); + return PON_POWER_OFF_WARM_RESET; + } + + return PON_POWER_OFF_HARD_RESET; } static int msm8905_pon_sys_poweroff(struct sys_off_data *data) @@ -176,8 +244,11 @@ static int msm8905_pon_sys_poweroff(struct sys_off_data *data) static int msm8905_pon_sys_restart(struct sys_off_data *data) { + unsigned int rst_type; + msm8905_torch_hw_disable(); - msm8905_pon_config_reset(PON_POWER_OFF_HARD_RESET); + rst_type = msm8905_pon_prepare_restart_mode(data ? data->cmd : NULL); + msm8905_pon_config_reset(rst_type); return NOTIFY_DONE; } @@ -191,7 +262,7 @@ static int msm8905_pon_reboot_notify(struct notifier_block *nb, if (action == SYS_POWER_OFF || action == SYS_HALT) rst_type = PON_POWER_OFF_SHUTDOWN; else - rst_type = PON_POWER_OFF_HARD_RESET; + rst_type = msm8905_pon_prepare_restart_mode((const char *)data); msm8905_pon_config_reset(rst_type); return NOTIFY_OK; @@ -199,7 +270,7 @@ static int msm8905_pon_reboot_notify(struct notifier_block *nb, static struct notifier_block msm8905_pon_reboot_nb = { .notifier_call = msm8905_pon_reboot_notify, - .priority = 128, + .priority = 64, }; static void msm8905_pon_lookup_regmap(void) diff --git a/src/sxmo-dmenu/dmenu.c b/src/sxmo-dmenu/dmenu.c index 076b53e..7c03bea 100644 --- a/src/sxmo-dmenu/dmenu.c +++ b/src/sxmo-dmenu/dmenu.c @@ -2190,6 +2190,10 @@ open_builtin_menu(const char *menu_name, int keep_idx) add_menu_item(label, "@menu:confirm_logout", 1); snprintf(label, sizeof(label), "%s Reboot", ICON_RLD); add_menu_item(label, "@menu:confirm_reboot", 1); + snprintf(label, sizeof(label), "%s Reboot to Fastboot", ICON_RLD); + add_menu_item(label, "@menu:confirm_fastboot", 1); + snprintf(label, sizeof(label), "%s Reboot to EDL", ICON_RLD); + add_menu_item(label, "@menu:confirm_edl", 1); snprintf(label, sizeof(label), "%s Poweroff", ICON_PWR); add_menu_item(label, "@menu:confirm_poweroff", 1); snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); @@ -2204,6 +2208,14 @@ open_builtin_menu(const char *menu_name, int keep_idx) reset_session_defaults(1, "Confirm Reboot"); add_menu_item("Yes", "sxmo_power.sh reboot", 0); add_menu_item("No", "@menu:power", 1); + } else if (strcmp(menu_name, "confirm_fastboot") == 0) { + reset_session_defaults(1, "Confirm Fastboot"); + add_menu_item("Yes", "sxmo_power.sh fastboot", 0); + add_menu_item("No", "@menu:power", 1); + } else if (strcmp(menu_name, "confirm_edl") == 0) { + reset_session_defaults(1, "Confirm EDL Mode"); + add_menu_item("Yes", "sxmo_power.sh edl", 0); + add_menu_item("No", "@menu:power", 1); } else if (strcmp(menu_name, "confirm_poweroff") == 0) { reset_session_defaults(1, "Confirm Poweroff"); add_menu_item("Yes", "sxmo_power.sh poweroff", 0);