Add xmusic tracker/audio player, xfiles media actions, ringtone volume & vibration fix

This commit is contained in:
Luxferre
2026-10-25 17:31:54 +02:00
parent ba2307d44c
commit 30c3e817dc
70 changed files with 2296 additions and 86 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ It uses a pure **X11 (`Xlibre` + `sxmo-de-dwm`)** stack (no Wayland) optimized f
- `src/dwm-plugins/`: Preloaded C++20 `240x320` DWM plugins and shared headers:
- `theme.h`: Unified `.Xresources` / `~/.xthemes/*.xth` parser, proportional font sizing (`theme.baseFontSize`), automatic shade derivation, and live Xft/Pango theme reloading (`PluginTheme`).
- `phone_data.h`: Shared X11 geometry (`240x296` below the 24px bar), FIFO IPC (`/tmp/dwm_plugin_<name>.fifo`), softkey chord detection, Sxmo TSV contact/call/SMS parsers, and `libjpeg`/`libpng` helpers.
- `dialer_plugin.cpp` (`dialer.so` / `xdialer`), `calllog_plugin.cpp` (`calllog.so` / `xcalllog`), `callscreen_plugin.cpp` (`callscreen.so` / `xcallscreen`), `contacts_plugin.cpp` (`contacts.so` / `xcontacts`), `sms_plugin.cpp` (`sms.so` / `xsms`), `files_plugin.cpp` (`files.so` / `xfiles`), `camera_plugin.cpp` (`camera.so` / `xcamera`), `themes_plugin.cpp` (`themes.so` / `xthemes`), `modem_plugin.cpp` (`modem.so` / `xmodem`), `networks_plugin.cpp` (`networks.so` / `xnetworks` / `xbluetooth`), `ringtones_plugin.cpp` (`ringtones.so` / `xringtones`), `wallpapers_plugin.cpp` (`wallpapers.so` / `xwallpapers` / `xbackground`).
- `dwm_plugin_runner.c`: Lightweight `/usr/bin/dwm-plugin` launcher symlinked by `xdrawer`, `xdialer`, `xcalllog`, `xcallscreen`, `xcontacts`, `xsms`, `xfiles`, `xcamera`, `xthemes`, `xmodem`, `xnetworks`, `xbluetooth`, `xringtones`, `xwallpapers`, `xbackground`, `dmenu`, and `xdmenu`.
- `dialer_plugin.cpp` (`dialer.so` / `xdialer`), `calllog_plugin.cpp` (`calllog.so` / `xcalllog`), `callscreen_plugin.cpp` (`callscreen.so` / `xcallscreen`), `contacts_plugin.cpp` (`contacts.so` / `xcontacts`), `sms_plugin.cpp` (`sms.so` / `xsms`), `files_plugin.cpp` (`files.so` / `xfiles`), `camera_plugin.cpp` (`camera.so` / `xcamera`), `themes_plugin.cpp` (`themes.so` / `xthemes`), `modem_plugin.cpp` (`modem.so` / `xmodem`), `networks_plugin.cpp` (`networks.so` / `xnetworks` / `xbluetooth`), `ringtones_plugin.cpp` (`ringtones.so` / `xringtones`), `wallpapers_plugin.cpp` (`wallpapers.so` / `xwallpapers` / `xbackground`), `music_plugin.cpp` (`music.so` / `xmusic` / `xplayer`).
- `dwm_plugin_runner.c`: Lightweight `/usr/bin/dwm-plugin` launcher symlinked by `xdrawer`, `xdialer`, `xcalllog`, `xcallscreen`, `xcontacts`, `xsms`, `xfiles`, `xcamera`, `xthemes`, `xmodem`, `xnetworks`, `xbluetooth`, `xringtones`, `xwallpapers`, `xbackground`, `xmusic`, `xplayer`, `dmenu`, and `xdmenu`.
- `src/xdrawer/`: Global phone controls overlay (`xdrawer.so` / `xdrawer`), toggled by pressing both softkeys simultaneously.
- `src/sxmo-st/`: Patched `st` terminal with HarfBuzz OpenType ligatures (`Fira Code`), `XIM` (`fcitx5-t9`), and live `SIGUSR1` `.Xresources` reload.
- `src/fcitx5-t9/`: 6-mode keypad input method (`abc`, `Abc`, `ABC`, `Sym`, `Ctl`, `123` + `*` long-press `Ctrl+` latch) with fixed bottom preedit bar (`Y=290, H=30`).
+2 -2
View File
@@ -25,12 +25,12 @@ Initial public release of **Bananarchy Linux**, focused on complete hardware and
### Preloaded native keypad applications
- **System and context menus (`dmenu`)**: resident menu daemon with instant (`< 2 ms`) wakeup, 10-line vertical layout, live T9 filtering, and built-in submenus and hardware toggles.
- **Global controls drawer (`xdrawer`)**: instant overlay toggled by pressing both softkeys together to adjust speaker volume, earpiece volume, ringer mode, flashlight, sound/microphone mute, screen brightness, and keypad backlight.
- **Global controls drawer (`xdrawer`)**: instant overlay toggled by pressing both softkeys together to adjust speaker volume, earpiece volume, ringtone volume, ringer mode, flashlight, sound/microphone mute, screen brightness, and keypad backlight.
- **Phone dialer (`xdialer`), call log (`xcalllog`), and call screen (`xcallscreen`)**: home-screen digit dialing with live T9 contact matching, interactive carrier USSD sessions (`*100#`, etc.), Dual-SIM `*#06#` IMEI inspection, call history filtering, and an in-call screen with speaker/earpiece switching, microphone mute, DTMF tones, and one-key WAV call recording (`~/Recordings/`).
- **Contacts (`xcontacts`) and SMS manager (`xsms`)**: full phonebook editor with direct Qualcomm QMI UIM SIM card contact import and export across SIM 1 and SIM 2 (`EF_ADN`), paired with a threaded SMS conversation view and multi-tap message composer with draft support.
- **Modem and Dual-SIM manager (`xmodem`)**: 4-tab cellular control center (`SIMs`, `Radio`, `AT`, `Log`) supporting physical SIM 1 / SIM 2 slot switching, SIM PIN unlock and PUK unblock wizards, PIN enable/disable/change, 2G/3G/4G radio mode selection, USSD requests, and a live AT command console.
- **Networks and Bluetooth manager (`xnetworks`)**: 3-tab connectivity manager for Wi-Fi scanning and WPA2 entry, Wi-Fi hotspot, cellular data and APN configuration, USB tethering, and Bluetooth pairing.
- **File manager (`xfiles`) and camera (`xcamera`)**: keypad directory browser with built-in text and image viewers, plus a live V4L2 camera viewfinder with digital zoom (`1x`/`2x`/`4x`), auto-flash, continuous torch, 3-second self-timer, and photo gallery.
- **File manager (`xfiles`), music and tracker player (`xmusic`), and camera (`xcamera`)**: keypad directory browser with built-in text and image viewers (including one-click actions to preview images, set any image as wallpaper, play audio, or install any audio file as ringtone), a 3-tab music and chiptune tracker module player (`WAV`, `MP3`, `OGG`, `FLAC`, `Opus`, `MOD`, `XM`, `IT`, `S3M`, and more), and a live V4L2 camera viewfinder with digital zoom (`1x`/`2x`/`4x`), auto-flash, continuous torch, 3-second self-timer, and photo gallery.
- **Themes (`xthemes`), wallpapers (`xwallpapers`), and ringtones (`xringtones`)**: unified `.Xresources` theming engine (`~/.xthemes/*.xth`) with 20 preinstalled color schemes, live zero-restart reload across all apps and terminals, interactive color/font customization, wallpaper scaling and preview, and ringtone/vibration settings.
### Keypad input, terminal, games, and developer tools
+13 -5
View File
@@ -25,7 +25,7 @@ This manual covers flashing, operating, and customizing **Bananarchy Linux** (`2
- Uses a lightweight cross-compiled **Xlibre** server with `modesetting` KMS, partial-rectangle `ShadowFB` SPI updates, and zero Wayland overhead.
- **Patched `dwm`** provides a `240x320` vertical bottom-stack (`bstack`) layout, a 24px top status bar, a transparent `"Menu"` / `"Names"` desktop softkey bar, a Conky desktop clock and status widget (which refreshes automatically when changing time, timezone, or wallpaper, or when logging out), and `< 2 ms` handling of physical slider (`SW_LID`) and wake keys.
2. **Preloaded resident DWM plugins (`< 2 ms` wakeup)**:
- Core phone applications (`dmenu`, `xdrawer`, `dialer`, `calllog`, `callscreen`, `contacts`, `sms`, `files`, `camera`, `themes`, `modem`, `networks`, `ringtones`, `wallpapers`) run as preloaded resident daemons (`dwm-plugin <name> --preload`) that sleep at 0% CPU and open instantaneously over FIFO/socket IPC.
- Core phone applications (`dmenu`, `xdrawer`, `dialer`, `calllog`, `callscreen`, `contacts`, `sms`, `files`, `camera`, `themes`, `modem`, `networks`, `ringtones`, `wallpapers`, `music`) run as preloaded resident daemons (`dwm-plugin <name> --preload`) that sleep at 0% CPU and open instantaneously over FIFO/socket IPC.
- All UI elements share a single **XRDB theming engine** (`~/.xthemes/*.xth` -> `~/.Xresources`) with 20 preinstalled color schemes, proportional font sizing (`theme.baseFontSize`), and live reload without restarting X11.
3. **First-class terminal (`st`) and 6-mode `fcitx5-t9` keypad input**:
- `st` is patched with HarfBuzz OpenType ligature shaping (`Fira Code` default) and live `.Xresources` reload.
@@ -120,7 +120,7 @@ You can switch layouts and focus using either physical keypad multi-presses or a
- `Mod4+Shift+q`: close the focused window.
#### 3.1.2 Preloaded native DWM applications
All fourteen phone applications are compiled as shared-object plugins (`/usr/lib/dwm/plugins/*.so`), preloaded at `dwm` startup, and awakened over `/tmp/dwm_plugin_<name>.fifo` in `< 2 ms`:
All fifteen phone applications are compiled as shared-object plugins (`/usr/lib/dwm/plugins/*.so`), preloaded at `dwm` startup, and awakened over `/tmp/dwm_plugin_<name>.fifo` in `< 2 ms`:
1. **System and context menus (`dmenu.so` / `dmenu` / `xdmenu`)**:
- **Open / close**: press **Left softkey (`Menu`)** for the **System menu** or **Right softkey (`Back`)** inside an application for its **Context menu**; press the same softkey or `Escape` to close.
@@ -129,7 +129,7 @@ All fourteen phone applications are compiled as shared-object plugins (`/usr/lib
2. **Global phone controls (`xdrawer.so` / `xdrawer`)**:
- **Open / close**: press **both softkeys simultaneously** (`KEY_MENU` + `KEY_BACK`) from anywhere, or select **XDrawer** from the System menu.
- **Controls**: use `Up`/`Down` (or `1`–`8`) to select **Speaker Volume**, **Earpiece Volume**, **Ringer Mode** (`Ring+Vib` / `Vibrate` / `Silent` / `DND`), **Flashlight**, **Sound**, **Microphone**, **Screen Brightness**, or **Keypad Backlight**; use `Left`/`Right` or `Return` to adjust or toggle.
- **Controls**: use `Up`/`Down` (or `1`–`9`) to select **Speaker**, **Earpiece**, **Ring Vol**, **Ringer** (`Ring+Vib` / `Vibrate` / `Silent` / `DND`), **Flashlight**, **Sound**, **Microphone**, **Screen**, or **Keypad**; use `Left`/`Right` or `Return` to adjust or toggle.
3. **Phone dialer (`dialer.so` / `xdialer`)**:
- **Open**: press any **`0`–`9`, `*`, or `#` key** on the empty desktop, or open **Dialer** from the System menu or Call Log.
@@ -166,7 +166,7 @@ All fourteen phone applications are compiled as shared-object plugins (`/usr/lib
8. **File manager (`files.so` / `xfiles`)**:
- **Open**: select **Files** from the System menu or press `Mod4+d`.
- **Controls and viewers**: navigate directories with `Up`/`Down`/`Left`/`Right`/`Return`, filter with `0`–`9`, toggle dotfiles with `*`, and cycle sort order (**Name** / **Date** / **Size**) with `#`. Includes built-in text and image (`libjpeg`/`libpng`) viewers plus file operations (**Edit in Nano**, **New Folder**, **New File**, **Rename**, **Copy**, **Cut / Move**, **Paste Here**, **Properties**, **Open Terminal Here**, **Delete**) on the **Left softkey**.
- **Controls and viewers**: navigate directories with `Up`/`Down`/`Left`/`Right`/`Return`, filter with `0`–`9`, toggle dotfiles with `*`, and cycle sort order (**Name** / **Date** / **Size**) with `#`. Pressing `Return` on an image opens the built-in image viewer (`*` toggles full-screen preview, **Left softkey (`Set Wall`)** installs and applies the image as the desktop wallpaper); pressing `Return` on an audio or tracker file opens it in **Music (`xmusic`)**. The **Left softkey (`Actions`)** menu provides **Preview Image**, **Set as Wallpaper**, **Play in Music Player**, **Set as Ringtone**, **View Text**, **Edit in Nano**, **New Folder**, **New File**, **Rename**, **Copy**, **Cut / Move**, **Paste Here**, **Properties**, **Open Terminal Here**, and **Delete**.
9. **Camera (`camera.so` / `xcamera`)**:
- **Open**: select **Camera** from the System menu or run `xcamera`.
@@ -193,12 +193,20 @@ All fourteen phone applications are compiled as shared-object plugins (`/usr/lib
13. **Ringtones and ringer manager (`ringtones.so` / `xringtones`)**:
- **Open**: select **Ringtones** from the System, Config, or Audio menu, or run `xringtones`.
- **Controls**: preview built-in and user ringtones (`~/Ringtones/`, `~/Music/`) with `*` or **Left softkey (`Play` / `Stop`)**, press `Return` to set the active ringtone, and switch to **`2:Settings`** to configure **Ringer Mode**, **Ring Duration**, **Ring Loop Count**, and **Vibrate Strength**.
- **Controls**: preview built-in and user ringtones (`~/Ringtones/`, `~/Music/`) with `*` or **Left softkey (`Play` / `Stop`)**, press `Return` to set the active ringtone, and switch to **`2:Settings`** to configure **Ringer Mode**, **Ring Volume**, **Ring Duration**, **Ring Loop Count**, and **Vibrate Strength**.
14. **Desktop wallpaper manager (`wallpapers.so` / `xwallpapers` / `xbackground`)**:
- **Open**: select **Wallpapers** from the System, Config, or Apps menu, or run `xwallpapers`.
- **Controls**: browse system and user wallpapers with a live portrait thumbnail, cycle scaling mode (`Fill`, `Scale`, `Fit`, `Center`, `Tile`) with `Left`/`Right` or `#`, toggle full-screen preview with `*`, and press `Return` to apply the wallpaper and immediately refresh the Conky desktop overlay.
15. **Music and tracker module player (`music.so` / `xmusic` / `xplayer`)**:
- **Open**: select **Music** from the System, Applications, or Audio menu, open any audio/tracker file from **Files (`xfiles`)**, or run `xmusic [file_or_dir]`.
- **Supported formats**: plays standard audio (`.wav`, `.mp3`, `.ogg`, `.flac`, `.opus`, `.m4a`, `.aac`) as well as classic chiptune/tracker modules (`.mod`, `.xm`, `.it`, `.s3m`, `.mptm`, `.stm`, `.med`, `.okt`, `.669`, `.ult`, `.far`, `.mtm`, `.ptm`, `.amf`, `.dsm`, `.imf`, `.gdm`, `.stx`, `.umx`, `.mo3`) via an embedded `mpv` + `FFmpeg` + `libopenmpt` backend that automatically pauses on incoming calls.
- **Three tabs (`1:Player`, `2:Queue`, `3:Files` — switch with `1`–`3` or `Left`/`Right` in Queue/Files)**:
- **`1:Player`**: displays format badge (`MOD`, `XM`, `MP3`, etc.), scrolling track title, artist/metadata, animated equalizer bars, elapsed/total progress bar, repeat mode (`Off`, `All`, `One`), shuffle state, and volume. Press `Return` or `5` to **Play/Pause**, `Left`/`Right` (`4`/`6`) to **Seek `-5s`/`+5s`**, `Up`/`Down` (`2`/`8`) to adjust **Volume**, `7`/`9` for **Prev/Next track**, `*` to cycle **Repeat**, `#` to toggle **Shuffle**, `0` to **Stop**, or **Left softkey (`Options`)** to install the current track as the phone ringtone.
- **`2:Queue`**: browse the active playlist queue, filter with `0`–`9`, press `Return` to jump to a track, `*` to remove a track, `#` to shuffle, or **Left softkey (`Actions`)** to set the selected track as ringtone or clear the queue.
- **`3:Files`**: browse the filesystem (`~/Music`, `/usr/share/sxmo/music`, `/usr/share/sxmo/ringtones`, or any folder), press `Return` to play a file and load its folder into the queue, `*` to append a file or folder to the queue, or `#` to play the entire current folder.
#### 3.1.3 Standalone keypad games (`Snake` and `Tetris`)
Two classic X11 games (`/usr/bin/snake` and `/usr/bin/tetris`) are accessible from **System menu -> Games**:
- **Snake (`snake`)**: steer with `2`/`8`/`4`/`6` or D-Pad; pause or restart with `5`/`Return`/`#`/`*`; quit with `Right softkey`/`BackSpace`/`Escape`.
+2 -2
View File
@@ -20,12 +20,12 @@
### Keypad-first user experience and native DWM plugins
Instead of spawning slow shell scripts for everyday phone tasks, Bananarchy runs a suite of preloaded C/C++ plugins inside DWM that sleep at 0% CPU and open in `< 2 ms`:
- **System and context menus (`dmenu`)**: instant full-screen 10-line vertical menus with live T9 filtering, built-in submenus, and hardware toggles.
- **Global controls drawer (`xdrawer`)**: press **both softkeys together** from anywhere to adjust speaker and earpiece volume, ringer mode, flashlight, sound and microphone mute, screen brightness, and keypad backlight.
- **Global controls drawer (`xdrawer`)**: press **both softkeys together** from anywhere to adjust speaker, earpiece, and ringtone volume, ringer mode, flashlight, sound and microphone mute, screen brightness, and keypad backlight.
- **Phone dialer (`xdialer`), call log (`xcalllog`), and call screen (`xcallscreen`)**: press any digit on the home screen to start dialing with live T9 contact matching, run interactive carrier USSD codes (`*100#`, etc.), or type `*#06#` to inspect both IMEIs. Incoming and active calls open a dedicated call screen with earpiece/speaker switching, microphone mute, DTMF keypad, and one-key call recording (`~/Recordings/`).
- **Contacts (`xcontacts`) and SMS manager (`xsms`)**: full phonebook editor with direct Qualcomm QMI SIM card contact import and export across SIM 1 and SIM 2, paired with a threaded SMS conversation view and multi-tap message composer.
- **Modem and Dual-SIM manager (`xmodem`)**: 4-tab cellular control center (`SIMs`, `Radio`, `AT`, `Log`) supporting physical SIM 1 / SIM 2 active slot switching, SIM PIN unlock and PUK unblock wizards, 2G/3G/4G radio mode selection, interactive USSD sessions, and a live AT command console.
- **Networks and Bluetooth manager (`xnetworks`)**: 3-tab connectivity manager for Wi-Fi networks, WPA2 password entry, Wi-Fi hotspot, USB tethering, mobile data and APN editing, and Bluetooth pairing.
- **File manager (`xfiles`) and camera (`xcamera`)**: keypad 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`), music and tracker player (`xmusic`), and camera (`xcamera`)**: keypad directory browser with built-in text and image viewers (plus one-click actions to play audio, preview images, set any image as wallpaper, or install any audio file as ringtone), a 3-tab music and chiptune tracker module player (`WAV`, `MP3`, `OGG`, `FLAC`, `Opus`, `MOD`, `XM`, `IT`, `S3M`, and more), and a live V4L2 camera viewfinder with digital zoom, flash/torch control, self-timer, and photo gallery.
- **Themes (`xthemes`), wallpapers (`xwallpapers`), and ringtones (`xringtones`)**: installable `.Xresources` color and font themes (`~/.xthemes/*.xth`, 20 preinstalled schemes) with live zero-restart reload across the entire UI and terminal, alongside dedicated managers for desktop wallpapers and call ringtones/vibration.
### Input, terminal, and developer tools
+3 -3
View File
@@ -4,8 +4,8 @@ VERSION_ID="@BANANARCHY_VERSION@"
VERSION="@BANANARCHY_VERSION@ (msm8909)"
ID="bananarchy"
ID_LIKE="postmarketos alpine"
HOME_URL="https://bananahackers.net/"
SUPPORT_URL="https://wiki.nura.eco/wiki/Nokia_8110_4G_(nokia-argon)"
BUG_REPORT_URL="https://gitlab.postmarketos.org/postmarketOS/pmaports/issues"
HOME_URL="https://bananarchy.top"
SUPPORT_URL="https://bananarchy.top"
BUG_REPORT_URL="https://bananarchy.top"
LOGO="bananarchy"
ANSI_COLOR="1;33"
@@ -43,6 +43,24 @@ if [ -z "$SXMO_NO_AUDIO" ]; then
# monitor for headphone for statusbar
superctl start sxmo_soundmonitor
(
sleep 1
spk_vol=100
if [ -r "$XDG_CONFIG_HOME/sxmo/speaker_vol" ]; then
read -r spk_vol < "$XDG_CONFIG_HOME/sxmo/speaker_vol" 2>/dev/null || spk_vol=100
fi
case "$spk_vol" in ''|*[!0-9]*) spk_vol=100 ;; esac
if [ "$spk_vol" -le 0 ]; then
amixer -c 0 cset "name='RX3 Digital Volume'" 0 >/dev/null 2>&1 || true
wpctl set-volume @DEFAULT_AUDIO_SINK@ 0% >/dev/null 2>&1 || true
else
rx3_hw=$(( 60 + spk_vol * 32 / 100 ))
amixer -c 0 cset "name='SPK DAC Switch'" 1 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX3 Digital Volume'" "$rx3_hw" >/dev/null 2>&1 || true
wpctl set-volume @DEFAULT_AUDIO_SINK@ "${spk_vol}%" >/dev/null 2>&1 || true
fi
sxmo_hook_statusbar.sh volume >/dev/null 2>&1 || true
) &
fi
# Periodically update some status bar components
Binary file not shown.
Binary file not shown.
Binary file not shown.
+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.
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.
Binary file not shown.
@@ -0,0 +1,27 @@
SectionDevice."Speaker" {
Comment "Speaker playback"
ConflictingDevice [
"Headphones"
"Earpiece"
]
EnableSequence [
cset "name='SPK DAC Switch' 1"
cset "name='RX3 MIX1 INP1' RX1"
## gain to +8dB
cset "name='RX3 Digital Volume' 92"
]
DisableSequence [
cset "name='RX3 Digital Volume' 0"
cset "name='SPK DAC Switch' 0"
cset "name='RX3 MIX1 INP1' ZERO"
]
Value {
PlaybackPCM "${var:WcdPlaybackPCM}"
PlaybackChannels 2
PlaybackPriority 200
}
}
@@ -50,6 +50,7 @@ write_line_app baobab "$icon_pie Baobab" "baobab"
write_line_app bluetuith "$icon_bth Bluetuith" "sxmo_terminal.sh bluetuith"
write_line_app calcurse "$icon_clk Calcurse" "sxmo_terminal.sh calcurse"
write_line_app xcamera "$icon_cam Camera" "xcamera"
write_line_app xmusic "$icon_mus Music" "xmusic"
write_line_app celluloid "$icon_mvi Celluloid" "celluloid"
write_line_app chatty "$icon_msg Chatty" "chatty"
([ -z "$WAYLAND_DISPLAY" ] && command -v clipmenu >/dev/null) && \
@@ -27,8 +27,8 @@ fi
# pm8xxx-vib shifts FF_RUMBLE strong_magnitude right by 8 bits (valid 256..65535)
case "${SXMO_VIBRATE_STRENGTH:-}" in
1) SXMO_VIBRATE_STRENGTH=20000 ;;
2|"") SXMO_VIBRATE_STRENGTH=40000 ;;
1) SXMO_VIBRATE_STRENGTH=8000 ;;
2|"") SXMO_VIBRATE_STRENGTH=32000 ;;
3) SXMO_VIBRATE_STRENGTH=65535 ;;
esac
@@ -54,15 +54,41 @@ if ! sxmo_modemcall.sh list_active_calls \
exit
fi
case "${SXMO_RINGVOL:-100}" in
''|*[!0-9]*) SXMO_RINGVOL=100 ;;
esac
[ "$SXMO_RINGVOL" -gt 100 ] && SXMO_RINGVOL=100
SAVED_RX3="$(amixer -c 0 cget "name='RX3 Digital Volume'" 2>/dev/null | awk -F= '/: values=/ {print $2; exit}')"
SAVED_SPK_VOL=100
if [ -r "$XDG_CONFIG_HOME/sxmo/speaker_vol" ]; then
read -r SAVED_SPK_VOL < "$XDG_CONFIG_HOME/sxmo/speaker_vol" 2>/dev/null || SAVED_SPK_VOL=100
fi
case "$SAVED_SPK_VOL" in ''|*[!0-9]*) SAVED_SPK_VOL=100 ;; esac
finish() {
trap - INT TERM EXIT
jobs -p | xargs -r kill
wait
jobs -p | xargs -r kill 2>/dev/null || true
wait 2>/dev/null || true
if [ -n "${SAVED_RX3:-}" ]; then
amixer -c 0 cset "name='RX3 Digital Volume'" "$SAVED_RX3" >/dev/null 2>&1 || true
fi
wpctl set-volume @DEFAULT_AUDIO_SINK@ "${SAVED_SPK_VOL}%" >/dev/null 2>&1 || true
exit
}
ring() {
if [ "${SXMO_RINGVOL:-100}" -le 0 ]; then
return 0
fi
ring_rx3=$(( 60 + SXMO_RINGVOL * 34 / 100 ))
mpv_vol=$(( SXMO_RINGVOL * 130 / 100 ))
amixer -c 0 cset "name='SPK DAC Switch'" 1 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX3 Digital Volume'" "$ring_rx3" >/dev/null 2>&1 || true
wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 >/dev/null 2>&1 || true
wpctl set-volume @DEFAULT_AUDIO_SINK@ 100% >/dev/null 2>&1 || true
mpv --no-resume-playback --quiet --no-video \
--volume-max=150 --volume="$mpv_vol" \
--loop="${SXMO_RINGNUMBER:-10}" "$SXMO_RINGTONE" >/dev/null &
}
@@ -90,6 +116,9 @@ if [ ! -f "$XDG_CONFIG_HOME"/sxmo/.noring ] && [ ! -f "$XDG_CONFIG_HOME"/sxmo/.n
sxmo_log "RING AND VIBRATE"
sxmo_playerctl.sh pause_all
if [ -p /tmp/dwm_plugin_music.fifo ]; then
echo "PAUSE" > /tmp/dwm_plugin_music.fifo 2>/dev/null || true
fi
ring
vibrate
@@ -99,6 +128,9 @@ elif [ ! -f "$XDG_CONFIG_HOME"/sxmo/.noring ] && [ -f "$XDG_CONFIG_HOME"/sxmo/.n
sxmo_log "RING ONLY"
sxmo_playerctl.sh pause_all
if [ -p /tmp/dwm_plugin_music.fifo ]; then
echo "PAUSE" > /tmp/dwm_plugin_music.fifo 2>/dev/null || true
fi
ring
@@ -30,12 +30,23 @@ if [ -f "$XDG_CONFIG_HOME"/sxmo/.busy ]; then
fi
fi
if [ -f "$XDG_CONFIG_HOME/sxmo/ringsettings" ]; then
# shellcheck disable=SC1091
. "$XDG_CONFIG_HOME/sxmo/ringsettings"
fi
if [ ! -f "$XDG_CONFIG_HOME"/sxmo/.noring ]; then
mpv --no-resume-playback --quiet --no-video "$SXMO_TEXTSOUND" >> /dev/null 2>&1 &
case "${SXMO_RINGVOL:-100}" in
''|*[!0-9]*) SXMO_RINGVOL=100 ;;
esac
if [ "$SXMO_RINGVOL" -gt 0 ]; then
mpv_vol=$(( SXMO_RINGVOL * 130 / 100 ))
mpv --no-resume-playback --quiet --no-video --volume-max=150 --volume="$mpv_vol" "$SXMO_TEXTSOUND" >> /dev/null 2>&1 &
fi
fi
if [ ! -f "$XDG_CONFIG_HOME"/sxmo/.novibrate ]; then
sxmo_vibrate 500 "${SXMO_VIBRATE_STRENGTH:-1}" &
sxmo_vibrate 500 "${SXMO_VIBRATE_STRENGTH:-2}" &
fi
wait
@@ -43,6 +43,24 @@ if [ -z "$SXMO_NO_AUDIO" ]; then
# monitor for headphone for statusbar
superctl start sxmo_soundmonitor
(
sleep 1
spk_vol=100
if [ -r "$XDG_CONFIG_HOME/sxmo/speaker_vol" ]; then
read -r spk_vol < "$XDG_CONFIG_HOME/sxmo/speaker_vol" 2>/dev/null || spk_vol=100
fi
case "$spk_vol" in ''|*[!0-9]*) spk_vol=100 ;; esac
if [ "$spk_vol" -le 0 ]; then
amixer -c 0 cset "name='RX3 Digital Volume'" 0 >/dev/null 2>&1 || true
wpctl set-volume @DEFAULT_AUDIO_SINK@ 0% >/dev/null 2>&1 || true
else
rx3_hw=$(( 60 + spk_vol * 32 / 100 ))
amixer -c 0 cset "name='SPK DAC Switch'" 1 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX3 Digital Volume'" "$rx3_hw" >/dev/null 2>&1 || true
wpctl set-volume @DEFAULT_AUDIO_SINK@ "${spk_vol}%" >/dev/null 2>&1 || true
fi
sxmo_hook_statusbar.sh volume >/dev/null 2>&1 || true
) &
fi
# Periodically update some status bar components
Binary file not shown.
Binary file not shown.
+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.
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.
Binary file not shown.
+13 -1
View File
@@ -158,6 +158,10 @@ EOF
/bananarchy/src/dwm-plugins/wallpapers_plugin.cpp $PLUG_LIBS $MEDIA_LIBS \
-o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/wallpapers.so
$CXX $OPT_CFLAGS -fPIC -shared -std=c++17 -Wall -Wextra $PLUG_INCS \
/bananarchy/src/dwm-plugins/music_plugin.cpp $PLUG_LIBS \
-o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/music.so
$STRIP --strip-unneeded \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so \
@@ -171,13 +175,19 @@ EOF
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/modem.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/networks.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/ringtones.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/wallpapers.so
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/wallpapers.so \
/bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/music.so
$CC $OPT_CFLAGS -std=c99 -D_DEFAULT_SOURCE -Wall -Wextra \
/bananarchy/src/dwm-plugins/dwm_plugin_runner.c -ldl \
-o /bananarchy/prebuilt/sxmo-x11/usr/bin/dwm-plugin
$STRIP --strip-unneeded /bananarchy/prebuilt/sxmo-x11/usr/bin/dwm-plugin
$CC $OPT_CFLAGS -std=c99 -D_DEFAULT_SOURCE -Wall -Wextra \
/bananarchy/src/dwm-plugins/sxmo_vibrate.c \
-o /bananarchy/prebuilt/sxmo-x11/usr/bin/sxmo_vibrate
$STRIP --strip-unneeded /bananarchy/prebuilt/sxmo-x11/usr/bin/sxmo_vibrate
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/dmenu
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xdmenu
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xdrawer
@@ -196,6 +206,8 @@ EOF
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xringtones
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xwallpapers
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xbackground
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xmusic
ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xplayer
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/
+4
View File
@@ -281,6 +281,10 @@ static const char *resolve_plugin_name_from_argv0(const char *argv0) {
strcasecmp(base, "xbackground") == 0 || strcasecmp(base, "background") == 0) {
return "wallpapers";
}
if (strcasecmp(base, "xmusic") == 0 || strcasecmp(base, "music") == 0 ||
strcasecmp(base, "xplayer") == 0) {
return "music";
}
return NULL;
}
+177 -20
View File
@@ -93,12 +93,28 @@ std::string getExtLower(const std::string& name) {
}
bool isImageExt(const std::string& ext) {
return ext == "jpg" || ext == "jpeg" || ext == "png";
return ext == "jpg" || ext == "jpeg" || ext == "png" || ext == "bmp" ||
ext == "gif" || ext == "webp" || ext == "ppm" || ext == "pgm" ||
ext == "pbm" || ext == "tif" || ext == "tiff" || ext == "ico" ||
ext == "svg" || ext == "avif" || ext == "heic";
}
bool isAudioExt(const std::string& ext) {
return ext == "mp3" || ext == "wav" || ext == "ogg" || ext == "oga" ||
ext == "opus" || ext == "flac" || ext == "m4a" || ext == "aac" ||
ext == "wma" || ext == "aiff" || ext == "wv" || ext == "ape" ||
ext == "spx" || ext == "mod" || ext == "xm" || ext == "it" ||
ext == "s3m" || ext == "mptm" || ext == "stm" || ext == "med" ||
ext == "okt" || ext == "669" || ext == "ult" || ext == "far" ||
ext == "mtm" || ext == "ptm" || ext == "amf" || ext == "dsm" ||
ext == "imf" || ext == "gdm" || ext == "stx" || ext == "umx" ||
ext == "mo3" || ext == "j2b" || ext == "psm" || ext == "dbm" ||
ext == "digi" || ext == "mdl" || ext == "mt2" || ext == "ams" ||
ext == "plm" || ext == "sfx";
}
bool isMediaExt(const std::string& ext) {
return ext == "mp3" || ext == "wav" || ext == "ogg" || ext == "flac" ||
ext == "m4a" || ext == "mp4" || ext == "mkv" || ext == "webm" ||
return isAudioExt(ext) || ext == "mp4" || ext == "mkv" || ext == "webm" ||
ext == "avi";
}
@@ -311,9 +327,17 @@ int runFilesApp(int argc, char** argv) {
dpy, visual, DefaultDepth(dpy, screen), ZPixmap, 0,
reinterpret_cast<char*>(imgCanvas.data()), kImgCanvasW, kImgCanvasH, 32,
kImgCanvasW * 4);
constexpr int kImgFullW = 240;
constexpr int kImgFullH = 270;
std::vector<uint32_t> imgFullCanvas(kImgFullW * kImgFullH, 0x111111);
XImage* xImgFullPreview = XCreateImage(
dpy, visual, DefaultDepth(dpy, screen), ZPixmap, 0,
reinterpret_cast<char*>(imgFullCanvas.data()), kImgFullW, kImgFullH, 32,
kImgFullW * 4);
int imgOrigW = 0;
int imgOrigH = 0;
bool imgValid = false;
bool imgFullscreen = false;
// Modal input state (New Folder / New File / Rename)
InputAction inputAction = InputAction::NewFolder;
@@ -487,19 +511,113 @@ int runFilesApp(int argc, char** argv) {
if (imgValid) {
scaleRgb24ToBgrxCanvas(rgb.data(), imgOrigW, imgOrigH, imgCanvas.data(),
kImgCanvasW, kImgCanvasH, 0x111111);
scaleRgb24ToBgrxCanvas(rgb.data(), imgOrigW, imgOrigH,
imgFullCanvas.data(), kImgFullW, kImgFullH,
0x111111);
} else {
std::fill(imgCanvas.begin(), imgCanvas.end(), 0x111111);
std::fill(imgFullCanvas.begin(), imgFullCanvas.end(), 0x111111);
}
mode = ViewMode::ImageView;
};
auto installAsWallpaper = [&](const std::string& srcPath,
const std::string& fileName) {
std::string targetPath = srcPath;
std::string wallDir = homeDir + "/Pictures/Wallpapers";
std::string picDir = homeDir + "/Pictures";
if (srcPath.rfind("/usr/share/wallpapers/", 0) != 0 &&
srcPath.rfind(wallDir + "/", 0) != 0 &&
srcPath.rfind(picDir + "/", 0) != 0) {
::mkdir(picDir.c_str(), 0755);
::mkdir(wallDir.c_str(), 0755);
std::string dst = wallDir + "/" + fileName;
if (copyRecursive(srcPath, dst)) {
targetPath = dst;
}
}
std::string cfgDir = homeDir + "/.config/sxmo";
const char* xdgCfg = std::getenv("XDG_CONFIG_HOME");
if (xdgCfg && *xdgCfg) cfgDir = std::string(xdgCfg) + "/sxmo";
::mkdir((homeDir + "/.config").c_str(), 0755);
::mkdir(cfgDir.c_str(), 0755);
{
std::ofstream ofs(cfgDir + "/wallpaper", std::ios::trunc);
if (ofs) ofs << targetPath << "\n";
}
std::string fehFlag = "--bg-fill";
{
std::ifstream ifs(cfgDir + "/wallpaper_mode");
std::string m;
if (ifs && std::getline(ifs, m)) {
m = toLowerStr(trimStr(m));
if (m == "max" || m == "--bg-max") fehFlag = "--bg-max";
else if (m == "scale" || m == "--bg-scale") fehFlag = "--bg-scale";
else if (m == "center" || m == "--bg-center") fehFlag = "--bg-center";
else if (m == "tile" || m == "--bg-tile") fehFlag = "--bg-tile";
}
}
runAsyncCmd("feh --no-fehbg " + fehFlag + " " + shellQuote(targetPath) +
" >/dev/null 2>&1; "
"PATH=\"$HOME/.config/sxmo/hooks:/usr/share/sxmo/default_hooks:$PATH\" "
"sxmo_hook_statusbar.sh time >/dev/null 2>&1");
statusBanner = "Wallpaper set: " + fileName;
statusBannerUntilMs = nowMs() + 2200;
};
auto installAsRingtone = [&](const std::string& srcPath,
const std::string& fileName) {
std::string targetPath = srcPath;
std::string ringDir = homeDir + "/Ringtones";
std::string musDir = homeDir + "/Music";
if (srcPath.rfind("/usr/share/sxmo/", 0) != 0 &&
srcPath.rfind(ringDir + "/", 0) != 0 &&
srcPath.rfind(musDir + "/", 0) != 0) {
::mkdir(ringDir.c_str(), 0755);
std::string dst = ringDir + "/" + fileName;
if (copyRecursive(srcPath, dst)) {
targetPath = dst;
}
}
std::string cfgDir = homeDir + "/.config/sxmo";
const char* xdgCfg = std::getenv("XDG_CONFIG_HOME");
if (xdgCfg && *xdgCfg) cfgDir = std::string(xdgCfg) + "/sxmo";
::mkdir((homeDir + "/.config").c_str(), 0755);
::mkdir(cfgDir.c_str(), 0755);
{
std::ofstream ofs(cfgDir + "/ringtone", std::ios::trunc);
if (ofs) ofs << targetPath << "\n";
}
statusBanner = "Ringtone set: " + fileName;
statusBannerUntilMs = nowMs() + 2200;
};
auto buildActionsList = [&]() {
actionsList.clear();
bool hasSel = !filteredItems.empty();
bool isParent = hasSel && filteredItems[selected].isParent;
if (hasSel) {
actionsList.push_back({1, kIconEye, "Open / View"});
if (!filteredItems[selected].isDir) {
std::string ext = getExtLower(filteredItems[selected].name);
if (isImageExt(ext)) {
actionsList.push_back({1, kIconEye, "Preview Image"});
actionsList.push_back({14, kIconImage, "Set as Wallpaper"});
} else if (isAudioExt(ext)) {
actionsList.push_back({1, kIconMusic, "Play in Music Player"});
actionsList.push_back({15, kIconBell, "Set as Ringtone"});
} else {
actionsList.push_back({1, kIconEye, "Open / View"});
}
} else {
actionsList.push_back({1, kIconFolderOpen, "Open Folder"});
}
}
if (!clipPath.empty()) {
actionsList.push_back(
@@ -540,6 +658,15 @@ int runFilesApp(int argc, char** argv) {
::unlink(activePidPath.c_str());
};
auto openInMusicPlayer = [&](const std::string& path) {
hideWindow();
if (isPluginDaemonAlive("music")) {
sendPluginCommand("music", "OPEN " + path);
} else {
runAsyncArgv("xmusic", path.c_str());
}
};
auto openSelectedItem = [&]() {
if (filteredItems.empty()) return;
const FileItem item = filteredItems[selected];
@@ -557,7 +684,11 @@ int runFilesApp(int argc, char** argv) {
}
std::string ext = getExtLower(item.name);
if (isImageExt(ext)) {
imgFullscreen = false;
openImageViewer(item);
} else if (isAudioExt(ext)) {
openInMusicPlayer(item.fullPath);
return;
} else if (isMediaExt(ext)) {
hideWindow();
runAsyncArgv("sxmo_open.sh", item.fullPath.c_str());
@@ -786,19 +917,25 @@ int runFilesApp(int argc, char** argv) {
drawSoftkeyBar("Edit", "Down", "Back");
} else if (mode == ViewMode::ImageView) {
XSetForeground(dpy, gc, pt.spec.bgHeader);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36);
drawPango(layoutIcon, &colGreen, 6, 1, 18, kIconImage);
drawPango(layoutMain, &colWhite, 28, 0, 20, viewingFileName, 145);
std::string dim = imgValid
? (std::to_string(imgOrigW) + "x" +
std::to_string(imgOrigH))
: "Decode failed";
drawPango(layoutSmall, &colMuted, 28, 19, 15, dim, 200);
if (imgFullscreen) {
XPutImage(dpy, pix, gc, xImgFullPreview, 0, 0, 0, 0, kImgFullW,
kImgFullH);
} else {
XSetForeground(dpy, gc, pt.spec.bgHeader);
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36);
drawPango(layoutIcon, &colGreen, 6, 1, 18, kIconImage);
drawPango(layoutMain, &colWhite, 28, 0, 20, viewingFileName, 145);
std::string dim = imgValid
? (std::to_string(imgOrigW) + "x" +
std::to_string(imgOrigH) + " ([*] Full)")
: "Decode failed";
drawPango(layoutSmall, &colMuted, 28, 19, 15, dim, 200);
XPutImage(dpy, pix, gc, xImgPreview, 0, 0, 0, 36, kImgCanvasW,
kImgCanvasH);
drawSoftkeyBar("Full", "Next", "Back");
XPutImage(dpy, pix, gc, xImgPreview, 0, 0, 0, 36, kImgCanvasW,
kImgCanvasH);
}
drawSoftkeyBar("Set Wall", "Next",
imgFullscreen ? "Header" : "Back");
} else if (mode == ViewMode::PropsView && !filteredItems.empty()) {
const auto& it = filteredItems[selected];
@@ -1027,6 +1164,14 @@ int runFilesApp(int argc, char** argv) {
} else if (actId == 13 && !filteredItems.empty()) {
confirmSelected = 0;
mode = ViewMode::ConfirmDelete;
} else if (actId == 14 && !filteredItems.empty()) {
installAsWallpaper(filteredItems[selected].fullPath,
filteredItems[selected].name);
mode = ViewMode::Browser;
} else if (actId == 15 && !filteredItems.empty()) {
installAsRingtone(filteredItems[selected].fullPath,
filteredItems[selected].name);
mode = ViewMode::Browser;
}
};
@@ -1077,9 +1222,13 @@ int runFilesApp(int argc, char** argv) {
}
} else if (mode == ViewMode::ImageView) {
if (isLeft) {
runAsyncArgv("feh", viewingFilePath.c_str());
installAsWallpaper(viewingFilePath, viewingFileName);
} else {
mode = ViewMode::Browser;
if (imgFullscreen) {
imgFullscreen = false;
} else {
mode = ViewMode::Browser;
}
}
} else if (mode == ViewMode::PropsView) {
mode = ViewMode::Browser;
@@ -1300,8 +1449,12 @@ int runFilesApp(int argc, char** argv) {
drawUI();
}
} else if (mode == ViewMode::ImageView) {
if (sym == XK_Left || sym == XK_KP_Left || sym == XK_Up ||
sym == XK_KP_Up) {
char d = mapDialKey(sym, kc, ev.xkey.state);
if (d == '*') {
imgFullscreen = !imgFullscreen;
drawUI();
} else if (sym == XK_Left || sym == XK_KP_Left || sym == XK_Up ||
sym == XK_KP_Up) {
stepImageInFolder(-1);
drawUI();
} else if (sym == XK_Right || sym == XK_KP_Right || sym == XK_Down ||
@@ -1395,6 +1548,10 @@ int runFilesApp(int argc, char** argv) {
xImgPreview->data = nullptr;
XDestroyImage(xImgPreview);
}
if (xImgFullPreview) {
xImgFullPreview->data = nullptr;
XDestroyImage(xImgFullPreview);
}
if (fifoFd >= 0) ::close(fifoFd);
::unlink(daemonPidPath.c_str());
return 0;
+72
View File
@@ -14,6 +14,8 @@
#include <jpeglib.h>
#include <png.h>
#include <sys/wait.h>
#include <unistd.h>
namespace bananarchy {
@@ -159,6 +161,73 @@ inline bool loadPngRgb24(const std::string& path,
return true;
}
inline bool loadFfmpegImageRgb24(const std::string& path,
std::vector<uint8_t>* outRgb,
int* outW,
int* outH) {
int pipefd[2];
if (::pipe(pipefd) != 0) {
return false;
}
pid_t pid = ::fork();
if (pid < 0) {
::close(pipefd[0]);
::close(pipefd[1]);
return false;
}
if (pid == 0) {
::close(pipefd[0]);
::dup2(pipefd[1], STDOUT_FILENO);
if (pipefd[1] != STDOUT_FILENO) ::close(pipefd[1]);
int devnull = ::open("/dev/null", O_RDWR);
if (devnull >= 0) {
::dup2(devnull, STDIN_FILENO);
::dup2(devnull, STDERR_FILENO);
if (devnull > 2) ::close(devnull);
}
::execlp("ffmpeg", "ffmpeg", "-v", "error", "-i", path.c_str(),
"-frames:v", "1", "-f", "image2pipe", "-vcodec", "png", "-",
static_cast<char*>(nullptr));
::_exit(127);
}
::close(pipefd[1]);
std::vector<uint8_t> pngBuf;
uint8_t chunk[4096];
ssize_t n = 0;
while ((n = ::read(pipefd[0], chunk, sizeof(chunk))) > 0) {
if (pngBuf.size() + static_cast<size_t>(n) > 16 * 1024 * 1024) break;
pngBuf.insert(pngBuf.end(), chunk, chunk + n);
}
::close(pipefd[0]);
int status = 0;
::waitpid(pid, &status, 0);
if (pngBuf.empty()) {
return false;
}
png_image image {};
image.version = PNG_IMAGE_VERSION;
if (!png_image_begin_read_from_memory(&image, pngBuf.data(), pngBuf.size())) {
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,
@@ -178,6 +247,9 @@ inline bool loadImageRgb24(const std::string& path,
if (loadPngRgb24(path, outRgb, outW, outH)) {
return true;
}
if (loadFfmpegImageRgb24(path, outRgb, outW, outH)) {
return true;
}
return false;
}
File diff suppressed because it is too large Load Diff
+3
View File
@@ -1156,6 +1156,7 @@ inline void closeOtherPlugins(const char* selfPluginName) {
{"xnetworks.pid", "networks"},
{"xringtones.pid", "ringtones"},
{"xwallpapers.pid", "wallpapers"},
{"xmusic.pid", "music"},
};
for (const auto& p : kAllPlugins) {
if (selfPluginName && std::strcmp(p.name, selfPluginName) == 0) {
@@ -1190,6 +1191,7 @@ inline bool isAnotherPluginActive(const char* selfPluginName) {
{"xnetworks.pid", "networks"},
{"xringtones.pid", "ringtones"},
{"xwallpapers.pid", "wallpapers"},
{"xmusic.pid", "music"},
};
for (const auto& p : kAllPlugins) {
if (selfPluginName && std::strcmp(p.name, selfPluginName) == 0) {
@@ -1233,6 +1235,7 @@ inline void broadcastThemeReload(const char* selfPluginName = nullptr) {
"networks",
"ringtones",
"wallpapers",
"music",
};
for (const char* name : kPlugins) {
if (selfPluginName && std::strcmp(name, selfPluginName) == 0) {
+38 -17
View File
@@ -43,6 +43,7 @@ struct RingtoneEntry {
struct RingSettings {
int ringerMode = 0; // 0 = Ring + Vibrate, 1 = Ring Only, 2 = Vibrate Only, 3 = Silent
int ringVol = 100;
int ringTimeSec = 20;
int ringLoops = 10;
int vibrateStrength = 1;
@@ -198,6 +199,8 @@ RingSettings loadRingSettings() {
s.ringLoops = val;
} else if (k == "SXMO_VIBRATE_STRENGTH" && val >= 1 && val <= 3) {
s.vibrateStrength = val;
} else if (k == "SXMO_RINGVOL" && val >= 0 && val <= 100) {
s.ringVol = (val / 5) * 5;
}
}
return s;
@@ -228,6 +231,7 @@ void saveRingSettings(const RingSettings& s) {
ofs << "SXMO_RINGTIME=" << s.ringTimeSec << "\n";
ofs << "SXMO_RINGNUMBER=" << s.ringLoops << "\n";
ofs << "SXMO_VIBRATE_STRENGTH=" << s.vibrateStrength << "\n";
ofs << "SXMO_RINGVOL=" << std::clamp(s.ringVol, 0, 100) << "\n";
}
runAsyncCmd("sxmo_hook_statusbar.sh audio >/dev/null 2>&1");
}
@@ -372,6 +376,16 @@ int runRingtonesApp(int argc, char** argv) {
return;
}
stopPreview();
settings = loadRingSettings();
int rvol = std::clamp(settings.ringVol, 0, 100);
int rx3 = (rvol <= 0) ? 0 : (60 + rvol * 34 / 100);
int mpvVol = rvol * 130 / 100;
std::string volArg = "--volume=" + std::to_string(mpvVol);
runAsyncCmd("amixer -c 0 cset \"name='SPK DAC Switch'\" 1 >/dev/null 2>&1; "
"amixer -c 0 cset \"name='RX3 Digital Volume'\" " +
std::to_string(rx3) + " >/dev/null 2>&1; "
"wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 >/dev/null 2>&1; "
"wpctl set-volume @DEFAULT_AUDIO_SINK@ 100% >/dev/null 2>&1");
pid_t pid = ::fork();
if (pid == 0) {
int devNull = ::open("/dev/null", O_RDWR);
@@ -382,7 +396,8 @@ int runRingtonesApp(int argc, char** argv) {
if (devNull > 2) ::close(devNull);
}
::execlp("mpv", "mpv", "--no-resume-playback", "--quiet",
"--no-video", "--loop=2", path.c_str(), nullptr);
"--no-video", "--volume-max=150", volArg.c_str(),
"--loop=2", path.c_str(), nullptr);
::_exit(127);
} else if (pid > 0) {
previewPid = pid;
@@ -532,9 +547,11 @@ int runRingtonesApp(int argc, char** argv) {
"Vibrate Only",
"Silent",
};
std::string rows[5][2] = {
std::string rows[6][2] = {
{"Ringer Mode",
"< " + std::string(modeLabels[settings.ringerMode % 4]) + " >"},
{"Ring Volume",
"< " + std::to_string(settings.ringVol) + "% >"},
{"Ring Duration",
"< " + std::to_string(settings.ringTimeSec) + " sec >"},
{"Ring Loop Count",
@@ -544,18 +561,18 @@ int runRingtonesApp(int argc, char** argv) {
{"Test Vibration", "Press OK to vibrate"},
};
for (int i = 0; i < 5; ++i) {
int ry = 32 + i * 42;
for (int i = 0; i < 6; ++i) {
int ry = 30 + i * 35;
bool isSel = (settingsRow == i);
XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgAltEven);
XFillRectangle(dpy, pix, gc, 4, ry, kWinWidth - 8, 38);
XFillRectangle(dpy, pix, gc, 4, ry, kWinWidth - 8, 33);
if (isSel) {
XSetForeground(dpy, gc, pt.spec.accent);
XDrawRectangle(dpy, pix, gc, 4, ry, kWinWidth - 9, 37);
XDrawRectangle(dpy, pix, gc, 4, ry, kWinWidth - 9, 32);
}
drawPango(layoutSmall, isSel ? &colWhite : &colMuted, 10, ry + 2, 17,
drawPango(layoutSmall, isSel ? &colWhite : &colMuted, 10, ry + 1, 15,
rows[i][0], kWinWidth - 20);
drawPango(layoutMain, isSel ? &colYellow : &colBlue, 10, ry + 18, 19,
drawPango(layoutMain, isSel ? &colYellow : &colBlue, 10, ry + 15, 17,
rows[i][1], kWinWidth - 20);
}
drawSoftkeyBar("Change", "Select", "Close");
@@ -629,6 +646,10 @@ int runRingtonesApp(int argc, char** argv) {
saveRingSettings(settings);
setBanner("Ringer mode updated");
} else if (settingsRow == 1) {
settings.ringVol = std::clamp(settings.ringVol + delta * 5, 0, 100);
saveRingSettings(settings);
setBanner("Ring volume: " + std::to_string(settings.ringVol) + "%");
} else if (settingsRow == 2) {
int curIdx = 2;
for (int i = 0; i < kNumRingTimeSteps; ++i) {
if (kRingTimeSteps[i] == settings.ringTimeSec) {
@@ -639,7 +660,7 @@ int runRingtonesApp(int argc, char** argv) {
curIdx = (curIdx + kNumRingTimeSteps + delta) % kNumRingTimeSteps;
settings.ringTimeSec = kRingTimeSteps[curIdx];
saveRingSettings(settings);
} else if (settingsRow == 2) {
} else if (settingsRow == 3) {
int curIdx = 2;
for (int i = 0; i < kNumRingLoopSteps; ++i) {
if (kRingLoopSteps[i] == settings.ringLoops) {
@@ -650,14 +671,14 @@ int runRingtonesApp(int argc, char** argv) {
curIdx = (curIdx + kNumRingLoopSteps + delta) % kNumRingLoopSteps;
settings.ringLoops = kRingLoopSteps[curIdx];
saveRingSettings(settings);
} else if (settingsRow == 3) {
} else if (settingsRow == 4) {
settings.vibrateStrength =
((settings.vibrateStrength - 1 + 3 + delta) % 3) + 1;
saveRingSettings(settings);
} else if (settingsRow == 4) {
} else if (settingsRow == 5) {
int mag = (settings.vibrateStrength == 1)
? 20000
: (settings.vibrateStrength == 3 ? 65535 : 40000);
? 8000
: (settings.vibrateStrength == 3 ? 65535 : 32000);
runAsyncCmd(
"for d in /dev/input/by-path/platform-vibrator-event "
"/dev/input/by-path/*vibrator*-event; do "
@@ -744,7 +765,7 @@ int runRingtonesApp(int argc, char** argv) {
}
if (sym == XK_Left || sym == XK_KP_Left) {
if (activeTab == TabId::Settings && settingsRow < 4) {
if (activeTab == TabId::Settings && settingsRow < 5) {
adjustSettingRow(-1);
} else {
activeTab = (activeTab == TabId::Ringtones) ? TabId::Settings
@@ -752,7 +773,7 @@ int runRingtonesApp(int argc, char** argv) {
}
render();
} else if (sym == XK_Right || sym == XK_KP_Right) {
if (activeTab == TabId::Settings && settingsRow < 4) {
if (activeTab == TabId::Settings && settingsRow < 5) {
adjustSettingRow(1);
} else {
activeTab = (activeTab == TabId::Ringtones) ? TabId::Settings
@@ -764,14 +785,14 @@ int runRingtonesApp(int argc, char** argv) {
selIdx = (selIdx + static_cast<int>(tones.size()) - 1) %
static_cast<int>(tones.size());
} else if (activeTab == TabId::Settings) {
settingsRow = (settingsRow + 4) % 5;
settingsRow = (settingsRow + 5) % 6;
}
render();
} else if (sym == XK_Down || sym == XK_KP_Down) {
if (activeTab == TabId::Ringtones && !tones.empty()) {
selIdx = (selIdx + 1) % static_cast<int>(tones.size());
} else if (activeTab == TabId::Settings) {
settingsRow = (settingsRow + 1) % 5;
settingsRow = (settingsRow + 1) % 6;
}
render();
} else if (sym == XK_Return || sym == XK_KP_Enter) {
+150
View File
@@ -0,0 +1,150 @@
#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>
static int read_configured_level(void) {
char path[512];
const char* cfg = getenv("XDG_CONFIG_HOME");
const char* home = getenv("HOME");
if (cfg && *cfg) {
snprintf(path, sizeof(path), "%s/sxmo/ringsettings", cfg);
} else if (home && *home) {
snprintf(path, sizeof(path), "%s/.config/sxmo/ringsettings", home);
} else {
snprintf(path, sizeof(path), "/home/user/.config/sxmo/ringsettings");
}
FILE* fp = fopen(path, "r");
if (!fp) return 0;
int level = 0;
char line[256];
while (fgets(line, sizeof(line), fp)) {
char* p = line;
while (*p == ' ' || *p == '\t') ++p;
if (strncmp(p, "SXMO_VIBRATE_STRENGTH=", 22) == 0) {
int v = atoi(p + 22);
if (v >= 1 && v <= 3) level = v;
}
}
fclose(fp);
return level;
}
static int resolve_magnitude(int raw_arg, int has_arg) {
int val = raw_arg;
if (!has_arg || (val == 1 && !getenv("SXMO_VIBRATE_STRENGTH"))) {
int cfg = read_configured_level();
if (cfg >= 1 && cfg <= 3) val = cfg;
}
if (val <= 1) return 8000;
if (val == 2) return 32000;
if (val == 3) return 65535;
if (val < 256) return val * 257;
if (val > 65535) return 65535;
return val;
}
static int open_vibrator_device(void) {
const char* env_dev = getenv("SXMO_VIBRATE_DEV");
if (env_dev && *env_dev) {
int fd = open(env_dev, O_RDWR | O_CLOEXEC);
if (fd >= 0) return fd;
}
int fd = open("/dev/input/by-path/platform-vibrator-event", O_RDWR | O_CLOEXEC);
if (fd >= 0) return fd;
glob_t g;
memset(&g, 0, sizeof(g));
if (glob("/dev/input/by-path/*vibrator*-event", 0, NULL, &g) == 0) {
for (size_t i = 0; i < g.gl_pathc; ++i) {
fd = open(g.gl_pathv[i], O_RDWR | O_CLOEXEC);
if (fd >= 0) {
globfree(&g);
return fd;
}
}
globfree(&g);
}
return -1;
}
int main(int argc, char** argv) {
if (argc < 2 || argc > 3) {
fprintf(stderr, "Usage: sxmo_vibrate duration_ms [strength_number]\n");
return 1;
}
char* endp = NULL;
errno = 0;
long duration_ms = strtol(argv[1], &endp, 10);
if (errno != 0 || !endp || *endp != '\0' || duration_ms <= 0) {
fprintf(stderr, "sxmo_vibrate: invalid duration\n");
return 1;
}
int raw_strength = 2;
int has_strength = 0;
if (argc == 3) {
endp = NULL;
errno = 0;
long s = strtol(argv[2], &endp, 10);
if (errno == 0 && endp && *endp == '\0' && s >= 1 && s <= 65535) {
raw_strength = (int)s;
has_strength = 1;
}
}
int magnitude = resolve_magnitude(raw_strength, has_strength);
int fd = open_vibrator_device();
if (fd < 0) {
fprintf(stderr, "sxmo_vibrate: cannot open vibrator device: %s\n",
strerror(errno));
return 1;
}
struct ff_effect effect;
memset(&effect, 0, sizeof(effect));
effect.type = FF_RUMBLE;
effect.id = -1;
effect.u.rumble.strong_magnitude = (unsigned short)magnitude;
effect.u.rumble.weak_magnitude = (unsigned short)magnitude;
if (ioctl(fd, EVIOCSFF, &effect) < 0) {
close(fd);
return 1;
}
struct input_event play;
memset(&play, 0, sizeof(play));
play.type = EV_FF;
play.code = (unsigned short)effect.id;
play.value = 3;
if (write(fd, &play, sizeof(play)) < 0) {
ioctl(fd, EVIOCRMFF, effect.id);
close(fd);
return 1;
}
struct timespec req;
req.tv_sec = duration_ms / 1000;
req.tv_nsec = (duration_ms % 1000) * 1000000L;
while (nanosleep(&req, &req) == -1 && errno == EINTR) {
}
play.value = 0;
(void)write(fd, &play, sizeof(play));
(void)ioctl(fd, EVIOCRMFF, effect.id);
close(fd);
return 0;
}
+8
View File
@@ -2275,6 +2275,9 @@ open_builtin_menu(const char *menu_name, int keep_idx)
snprintf(label, sizeof(label), "%s Camera", ICON_CAM);
add_menu_item(label, "@plugin:camera", 0);
snprintf(label, sizeof(label), "%s Music", ICON_MUS);
add_menu_item(label, "@plugin:music", 0);
snprintf(label, sizeof(label), "%s Themes", ICON_THM);
add_menu_item(label, "@plugin:themes", 0);
@@ -2337,6 +2340,8 @@ open_builtin_menu(const char *menu_name, int keep_idx)
add_menu_item(label, "@plugin:wallpapers", 0);
snprintf(label, sizeof(label), "%s Ringtones", ICON_BEL);
add_menu_item(label, "@plugin:ringtones", 0);
snprintf(label, sizeof(label), "%s Music", ICON_MUS);
add_menu_item(label, "@plugin:music", 0);
snprintf(label, sizeof(label), "%s System Menu", ICON_MNU);
add_menu_item(label, "@menu:sys", 1);
snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS);
@@ -2401,6 +2406,9 @@ open_builtin_menu(const char *menu_name, int keep_idx)
rdir = "/run/user/10000";
char pbuf[256];
snprintf(label, sizeof(label), "%s Music Player", ICON_MUS);
add_menu_item(label, "@plugin:music", 0);
snprintf(label, sizeof(label), "%s Volume & Controls (XDrawer)", ICON_MUS);
add_menu_item(label, "@plugin:xdrawer", 0);
+107 -28
View File
@@ -26,6 +26,7 @@
#include <cstring>
#include <ctime>
#include <fcntl.h>
#include <fstream>
#include <poll.h>
#include <sstream>
#include <string>
@@ -42,7 +43,7 @@ constexpr int kWinX = 0;
constexpr int kWinY = kTopBarHeight;
constexpr int kWinWidth = kScreenWidth;
constexpr int kWinHeight = kScreenHeight - kTopBarHeight; // 296px flush below 24px dwm bar
constexpr int kRowCount = 8;
constexpr int kRowCount = 9;
// Nerd Font PUA icons matching sxmo_hook_icons.sh & dwm ("Fira Code, DejaVuSansM Nerd Font")
constexpr const char* kIconSpeaker = "\xef\x80\xa8"; // U+F028 speaker
@@ -239,12 +240,13 @@ class AlsaControl {
enum class ControlId {
SpeakerVol = 0,
EarpieceVol = 1,
Flashlight = 2,
SoundMute = 3,
MicMute = 4,
RingerMode = 5,
ScreenBright = 6,
KbdBright = 7,
RingVol = 2,
RingerMode = 3,
Flashlight = 4,
SoundMute = 5,
MicMute = 6,
ScreenBright = 7,
KbdBright = 8,
};
struct ControlItem {
@@ -282,31 +284,55 @@ class PhoneController {
void refreshAll() {
items_.clear();
// 1. Speaker volume (0..100%, mapped to ALSA RX3 Digital Volume 0..84)
long rx3 = alsa_.getInteger("RX3 Digital Volume", 84);
int spkPct = std::clamp(static_cast<int>(std::lround((rx3 * 100.0) / 84.0)), 0, 100);
// 1. Speaker volume (0..100%, mapped to ALSA RX3 Digital Volume 60..92 + PipeWire 0..100%)
int spkPct = readIntFile(getSxmoCfgDir() + "/speaker_vol", -1);
if (spkPct < 0 || spkPct > 100) {
long rx3 = alsa_.getInteger("RX3 Digital Volume", 92);
if (rx3 <= 0) {
spkPct = 0;
} else if (rx3 >= 84) {
spkPct = 100;
} else {
spkPct = std::clamp(static_cast<int>(std::lround(((rx3 - 60) * 100.0) / 32.0)), 5, 100);
}
}
spkPct = (spkPct / 5) * 5;
items_.push_back({ControlId::SpeakerVol, kIconSpeaker, kIconSoundOff,
"Speaker", true, 0, 100, 5, spkPct});
// 2. Earpiece volume (0..100%, mapped to ALSA RX1 Digital Volume 0..84)
// 2. Earpiece volume (0..100%, mapped to ALSA RX1 Digital Volume 58..90)
long rx1 = alsa_.getInteger("RX1 Digital Volume", 84);
int earPct = std::clamp(static_cast<int>(std::lround((rx1 * 100.0) / 84.0)), 0, 100);
int earPct = 100;
if (rx1 <= 0) {
earPct = 0;
} else if (rx1 < 84) {
earPct = std::clamp(static_cast<int>(std::lround((rx1 * 100.0) / 84.0)), 5, 100);
}
earPct = (earPct / 5) * 5;
items_.push_back({ControlId::EarpieceVol, kIconEarpiece, kIconEarpiece,
"Earpiece", true, 0, 100, 5, earPct});
// 3. Flashlight on/off (toggle)
// 3. Ringtone volume (0..100%, stored as SXMO_RINGVOL in ~/.config/sxmo/ringsettings)
int ringPct = readRingVolume();
items_.push_back({ControlId::RingVol, kIconBell, kIconBellSlash,
"Ring Vol", true, 0, 100, 5, ringPct});
// 4. Ringer Mode (0=Ring+Vib, 1=Vibrate, 2=Silent, 3=DND)
int ringerVal = readRingerMode();
items_.push_back({ControlId::RingerMode, kIconBell, kIconBellSlash,
"Ringer", false, 0, 3, 1, ringerVal});
// 5. Flashlight on/off (toggle)
int torchCur = readIntFile(torchDir_ + "/brightness", 0);
items_.push_back({ControlId::Flashlight, kIconFlashlight, kIconFlashlight,
"Flashlight", false, 0, 1, 1, (torchCur > 0) ? 1 : 0});
// 4. Sound on/off (toggle)
// 6. Sound on/off (toggle)
bool spkOn = alsa_.getBoolean("SPK DAC Switch", true);
items_.push_back({ControlId::SoundMute, kIconSoundOn, kIconSoundOff,
"Sound", false, 0, 1, 1, spkOn ? 1 : 0});
// 5. Microphone on/off (toggle)
// 7. Microphone on/off (toggle)
unsigned int dec1 = alsa_.getEnum("DEC1 MUX", 2);
if (dec1 > 0) {
savedMicMux_ = dec1;
@@ -317,18 +343,13 @@ class PhoneController {
items_.push_back({ControlId::MicMute, kIconMicOn, kIconMicOff,
"Microphone", false, 0, 1, 1, (dec1 > 0) ? 1 : 0});
// 6. Ringer Mode (0=Ring+Vib, 1=Vibrate, 2=Silent, 3=DND)
int ringerVal = readRingerMode();
items_.push_back({ControlId::RingerMode, kIconBell, kIconBellSlash,
"Ringer", false, 0, 3, 1, ringerVal});
// 7. Screen brightness (slider 1..screenMax_)
// 8. Screen brightness (slider 1..screenMax_)
int scrCur = std::clamp(readIntFile(backlightDir_ + "/brightness", screenMax_), 1, screenMax_);
int scrStep = std::max(1, screenMax_ / 14);
items_.push_back({ControlId::ScreenBright, kIconBrightness, kIconBrightness,
"Screen", true, 1, screenMax_, scrStep, scrCur});
// 7. Keyboard brightness (slider if max_brightness > 1, toggle otherwise)
// 9. Keyboard brightness (slider if max_brightness > 1, toggle otherwise)
int kbdCur = std::clamp(readIntFile(kbdDir_ + "/brightness", 0), 0, kbdMax_);
if (kbdSupportsSlider_) {
int kbdStep = std::max(1, kbdMax_ / 10);
@@ -395,25 +416,81 @@ class PhoneController {
return 0; // Ring + Vibrate
}
static int readRingVolume() {
std::ifstream ifs(getSxmoCfgDir() + "/ringsettings");
std::string line;
while (std::getline(ifs, line)) {
auto eq = line.find('=');
if (eq == std::string::npos) continue;
std::string k = line.substr(0, eq);
std::string v = line.substr(eq + 1);
if (k == "SXMO_RINGVOL") {
int val = std::clamp(std::atoi(v.c_str()), 0, 100);
return (val / 5) * 5;
}
}
return 100;
}
static void writeRingVolume(int ringVol) {
std::string cfg = getSxmoCfgDir();
::mkdir(cfg.c_str(), 0755);
std::string path = cfg + "/ringsettings";
int ringTime = 20;
int ringLoops = 10;
int vibStrength = 2;
{
std::ifstream ifs(path);
std::string line;
while (std::getline(ifs, line)) {
auto eq = line.find('=');
if (eq == std::string::npos) continue;
std::string k = line.substr(0, eq);
int val = std::atoi(line.substr(eq + 1).c_str());
if (k == "SXMO_RINGTIME" && val >= 5 && val <= 120) ringTime = val;
else if (k == "SXMO_RINGNUMBER" && val >= 1 && val <= 100) ringLoops = val;
else if (k == "SXMO_VIBRATE_STRENGTH" && val >= 1 && val <= 3) vibStrength = val;
}
}
std::ofstream ofs(path, std::ios::trunc);
if (ofs) {
ofs << "SXMO_RINGTIME=" << ringTime << "\n";
ofs << "SXMO_RINGNUMBER=" << ringLoops << "\n";
ofs << "SXMO_VIBRATE_STRENGTH=" << vibStrength << "\n";
ofs << "SXMO_RINGVOL=" << std::clamp(ringVol, 0, 100) << "\n";
}
}
void apply(ControlItem& it) {
switch (it.id) {
case ControlId::SpeakerVol: {
long hw = std::lround((it.value * 84.0) / 100.0);
std::string cfg = getSxmoCfgDir();
::mkdir(cfg.c_str(), 0755);
writeIntFile(cfg + "/speaker_vol", it.value);
long hw = (it.value <= 0) ? 0 : (60 + std::lround((it.value * 32.0) / 100.0));
if (it.value > 0) {
alsa_.setBoolean("SPK DAC Switch", true);
}
alsa_.setInteger("RX3 Digital Volume", hw);
char cmd[128];
char cmd[160];
std::snprintf(
cmd, sizeof(cmd),
"wpctl set-mute @DEFAULT_AUDIO_SINK@ %d >/dev/null 2>&1; "
"wpctl set-volume @DEFAULT_AUDIO_SINK@ %d%% >/dev/null 2>&1; "
"sxmo_hook_statusbar.sh audio >/dev/null 2>&1",
it.value);
"sxmo_hook_statusbar.sh volume >/dev/null 2>&1",
(it.value <= 0) ? 1 : 0, it.value);
runAsyncCmd(cmd);
break;
}
case ControlId::EarpieceVol: {
long hw = std::lround((it.value * 84.0) / 100.0);
long hw = (it.value <= 0) ? 0 : (58 + std::lround((it.value * 32.0) / 100.0));
alsa_.setInteger("RX1 Digital Volume", hw);
break;
}
case ControlId::RingVol: {
writeRingVolume(it.value);
break;
}
case ControlId::Flashlight: {
int v = it.value ? torchMax_ : 0;
if (!writeIntFile(torchDir_ + "/brightness", v)) {
@@ -994,7 +1071,9 @@ static int xdrawer_run_main(int argc, char** argv) {
case XK_4:
case XK_5:
case XK_6:
case XK_7: {
case XK_7:
case XK_8:
case XK_9: {
size_t idx = static_cast<size_t>(sym - XK_1);
if (idx < count) {
selected = idx;