Add preloaded SMS, File Manager, and Camera (live viewfinder) DWM plugins

This commit is contained in:
Luxferre
2026-10-21 14:46:11 +03:00
parent c1e5c646d6
commit 55b20c5b85
45 changed files with 4767 additions and 126 deletions
+26 -8
View File
@@ -139,8 +139,8 @@ You can switch between **vertical stack (`TTT`, `bstack`)**, **horizontal tile (
- `Mod4+1`..`4`: switch to workspace tag `1`..`4`
- `Mod4+Shift+q`: close focused window
#### 3.1.2 Preloaded DWM plugins (`XDrawer`, `Dialer`, `Call Log`, `Contacts`)
All four core phone overlays are compiled as shared-object plugins (`/usr/lib/dwm/plugins/*.so`), preloaded at `dwm` startup (`dwm-plugin <name> --preload`), and awakened over `/tmp/dwm_plugin_<name>.fifo` in `< 10 ms`:
#### 3.1.2 Preloaded DWM plugins (`XDrawer`, `Dialer`, `Call Log`, `Contacts`, `SMS`, `Files`, `Camera`)
All seven core phone overlays are compiled as shared-object plugins (`/usr/lib/dwm/plugins/*.so`), preloaded at `dwm` startup (`dwm-plugin <name> --preload`), and awakened over `/tmp/dwm_plugin_<name>.fifo` in `< 10 ms`:
1. **`XDrawer` (`xdrawer.so` / `xdrawer`) — Global Phone Controls**:
- **Open / Close**: Press **both softkeys simultaneously** (`KEY_MENU` + `KEY_BACK`) from anywhere, or select `XDrawer` from the Sxmo menu.
@@ -161,6 +161,22 @@ All four core phone overlays are compiled as shared-object plugins (`/usr/lib/dw
- **Contact Details View**: Provides one-click actions for **Call Contact**, **Send SMS**, **Read Messages**, **Open in Dialer**, **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`.
5. **`SMS Manager / Editor` (`sms.so` / `xsms` / `sxmo_modemtext.sh`) — Conversations & Composer**:
- **Open**: Select **Texts** from the Sxmo system menu (`Left softkey`), or choose **Send SMS** / **Read Messages** from Contacts.
- **Conversations List**: Shows all SMS threads and drafts from `~/.local/share/sxmo/modem/<number>/sms.txt` with contact name resolution, message previews, and timestamps. Press `0`–`9` for live T9/number search, `*` for a new message, **Center `OK` (`Return`)** to open the conversation, **Call (green)** to call the sender, or **Left softkey (`Options`)** for **New Message**, **Open Thread**, **Reply / Compose**, **Call Number**, **Save/View Contact**, and **Delete Thread**.
- **Thread View**: Displays incoming (`Recv`, green left accent) and outgoing (`Sent`, blue/gold right accent) message bubbles; `Up`/`Down` scrolls; **Center `OK` (`Return`)** opens the reply composer; **Call (green)** calls the contact.
- **Multi-Tap SMS Composer**: Supports picking a recipient from Contacts/Recents or entering a number directly (hold `0` for `+`), multi-tap text entry on keys `0`–`9` and `*` (`#` cycles `[Abc]` -> `[ABC]` -> `[abc]` -> `[123]`), live character and SMS segment counter (`0/160`), automatic draft persistence (`draft.txt`), and asynchronous sending via `sxmo_modemsendsms.sh` (`Call` key, `Send SMS` button, or `Left softkey`).
6. **`File Manager` (`files.so` / `xfiles` / `sxmo_files.sh`) — Graphical Directory Browser & Viewer**:
- **Open**: Select **Files** from the Sxmo system menu (`Left softkey`) or press `Mod4+d`.
- **Browser Controls**: `Up`/`Down` navigates files and directories; `Return`/`Right` enters a directory or opens the selected file; `Left`/`BackSpace` ascends to the parent directory (`..`); `0`–`9` filters items in real time; `*` toggles hidden dotfiles (`.*`); `#` cycles sort order (**Name** -> **Date** -> **Size**).
- **Built-in Viewers & File Actions (`Left softkey`)**: Text files and scripts open in a fast built-in scrollable viewer (with `Left softkey` shortcut to edit in `st -e nano`); JPEG and PNG images open in a built-in `libjpeg`/`libpng` previewer (`Left`/`Right` steps through images in the folder, `Left softkey` opens full screen in `feh`). The **Actions** menu supports **Open**, **Edit in Nano**, **New Folder**, **New File**, **Rename**, **Copy**, **Cut / Move**, **Paste Here**, **Properties**, **Toggle Hidden (`*`)**, **Cycle Sort (`#`)**, **Open Terminal Here**, and **Delete**.
7. **`Camera` (`camera.so` / `xcamera`) — Live Viewfinder & Photo Gallery**:
- **Open**: Select **Camera** from the Sxmo system menu (`Left softkey`) or run `xcamera` / `bananarchy-photo --menu`.
- **Live Viewfinder**: Streams `800x600` `RGB24` frames directly from `/dev/video0` (`msm8905-cam` / `GC2395`) into a `240x180` 4:3 viewfinder with a center reticle. Press **Center `OK` (`Return`)**, **`5`**, or **Call (green)** to snap a JPEG photo to `~/Pictures/photo-YYYYMMDD-HHMMSS.jpg`; `*` or `1` toggles **Auto-Flash**; `0` or `2` toggles continuous **Torch** (`white:torch`); `Up`/`Down` adjusts **Digital Zoom** (`1.0x` / `2.0x` / `4.0x`); `#` or `3` toggles the **3-second Self-Timer**; **Left softkey (`Gallery`)** opens the built-in photo gallery.
- **Built-in Photo Gallery**: Browse captured photos with `Left`/`Right` or `Up`/`Down`, press `Return` to open full-screen in `feh`, or press **Left softkey (`Delete`)** to delete a photo.
### 3.2 Multi-mode `fcitx5-t9` keypad reference
- **Short-press `#`** *(when no character is in preedit)*: cycles through the 6 input modes (`abc` -> `Abc` -> `ABC` -> `Sym` -> `Ctl` -> `123` -> `abc`), displaying a `1500ms` preedit status banner (`[Mode: abc]`, `[Mode: Abc]`, `[Mode: ABC]`, `[Mode: Sym]`, `[Mode: Ctl]`, `[Mode: 123]`).
@@ -254,15 +270,17 @@ All four core phone overlays are compiled as shared-object plugins (`/usr/lib/dw
## 5. Camera, audio, flashlight & cellular modem reference
### 5.1 Taking photos with the rear camera (`bananarchy-photo` & `/dev/video0`)
### 5.1 Taking photos with the rear camera (`xcamera`, `bananarchy-photo` & `/dev/video0`)
The `msm8905-cam` kernel module drives the rear 2MP GalaxyCore `GC2395` sensor through MSM8909 `CAMSS` (`CSIPHY0 -> CSID0 -> ISPIF -> VFE0 -> VBIF`) and registers `/dev/video0` (`800x600` 24-bit `RGB3`).
- **From the Sxmo keypad UI**:
Open the **System menu** (`Left softkey`) or **Apps menu** (`Left softkey` -> `Apps`) and select **Camera** (`bananarchy-photo --menu`). From the Camera menu you can:
- **Take photo**: capture a photo to `~/Pictures/photo-YYYYMMDD-HHMMSS.jpg` and immediately open it in `imv`.
- **Take photo (flash)**: turn on the rear flashlight (`white:torch`), capture a photo, restore the flashlight state, and open the photo in `imv`.
- **View photos**: browse all saved photos in `~/Pictures` using `imv` (press `Right softkey` inside `imv` to navigate, zoom, rotate, or quit).
- **From the Sxmo keypad UI (`xcamera` / `camera.so`)**:
Open the **System menu** (`Left softkey`) and select **Camera** (`xcamera`). The native DWM camera plugin streams a live `240x180` viewfinder from `/dev/video0` with:
- **Shutter (`Return` / `5` / `Call`)**: encodes an `800x600` JPEG directly to `~/Pictures/photo-YYYYMMDD-HHMMSS.jpg`.
- **Auto-Flash (`*` / `1`) & Continuous Torch (`0` / `2`)**: controls `white:torch` during or before capture.
- **Digital Zoom (`Up` / `Down`)**: cycles `1.0x`, `2.0x`, and `4.0x` center-crop zoom.
- **Self-Timer (`#` / `3`)**: toggles a 3-second countdown before capture.
- **Built-in Gallery (`Left softkey`)**: browse and delete saved photos in `~/Pictures` or press `Return` to open full-screen in `feh`.
- **From the CLI (`bananarchy-photo`)**:
```bash
# Capture to ~/Pictures/photo-YYYYMMDD-HHMMSS.jpg and print path
+5 -2
View File
@@ -10,11 +10,14 @@ Bananarchy targets an ultra-lightweight **Sxmo X11 (`sxmo-de-dwm`)** environment
- **Cross-compiled Xlibre (`X11Libre/xserver` 25.2.0) display server**: `modesetting` KMS + `ShadowFB` Damage partial-rectangle SPI transfers, software cursor fallback, and rootless `elogind` integration.
- **Patched `sxmo-dwm` with native C/C++ plugin architecture**: `240x320` vertical `bstack` layout, 24px Pango status bar with an overflow-protected right-aligned clock, transparent-backgrounded `"Menu"` and `"Names"` desktop softkey labels, direct C `SW_LID` slider and locked-key wake handling in `< 2 ms`, and a dynamic `.so` plugin API (`dwm_plugin.h`) with preloaded resident daemons (`--preload`) and non-blocking FIFO IPC (`< 10 ms` wakeup latency).
- **Preloaded `240x320` keypad-driven DWM plugins (`xdrawer`, `dialer`, `calllog`, `contacts`)**:
- **Preloaded `240x320` keypad-driven DWM plugins (`xdrawer`, `dialer`, `calllog`, `contacts`, `sms`, `files`, `camera`)**:
- **`XDrawer` (`xdrawer.so` / `xdrawer`)**: global phone controls overlay invoked from anywhere by pressing **both softkeys simultaneously**, providing instant sliders and toggles for speaker volume, earpiece volume, flashlight, sound mute, microphone mute, screen brightness, and keypad backlight.
- **`Dialer` (`dialer.so` / `xdialer`)**: opens automatically from the idle desktop when pressing any **`0`–`9`, `*`, or `#` key** (inserting that character immediately), with live T9/number contact filtering, hold-`0` for `+`, and single-press **Call** dialing.
- **`Call Log` (`calllog.so` / `xcalllog`)**: opens automatically from the idle desktop when pressing the **Call (green)** button, with `All` / `Missed` / `In` / `Out` tabs, contact resolution, call durations, and instant redial.
- **`Contacts` (`contacts.so` / `xcontacts`)**: full-featured phonebook application opened from the idle desktop via the **Right softkey (`Names`)** or from the Sxmo menu, integrated with `~/.config/sxmo/contacts.tsv` (live T9/number search, call/SMS/dialer actions, built-in contact editor with keypad multi-tap name entry and recent-caller number picker, and contact deletion).
- **`SMS Manager` (`sms.so` / `xsms`)**: conversation thread manager, bubble message history reader, and multi-tap keypad SMS composer/draft editor integrated with `$SXMO_LOGDIR/<number>/sms.txt`, `draft.txt`, `modemlog.tsv`, and `sxmo_modemsendsms.sh`.
- **`File Manager` (`files.so` / `xfiles`)**: graphical directory browser with live T9/number filtering, dotfiles toggle (`*`), sort mode cycle (`#`), file operations (new folder/file, rename, copy, cut/move, paste, delete, properties, edit in `nano`, open terminal here), built-in text viewer, and built-in `libjpeg`/`libpng` image previewer.
- **`Camera` (`camera.so` / `xcamera`)**: live V4L2 `/dev/video0` (`800x600` `RGB24` -> `240x180` 4:3) viewfinder with auto-flash (`*`/`1`), continuous torch (`0`/`2`), digital zoom (`1x`/`2x`/`4x` via `Up`/`Down`), 3-second self-timer (`#`/`3`), direct `libjpeg` photo capture to `~/Pictures/photo-YYYYMMDD-HHMMSS.jpg`, and built-in photo gallery.
- **Patched `sxmo-dmenu`**: `240x320` full-screen 10-line vertical menu (`240x296` flush below the 24px top bar) with a `#383838` search bar, `#005577` prompt badge, and `XIM` `fcitx5-t9` text input in `< 15 ms`.
- **Patched `sxmo-st` terminal (`pixelsize=18`)**: **HarfBuzz (`libharfbuzz`) OpenType ligature shaping** (`liga`, `calt`, `clig`, `dlig`) and `XIM` support.
- **Typography**: non-Nerd **`Fira Code` (`font-fira-code`) with ligatures** as the primary system and terminal font (with `DejaVu SansM Nerd Font` used strictly as an icon fallback).
@@ -90,7 +93,7 @@ bananarchy/
│ └── 0001-bananarchy-multimode-t9-and-fcitx5-5.1.19.patch
├── src/
│ ├── sxmo-dwm/ # Patched sxmo-dwm (240x320 bstack, Pango bar, desktop softkey bar & plugin API)
│ ├── dwm-plugins/ # DWM plugins (dialer.so, calllog.so, contacts.so), shared phone/TSV lib & runner
│ ├── dwm-plugins/ # DWM plugins (dialer, calllog, contacts, sms, files, camera), shared phone/media lib & runner
│ ├── xdrawer/ # XDrawer global phone controls DWM plugin (xdrawer.so / xdrawer)
│ ├── sxmo-dmenu/ # Patched sxmo-dmenu (240x320 full-screen 10-line menu, #383838 search bar, XIM fcitx5-t9)
│ ├── sxmo-st/ # Patched sxmo-st terminal with HarfBuzz Fira Code ligature shaping & XIM
+3
View File
@@ -189,6 +189,9 @@ run_menu() {
}
if [ "$MENU_MODE" -eq 1 ]; then
if command -v xcamera >/dev/null 2>&1; then
exec xcamera
fi
run_menu
else
take_photo "$USE_FLASH" "$OUT_FILE"
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -153,12 +153,12 @@ fast_sys_choices() {
CHOICES="$icon_trm Terminal^0^st -e ${SHELL:-/bin/sh}
$icon_grd Apps^0^sxmo_appmenu.sh applications
$icon_grd Scripts^0^sxmo_appmenu.sh scripts
$icon_dir Files^0^sxmo_files.sh
$icon_dir Files^0^xfiles
$icon_phn Dialer^0^xdialer
$icon_phl Call Log^0^xcalllog
$icon_msg Texts^0^sxmo_modemtext.sh
$icon_msg Texts^0^xsms
$icon_usr Contacts^0^xcontacts
${_bt_line}$icon_cam Camera^0^bananarchy-photo --menu
${_bt_line}$icon_cam Camera^0^xcamera
${_flash_line}$icon_net Networks^0^sxmo_networks.sh
$icon_mus Audio^0^sxmo_audio.sh
$icon_phn Modem^0^sxmo_appmenu.sh modem
+127
View File
@@ -0,0 +1,127 @@
#!/bin/sh
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2022 Sxmo Contributors
# title="$icon_dir Files"
case " $* " in
*" --select-only "*)
;;
*)
if command -v xfiles >/dev/null 2>&1; then
if [ $# -gt 0 ] && [ -d "$1" ]; then
exec xfiles "$1"
fi
exec xfiles
fi
;;
esac
. sxmo_common.sh
set -e
usage() {
printf "%s <FILES> [--date-sort] [--reverse-sort] [--select-only] [-h help]\n" \
"$(basename "$0")"
exit 1
}
DIR="$HOME"
SORT=
REVERSE=
SELECTONLY=0
while [ -n "${1:-}" ]; do
case "$1" in
--select-only)
SELECTONLY=1
;;
--date-sort)
SORT="--sort=t"
;;
--reverse-sort)
REVERSE="-r"
;;
-h)
usage
;;
*)
DIR="$1"
;;
esac
shift
done
cd "$DIR"
sort_loop() {
CHOICES="$([ -z "$SORT" ] && printf "date" || printf "name")\n$([ -z "$REVERSE" ] && printf "desc" || printf "asc")\n"
PICKED="$(
printf %b "$CHOICES" |
sxmo_dmenu.sh -p "Sort" -i
)"
case "$PICKED" in
"date")
SORT="--sort=t"
;;
"name")
SORT=
;;
"desc")
REVERSE="-r"
;;
"asc")
REVERSE=
;;
esac
}
while true; do
# shellcheck disable=SC2086
FILES="$(ls -1p $SORT $REVERSE)"
CHOICES="$(printf %b 'Reload\nOpen in terminal\nClose Menu\nSort By\n../\n*\n'"$FILES")"
DIR="$(basename "$(pwd)")"
TRUNCATED="$(printf %.7s "$DIR")"
if [ "$DIR" != "$TRUNCATED" ]; then
DIR="$TRUNCATED..."
fi
PICKED="$(
printf %b "$CHOICES" |
sxmo_dmenu.sh -p "$DIR" -i
)"
case "$PICKED" in
"Sort By")
sort_loop
;;
"Open in terminal")
exec sxmo_terminal.sh
;;
"Close Menu")
exit 0
;;
"Reload")
continue
;;
\*)
if [ -n "$SELECTONLY" ]; then
printf %s "Can't do this"
else
sxmo_open.sh -a ./*
fi
;;
*)
[ -d "$PICKED" ] && cd "$PICKED" && continue
if [ -f "$PICKED" ]; then
if [ "$SELECTONLY" -eq 1 ]; then
printf "%s" "$(pwd)/$PICKED" && exit
else
sxmo_open.sh -a "$PICKED"
fi
fi
;;
esac
done
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
if command -v xsms >/dev/null 2>&1; then
exec xsms "$@"
fi
exec /usr/bin/dwm-plugin sms "$@"
+1
View File
@@ -0,0 +1 @@
dwm-plugin
+1
View File
@@ -0,0 +1 @@
dwm-plugin
+1
View File
@@ -0,0 +1 @@
dwm-plugin
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -766,16 +766,16 @@ case "$WMCLASS" in
$icon_trm Terminal ^ 0 ^ st -e ${SHELL:-/bin/sh}
$icon_grd Apps ^ 0 ^ sxmo_appmenu.sh applications
$icon_grd Scripts ^ 0 ^ sxmo_appmenu.sh scripts
$icon_dir Files ^ 0 ^ sxmo_files.sh
$icon_dir Files ^ 0 ^ xfiles
$icon_phn Dialer ^ 0 ^ xdialer
$icon_phl Call Log ^ 0 ^ xcalllog
$icon_msg Texts ^ 0 ^ sxmo_modemtext.sh
$icon_msg Texts ^ 0 ^ xsms
$icon_usr Contacts ^ 0 ^ xcontacts
$(
bt_is_unblocked &&
printf %b "$icon_bth Bluetooth ^ 1 ^ sxmo_bluetoothmenu.sh"
)
$icon_cam Camera ^ 0 ^ bananarchy-photo --menu
$icon_cam Camera ^ 0 ^ xcamera
$(
brightness=""
if [ -r /sys/class/leds/white:torch/brightness ]; then
@@ -3,55 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2022 Sxmo Contributors
# This hook displays the sms log for a numbers passed as $1
LOGDIRNUM="$1"
# Wrap this in a terminal if not already done
if [ -z "$TERMNAME" ]; then
TERMNAME="$LOGDIRNUM SMS"
# If it's already open, switch to it.
if [ "$SXMO_WM" = "sway" ] && [ -z "$SSH_CLIENT" ]; then
regesc_termname="$(echo "$TERMNAME" | sed 's|+|\\+|g')"
if swaymsg -q "[title=\"^$regesc_termname\$\"]" focus; then
exit 0
fi
fi
export TERMNAME
exec sxmo_terminal.sh "$0" "$@"
if command -v xsms >/dev/null 2>&1; then
exec xsms tailtextlog "$@"
fi
# include common definitions
# shellcheck source=scripts/core/sxmo_common.sh
. sxmo_common.sh
mkcontactssedcmd() {
pnc find "$LOGDIRNUM" | while read -r NUMBER; do
CONTACT="$(sxmo_contacts.sh --name-or-number "$NUMBER")"
if [ ! "$CONTACT" = "$NUMBER" ]; then
printf %s "s/$NUMBER/$CONTACT/;"
fi
done
}
# Replace certain dates with human readable versions
TODAY="$(date +%F)"
YESTERDAY="$(date -d "- 1 day" +%F)"
TWO_DAYS="$(date -d "- 2 day" +%F)"
DATESEDCMD="s/ at ${TODAY}T/ today at /; s/ at ${YESTERDAY}T/ yesterday at /; s/ at ${TWO_DAYS}T/ two days ago at /; s/[-+][0-9][0-9][0-9][0-9]://; s/\(-[0-9][0-9]\)T\([0-9][0-9]\)/\1 \2/;"
# TODO
#ALIGNSEDCMD="s/^Sent/<right align>/;s/^Received/<left align>/;"
# Colorize Sent and Received lines
RECEIVED_COLOR="2"
SENT_COLOR="3"
COLORSEDCMD="s#^\(Sent.*\)#$(tput setaf "$SENT_COLOR")\1$(tput op)#;s#^\(Received.*\)#$(tput setaf "$RECEIVED_COLOR")\1$(tput op)#;"
# Replace phone numbers in the filename with contacts from contact book
CONTACTSSEDCMD="$(mkcontactssedcmd)"
tail -n9999 -f "$SXMO_LOGDIR/$LOGDIRNUM/sms.txt" |\
sed -e "$CONTACTSSEDCMD" -e "$DATESEDCMD" -e "$COLORSEDCMD"
exec /usr/bin/dwm-plugin sms tailtextlog "$@"
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
dwm-plugin
+1
View File
@@ -0,0 +1 @@
dwm-plugin
+1
View File
@@ -0,0 +1 @@
dwm-plugin
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+25 -4
View File
@@ -55,7 +55,9 @@ EOF
harfbuzz-dev \
pango-dev \
glib-dev \
cairo-dev >/dev/null
cairo-dev \
libjpeg-turbo-dev \
libpng-dev >/dev/null
export PKG_CONFIG_SYSROOT_DIR="$SYSROOT"
export PKG_CONFIG_LIBDIR="$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/share/pkgconfig"
@@ -97,10 +99,11 @@ EOF
dwm.c drw.c util.c $DWM_LIBS -o /bananarchy/prebuilt/sxmo-x11/usr/bin/dwm
$STRIP --strip-unneeded /bananarchy/prebuilt/sxmo-x11/usr/bin/dwm
echo "[6/6] Compiling DWM Plugins (xdrawer.so, dialer.so, calllog.so, contacts.so) and dwm-plugin runner..."
echo "[6/6] Compiling DWM Plugins (xdrawer, dialer, calllog, contacts, sms, files, camera) and dwm-plugin runner..."
mkdir -p /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins
PLUG_INCS="$(pkgconf --cflags x11 xft fontconfig freetype2 pango pangoxft alsa)"
PLUG_INCS="$(pkgconf --cflags x11 xft fontconfig freetype2 pango pangoxft alsa libjpeg libpng)"
PLUG_LIBS="$(pkgconf --libs x11 xft fontconfig freetype2 pango pangoxft alsa)"
MEDIA_LIBS="$(pkgconf --libs libjpeg libpng)"
$CXX $OPT_CFLAGS -fPIC -shared -std=c++17 -Wall -Wextra $PLUG_INCS \
/bananarchy/src/xdrawer/xdrawer.cpp $PLUG_LIBS \
@@ -118,11 +121,26 @@ EOF
/bananarchy/src/dwm-plugins/contacts_plugin.cpp $PLUG_LIBS \
-o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so
$CXX $OPT_CFLAGS -fPIC -shared -std=c++17 -Wall -Wextra $PLUG_INCS \
/bananarchy/src/dwm-plugins/sms_plugin.cpp $PLUG_LIBS \
-o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so
$CXX $OPT_CFLAGS -fPIC -shared -std=c++17 -Wall -Wextra $PLUG_INCS \
/bananarchy/src/dwm-plugins/files_plugin.cpp $PLUG_LIBS $MEDIA_LIBS \
-o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so
$CXX $OPT_CFLAGS -fPIC -shared -std=c++17 -Wall -Wextra $PLUG_INCS \
/bananarchy/src/dwm-plugins/camera_plugin.cpp $PLUG_LIBS $MEDIA_LIBS -lpthread \
-o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so
$STRIP --strip-unneeded \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so
$CC $OPT_CFLAGS -std=c99 -D_DEFAULT_SOURCE -Wall -Wextra \
/bananarchy/src/dwm-plugins/dwm_plugin_runner.c -ldl \
@@ -134,6 +152,9 @@ EOF
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xdialer
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xcalllog
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xcontacts
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xsms
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xfiles
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xcamera
mkdir -p /bananarchy/overlay/rootfs/usr/bin /bananarchy/overlay/rootfs/usr/lib/dwm/plugins
cp -a /bananarchy/prebuilt/sxmo-x11/usr/bin/. /bananarchy/overlay/rootfs/usr/bin/
+1 -14
View File
@@ -347,15 +347,7 @@ int calllog_run_main(int argc, char** argv) {
XSync(dpy, False);
auto showWindow = [&]() {
if (isPidFileAlive(getRuntimeFile("xdialer.pid"))) {
sendPluginCommand("dialer", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xcontacts.pid"))) {
sendPluginCommand("contacts", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
sendPluginCommand("xdrawer", "CLOSE");
}
closeOtherPlugins("calllog");
if (!visible) {
openStartMs = nowMs();
startupIgnoreUntilMs = openStartMs + 90;
@@ -457,11 +449,6 @@ int calllog_run_main(int argc, char** argv) {
if (ev.xexpose.count == 0) {
render();
}
} else if (ev.type == VisibilityNotify) {
if (ev.xvisibility.state != VisibilityUnobscured) {
XRaiseWindow(dpy, win);
XFlush(dpy);
}
} else if (ev.type == KeyRelease) {
KeySym sym = XLookupKeysym(&ev.xkey, 0);
if (sym == XK_Menu || sym == XF86XK_KbdLcdMenu1 ||
File diff suppressed because it is too large Load Diff
+9 -18
View File
@@ -692,15 +692,7 @@ int runContactsApp(int argc, char** argv) {
};
auto showWindow = [&](const std::string& arg) {
if (isPidFileAlive(getRuntimeFile("xdialer.pid"))) {
sendPluginCommand("dialer", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xcalllog.pid"))) {
sendPluginCommand("calllog", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
sendPluginCommand("xdrawer", "CLOSE");
}
closeOtherPlugins("contacts");
bool openNewMode = (arg.rfind("new:", 0) == 0);
std::string prefill = openNewMode ? arg.substr(4) : "";
@@ -844,13 +836,17 @@ int runContactsApp(int argc, char** argv) {
break;
case 1: // Send SMS
writeSoftkeySuppress(nowMs() + 350);
hideWindow(true);
runAsyncCmd("sxmo_modemtext.sh sendtextmenu '" + c.number + "'");
hideWindow(false);
if (!sendPluginCommand("sms", "OPEN send:" + c.number)) {
runAsyncArgv("xsms", "sendtextmenu", c.number.c_str());
}
break;
case 2: // Read Messages
writeSoftkeySuppress(nowMs() + 350);
hideWindow(true);
runAsyncCmd("sxmo_hook_tailtextlog.sh '" + c.number + "'");
hideWindow(false);
if (!sendPluginCommand("sms", "OPEN thread:" + c.number)) {
runAsyncArgv("xsms", "tailtextlog", c.number.c_str());
}
break;
case 3: // Open in Dialer
writeSoftkeySuppress(nowMs() + 300);
@@ -961,11 +957,6 @@ int runContactsApp(int argc, char** argv) {
if (ev.xexpose.count == 0) {
render();
}
} else if (ev.type == VisibilityNotify) {
if (ev.xvisibility.state != VisibilityUnobscured) {
XRaiseWindow(dpy, win);
XFlush(dpy);
}
} else if (ev.type == KeyRelease) {
KeySym sym = XLookupKeysym(&ev.xkey, 0);
if (ev.xkey.keycode == 19 || sym == XK_0) {
+1 -14
View File
@@ -386,15 +386,7 @@ int dialer_run_main(int argc, char** argv) {
XSync(dpy, False);
auto showWindow = [&](const std::string& arg) {
if (isPidFileAlive(getRuntimeFile("xcalllog.pid"))) {
sendPluginCommand("calllog", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xcontacts.pid"))) {
sendPluginCommand("contacts", "CLOSE");
}
if (isPidFileAlive(getRuntimeFile("xdrawer.pid"))) {
sendPluginCommand("xdrawer", "CLOSE");
}
closeOtherPlugins("dialer");
std::string cleanArg = normalizePhone(arg);
if (!visible) {
openStartMs = nowMs();
@@ -536,11 +528,6 @@ int dialer_run_main(int argc, char** argv) {
if (ev.xexpose.count == 0) {
render();
}
} else if (ev.type == VisibilityNotify) {
if (ev.xvisibility.state != VisibilityUnobscured) {
XRaiseWindow(dpy, win);
XFlush(dpy);
}
} else if (ev.type == KeyRelease) {
KeySym sym = XLookupKeysym(&ev.xkey, 0);
if (ev.xkey.keycode == 19 || sym == XK_0) {
+17
View File
@@ -41,6 +41,14 @@ static int try_send_plugin_fifo(const char *plugin_name, int sub_argc, char **su
} else if (sub_argc >= 2 && sub_argv[1] && strcmp(sub_argv[1], "newcontact") == 0) {
const char *num = (sub_argc >= 3 && sub_argv[2]) ? sub_argv[2] : "";
len = snprintf(cmd, sizeof(cmd), "OPEN new:%s\n", num);
} else if (sub_argc >= 2 && sub_argv[1] && strcmp(sub_argv[1], "sendtextmenu") == 0) {
const char *num = (sub_argc >= 3 && sub_argv[2]) ? sub_argv[2] : "";
len = snprintf(cmd, sizeof(cmd), "OPEN send:%s\n", num);
} else if (sub_argc >= 2 && sub_argv[1] &&
(strcmp(sub_argv[1], "tailtextlog") == 0 ||
strcmp(sub_argv[1], "conversationloop") == 0)) {
const char *num = (sub_argc >= 3 && sub_argv[2]) ? sub_argv[2] : "";
len = snprintf(cmd, sizeof(cmd), "OPEN thread:%s\n", num);
} else if (sub_argc >= 2 && sub_argv[1] && sub_argv[1][0]) {
len = snprintf(cmd, sizeof(cmd), "OPEN %s\n", sub_argv[1]);
} else {
@@ -73,6 +81,15 @@ static const char *resolve_plugin_name_from_argv0(const char *argv0) {
if (strcasecmp(base, "xcontacts") == 0 || strcasecmp(base, "contacts") == 0) {
return "contacts";
}
if (strcasecmp(base, "xsms") == 0 || strcasecmp(base, "sms") == 0) {
return "sms";
}
if (strcasecmp(base, "xfiles") == 0 || strcasecmp(base, "files") == 0) {
return "files";
}
if (strcasecmp(base, "xcamera") == 0 || strcasecmp(base, "camera") == 0) {
return "camera";
}
return NULL;
}
File diff suppressed because it is too large Load Diff
+269
View File
@@ -0,0 +1,269 @@
#ifndef DWM_PLUGINS_MEDIA_UTILS_H
#define DWM_PLUGINS_MEDIA_UTILS_H
#include <algorithm>
#include <cctype>
#include <csetjmp>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
#include <jpeglib.h>
#include <png.h>
namespace bananarchy {
struct JpegErrMgr {
struct jpeg_error_mgr pub;
jmp_buf setjmpBuf;
};
inline void jpegErrorExit(j_common_ptr cinfo) {
JpegErrMgr* myerr = reinterpret_cast<JpegErrMgr*>(cinfo->err);
longjmp(myerr->setjmpBuf, 1);
}
inline bool saveRgb24AsJpeg(const std::string& path,
const uint8_t* rgb,
int width,
int height,
int quality = 92) {
if (!rgb || width <= 0 || height <= 0) {
return false;
}
FILE* fp = std::fopen(path.c_str(), "wb");
if (!fp) {
return false;
}
struct jpeg_compress_struct cinfo {};
JpegErrMgr jerr {};
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpegErrorExit;
if (setjmp(jerr.setjmpBuf)) {
jpeg_destroy_compress(&cinfo);
std::fclose(fp);
return false;
}
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, fp);
cinfo.image_width = static_cast<JDIMENSION>(width);
cinfo.image_height = static_cast<JDIMENSION>(height);
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE);
jpeg_start_compress(&cinfo, TRUE);
int rowStride = width * 3;
while (cinfo.next_scanline < cinfo.image_height) {
JSAMPROW rowPtr =
const_cast<JSAMPROW>(rgb + cinfo.next_scanline * rowStride);
jpeg_write_scanlines(&cinfo, &rowPtr, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
std::fclose(fp);
return true;
}
inline bool loadJpegRgb24(const std::string& path,
std::vector<uint8_t>* outRgb,
int* outW,
int* outH) {
FILE* fp = std::fopen(path.c_str(), "rb");
if (!fp) {
return false;
}
struct jpeg_decompress_struct cinfo {};
JpegErrMgr jerr {};
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpegErrorExit;
if (setjmp(jerr.setjmpBuf)) {
jpeg_destroy_decompress(&cinfo);
std::fclose(fp);
return false;
}
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, fp);
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
jpeg_destroy_decompress(&cinfo);
std::fclose(fp);
return false;
}
// Use libjpeg fast DCT downscaling for large images so decoding is instant
if (cinfo.image_width > 960 || cinfo.image_height > 960) {
cinfo.scale_num = 1;
cinfo.scale_denom = 2;
}
cinfo.out_color_space = JCS_RGB;
jpeg_start_decompress(&cinfo);
int w = static_cast<int>(cinfo.output_width);
int h = static_cast<int>(cinfo.output_height);
if (w <= 0 || h <= 0 || w > 8192 || h > 8192) {
jpeg_destroy_decompress(&cinfo);
std::fclose(fp);
return false;
}
outRgb->resize(static_cast<size_t>(w) * h * 3);
int rowStride = w * 3;
while (cinfo.output_scanline < cinfo.output_height) {
JSAMPROW rowPtr = outRgb->data() + cinfo.output_scanline * rowStride;
jpeg_read_scanlines(&cinfo, &rowPtr, 1);
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
std::fclose(fp);
*outW = w;
*outH = h;
return true;
}
inline bool loadPngRgb24(const std::string& path,
std::vector<uint8_t>* outRgb,
int* outW,
int* outH) {
png_image image {};
image.version = PNG_IMAGE_VERSION;
if (!png_image_begin_read_from_file(&image, path.c_str())) {
return false;
}
image.format = PNG_FORMAT_RGB;
int w = static_cast<int>(image.width);
int h = static_cast<int>(image.height);
if (w <= 0 || h <= 0 || w > 8192 || h > 8192) {
png_image_free(&image);
return false;
}
outRgb->resize(PNG_IMAGE_SIZE(image));
if (!png_image_finish_read(&image, nullptr, outRgb->data(), 0, nullptr)) {
png_image_free(&image);
return false;
}
png_image_free(&image);
*outW = w;
*outH = h;
return true;
}
inline bool loadImageRgb24(const std::string& path,
std::vector<uint8_t>* outRgb,
int* outW,
int* outH) {
std::string low = path;
for (char& c : low) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
if (low.size() >= 4 && low.substr(low.size() - 4) == ".png") {
if (loadPngRgb24(path, outRgb, outW, outH)) {
return true;
}
}
if (loadJpegRgb24(path, outRgb, outW, outH)) {
return true;
}
if (loadPngRgb24(path, outRgb, outW, outH)) {
return true;
}
return false;
}
inline void scaleRgb24ToBgrxCanvas(const uint8_t* srcRgb,
int srcW,
int srcH,
uint32_t* dstBgrx,
int canvasW,
int canvasH,
uint32_t bgPixel = 0x111111) {
for (int i = 0; i < canvasW * canvasH; ++i) {
dstBgrx[i] = bgPixel;
}
if (!srcRgb || srcW <= 0 || srcH <= 0 || canvasW <= 0 || canvasH <= 0) {
return;
}
int drawW = canvasW;
int drawH = (srcH * canvasW) / srcW;
if (drawH > canvasH) {
drawH = canvasH;
drawW = (srcW * canvasH) / srcH;
}
drawW = std::max(1, drawW);
drawH = std::max(1, drawH);
int offX = (canvasW - drawW) / 2;
int offY = (canvasH - drawH) / 2;
for (int dy = 0; dy < drawH; ++dy) {
int sy = std::min(srcH - 1, (dy * srcH) / drawH);
const uint8_t* srow = srcRgb + static_cast<size_t>(sy) * srcW * 3;
uint32_t* drow = dstBgrx + static_cast<size_t>(offY + dy) * canvasW + offX;
for (int dx = 0; dx < drawW; ++dx) {
int sx = std::min(srcW - 1, (dx * srcW) / drawW);
const uint8_t* px = srow + sx * 3;
drow[dx] = (static_cast<uint32_t>(px[0]) << 16) |
(static_cast<uint32_t>(px[1]) << 8) |
static_cast<uint32_t>(px[2]);
}
}
}
inline void downscaleViewfinder800x600To240x180(const uint8_t* srcRgb,
uint32_t* dstBgrx,
int zoomFactor = 1) {
if (!srcRgb || !dstBgrx) {
return;
}
zoomFactor = std::clamp(zoomFactor, 1, 4);
int cropW = 800 / zoomFactor;
int cropH = 600 / zoomFactor;
int startX = (800 - cropW) / 2;
int startY = (600 - cropH) / 2;
for (int dy = 0; dy < 180; ++dy) {
int sy = startY + (dy * cropH) / 180;
const uint8_t* srow = srcRgb + static_cast<size_t>(sy) * 800 * 3;
uint32_t* drow = dstBgrx + static_cast<size_t>(dy) * 240;
for (int dx = 0; dx < 240; ++dx) {
int sx = startX + (dx * cropW) / 240;
const uint8_t* px = srow + sx * 3;
drow[dx] = (static_cast<uint32_t>(px[0]) << 16) |
(static_cast<uint32_t>(px[1]) << 8) |
static_cast<uint32_t>(px[2]);
}
}
}
inline std::string formatFileSize(off_t bytes) {
char buf[32];
if (bytes >= 1024LL * 1024LL * 1024LL) {
std::snprintf(buf, sizeof(buf), "%.1fG",
static_cast<double>(bytes) / (1024.0 * 1024.0 * 1024.0));
} else if (bytes >= 1024LL * 1024LL) {
std::snprintf(buf, sizeof(buf), "%.1fM",
static_cast<double>(bytes) / (1024.0 * 1024.0));
} else if (bytes >= 1024LL) {
std::snprintf(buf, sizeof(buf), "%.1fK",
static_cast<double>(bytes) / 1024.0);
} else {
std::snprintf(buf, sizeof(buf), "%lldB", static_cast<long long>(bytes));
}
return buf;
}
} // namespace bananarchy
#endif // DWM_PLUGINS_MEDIA_UTILS_H
+316 -2
View File
@@ -16,6 +16,7 @@
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <dirent.h>
#include <fcntl.h>
#include <fstream>
#include <sstream>
@@ -53,6 +54,21 @@ constexpr const char* kIconTrash = "\xef\x87\xb8"; // U+F1F8 delete/trash
constexpr const char* kIconSms = "\xef\x83\xa0"; // U+F0E0 envelope/SMS
constexpr const char* kIconList = "\xef\x80\xba"; // U+F03A list
constexpr const char* kIconCheck = "\xef\x80\x8c"; // U+F00C check
constexpr const char* kIconFolder = "\xef\x81\xbb"; // U+F07B folder
constexpr const char* kIconFolderOpen = "\xef\x81\xbc"; // U+F07C folder-open
constexpr const char* kIconFile = "\xef\x80\x96"; // U+F016 file
constexpr const char* kIconFileText = "\xef\x85\x9c"; // U+F15C file-text
constexpr const char* kIconImage = "\xef\x80\xbe"; // U+F03E image
constexpr const char* kIconMusic = "\xef\x80\x81"; // U+F001 music/audio
constexpr const char* kIconTerm = "\xef\x84\xa0"; // U+F120 terminal
constexpr const char* kIconCamera = "\xef\x80\xb0"; // U+F030 camera
constexpr const char* kIconFlash = "\xef\x83\xa7"; // U+F0E7 bolt/flash
constexpr const char* kIconSend = "\xef\x87\x98"; // U+F1D8 paper-plane
constexpr const char* kIconCopy = "\xef\x83\x85"; // U+F0C5 copy
constexpr const char* kIconInfo = "\xef\x81\x9a"; // U+F05A info-circle
constexpr const char* kIconRefresh = "\xef\x80\xa1"; // U+F021 refresh
constexpr const char* kIconSort = "\xef\x83\x9c"; // U+F0DC sort
constexpr const char* kIconEye = "\xef\x81\xae"; // U+F06E eye/view
inline int64_t nowMs() {
struct timespec ts {};
@@ -249,7 +265,9 @@ inline void runAsyncCmd(const std::string& cmd) {
}
}
inline void runAsyncArgv(const char* prog, const char* arg1 = nullptr) {
inline void runAsyncArgv(const char* prog,
const char* arg1 = nullptr,
const char* arg2 = nullptr) {
pid_t pid = ::fork();
if (pid == 0) {
::setsid();
@@ -262,7 +280,13 @@ inline void runAsyncArgv(const char* prog, const char* arg1 = nullptr) {
::close(nullfd);
}
}
if (arg1) {
if (arg1 && arg2) {
execlp(prog, prog, arg1, arg2, static_cast<char*>(nullptr));
std::string localBin = std::string("/home/user/.local/bin/") + prog;
execl(localBin.c_str(), prog, arg1, arg2, static_cast<char*>(nullptr));
std::string usrBin = std::string("/usr/bin/") + prog;
execl(usrBin.c_str(), prog, arg1, arg2, static_cast<char*>(nullptr));
} else if (arg1) {
execlp(prog, prog, arg1, static_cast<char*>(nullptr));
std::string localBin = std::string("/home/user/.local/bin/") + prog;
execl(localBin.c_str(), prog, arg1, static_cast<char*>(nullptr));
@@ -896,6 +920,296 @@ inline void placePhoneCall(const std::string& rawNumber) {
runAsyncArgv("sxmo_modemdial.sh", num.c_str());
}
inline void closeOtherPlugins(const char* selfPluginName) {
struct PluginRef {
const char* pidFile;
const char* name;
};
static const PluginRef kAllPlugins[] = {
{"xdrawer.pid", "xdrawer"},
{"xdialer.pid", "dialer"},
{"xcalllog.pid", "calllog"},
{"xcontacts.pid", "contacts"},
{"xsms.pid", "sms"},
{"xfiles.pid", "files"},
{"xcamera.pid", "camera"},
};
for (const auto& p : kAllPlugins) {
if (selfPluginName && std::strcmp(p.name, selfPluginName) == 0) {
continue;
}
if (isPidFileAlive(getRuntimeFile(p.pidFile))) {
sendPluginCommand(p.name, "CLOSE");
}
}
}
inline std::string getModemLogDirPath() {
const char* env = std::getenv("SXMO_LOGDIR");
if (env && env[0]) {
return env;
}
const char* xdg = std::getenv("XDG_DATA_HOME");
if (xdg && xdg[0]) {
return std::string(xdg) + "/sxmo/modem";
}
const char* home = std::getenv("HOME");
return std::string(home && home[0] ? home : "/home/user") +
"/.local/share/sxmo/modem";
}
struct SmsMessage {
bool incoming = true;
std::string sender;
std::string isoTime;
std::string displayTime;
time_t epoch = 0;
std::string text;
};
struct SmsThread {
std::string number;
std::string normNumber;
std::string contactName;
std::string t9Name;
std::string lowerName;
std::string lastPreview;
std::string lastDisplayTime;
time_t lastEpoch = 0;
bool lastIncoming = true;
bool hasDraft = false;
int messageCount = 0;
};
inline bool parseSmsHeaderLine(const std::string& line,
bool* outIncoming,
std::string* outSender,
std::string* outIsoTime) {
if (line.empty() || line.back() != ':') {
return false;
}
if (line.rfind("Received at ", 0) == 0 && line.size() > 13) {
*outIncoming = true;
outSender->clear();
*outIsoTime = line.substr(12, line.size() - 13);
return true;
}
if (line.rfind("Received from ", 0) == 0) {
size_t atPos = line.find(" at ");
if (atPos != std::string::npos && atPos + 4 < line.size() - 1) {
*outIncoming = true;
*outSender = line.substr(14, atPos - 14);
*outIsoTime = line.substr(atPos + 4, line.size() - (atPos + 4) - 1);
return true;
}
}
if (line.rfind("Sent at ", 0) == 0 && line.size() > 9) {
*outIncoming = false;
outSender->clear();
*outIsoTime = line.substr(8, line.size() - 9);
return true;
}
return false;
}
inline std::vector<SmsMessage> loadSmsThreadMessages(const std::string& number) {
std::vector<SmsMessage> msgs;
std::string path = getModemLogDirPath() + "/" + number + "/sms.txt";
std::ifstream in(path);
if (!in.is_open()) {
return msgs;
}
std::string line;
bool hasCur = false;
SmsMessage cur;
auto flushCur = [&]() {
if (!hasCur) {
return;
}
while (!cur.text.empty() &&
(cur.text.back() == '\n' || cur.text.back() == '\r')) {
cur.text.pop_back();
}
msgs.push_back(cur);
cur = SmsMessage{};
hasCur = false;
};
while (std::getline(in, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
bool inc = true;
std::string sender;
std::string iso;
if (parseSmsHeaderLine(line, &inc, &sender, &iso)) {
flushCur();
hasCur = true;
cur.incoming = inc;
cur.sender = sender.empty() ? number : sender;
cur.isoTime = iso;
cur.displayTime = formatShortTime(iso);
cur.epoch = parseIsoTime(iso);
cur.text.clear();
} else if (hasCur) {
if (!cur.text.empty()) {
cur.text.push_back('\n');
}
cur.text += line;
} else if (!trimStr(line).empty()) {
hasCur = true;
cur.incoming = true;
cur.sender = number;
cur.text = line;
}
}
flushCur();
return msgs;
}
inline std::string loadSmsDraft(const std::string& number) {
if (number.empty()) {
return "";
}
std::string path = getModemLogDirPath() + "/" + number + "/draft.txt";
std::ifstream in(path);
if (!in.is_open()) {
return "";
}
std::ostringstream ss;
ss << in.rdbuf();
std::string s = ss.str();
while (!s.empty() && (s.back() == '\n' || s.back() == '\r')) {
s.pop_back();
}
if (s == "Enter message here") {
return "";
}
return s;
}
inline bool saveSmsDraft(const std::string& number, const std::string& text) {
if (number.empty()) {
return false;
}
std::string dir = getModemLogDirPath() + "/" + number;
std::string path = dir + "/draft.txt";
if (trimStr(text).empty()) {
::unlink(path.c_str());
return true;
}
ContactBook::ensureParentDir(path);
std::ofstream out(path, std::ios::trunc);
if (!out.is_open()) {
return false;
}
out << text << "\n";
return true;
}
inline void removeSmsDraft(const std::string& number) {
if (number.empty()) {
return;
}
std::string path = getModemLogDirPath() + "/" + number + "/draft.txt";
::unlink(path.c_str());
}
inline bool deleteSmsThread(const std::string& number) {
if (number.empty()) {
return false;
}
std::string dir = getModemLogDirPath() + "/" + number;
std::string smsPath = dir + "/sms.txt";
std::string draftPath = dir + "/draft.txt";
::unlink(smsPath.c_str());
::unlink(draftPath.c_str());
::rmdir(dir.c_str());
return true;
}
inline std::vector<SmsThread> loadSmsThreads(const ContactBook& book) {
std::vector<SmsThread> threads;
std::string baseDir = getModemLogDirPath();
DIR* d = ::opendir(baseDir.c_str());
if (!d) {
return threads;
}
struct dirent* ent;
while ((ent = ::readdir(d)) != nullptr) {
std::string name = ent->d_name;
if (name.empty() || name[0] == '.') {
continue;
}
std::string subDir = baseDir + "/" + name;
struct stat st {};
if (::stat(subDir.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) {
continue;
}
std::string smsPath = subDir + "/sms.txt";
std::string draftPath = subDir + "/draft.txt";
bool hasSms = (::access(smsPath.c_str(), R_OK) == 0);
std::string draft = loadSmsDraft(name);
bool hasDraft = !draft.empty();
if (!hasSms && !hasDraft) {
continue;
}
auto msgs = loadSmsThreadMessages(name);
if (msgs.empty() && !hasDraft) {
continue;
}
SmsThread th;
th.number = name;
th.normNumber = normalizePhone(name);
th.contactName = book.resolveName(name);
std::string label = th.contactName.empty() ? name : th.contactName;
th.t9Name = toT9Digits(label);
th.lowerName = toLowerStr(label);
th.hasDraft = hasDraft;
th.messageCount = static_cast<int>(msgs.size());
if (!msgs.empty()) {
const auto& last = msgs.back();
th.lastIncoming = last.incoming;
th.lastDisplayTime = last.displayTime;
th.lastEpoch = last.epoch;
std::string prev = last.text;
for (char& c : prev) {
if (c == '\n' || c == '\r' || c == '\t') {
c = ' ';
}
}
th.lastPreview = hasDraft ? ("[Draft] " + draft) : prev;
} else {
th.lastIncoming = false;
th.lastPreview = "[Draft] " + draft;
struct stat dst {};
if (::stat(draftPath.c_str(), &dst) == 0) {
th.lastEpoch = dst.st_mtim.tv_sec;
}
}
if (th.lastEpoch == 0 && ::stat(smsPath.c_str(), &st) == 0) {
th.lastEpoch = st.st_mtim.tv_sec;
}
threads.push_back(th);
}
::closedir(d);
std::sort(threads.begin(), threads.end(),
[](const SmsThread& a, const SmsThread& b) {
if (a.lastEpoch != b.lastEpoch) {
return a.lastEpoch > b.lastEpoch;
}
return a.number < b.number;
});
return threads;
}
} // namespace bananarchy
#endif // DWM_PLUGINS_PHONE_DATA_H
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -103,7 +103,7 @@ static Key keys[] = {
/* Quick Navigation */
{ KeyPress, MODKEY, XK_p, spawn, SHCMD("sxmo_appmenu.sh") },
{ KeyPress, MODKEY, XK_d, spawn, SHCMD("sxmo_files.sh") },
{ KeyPress, MODKEY, XK_d, spawn, SHCMD("xfiles") },
{ KeyPress, MODKEY|ShiftMask, XK_Return, spawn, SHCMD("st") },
{ KeyPress, MODKEY, XK_b, togglebar, {0} },
{ KeyPress, MODKEY, XK_j, focusstack, {.i = +1 } },
+1 -1
View File
@@ -103,7 +103,7 @@ static Key keys[] = {
/* Quick Navigation */
{ KeyPress, MODKEY, XK_p, spawn, SHCMD("sxmo_appmenu.sh") },
{ KeyPress, MODKEY, XK_d, spawn, SHCMD("sxmo_files.sh") },
{ KeyPress, MODKEY, XK_d, spawn, SHCMD("xfiles") },
{ KeyPress, MODKEY|ShiftMask, XK_Return, spawn, SHCMD("st") },
{ KeyPress, MODKEY, XK_b, togglebar, {0} },
{ KeyPress, MODKEY, XK_j, focusstack, {.i = +1 } },
+3 -5
View File
@@ -704,6 +704,9 @@ static int xdrawer_run_main(int argc, char** argv) {
closeOtherPlugin("xdialer.pid", "/tmp/dwm_plugin_dialer.fifo");
closeOtherPlugin("xcalllog.pid", "/tmp/dwm_plugin_calllog.fifo");
closeOtherPlugin("xcontacts.pid", "/tmp/dwm_plugin_contacts.fifo");
closeOtherPlugin("xsms.pid", "/tmp/dwm_plugin_sms.fifo");
closeOtherPlugin("xfiles.pid", "/tmp/dwm_plugin_files.fifo");
closeOtherPlugin("xcamera.pid", "/tmp/dwm_plugin_camera.fifo");
openStartMs = nowMs();
startupIgnoreUntilMs = openStartMs + 180;
initialKeysReleased = (querySoftkeysDown(dpy) == 0);
@@ -795,11 +798,6 @@ static int xdrawer_run_main(int argc, char** argv) {
if (ev.xexpose.count == 0) {
render();
}
} else if (ev.type == VisibilityNotify) {
if (ev.xvisibility.state != VisibilityUnobscured) {
XRaiseWindow(dpy, win);
XFlush(dpy);
}
} else if (ev.type == KeyRelease) {
KeySym sym = XLookupKeysym(&ev.xkey, 0);
if (sym == XK_Menu || sym == XF86XK_KbdLcdMenu1 ||