Add Fastboot/EDL reboot modes and SIM contact import/export

This commit is contained in:
Luxferre
2026-10-25 07:56:44 +02:00
parent a1e4f6ef8c
commit a117e74452
17 changed files with 864 additions and 37 deletions
+6 -4
View File
@@ -52,6 +52,7 @@ sudo ./flash-edl.sh
The script will: The script will:
1. Temporarily pause host `ModemManager` so it does not interfere with `05c6:9008`. 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`): 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. - **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. - **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. - **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) ### 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`: 2. Flash the system image to `userdata`:
```bash ```bash
fastboot flash userdata ./images/bananarchy-nokia-argon.img 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**: 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. - **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. - **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**: 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**: 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. - **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**). - **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**).
- **Contact Details View**: Provides one-click actions for **Call Contact**, **Send SMS**, **Read Messages**, **Open in Dialer**, **Edit Contact**, and **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`. - **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**: 7. **`SMS Manager / Editor` (`sms.so` / `xsms` / `sxmo_modemtext.sh`) — Conversations & Composer**:
+1 -1
View File
@@ -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. - **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. - **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/`). - **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. - **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. - **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. - **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.
+3
View File
@@ -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_lock
permit nopass :wheel as root cmd tee args -a /sys/power/wake_unlock 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/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 rc-service args hexagonrpcd-adsp-sensorspd stop
permit nopass :wheel as root cmd systemctl args stop hexagonrpcd-adsp-sensorspd permit nopass :wheel as root cmd systemctl args stop hexagonrpcd-adsp-sensorspd
+2
View File
@@ -210,6 +210,8 @@ getprogchoices() {
$icon_lck Lock (Screen off)^0^sxmo_state.sh set screenoff $icon_lck Lock (Screen off)^0^sxmo_state.sh set screenoff
$icon_out Logout^0^confirm Logout && sxmo_power.sh logout $icon_out Logout^0^confirm Logout && sxmo_power.sh logout
$icon_rld Reboot^0^confirm Reboot && sxmo_power.sh reboot $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" $icon_pwr Poweroff^0^confirm Poweroff && sxmo_power.sh poweroff"
;; ;;
modem) modem)
+41 -1
View File
@@ -8,7 +8,7 @@ export PATH="/sbin:/usr/sbin:$PATH"
. sxmo_common.sh . sxmo_common.sh
usage() { 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() { run_polkit_login1() {
@@ -89,9 +89,29 @@ run_priv_power() {
return 1 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 case "$1" in
reboot) reboot)
sync sync
set_kernel_reboot_mode ""
sxmo_hook_power.sh reboot || true 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) & (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 if ! run_priv_power reboot; then
@@ -102,6 +122,26 @@ case "$1" in
fi fi
sxmo_jobs.sh stop all >/dev/null 2>&1 || true 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) poweroff)
sync sync
sxmo_hook_power.sh poweroff || true sxmo_hook_power.sh poweroff || true
Binary file not shown.
Binary file not shown.
@@ -558,8 +558,331 @@ case "$cmd" in
exit 1 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 <slot> <pin>|unblock-puk <slot> <puk> <newpin>|set-pin-lock <slot> <enable|disable> <pin>|change-pin <slot> <oldpin> <newpin>|imei|usb-tether <on|off|status>|at <port> <cmd>]" >&2 echo "Usage: $0 [boot|status|switch <1|2>|verify-pin <slot> <pin>|unblock-puk <slot> <puk> <newpin>|set-pin-lock <slot> <enable|disable> <pin>|change-pin <slot> <oldpin> <newpin>|imei|usb-tether <on|off|status>|at <port> <cmd>|sim-contacts-import [1|2|auto]|sim-contacts-export [1|2|auto] [tsv_file]]" >&2
exit 1 exit 1
;; ;;
esac esac
@@ -91,6 +91,8 @@ case "$WMCLASS" in
$icon_lck Lock (Screen off) ^ 0 ^ sxmo_state.sh set screenoff $icon_lck Lock (Screen off) ^ 0 ^ sxmo_state.sh set screenoff
$icon_out Logout ^ 0 ^ confirm Logout && sxmo_power.sh logout $icon_out Logout ^ 0 ^ confirm Logout && sxmo_power.sh logout
$icon_rld Reboot ^ 0 ^ confirm Reboot && sxmo_power.sh reboot $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 $icon_pwr Poweroff ^ 0 ^ confirm Poweroff && sxmo_power.sh poweroff
" "
WINNAME=Power WINNAME=Power
Binary file not shown.
Binary file not shown.
+325 -25
View File
@@ -25,6 +25,13 @@ enum class ViewMode {
Edit = 3, Edit = 3,
PickRecent = 4, PickRecent = 4,
ConfirmDelete = 5, ConfirmDelete = 5,
SelectSim = 6,
};
enum class SimOp {
ImportAll = 0,
ExportAll = 1,
ExportSingle = 2,
}; };
enum class TextCaseMode { enum class TextCaseMode {
@@ -153,12 +160,19 @@ int runContactsApp(int argc, char** argv) {
ViewMode mode = ViewMode::List; ViewMode mode = ViewMode::List;
ViewMode prevModeBeforeEdit = ViewMode::List; ViewMode prevModeBeforeEdit = ViewMode::List;
ViewMode prevModeBeforeSim = ViewMode::List;
SimOp pendingSimOp = SimOp::ImportAll;
std::string searchQuery; std::string searchQuery;
int selected = 0; int selected = 0;
int detailSelected = 0; int detailSelected = 0;
int optionSelected = 0; int optionSelected = 0;
int confirmSelected = 0; int confirmSelected = 0;
int recentSelected = 0; int recentSelected = 0;
int simSelected = 0;
int activeGwSlot = 1;
std::string statusMsg;
bool statusIsError = false;
int64_t statusUntilMs = 0;
// Editor state // Editor state
bool editIsNew = true; bool editIsNew = true;
@@ -360,44 +374,66 @@ int runContactsApp(int argc, char** argv) {
if (mode == ViewMode::Options) { if (mode == ViewMode::Options) {
// Popup options menu overlay // Popup options menu overlay
const char* optLabels[6] = { const char* optLabels[8] = {
"New Contact", "New Contact",
"Add from Recent", "Add from Recent",
"Import from SIM",
"Export to SIM",
"Call Contact", "Call Contact",
"Send SMS", "Send SMS",
"Edit Contact", "Edit Contact",
"Delete Contact" "Delete Contact"
}; };
const char* optIcons[6] = { const char* optIcons[8] = {
kIconPlus, kIconPlus,
kIconClock, kIconClock,
kIconSim,
kIconSim,
kIconPhone, kIconPhone,
kIconSms, kIconSms,
kIconEdit, kIconEdit,
kIconTrash 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 menuRowH = 34;
int menuH = optCount * menuRowH + 4; int menuH = visCount * menuRowH + 4;
int menuY = kWinHeight - kSoftbarH - menuH; int menuY = kWinHeight - kSoftbarH - menuH;
XSetForeground(dpy, gc, theme.spec.bgFooter); XSetForeground(dpy, gc, theme.spec.bgFooter);
XFillRectangle(dpy, pix, gc, 8, menuY, kWinWidth - 16, menuH); XFillRectangle(dpy, pix, gc, 8, menuY, kWinWidth - 16, menuH);
XSetForeground(dpy, gc, theme.spec.selBg); XSetForeground(dpy, gc, theme.spec.selBg);
XDrawRectangle(dpy, pix, gc, 8, menuY, kWinWidth - 17, menuH - 1); 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; int ry = menuY + 2 + i * menuRowH;
bool isSel = (i == optionSelected); bool isSel = (idx == optionSelected);
if (isSel) { if (isSel) {
XSetForeground(dpy, gc, theme.spec.selBg); XSetForeground(dpy, gc, theme.spec.selBg);
XFillRectangle(dpy, pix, gc, 10, ry, kWinWidth - 20, menuRowH); XFillRectangle(dpy, pix, gc, 10, ry, kWinWidth - 20, menuRowH);
XSetForeground(dpy, gc, theme.spec.accent); XSetForeground(dpy, gc, theme.spec.accent);
XFillRectangle(dpy, pix, gc, 10, ry, 4, menuRowH); XFillRectangle(dpy, pix, gc, 10, ry, 4, menuRowH);
} }
XftColor* icCol = (i == 5) ? &colRed : (isSel ? &colYellow : &colGreen); XftColor* icCol = (idx == 7) ? &colRed : (isSel ? &colYellow : &colGreen);
drawPango(layoutIcon, icCol, 18, ry, menuRowH, optIcons[i]); drawPango(layoutIcon, icCol, 18, ry, menuRowH, optIcons[idx]);
drawPango(layoutMain, isSel ? &colWhite : &colLight, 42, ry, menuRowH, 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); drawSoftbar("Select", "OK", "Back", &colYellow);
} else { } else {
@@ -421,23 +457,25 @@ int runContactsApp(int argc, char** argv) {
drawPango(layoutMain, &colWhite, 34, 4, 26, c.name, 198); drawPango(layoutMain, &colWhite, 34, 4, 26, c.name, 198);
drawPango(layoutSmall, &colYellow, 34, 30, 20, c.number, 198); drawPango(layoutSmall, &colYellow, 34, 30, 20, c.number, 198);
const char* actLabels[6] = { const char* actLabels[7] = {
"Call Contact", "Call Contact",
"Send SMS", "Send SMS",
"Read Messages", "Read Messages",
"Open in Dialer", "Open in Dialer",
"Copy to SIM",
"Edit Contact", "Edit Contact",
"Delete Contact" "Delete Contact"
}; };
const char* actIcons[6] = { const char* actIcons[7] = {
kIconPhone, kIconPhone,
kIconSms, kIconSms,
kIconList, kIconList,
kIconPhoneLog, kIconPhoneLog,
kIconSim,
kIconEdit, kIconEdit,
kIconTrash kIconTrash
}; };
constexpr int kActCount = 6; constexpr int kActCount = 7;
constexpr int kActVis = 5; constexpr int kActVis = 5;
int actListY = kCardH; int actListY = kCardH;
int actListH = kWinHeight - kCardH - kSoftbarH; int actListH = kWinHeight - kCardH - kSoftbarH;
@@ -462,10 +500,10 @@ int runContactsApp(int argc, char** argv) {
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH); XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
} }
XftColor* icCol = XftColor* icCol =
(idx == 5) ? &colRed : (isSel ? &colYellow : &colGreen); (idx == 6) ? &colRed : (isSel ? &colYellow : &colGreen);
drawPango(layoutIcon, icCol, 10, ry, rowH, actIcons[idx]); drawPango(layoutIcon, icCol, 10, ry, rowH, actIcons[idx]);
drawPango(layoutMain, drawPango(layoutMain,
(idx == 5 && !isSel) ? &colRed : (isSel ? &colWhite : &colLight), (idx == 6 && !isSel) ? &colRed : (isSel ? &colWhite : &colLight),
34, ry, rowH, actLabels[idx], 196); 34, ry, rowH, actLabels[idx], 196);
} }
@@ -608,6 +646,57 @@ int runContactsApp(int argc, char** argv) {
} }
drawSoftbar("Delete", "Select", "Cancel", &colRed); 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) { if (visible) {
@@ -620,7 +709,7 @@ int runContactsApp(int argc, char** argv) {
drawPango(layoutIcon, &colGreen, 0, 0, 20, drawPango(layoutIcon, &colGreen, 0, 0, 20,
std::string(kIconBook) + kIconUser + kIconSearch + kIconPlus + std::string(kIconBook) + kIconUser + kIconSearch + kIconPlus +
kIconEdit + kIconTrash + kIconSms + kIconList + kIconCheck + kIconEdit + kIconTrash + kIconSms + kIconList + kIconCheck +
kIconPhone + kIconClock); kIconPhone + kIconClock + kIconSim);
render(); render();
XSync(dpy, False); XSync(dpy, False);
@@ -810,6 +899,159 @@ int runContactsApp(int argc, char** argv) {
lastTapMs = now; 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<std::pair<std::string, std::string>> 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<int>(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) { auto executeDetailAction = [&](int actIdx) {
if (selected < 0 || selected >= static_cast<int>(filtered.size())) { if (selected < 0 || selected >= static_cast<int>(filtered.size())) {
return; return;
@@ -842,11 +1084,14 @@ int runContactsApp(int argc, char** argv) {
runAsyncArgv("xdialer", c.number.c_str()); runAsyncArgv("xdialer", c.number.c_str());
} }
break; break;
case 4: // Edit Contact case 4: // Copy to SIM
beginSimOperation(SimOp::ExportSingle);
break;
case 5: // Edit Contact
beginEditContact(c); beginEditContact(c);
render(); render();
break; break;
case 5: // Delete Contact case 6: // Delete Contact
confirmSelected = 0; confirmSelected = 0;
mode = ViewMode::ConfirmDelete; mode = ViewMode::ConfirmDelete;
render(); render();
@@ -865,6 +1110,10 @@ int runContactsApp(int argc, char** argv) {
beginNewContact(""); beginNewContact("");
mode = ViewMode::PickRecent; mode = ViewMode::PickRecent;
render(); render();
} else if (optIdx == 2) {
beginSimOperation(SimOp::ImportAll);
} else if (optIdx == 3) {
beginSimOperation(SimOp::ExportAll);
} }
return; return;
} }
@@ -880,18 +1129,24 @@ int runContactsApp(int argc, char** argv) {
mode = ViewMode::PickRecent; mode = ViewMode::PickRecent;
render(); render();
break; 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); executeDetailAction(0);
break; break;
case 3: // Send SMS case 5: // Send SMS
executeDetailAction(1); executeDetailAction(1);
break; break;
case 4: // Edit case 6: // Edit
executeDetailAction(4);
break;
case 5: // Delete
executeDetailAction(5); executeDetailAction(5);
break; break;
case 7: // Delete
executeDetailAction(6);
break;
} }
}; };
@@ -1072,7 +1327,8 @@ int runContactsApp(int argc, char** argv) {
break; break;
} }
} else if (mode == ViewMode::Options) { } else if (mode == ViewMode::Options) {
int optCount = filtered.empty() ? 2 : 6; int optCount =
filtered.empty() ? (book.savedContacts().empty() ? 3 : 4) : 8;
switch (sym) { switch (sym) {
case XK_Up: case XK_Up:
case XK_KP_Up: case XK_KP_Up:
@@ -1098,7 +1354,7 @@ int runContactsApp(int argc, char** argv) {
break; break;
} }
} else if (mode == ViewMode::Detail) { } else if (mode == ViewMode::Detail) {
constexpr int kActCount = 6; constexpr int kActCount = 7;
switch (sym) { switch (sym) {
case XK_Up: case XK_Up:
case XK_KP_Up: case XK_KP_Up:
@@ -1317,9 +1573,48 @@ int runContactsApp(int argc, char** argv) {
default: default:
break; 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) { if (visible && softkeyPending != 0 && nowMs() >= softkeyDeadlineMs) {
int comb = softkeyPending | querySoftkeysDown(dpy); int comb = softkeyPending | querySoftkeysDown(dpy);
@@ -1366,6 +1661,8 @@ int runContactsApp(int argc, char** argv) {
} }
mode = ViewMode::List; mode = ViewMode::List;
render(); render();
} else if (mode == ViewMode::SelectSim) {
performSimOperation(simSelected + 1);
} }
} else if (comb == 2) { } else if (comb == 2) {
// Right softkey // Right softkey
@@ -1404,6 +1701,9 @@ int runContactsApp(int argc, char** argv) {
} else if (mode == ViewMode::ConfirmDelete) { } else if (mode == ViewMode::ConfirmDelete) {
mode = ViewMode::Detail; mode = ViewMode::Detail;
render(); render();
} else if (mode == ViewMode::SelectSim) {
mode = prevModeBeforeSim;
render();
} }
} }
} }
+72
View File
@@ -809,6 +809,78 @@ class ContactBook {
return true; return true;
} }
int importContactsBatch(
const std::vector<std::pair<std::string, std::string>>& items) {
if (items.empty()) {
return 0;
}
std::string path = getContactsFilePath();
ensureParentDir(path);
std::vector<std::pair<std::string, std::string>> rows;
{
std::ifstream in(path);
std::string line;
while (std::getline(in, line)) {
if (line.empty()) continue;
size_t tab = line.find('\t');
if (tab == std::string::npos) continue;
std::string num = trimStr(line.substr(0, tab));
std::string name = trimStr(line.substr(tab + 1));
if (!num.empty() && !name.empty()) {
rows.push_back({num, name});
}
}
}
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) { bool deleteContact(const std::string& number, const std::string& name) {
std::string path = getContactsFilePath(); std::string path = getContactsFilePath();
std::vector<std::pair<std::string, std::string>> rows; std::vector<std::pair<std::string, std::string>> rows;
+76 -5
View File
@@ -8,10 +8,12 @@
*/ */
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/firmware/qcom/qcom_scm.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/notifier.h> #include <linux/notifier.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_platform.h> #include <linux/of_platform.h>
@@ -19,6 +21,7 @@
#include <linux/reboot.h> #include <linux/reboot.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/string.h>
#define MSM8905_TLMM_GPIO32_PHYS 0x01020000UL #define MSM8905_TLMM_GPIO32_PHYS 0x01020000UL
#define MSM8905_TLMM_GPIO32_SIZE 0x1000UL #define MSM8905_TLMM_GPIO32_SIZE 0x1000UL
@@ -42,16 +45,25 @@
#define RCGR_D_TORCH_50PCT 0xff7fU #define RCGR_D_TORCH_50PCT 0xff7fU
#define RCGR_CMD_ENABLE_UPDATE 0x0003U /* ROOT_EN | CMD_UPDATE */ #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_REVISION2 0x0801U
#define QPNP_PON_PS_HOLD_RST_CTL 0x085aU #define QPNP_PON_PS_HOLD_RST_CTL 0x085aU
#define QPNP_PON_PS_HOLD_RST_CTL2 0x085bU #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_S2_CNTL_EN 0x80U
#define QPNP_PON_RST_TYPE_MASK 0x0fU #define QPNP_PON_RST_TYPE_MASK 0x0fU
#define PON_POWER_OFF_WARM_RESET 0x01U #define PON_POWER_OFF_WARM_RESET 0x01U
#define PON_POWER_OFF_SHUTDOWN 0x04U #define PON_POWER_OFF_SHUTDOWN 0x04U
#define PON_POWER_OFF_HARD_RESET 0x07U #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 *tlmm_gpio32_base;
static void __iomem *gcc_camss_base; static void __iomem *gcc_camss_base;
static struct regmap *pon_regmap; static struct regmap *pon_regmap;
@@ -164,7 +176,63 @@ static void msm8905_pon_config_reset(unsigned int rst_type)
udelay(350); udelay(350);
pr_info("leds-msm8905-torch: configured PM8909 PON PS_HOLD_RST_CTL=0x%02x (%s)\n", 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) 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) static int msm8905_pon_sys_restart(struct sys_off_data *data)
{ {
unsigned int rst_type;
msm8905_torch_hw_disable(); 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; 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) if (action == SYS_POWER_OFF || action == SYS_HALT)
rst_type = PON_POWER_OFF_SHUTDOWN; rst_type = PON_POWER_OFF_SHUTDOWN;
else else
rst_type = PON_POWER_OFF_HARD_RESET; rst_type = msm8905_pon_prepare_restart_mode((const char *)data);
msm8905_pon_config_reset(rst_type); msm8905_pon_config_reset(rst_type);
return NOTIFY_OK; 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 = { static struct notifier_block msm8905_pon_reboot_nb = {
.notifier_call = msm8905_pon_reboot_notify, .notifier_call = msm8905_pon_reboot_notify,
.priority = 128, .priority = 64,
}; };
static void msm8905_pon_lookup_regmap(void) static void msm8905_pon_lookup_regmap(void)
+12
View File
@@ -2190,6 +2190,10 @@ open_builtin_menu(const char *menu_name, int keep_idx)
add_menu_item(label, "@menu:confirm_logout", 1); add_menu_item(label, "@menu:confirm_logout", 1);
snprintf(label, sizeof(label), "%s Reboot", ICON_RLD); snprintf(label, sizeof(label), "%s Reboot", ICON_RLD);
add_menu_item(label, "@menu:confirm_reboot", 1); 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); snprintf(label, sizeof(label), "%s Poweroff", ICON_PWR);
add_menu_item(label, "@menu:confirm_poweroff", 1); add_menu_item(label, "@menu:confirm_poweroff", 1);
snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); 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"); reset_session_defaults(1, "Confirm Reboot");
add_menu_item("Yes", "sxmo_power.sh reboot", 0); add_menu_item("Yes", "sxmo_power.sh reboot", 0);
add_menu_item("No", "@menu:power", 1); 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) { } else if (strcmp(menu_name, "confirm_poweroff") == 0) {
reset_session_defaults(1, "Confirm Poweroff"); reset_session_defaults(1, "Confirm Poweroff");
add_menu_item("Yes", "sxmo_power.sh poweroff", 0); add_menu_item("Yes", "sxmo_power.sh poweroff", 0);