diff --git a/MANUAL.md b/MANUAL.md index 1e590ce..739a660 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -29,7 +29,7 @@ This manual covers flashing, operating, and customizing **Bananarchy Linux** (`v - **Patched `sxmo-st` (`st`) with HarfBuzz ligatures & XIM**: patched with `libharfbuzz` (`hb.c`/`hb.h`) shaping (`liga`, `calt`, `clig`, `dlig`) so multi-character programming operators (`->`, `=>`, `!=`, `<=`, `>=`, `===`, `::`, `//`, `/*`) render as continuous ligatures at `pixelsize=18`, alongside full `XIM` (`fcitx5-t9`) input support. - **Patched `sxmo-dmenu` plugin (`dmenu.so` / `dmenu` / `xdmenu`) (`< 2 ms` popup/close)**: preloaded resident DWM plugin rendering a full-screen `240x296` 10-line vertical menu (positioned flush at `Y = 24..320` right below the 24px `dwm` bar, with a `26px` search bar and ten `27px` item rows) with a `#383838` top search bar, `#005577` prompt badge, `#1c1c1c` item list, `XIM` (`XSetLocaleModifiers("")`, `XSetICFocus`, `Xutf8LookupString`) for live `fcitx5-t9` search filtering and `#` mode switching, C-level focus preservation (`prevfocuswin`), built-in `< 0.5 ms` in-place Sxmo menu transitions (`Sys`, `Apps`, `Scripts`, `Modem`, `Config`, `Power`, `St`, `Nano`), and a `/tmp/dwm_plugin_dmenu.sock` IPC server for all `/usr/bin/dmenu` and `sxmo_dmenu.sh` calls. - **Patched `sxmo-dwm` (`dwm`) (`< 2 ms` display wake/unlock & native plugin system)**: configured for `240x320` (`bh = 24`, `#323232` top bar, `#005577` active tag/border, vertical `bstack` layout default, overflow-protected Pango status bar rendering keeping the right-aligned clock always visible, and a transparent-backgrounded bottom desktop softkey bar displaying `"Menu"` and `"Names"` over the wallpaper). Directly polls `/dev/input/by-path/platform-gpio-keys-event` (`SW_LID`) and intercepts locked-state wake keys (`XF86PowerOff`, `XF86PickupPhone`, `XF86KbdLcdMenu1`, `XF86KbdLcdMenu2`) in C to toggle `/sys/class/backlight/backlight/brightness` and `/sys/class/leds/white:kbd_backlight/brightness` in `< 2 ms` before running Sxmo state hooks asynchronously. - - **Preloaded DWM plugins (`src/sxmo-dmenu/`, `src/dwm-plugins/` & `src/xdrawer/`, `< 2 ms` FIFO/socket wakeup)**: `dwm` dynamically loads `.so` plugins from `/usr/lib/dwm/plugins/` and `~/.local/lib/dwm/plugins/` (`dmenu.so`, `xdrawer.so`, `dialer.so`, `calllog.so`, `contacts.so`, `sms.so`, `files.so`, `camera.so`) and preloads them as resident background daemons (`dwm-plugin --preload`) at startup. Each daemon keeps its X11 window, offscreen `Pixmap`, Xft/Pango layouts, and `stat()`-cached Sxmo TSV state initialized in memory while sleeping at 0% CPU on `/tmp/dwm_plugin_.fifo`. + - **Preloaded DWM plugins (`src/sxmo-dmenu/`, `src/dwm-plugins/` & `src/xdrawer/`, `< 2 ms` FIFO/socket wakeup & unified `.Xresources` theming)**: `dwm` dynamically loads `.so` plugins from `/usr/lib/dwm/plugins/` and `~/.local/lib/dwm/plugins/` (`dmenu.so`, `xdrawer.so`, `dialer.so`, `calllog.so`, `contacts.so`, `sms.so`, `files.so`, `camera.so`, `themes.so`) and preloads them as resident background daemons (`dwm-plugin --preload`) at startup. Each daemon keeps its X11 window, offscreen `Pixmap`, Xft/Pango layouts, and `stat()`-cached Sxmo TSV state initialized in memory while sleeping at 0% CPU on `/tmp/dwm_plugin_.fifo`. Every UI element across `dwm`, `dmenu`, `st`, and all C++ plugins obeys a single unified color and font scheme configured via `~/.Xresources` (`theme.*`, `dwm.*`, `dmenu.*`, `st.*`), with proportional font sizing derived from `theme.baseFontSize` (`12px` / `15px` / `16px` / `18px` at default `15`) and instant live reload via `xthemes`. 2. **Multi-mode `fcitx5-t9` keypad engine (`src/fcitx5-t9/`)**: - Supports 6 input modes cycled with `#` (`XF86NumericPound`): `abc`, `Abc`, `ABC`, `Sym`, `Ctl`, and `123`, updating the live top-bar status indicator (`[abc]`, `[Abc]`, `[ABC]`, `[Sym]`, `[Ctl]`, `[123]`) in real time. - Locks the candidate popup bar to the bottom of the screen (`X = 1, Y = 290, W = 238, H = 30`) with fixed-width 28-character monospace formatting (`Fira Code 9.5`), so neither the popup window nor individual letters ever jump when cycling through candidates. @@ -141,8 +141,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 (`DMenu`, `XDrawer`, `Dialer`, `Call Log`, `Contacts`, `SMS`, `Files`, `Camera`) -All eight core phone overlays are compiled as shared-object plugins (`/usr/lib/dwm/plugins/*.so`), preloaded at `dwm` startup (`dwm-plugin --preload` with an exclusive `/tmp/dwm_plugin_.lock` single-instance guard), and awakened over `/tmp/dwm_plugin_.fifo` in `< 2 ms`: +#### 3.1.2 Preloaded DWM plugins (`DMenu`, `XDrawer`, `Dialer`, `Call Log`, `Contacts`, `SMS`, `Files`, `Camera`, `Themes`) +All nine core phone overlays are compiled as shared-object plugins (`/usr/lib/dwm/plugins/*.so`), preloaded at `dwm` startup (`dwm-plugin --preload` with an exclusive `/tmp/dwm_plugin_.lock` single-instance guard), and awakened over `/tmp/dwm_plugin_.fifo` in `< 2 ms`: 1. **`DMenu` (`dmenu.so` / `dmenu` / `xdmenu`) — Zero-Lag System & Context Menus**: - **Open / Close**: Press **Left softkey (`Menu`)** for the **System menu** (`sys`) or **Right softkey (`Back`)** inside an application for its **Context menu** (`ctx`); press the same softkey or `Escape` to close. @@ -184,6 +184,12 @@ All eight core phone overlays are compiled as shared-object plugins (`/usr/lib/d - **Live Viewfinder**: Streams `800x600` `RGB24` frames directly from `/dev/video0` (`msm8905-cam` / `GC2395`) into a `240x180` 4:3 viewfinder with a center reticle and a 1-second V4L2 `poll()` frame watchdog. 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. +9. **`Theme Manager` (`themes.so` / `xthemes`) — Unified `.Xresources` Theme Browser & Customizer (`~/.xthemes/*.xth`)**: + - **Open**: Select **Themes** from the Sxmo **System menu**, **Apps** menu, or **Config** menu, or run `xthemes`. + - **Theme Files (`~/.xthemes/*.xth`)**: Every `.xth` file in `~/.xthemes/` is a standard `Xresources` file defining `theme.name`, `theme.fontFace`, `theme.iconFontFace`, `theme.baseFontSize` (from which `sizeSmall`, `sizeMain`, `sizeIcon`, and `sizeLarge` are proportionally derived across all UI elements), core and surface colors (`theme.bgMain`, `theme.bgBar`, `theme.bgHeader`, `theme.selBg`, `theme.selFg`, `theme.accent`, `theme.fgMain`, `theme.fgMuted`, etc.), and synchronized `dwm.*`, `dmenu.*`, `st.*`, and `svkbd.*` resources. Pre-installed themes include **`Default.xth`**, **`BananaGold.xth`**, **`MatrixGreen.xth`**, **`NordFrost.xth`**, and **`SolarizedLight.xth`**. + - **Controls & Live Switching**: `Up`/`Down` browses installed themes with live color swatches and typography details; **Center `OK` (`Return`)**, **Call (green)**, or keys **`1`–`9`** immediately writes `~/.Xresources`, runs `xrdb -load ~/.Xresources`, and broadcasts `RELOAD_THEME` to `dwm`, `dmenu`, `st`, and all resident DWM plugins for zero-restart live theme switching. + - **Built-in Theme Customizer & Options (`Left softkey` or `*`/`#`)**: Supports **Customize Theme Colors/Font** (`*` shortcut — adjust `Font Face`, `Base Size`, `Main BG`, `Bar BG`, `Header BG`, `Selected BG`, `Selected FG`, `Accent`, `Font Color`, and `Muted Color` with `Left`/`Right` presets or press `Return` to enter any 6-digit `#RRGGBB` hex code via the keypad), **Save Current as New Theme** (`#` shortcut), **Duplicate Theme**, **Edit `.xth` in Nano**, **Reset Default Theme**, and **Delete Theme**, plus CLI `xthemes --apply ` and `xthemes --export-default`. + ### 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]`). diff --git a/README.md b/README.md index 76d7280..ced6402 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ 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 (`dmenu`, `xdrawer`, `dialer`, `calllog`, `contacts`, `sms`, `files`, `camera`)**: +- **Preloaded `240x320` keypad-driven DWM plugins (`dmenu`, `xdrawer`, `dialer`, `calllog`, `contacts`, `sms`, `files`, `camera`, `themes`)**: - **`DMenu` (`dmenu.so` / `dmenu` / `xdmenu`)**: zero-lag (`< 2 ms` popup/close) resident `240x320` full-screen 10-line vertical menu (`240x296` flush below the 24px top bar) with a `#383838` search bar, `#005577` prompt badge, `XIM` `fcitx5-t9` text input, in-place `< 0.5 ms` native Sxmo submenu transitions (`Sys`, `Apps`, `Scripts`, `Modem`, `Config`, `Power`, `St`, `Nano`), and a `/tmp/dwm_plugin_dmenu.sock` client interface serving all `/usr/bin/dmenu` and `sxmo_dmenu.sh` pipes instantaneously. - **`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. @@ -19,6 +19,7 @@ Bananarchy targets an ultra-lightweight **Sxmo X11 (`sxmo-de-dwm`)** environment - **`SMS Manager` (`sms.so` / `xsms`)**: conversation thread manager, bubble message history reader, and multi-tap keypad SMS composer/draft editor (with dynamic `Send` / `Draft` center softkey action) integrated with `$SXMO_LOGDIR//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 1-second frame watchdog, 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. + - **`Theme Manager` (`themes.so` / `xthemes`)**: unified `.Xresources` theme browser and live customizer managing `~/.xthemes/*.xth` (where every `.xth` file is a valid `Xresources` file controlling `theme.*`, `dwm.*`, `dmenu.*`, `st.*`, and `svkbd.*` colors, font face, and `baseFontSize` with proportional derived font sizes, pre-populated with `Default.xth`, `BananaGold.xth`, `MatrixGreen.xth`, `NordFrost.xth`, and `SolarizedLight.xth`). Applying or editing a theme reloads `dwm`, `dmenu`, `st`, and all resident DWM plugins live without restarting X11. - **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). - **Custom 6-mode `fcitx5-t9` input engine**: fixed bottom candidate bar (`X=1, Y=290, W=238, H=30`) with a constant 28-character monospace preedit layout supporting lowercase, one-shot Shift, Caps Lock, all 32 ASCII punctuation symbols, terminal control codes (`^A`-`^Z`, `Esc`, `Tab`, `F1`-`F12`), and direct numeric entry with live top-bar mode indication (`[abc]`, `[Abc]`, `[ABC]`, `[Sym]`, `[Ctl]`, `[123]`). @@ -93,7 +94,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, calllog, contacts, sms, files, camera), shared phone/media lib & runner +│ ├── dwm-plugins/ # DWM plugins (dialer, calllog, contacts, sms, files, camera, themes), theme.h & runner │ ├── xdrawer/ # XDrawer global phone controls DWM plugin (xdrawer.so / xdrawer) │ ├── sxmo-dmenu/ # Patched sxmo-dmenu zero-lag DWM plugin (dmenu.so) & client/fallback (dmenu / xdmenu) │ ├── sxmo-st/ # Patched sxmo-st terminal with HarfBuzz Fira Code ligature shaping & XIM @@ -103,10 +104,10 @@ bananarchy/ │ ├── leds-msm8905-torch/ # Kernel driver for SY7807 rear torch LED, WCD codec init & PM8909 PON shutdown │ └── msm8905-cam/ # Kernel V4L2 driver for GC2395 2MP rear camera & MSM8909 CAMSS (/dev/video0) ├── overlay/ -│ └── rootfs/ # Complete Bananarchy rootfs overlay (OS branding, Sxmo X11, audio, camera, T9) +│ └── rootfs/ # Complete Bananarchy rootfs overlay (OS branding, Sxmo X11, audio, camera, T9, .xthemes) ├── prebuilt/ │ ├── xlibre/ # Prebuilt armv7 Xlibre Xorg binary and modesetting/libinput/fbdev DDX modules -│ ├── sxmo-x11/ # Prebuilt armv7 patched dwm, dmenu.so/dmenu, 7 C++ DWM plugins, and st (HarfBuzz ligatures) +│ ├── sxmo-x11/ # Prebuilt armv7 patched dwm, dmenu.so/dmenu, 8 C++ DWM plugins, and st (HarfBuzz ligatures) │ ├── kernel-6.12.1-msm8916/ # Prebuilt QDSP6/Q6Voice .ko, torch .ko, camera .ko, and 5 device DTBs │ ├── fcitx5-t9/ # Prebuilt armv7 libt9.so and fcitx5 addon/inputmethod configs │ └── bantam/ # Prebuilt armv7 bantam AI agent binary diff --git a/overlay/rootfs/etc/skel/.Xresources b/overlay/rootfs/etc/skel/.Xresources index 4f477d6..90f1075 100644 --- a/overlay/rootfs/etc/skel/.Xresources +++ b/overlay/rootfs/etc/skel/.Xresources @@ -1,13 +1,61 @@ ! Space to setup configversion ! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: Default + +theme.name: Default +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #222222 +theme.bgBar: #323232 +theme.bgHeader: #1c1c1c +theme.bgFooter: #181818 +theme.bgOverlay: #161616 +theme.bgAltEven: #282828 +theme.bgAltOdd: #2e2e2e +theme.bgInput: #1a1a1a +theme.bgInputFocus: #1b3b4b +theme.bgButton: #2a2a2a +theme.bgSearch: #383838 +theme.bgBubbleIn: #262d32 +theme.bgBubbleOut: #1f2d38 +theme.bgToast: #102535 + +theme.fgMain: #dddddd +theme.fgBright: #ffffff +theme.fgMuted: #999999 + +theme.selBg: #005577 +theme.selFg: #ffffff +theme.accent: #f4d03f +theme.accentAlt: #0088bb +theme.highlight: #ffc978 +theme.border: #444444 +theme.divider: #666666 + +theme.success: #2ecc71 +theme.info: #5dade2 +theme.warning: #e67e22 +theme.danger: #e74c3c +theme.dangerBg: #922b21 dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true dmenu.background: #1c1c1c -dmenu.foreground: #e0e0e0 +dmenu.foreground: #ffffff dmenu.selbackground: #005577 dmenu.selforeground: #ffffff +dmenu.outbackground: #383838 +dmenu.outforeground: #ffffff +dmenu.highlight: #ffc978 dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #323232 +dwm.foreground: #dddddd +dwm.border: #444444 +dwm.selbackground: #005577 +dwm.selforeground: #ffffff +dwm.selborder: #005577 dwm.normbgcolor: #323232 dwm.normbordercolor: #444444 dwm.normfgcolor: #dddddd @@ -18,7 +66,7 @@ dwm.selfgcolor: #ffffff st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true st.termname: xterm-256color st.background: #1c1c1c -st.foreground: #e5e5e5 -st.cursorColor: #cccccc +st.foreground: #dddddd +st.cursorColor: #f4d03f svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/etc/skel/.xthemes/BananaGold.xth b/overlay/rootfs/etc/skel/.xthemes/BananaGold.xth new file mode 100644 index 0000000..3599993 --- /dev/null +++ b/overlay/rootfs/etc/skel/.xthemes/BananaGold.xth @@ -0,0 +1,72 @@ +! Space to setup configversion +! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: BananaGold + +theme.name: BananaGold +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #1d1a10 +theme.bgBar: #2b2616 +theme.bgHeader: #16130b +theme.bgFooter: #121009 +theme.bgOverlay: #100e08 +theme.bgAltEven: #242014 +theme.bgAltOdd: #2c2718 +theme.bgInput: #14120b +theme.bgInputFocus: #4d3b0b +theme.bgButton: #282315 +theme.bgSearch: #38311e +theme.bgBubbleIn: #262b18 +theme.bgBubbleOut: #382e12 +theme.bgToast: #33270a + +theme.fgMain: #f5ebc6 +theme.fgBright: #ffffff +theme.fgMuted: #a89f7d + +theme.selBg: #b58900 +theme.selFg: #ffffff +theme.accent: #ffd700 +theme.accentAlt: #e6b800 +theme.highlight: #ffe666 +theme.border: #4a4128 +theme.divider: #6b5e3a + +theme.success: #2ecc71 +theme.info: #5dade2 +theme.warning: #f39c12 +theme.danger: #e74c3c +theme.dangerBg: #922b21 + +dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +dmenu.background: #16130b +dmenu.foreground: #ffffff +dmenu.selbackground: #b58900 +dmenu.selforeground: #ffffff +dmenu.outbackground: #38311e +dmenu.outforeground: #ffffff +dmenu.highlight: #ffe666 + +dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #2b2616 +dwm.foreground: #f5ebc6 +dwm.border: #4a4128 +dwm.selbackground: #b58900 +dwm.selforeground: #ffffff +dwm.selborder: #b58900 +dwm.normbgcolor: #2b2616 +dwm.normbordercolor: #4a4128 +dwm.normfgcolor: #f5ebc6 +dwm.selbgcolor: #b58900 +dwm.selbordercolor: #b58900 +dwm.selfgcolor: #ffffff + +st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +st.termname: xterm-256color +st.background: #16130b +st.foreground: #f5ebc6 +st.cursorColor: #ffd700 + +svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/etc/skel/.xthemes/Default.xth b/overlay/rootfs/etc/skel/.xthemes/Default.xth new file mode 100644 index 0000000..90f1075 --- /dev/null +++ b/overlay/rootfs/etc/skel/.xthemes/Default.xth @@ -0,0 +1,72 @@ +! Space to setup configversion +! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: Default + +theme.name: Default +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #222222 +theme.bgBar: #323232 +theme.bgHeader: #1c1c1c +theme.bgFooter: #181818 +theme.bgOverlay: #161616 +theme.bgAltEven: #282828 +theme.bgAltOdd: #2e2e2e +theme.bgInput: #1a1a1a +theme.bgInputFocus: #1b3b4b +theme.bgButton: #2a2a2a +theme.bgSearch: #383838 +theme.bgBubbleIn: #262d32 +theme.bgBubbleOut: #1f2d38 +theme.bgToast: #102535 + +theme.fgMain: #dddddd +theme.fgBright: #ffffff +theme.fgMuted: #999999 + +theme.selBg: #005577 +theme.selFg: #ffffff +theme.accent: #f4d03f +theme.accentAlt: #0088bb +theme.highlight: #ffc978 +theme.border: #444444 +theme.divider: #666666 + +theme.success: #2ecc71 +theme.info: #5dade2 +theme.warning: #e67e22 +theme.danger: #e74c3c +theme.dangerBg: #922b21 + +dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +dmenu.background: #1c1c1c +dmenu.foreground: #ffffff +dmenu.selbackground: #005577 +dmenu.selforeground: #ffffff +dmenu.outbackground: #383838 +dmenu.outforeground: #ffffff +dmenu.highlight: #ffc978 + +dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #323232 +dwm.foreground: #dddddd +dwm.border: #444444 +dwm.selbackground: #005577 +dwm.selforeground: #ffffff +dwm.selborder: #005577 +dwm.normbgcolor: #323232 +dwm.normbordercolor: #444444 +dwm.normfgcolor: #dddddd +dwm.selbgcolor: #005577 +dwm.selbordercolor: #005577 +dwm.selfgcolor: #ffffff + +st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +st.termname: xterm-256color +st.background: #1c1c1c +st.foreground: #dddddd +st.cursorColor: #f4d03f + +svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/etc/skel/.xthemes/MatrixGreen.xth b/overlay/rootfs/etc/skel/.xthemes/MatrixGreen.xth new file mode 100644 index 0000000..ed60232 --- /dev/null +++ b/overlay/rootfs/etc/skel/.xthemes/MatrixGreen.xth @@ -0,0 +1,72 @@ +! Space to setup configversion +! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: MatrixGreen + +theme.name: MatrixGreen +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #0a140c +theme.bgBar: #112214 +theme.bgHeader: #070e08 +theme.bgFooter: #050b06 +theme.bgOverlay: #040905 +theme.bgAltEven: #0e1c11 +theme.bgAltOdd: #132416 +theme.bgInput: #071009 +theme.bgInputFocus: #143d1f +theme.bgButton: #102013 +theme.bgSearch: #18301c +theme.bgBubbleIn: #112b16 +theme.bgBubbleOut: #16381d +theme.bgToast: #0f3018 + +theme.fgMain: #a8ffb2 +theme.fgBright: #e0ffe4 +theme.fgMuted: #52995b + +theme.selBg: #196627 +theme.selFg: #ffffff +theme.accent: #00ff41 +theme.accentAlt: #2ecc71 +theme.highlight: #80ff9d +theme.border: #1e4224 +theme.divider: #2d6336 + +theme.success: #00ff41 +theme.info: #5dade2 +theme.warning: #f1c40f +theme.danger: #e74c3c +theme.dangerBg: #922b21 + +dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +dmenu.background: #070e08 +dmenu.foreground: #e0ffe4 +dmenu.selbackground: #196627 +dmenu.selforeground: #ffffff +dmenu.outbackground: #18301c +dmenu.outforeground: #e0ffe4 +dmenu.highlight: #80ff9d + +dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #112214 +dwm.foreground: #a8ffb2 +dwm.border: #1e4224 +dwm.selbackground: #196627 +dwm.selforeground: #ffffff +dwm.selborder: #196627 +dwm.normbgcolor: #112214 +dwm.normbordercolor: #1e4224 +dwm.normfgcolor: #a8ffb2 +dwm.selbgcolor: #196627 +dwm.selbordercolor: #196627 +dwm.selfgcolor: #ffffff + +st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +st.termname: xterm-256color +st.background: #070e08 +st.foreground: #a8ffb2 +st.cursorColor: #00ff41 + +svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/etc/skel/.xthemes/NordFrost.xth b/overlay/rootfs/etc/skel/.xthemes/NordFrost.xth new file mode 100644 index 0000000..e8de457 --- /dev/null +++ b/overlay/rootfs/etc/skel/.xthemes/NordFrost.xth @@ -0,0 +1,72 @@ +! Space to setup configversion +! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: NordFrost + +theme.name: NordFrost +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #2e3440 +theme.bgBar: #3b4252 +theme.bgHeader: #242933 +theme.bgFooter: #1f242d +theme.bgOverlay: #1d212a +theme.bgAltEven: #333a47 +theme.bgAltOdd: #39404f +theme.bgInput: #262b36 +theme.bgInputFocus: #394b68 +theme.bgButton: #363d4c +theme.bgSearch: #434c5e +theme.bgBubbleIn: #354249 +theme.bgBubbleOut: #344459 +theme.bgToast: #2b3c54 + +theme.fgMain: #e5e9f0 +theme.fgBright: #eceff4 +theme.fgMuted: #9099ab + +theme.selBg: #5e81ac +theme.selFg: #eceff4 +theme.accent: #88c0d0 +theme.accentAlt: #81a1c1 +theme.highlight: #ebcb8b +theme.border: #4c566a +theme.divider: #616e88 + +theme.success: #a3be8c +theme.info: #88c0d0 +theme.warning: #d08770 +theme.danger: #bf616a +theme.dangerBg: #7a373e + +dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +dmenu.background: #242933 +dmenu.foreground: #eceff4 +dmenu.selbackground: #5e81ac +dmenu.selforeground: #eceff4 +dmenu.outbackground: #434c5e +dmenu.outforeground: #eceff4 +dmenu.highlight: #ebcb8b + +dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #3b4252 +dwm.foreground: #e5e9f0 +dwm.border: #4c566a +dwm.selbackground: #5e81ac +dwm.selforeground: #eceff4 +dwm.selborder: #5e81ac +dwm.normbgcolor: #3b4252 +dwm.normbordercolor: #4c566a +dwm.normfgcolor: #e5e9f0 +dwm.selbgcolor: #5e81ac +dwm.selbordercolor: #5e81ac +dwm.selfgcolor: #eceff4 + +st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +st.termname: xterm-256color +st.background: #242933 +st.foreground: #e5e9f0 +st.cursorColor: #88c0d0 + +svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/etc/skel/.xthemes/SolarizedLight.xth b/overlay/rootfs/etc/skel/.xthemes/SolarizedLight.xth new file mode 100644 index 0000000..2285dda --- /dev/null +++ b/overlay/rootfs/etc/skel/.xthemes/SolarizedLight.xth @@ -0,0 +1,72 @@ +! Space to setup configversion +! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: SolarizedLight + +theme.name: SolarizedLight +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #fdf6e3 +theme.bgBar: #eee8d5 +theme.bgHeader: #e6dfca +theme.bgFooter: #e0d9c3 +theme.bgOverlay: #ddd6c1 +theme.bgAltEven: #f7f0dd +theme.bgAltOdd: #efe8d5 +theme.bgInput: #ffffff +theme.bgInputFocus: #d5e5f2 +theme.bgButton: #e8e1cc +theme.bgSearch: #d9d2bd +theme.bgBubbleIn: #e3f0d8 +theme.bgBubbleOut: #d5e6f5 +theme.bgToast: #1b4f72 + +theme.fgMain: #283739 +theme.fgBright: #073642 +theme.fgMuted: #657b83 + +theme.selBg: #268bd2 +theme.selFg: #ffffff +theme.accent: #b58900 +theme.accentAlt: #2aa198 +theme.highlight: #cb4b16 +theme.border: #93a1a1 +theme.divider: #839496 + +theme.success: #2aa198 +theme.info: #268bd2 +theme.warning: #cb4b16 +theme.danger: #dc322f +theme.dangerBg: #a93226 + +dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +dmenu.background: #e6dfca +dmenu.foreground: #073642 +dmenu.selbackground: #268bd2 +dmenu.selforeground: #ffffff +dmenu.outbackground: #d9d2bd +dmenu.outforeground: #073642 +dmenu.highlight: #cb4b16 + +dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #eee8d5 +dwm.foreground: #283739 +dwm.border: #93a1a1 +dwm.selbackground: #268bd2 +dwm.selforeground: #ffffff +dwm.selborder: #268bd2 +dwm.normbgcolor: #eee8d5 +dwm.normbordercolor: #93a1a1 +dwm.normfgcolor: #283739 +dwm.selbgcolor: #268bd2 +dwm.selbordercolor: #268bd2 +dwm.selfgcolor: #ffffff + +st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +st.termname: xterm-256color +st.background: #fdf6e3 +st.foreground: #283739 +st.cursorColor: #b58900 + +svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/home/user/.Xresources b/overlay/rootfs/home/user/.Xresources index 4f477d6..90f1075 100644 --- a/overlay/rootfs/home/user/.Xresources +++ b/overlay/rootfs/home/user/.Xresources @@ -1,13 +1,61 @@ ! Space to setup configversion ! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: Default + +theme.name: Default +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #222222 +theme.bgBar: #323232 +theme.bgHeader: #1c1c1c +theme.bgFooter: #181818 +theme.bgOverlay: #161616 +theme.bgAltEven: #282828 +theme.bgAltOdd: #2e2e2e +theme.bgInput: #1a1a1a +theme.bgInputFocus: #1b3b4b +theme.bgButton: #2a2a2a +theme.bgSearch: #383838 +theme.bgBubbleIn: #262d32 +theme.bgBubbleOut: #1f2d38 +theme.bgToast: #102535 + +theme.fgMain: #dddddd +theme.fgBright: #ffffff +theme.fgMuted: #999999 + +theme.selBg: #005577 +theme.selFg: #ffffff +theme.accent: #f4d03f +theme.accentAlt: #0088bb +theme.highlight: #ffc978 +theme.border: #444444 +theme.divider: #666666 + +theme.success: #2ecc71 +theme.info: #5dade2 +theme.warning: #e67e22 +theme.danger: #e74c3c +theme.dangerBg: #922b21 dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true dmenu.background: #1c1c1c -dmenu.foreground: #e0e0e0 +dmenu.foreground: #ffffff dmenu.selbackground: #005577 dmenu.selforeground: #ffffff +dmenu.outbackground: #383838 +dmenu.outforeground: #ffffff +dmenu.highlight: #ffc978 dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #323232 +dwm.foreground: #dddddd +dwm.border: #444444 +dwm.selbackground: #005577 +dwm.selforeground: #ffffff +dwm.selborder: #005577 dwm.normbgcolor: #323232 dwm.normbordercolor: #444444 dwm.normfgcolor: #dddddd @@ -18,7 +66,7 @@ dwm.selfgcolor: #ffffff st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true st.termname: xterm-256color st.background: #1c1c1c -st.foreground: #e5e5e5 -st.cursorColor: #cccccc +st.foreground: #dddddd +st.cursorColor: #f4d03f svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/home/user/.xthemes/BananaGold.xth b/overlay/rootfs/home/user/.xthemes/BananaGold.xth new file mode 100644 index 0000000..3599993 --- /dev/null +++ b/overlay/rootfs/home/user/.xthemes/BananaGold.xth @@ -0,0 +1,72 @@ +! Space to setup configversion +! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: BananaGold + +theme.name: BananaGold +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #1d1a10 +theme.bgBar: #2b2616 +theme.bgHeader: #16130b +theme.bgFooter: #121009 +theme.bgOverlay: #100e08 +theme.bgAltEven: #242014 +theme.bgAltOdd: #2c2718 +theme.bgInput: #14120b +theme.bgInputFocus: #4d3b0b +theme.bgButton: #282315 +theme.bgSearch: #38311e +theme.bgBubbleIn: #262b18 +theme.bgBubbleOut: #382e12 +theme.bgToast: #33270a + +theme.fgMain: #f5ebc6 +theme.fgBright: #ffffff +theme.fgMuted: #a89f7d + +theme.selBg: #b58900 +theme.selFg: #ffffff +theme.accent: #ffd700 +theme.accentAlt: #e6b800 +theme.highlight: #ffe666 +theme.border: #4a4128 +theme.divider: #6b5e3a + +theme.success: #2ecc71 +theme.info: #5dade2 +theme.warning: #f39c12 +theme.danger: #e74c3c +theme.dangerBg: #922b21 + +dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +dmenu.background: #16130b +dmenu.foreground: #ffffff +dmenu.selbackground: #b58900 +dmenu.selforeground: #ffffff +dmenu.outbackground: #38311e +dmenu.outforeground: #ffffff +dmenu.highlight: #ffe666 + +dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #2b2616 +dwm.foreground: #f5ebc6 +dwm.border: #4a4128 +dwm.selbackground: #b58900 +dwm.selforeground: #ffffff +dwm.selborder: #b58900 +dwm.normbgcolor: #2b2616 +dwm.normbordercolor: #4a4128 +dwm.normfgcolor: #f5ebc6 +dwm.selbgcolor: #b58900 +dwm.selbordercolor: #b58900 +dwm.selfgcolor: #ffffff + +st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +st.termname: xterm-256color +st.background: #16130b +st.foreground: #f5ebc6 +st.cursorColor: #ffd700 + +svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/home/user/.xthemes/Default.xth b/overlay/rootfs/home/user/.xthemes/Default.xth new file mode 100644 index 0000000..90f1075 --- /dev/null +++ b/overlay/rootfs/home/user/.xthemes/Default.xth @@ -0,0 +1,72 @@ +! Space to setup configversion +! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: Default + +theme.name: Default +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #222222 +theme.bgBar: #323232 +theme.bgHeader: #1c1c1c +theme.bgFooter: #181818 +theme.bgOverlay: #161616 +theme.bgAltEven: #282828 +theme.bgAltOdd: #2e2e2e +theme.bgInput: #1a1a1a +theme.bgInputFocus: #1b3b4b +theme.bgButton: #2a2a2a +theme.bgSearch: #383838 +theme.bgBubbleIn: #262d32 +theme.bgBubbleOut: #1f2d38 +theme.bgToast: #102535 + +theme.fgMain: #dddddd +theme.fgBright: #ffffff +theme.fgMuted: #999999 + +theme.selBg: #005577 +theme.selFg: #ffffff +theme.accent: #f4d03f +theme.accentAlt: #0088bb +theme.highlight: #ffc978 +theme.border: #444444 +theme.divider: #666666 + +theme.success: #2ecc71 +theme.info: #5dade2 +theme.warning: #e67e22 +theme.danger: #e74c3c +theme.dangerBg: #922b21 + +dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +dmenu.background: #1c1c1c +dmenu.foreground: #ffffff +dmenu.selbackground: #005577 +dmenu.selforeground: #ffffff +dmenu.outbackground: #383838 +dmenu.outforeground: #ffffff +dmenu.highlight: #ffc978 + +dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #323232 +dwm.foreground: #dddddd +dwm.border: #444444 +dwm.selbackground: #005577 +dwm.selforeground: #ffffff +dwm.selborder: #005577 +dwm.normbgcolor: #323232 +dwm.normbordercolor: #444444 +dwm.normfgcolor: #dddddd +dwm.selbgcolor: #005577 +dwm.selbordercolor: #005577 +dwm.selfgcolor: #ffffff + +st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +st.termname: xterm-256color +st.background: #1c1c1c +st.foreground: #dddddd +st.cursorColor: #f4d03f + +svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/home/user/.xthemes/MatrixGreen.xth b/overlay/rootfs/home/user/.xthemes/MatrixGreen.xth new file mode 100644 index 0000000..ed60232 --- /dev/null +++ b/overlay/rootfs/home/user/.xthemes/MatrixGreen.xth @@ -0,0 +1,72 @@ +! Space to setup configversion +! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: MatrixGreen + +theme.name: MatrixGreen +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #0a140c +theme.bgBar: #112214 +theme.bgHeader: #070e08 +theme.bgFooter: #050b06 +theme.bgOverlay: #040905 +theme.bgAltEven: #0e1c11 +theme.bgAltOdd: #132416 +theme.bgInput: #071009 +theme.bgInputFocus: #143d1f +theme.bgButton: #102013 +theme.bgSearch: #18301c +theme.bgBubbleIn: #112b16 +theme.bgBubbleOut: #16381d +theme.bgToast: #0f3018 + +theme.fgMain: #a8ffb2 +theme.fgBright: #e0ffe4 +theme.fgMuted: #52995b + +theme.selBg: #196627 +theme.selFg: #ffffff +theme.accent: #00ff41 +theme.accentAlt: #2ecc71 +theme.highlight: #80ff9d +theme.border: #1e4224 +theme.divider: #2d6336 + +theme.success: #00ff41 +theme.info: #5dade2 +theme.warning: #f1c40f +theme.danger: #e74c3c +theme.dangerBg: #922b21 + +dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +dmenu.background: #070e08 +dmenu.foreground: #e0ffe4 +dmenu.selbackground: #196627 +dmenu.selforeground: #ffffff +dmenu.outbackground: #18301c +dmenu.outforeground: #e0ffe4 +dmenu.highlight: #80ff9d + +dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #112214 +dwm.foreground: #a8ffb2 +dwm.border: #1e4224 +dwm.selbackground: #196627 +dwm.selforeground: #ffffff +dwm.selborder: #196627 +dwm.normbgcolor: #112214 +dwm.normbordercolor: #1e4224 +dwm.normfgcolor: #a8ffb2 +dwm.selbgcolor: #196627 +dwm.selbordercolor: #196627 +dwm.selfgcolor: #ffffff + +st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +st.termname: xterm-256color +st.background: #070e08 +st.foreground: #a8ffb2 +st.cursorColor: #00ff41 + +svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/home/user/.xthemes/NordFrost.xth b/overlay/rootfs/home/user/.xthemes/NordFrost.xth new file mode 100644 index 0000000..e8de457 --- /dev/null +++ b/overlay/rootfs/home/user/.xthemes/NordFrost.xth @@ -0,0 +1,72 @@ +! Space to setup configversion +! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: NordFrost + +theme.name: NordFrost +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #2e3440 +theme.bgBar: #3b4252 +theme.bgHeader: #242933 +theme.bgFooter: #1f242d +theme.bgOverlay: #1d212a +theme.bgAltEven: #333a47 +theme.bgAltOdd: #39404f +theme.bgInput: #262b36 +theme.bgInputFocus: #394b68 +theme.bgButton: #363d4c +theme.bgSearch: #434c5e +theme.bgBubbleIn: #354249 +theme.bgBubbleOut: #344459 +theme.bgToast: #2b3c54 + +theme.fgMain: #e5e9f0 +theme.fgBright: #eceff4 +theme.fgMuted: #9099ab + +theme.selBg: #5e81ac +theme.selFg: #eceff4 +theme.accent: #88c0d0 +theme.accentAlt: #81a1c1 +theme.highlight: #ebcb8b +theme.border: #4c566a +theme.divider: #616e88 + +theme.success: #a3be8c +theme.info: #88c0d0 +theme.warning: #d08770 +theme.danger: #bf616a +theme.dangerBg: #7a373e + +dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +dmenu.background: #242933 +dmenu.foreground: #eceff4 +dmenu.selbackground: #5e81ac +dmenu.selforeground: #eceff4 +dmenu.outbackground: #434c5e +dmenu.outforeground: #eceff4 +dmenu.highlight: #ebcb8b + +dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #3b4252 +dwm.foreground: #e5e9f0 +dwm.border: #4c566a +dwm.selbackground: #5e81ac +dwm.selforeground: #eceff4 +dwm.selborder: #5e81ac +dwm.normbgcolor: #3b4252 +dwm.normbordercolor: #4c566a +dwm.normfgcolor: #e5e9f0 +dwm.selbgcolor: #5e81ac +dwm.selbordercolor: #5e81ac +dwm.selfgcolor: #eceff4 + +st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +st.termname: xterm-256color +st.background: #242933 +st.foreground: #e5e9f0 +st.cursorColor: #88c0d0 + +svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/home/user/.xthemes/SolarizedLight.xth b/overlay/rootfs/home/user/.xthemes/SolarizedLight.xth new file mode 100644 index 0000000..2285dda --- /dev/null +++ b/overlay/rootfs/home/user/.xthemes/SolarizedLight.xth @@ -0,0 +1,72 @@ +! Space to setup configversion +! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: SolarizedLight + +theme.name: SolarizedLight +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #fdf6e3 +theme.bgBar: #eee8d5 +theme.bgHeader: #e6dfca +theme.bgFooter: #e0d9c3 +theme.bgOverlay: #ddd6c1 +theme.bgAltEven: #f7f0dd +theme.bgAltOdd: #efe8d5 +theme.bgInput: #ffffff +theme.bgInputFocus: #d5e5f2 +theme.bgButton: #e8e1cc +theme.bgSearch: #d9d2bd +theme.bgBubbleIn: #e3f0d8 +theme.bgBubbleOut: #d5e6f5 +theme.bgToast: #1b4f72 + +theme.fgMain: #283739 +theme.fgBright: #073642 +theme.fgMuted: #657b83 + +theme.selBg: #268bd2 +theme.selFg: #ffffff +theme.accent: #b58900 +theme.accentAlt: #2aa198 +theme.highlight: #cb4b16 +theme.border: #93a1a1 +theme.divider: #839496 + +theme.success: #2aa198 +theme.info: #268bd2 +theme.warning: #cb4b16 +theme.danger: #dc322f +theme.dangerBg: #a93226 + +dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +dmenu.background: #e6dfca +dmenu.foreground: #073642 +dmenu.selbackground: #268bd2 +dmenu.selforeground: #ffffff +dmenu.outbackground: #d9d2bd +dmenu.outforeground: #073642 +dmenu.highlight: #cb4b16 + +dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #eee8d5 +dwm.foreground: #283739 +dwm.border: #93a1a1 +dwm.selbackground: #268bd2 +dwm.selforeground: #ffffff +dwm.selborder: #268bd2 +dwm.normbgcolor: #eee8d5 +dwm.normbordercolor: #93a1a1 +dwm.normfgcolor: #283739 +dwm.selbgcolor: #268bd2 +dwm.selbordercolor: #268bd2 +dwm.selfgcolor: #ffffff + +st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true +st.termname: xterm-256color +st.background: #fdf6e3 +st.foreground: #283739 +st.cursorColor: #b58900 + +svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/usr/bin/dwm b/overlay/rootfs/usr/bin/dwm index b7b41f6..79ec4b8 100755 Binary files a/overlay/rootfs/usr/bin/dwm and b/overlay/rootfs/usr/bin/dwm differ diff --git a/overlay/rootfs/usr/bin/dwm-plugin b/overlay/rootfs/usr/bin/dwm-plugin index f7ce7fb..81ac988 100755 Binary files a/overlay/rootfs/usr/bin/dwm-plugin and b/overlay/rootfs/usr/bin/dwm-plugin differ diff --git a/overlay/rootfs/usr/bin/st b/overlay/rootfs/usr/bin/st index dd0646d..18f6edf 100755 Binary files a/overlay/rootfs/usr/bin/st and b/overlay/rootfs/usr/bin/st differ diff --git a/overlay/rootfs/usr/bin/sxmo_appmenu.sh b/overlay/rootfs/usr/bin/sxmo_appmenu.sh index 5b4edcc..5f0c4fd 100755 --- a/overlay/rootfs/usr/bin/sxmo_appmenu.sh +++ b/overlay/rootfs/usr/bin/sxmo_appmenu.sh @@ -173,6 +173,7 @@ ${_flash_line}$icon_net Networks^0^sxmo_networks.sh $icon_mus Audio^0^sxmo_audio.sh $icon_phn Modem^0^sxmo_appmenu.sh modem $icon_win Windows^0^sxmo_wmmenu.sh +$icon_cfg Themes^0^xthemes $icon_cfg Config^0^sxmo_appmenu.sh config $icon_pwr Power^0^sxmo_appmenu.sh power" } @@ -220,7 +221,8 @@ $icon_phl Modem Log^0^sxmo_modemlog.sh" [ -e "${XDG_CACHE_HOME:-$HOME/.cache}/sxmo/sxmo.nosuspend" ] && _susp_icon="$icon_tof" _idle_icon="$icon_ton" [ -e "${XDG_CACHE_HOME:-$HOME/.cache}/sxmo/sxmo.noidle" ] && _idle_icon="$icon_tof" - CHOICES="$icon_aru Brightness^1^sxmo_brightness.sh up + CHOICES="$icon_cfg Themes^0^xthemes +$icon_aru Brightness^1^sxmo_brightness.sh up $icon_ard Brightness^1^sxmo_brightness.sh down $icon_cfg Toggle Bar^0^sxmo_wm.sh togglebar $icon_bth Bluetooth $_bt_icon^1^doas sxmo_bluetoothtoggle.sh && sxmo_hook_statusbar.sh bluetooth diff --git a/overlay/rootfs/usr/bin/xthemes b/overlay/rootfs/usr/bin/xthemes new file mode 120000 index 0000000..e492465 --- /dev/null +++ b/overlay/rootfs/usr/bin/xthemes @@ -0,0 +1 @@ +dwm-plugin \ No newline at end of file diff --git a/overlay/rootfs/usr/lib/dwm/plugins/calllog.so b/overlay/rootfs/usr/lib/dwm/plugins/calllog.so index 295e9a5..7e87009 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/calllog.so and b/overlay/rootfs/usr/lib/dwm/plugins/calllog.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/camera.so b/overlay/rootfs/usr/lib/dwm/plugins/camera.so index ebd5a78..c9d8508 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/camera.so and b/overlay/rootfs/usr/lib/dwm/plugins/camera.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/contacts.so b/overlay/rootfs/usr/lib/dwm/plugins/contacts.so index 9665b26..ffb3df3 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/contacts.so and b/overlay/rootfs/usr/lib/dwm/plugins/contacts.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/dialer.so b/overlay/rootfs/usr/lib/dwm/plugins/dialer.so index 6470b9e..310c92c 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/dialer.so and b/overlay/rootfs/usr/lib/dwm/plugins/dialer.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so b/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so index 466d87b..8a9e98b 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so and b/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/files.so b/overlay/rootfs/usr/lib/dwm/plugins/files.so index 44c3dcc..232b28a 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/files.so and b/overlay/rootfs/usr/lib/dwm/plugins/files.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/sms.so b/overlay/rootfs/usr/lib/dwm/plugins/sms.so index 912b42a..02fb22d 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/sms.so and b/overlay/rootfs/usr/lib/dwm/plugins/sms.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/themes.so b/overlay/rootfs/usr/lib/dwm/plugins/themes.so new file mode 100755 index 0000000..48baa9a Binary files /dev/null and b/overlay/rootfs/usr/lib/dwm/plugins/themes.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so b/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so index 534fe99..a752893 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so and b/overlay/rootfs/usr/lib/dwm/plugins/xdrawer.so differ diff --git a/overlay/rootfs/usr/share/sxmo/appcfg/Xresources b/overlay/rootfs/usr/share/sxmo/appcfg/Xresources index 4f477d6..90f1075 100644 --- a/overlay/rootfs/usr/share/sxmo/appcfg/Xresources +++ b/overlay/rootfs/usr/share/sxmo/appcfg/Xresources @@ -1,13 +1,61 @@ ! Space to setup configversion ! configversion: 7bff124b2de16292fe6e7a7e5d3322ae +! Bananarchy Unified Theme: Default + +theme.name: Default +theme.fontFace: Fira Code +theme.iconFontFace: DejaVuSansM Nerd Font +theme.baseFontSize: 15 + +theme.bgMain: #222222 +theme.bgBar: #323232 +theme.bgHeader: #1c1c1c +theme.bgFooter: #181818 +theme.bgOverlay: #161616 +theme.bgAltEven: #282828 +theme.bgAltOdd: #2e2e2e +theme.bgInput: #1a1a1a +theme.bgInputFocus: #1b3b4b +theme.bgButton: #2a2a2a +theme.bgSearch: #383838 +theme.bgBubbleIn: #262d32 +theme.bgBubbleOut: #1f2d38 +theme.bgToast: #102535 + +theme.fgMain: #dddddd +theme.fgBright: #ffffff +theme.fgMuted: #999999 + +theme.selBg: #005577 +theme.selFg: #ffffff +theme.accent: #f4d03f +theme.accentAlt: #0088bb +theme.highlight: #ffc978 +theme.border: #444444 +theme.divider: #666666 + +theme.success: #2ecc71 +theme.info: #5dade2 +theme.warning: #e67e22 +theme.danger: #e74c3c +theme.dangerBg: #922b21 dmenu.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true dmenu.background: #1c1c1c -dmenu.foreground: #e0e0e0 +dmenu.foreground: #ffffff dmenu.selbackground: #005577 dmenu.selforeground: #ffffff +dmenu.outbackground: #383838 +dmenu.outforeground: #ffffff +dmenu.highlight: #ffc978 dwm.font: Fira Code, DejaVuSansM Nerd Font 15px +dwm.background: #323232 +dwm.foreground: #dddddd +dwm.border: #444444 +dwm.selbackground: #005577 +dwm.selforeground: #ffffff +dwm.selborder: #005577 dwm.normbgcolor: #323232 dwm.normbordercolor: #444444 dwm.normfgcolor: #dddddd @@ -18,7 +66,7 @@ dwm.selfgcolor: #ffffff st.font: Fira Code:pixelsize=18:style=Regular:antialias=true:autohint=true st.termname: xterm-256color st.background: #1c1c1c -st.foreground: #e5e5e5 -st.cursorColor: #cccccc +st.foreground: #dddddd +st.cursorColor: #f4d03f svkbd.font: Fira Code:pixelsize=12 diff --git a/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_apps.sh b/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_apps.sh index 940df59..3ca2166 100755 --- a/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_apps.sh +++ b/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_apps.sh @@ -215,6 +215,7 @@ write_line_app termite "$icon_trm Termite" "termite -e $SHELL" write_line_app thunar "$icon_dir Thunar" "sxmo_terminal.sh thunar" write_line_app thunderbird "$icon_thu Thunderbird" "thunderbird" write_line_app tilix "$icon_trm Tilix" "tilix" +write_line_app xthemes "$icon_cfg Themes" "xthemes" write_line_app com.github.bleakgrey.tootle "$icon_msg Tootle" "com.github.bleakgrey.tootle" write_line_app re.sonny.Tangram "$icon_glb Tangram" "re.sonny.Tangram" write_line_app totem "$icon_mvi Totem" "totem" diff --git a/prebuilt/sxmo-x11/usr/bin/dwm b/prebuilt/sxmo-x11/usr/bin/dwm index b7b41f6..79ec4b8 100755 Binary files a/prebuilt/sxmo-x11/usr/bin/dwm and b/prebuilt/sxmo-x11/usr/bin/dwm differ diff --git a/prebuilt/sxmo-x11/usr/bin/dwm-plugin b/prebuilt/sxmo-x11/usr/bin/dwm-plugin index f7ce7fb..81ac988 100755 Binary files a/prebuilt/sxmo-x11/usr/bin/dwm-plugin and b/prebuilt/sxmo-x11/usr/bin/dwm-plugin differ diff --git a/prebuilt/sxmo-x11/usr/bin/st b/prebuilt/sxmo-x11/usr/bin/st index dd0646d..18f6edf 100755 Binary files a/prebuilt/sxmo-x11/usr/bin/st and b/prebuilt/sxmo-x11/usr/bin/st differ diff --git a/prebuilt/sxmo-x11/usr/bin/xthemes b/prebuilt/sxmo-x11/usr/bin/xthemes new file mode 120000 index 0000000..e492465 --- /dev/null +++ b/prebuilt/sxmo-x11/usr/bin/xthemes @@ -0,0 +1 @@ +dwm-plugin \ No newline at end of file diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so index 295e9a5..7e87009 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so index ebd5a78..c9d8508 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so index 9665b26..ffb3df3 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so index 6470b9e..310c92c 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so index 466d87b..8a9e98b 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so index 44c3dcc..232b28a 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so index 912b42a..02fb22d 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/themes.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/themes.so new file mode 100755 index 0000000..48baa9a Binary files /dev/null and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/themes.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so index 534fe99..a752893 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so differ diff --git a/scripts/build-sxmo-x11.sh b/scripts/build-sxmo-x11.sh index cec3fca..549c06b 100755 --- a/scripts/build-sxmo-x11.sh +++ b/scripts/build-sxmo-x11.sh @@ -100,7 +100,7 @@ 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, dialer, calllog, contacts, sms, files, camera) and dwm-plugin runner..." + echo "[6/6] Compiling DWM Plugins (xdrawer, dialer, calllog, contacts, sms, files, camera, themes) 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 libjpeg libpng)" PLUG_LIBS="$(pkgconf --libs x11 xft fontconfig freetype2 pango pangoxft alsa)" @@ -134,6 +134,10 @@ EOF /bananarchy/src/dwm-plugins/camera_plugin.cpp $PLUG_LIBS $MEDIA_LIBS -lpthread \ -o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so + $CXX $OPT_CFLAGS -fPIC -shared -std=c++17 -Wall -Wextra $PLUG_INCS \ + /bananarchy/src/dwm-plugins/themes_plugin.cpp $PLUG_LIBS \ + -o /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/themes.so + $STRIP --strip-unneeded \ /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/xdrawer.so \ /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so \ @@ -141,7 +145,8 @@ EOF /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 + /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so \ + /bananarchy/prebuilt/sxmo-x11/usr/lib/dwm/plugins/themes.so $CC $OPT_CFLAGS -std=c99 -D_DEFAULT_SOURCE -Wall -Wextra \ /bananarchy/src/dwm-plugins/dwm_plugin_runner.c -ldl \ @@ -158,6 +163,7 @@ EOF 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 + ln -sf dwm-plugin /bananarchy/prebuilt/sxmo-x11/usr/bin/xthemes 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/ diff --git a/src/dwm-plugins/calllog_plugin.cpp b/src/dwm-plugins/calllog_plugin.cpp index efbb443..d5d8f29 100644 --- a/src/dwm-plugins/calllog_plugin.cpp +++ b/src/dwm-plugins/calllog_plugin.cpp @@ -101,33 +101,19 @@ int calllog_run_main(int argc, char** argv) { PangoContext* pangoCtx = pango_font_map_create_context(fontMap); PangoLayout* layoutMain = pango_layout_new(pangoCtx); - PangoFontDescription* descMain = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 15px"); - pango_layout_set_font_description(layoutMain, descMain); - PangoLayout* layoutSmall = pango_layout_new(pangoCtx); - PangoFontDescription* descSmall = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 12px"); - pango_layout_set_font_description(layoutSmall, descSmall); - PangoLayout* layoutIcon = pango_layout_new(pangoCtx); - PangoFontDescription* descIcon = - pango_font_description_from_string("DejaVuSansM Nerd Font, Fira Code 16px"); - pango_layout_set_font_description(layoutIcon, descIcon); - auto allocColor = [&](const char* hex) { - XftColor col{}; - XftColorAllocName(dpy, visual, cmap, hex, &col); - return col; - }; - - XftColor colWhite = allocColor("#ffffff"); - XftColor colLight = allocColor("#dddddd"); - XftColor colMuted = allocColor("#999999"); - XftColor colYellow = allocColor("#f4d03f"); - XftColor colGreen = allocColor("#2ecc71"); - XftColor colBlue = allocColor("#5dade2"); - XftColor colRed = allocColor("#e74c3c"); + PluginTheme theme; + theme.load(dpy, screen, visual, cmap, layoutMain, layoutSmall, layoutIcon, + nullptr, win); + XftColor& colWhite = theme.colWhite; + XftColor& colLight = theme.colLight; + XftColor& colMuted = theme.colMuted; + XftColor& colYellow = theme.colYellow; + XftColor& colGreen = theme.colGreen; + XftColor& colBlue = theme.colBlue; + XftColor& colRed = theme.colRed; ContactBook book; std::vector allLogs; @@ -208,10 +194,10 @@ int calllog_run_main(int argc, char** argv) { const char* kTabLabels[4] = {"All", "Missed", "In", "Out"}; auto render = [&]() { - XSetForeground(dpy, gc, 0x222222); + XSetForeground(dpy, gc, theme.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); - XSetForeground(dpy, gc, 0x1c1c1c); + XSetForeground(dpy, gc, theme.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH); for (int t = 0; t < 4; ++t) { @@ -219,9 +205,9 @@ int calllog_run_main(int argc, char** argv) { int tw = ((t + 1) * kWinWidth) / 4 - tx; bool active = (t == filterTab); if (active) { - XSetForeground(dpy, gc, 0x005577); + XSetForeground(dpy, gc, theme.spec.selBg); XFillRectangle(dpy, pix, gc, tx, 0, tw, kHeaderH - 2); - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, tx, kHeaderH - 3, tw, 3); } std::string lbl(kTabLabels[t]); @@ -230,7 +216,7 @@ int calllog_run_main(int argc, char** argv) { tx + (tw - lw) / 2, 0, kHeaderH - 2, lbl); } - XSetForeground(dpy, gc, 0x444444); + XSetForeground(dpy, gc, theme.spec.border); XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1); if (filtered.empty()) { @@ -259,11 +245,11 @@ int calllog_run_main(int argc, char** argv) { bool isSel = (idx == selected); XSetForeground(dpy, gc, - isSel ? 0x005577 : ((i % 2 == 0) ? 0x282828 : 0x2e2e2e)); + isSel ? theme.spec.selBg : ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd)); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH); if (isSel) { - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH); } @@ -313,15 +299,15 @@ int calllog_run_main(int argc, char** argv) { int sbH = std::max(16, (kListH * kVisibleRows) / total); int sbY = kListY + ((kListH - sbH) * scrollOffset) / (total - kVisibleRows); - XSetForeground(dpy, gc, 0x666666); + XSetForeground(dpy, gc, theme.spec.divider); XFillRectangle(dpy, pix, gc, kWinWidth - 3, sbY, 3, sbH); } } int sbY = kWinHeight - kSoftbarH; - XSetForeground(dpy, gc, 0x181818); + XSetForeground(dpy, gc, theme.spec.bgFooter); XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, kSoftbarH); - XSetForeground(dpy, gc, 0x444444); + XSetForeground(dpy, gc, theme.spec.border); XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, 1); drawPango(layoutMain, &colLight, 6, sbY + 1, kSoftbarH - 1, "Dialer"); @@ -348,6 +334,9 @@ int calllog_run_main(int argc, char** argv) { auto showWindow = [&]() { closeOtherPlugins("calllog"); + bool themeChanged = theme.checkAndReload( + dpy, screen, visual, cmap, layoutMain, layoutSmall, layoutIcon, + nullptr, win); if (!visible) { openStartMs = nowMs(); startupIgnoreUntilMs = openStartMs + 90; @@ -356,7 +345,9 @@ int calllog_run_main(int argc, char** argv) { softkeyDeadlineMs = 0; visible = true; XMapRaised(dpy, win); - XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); + if (!themeChanged) { + XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); + } for (int i = 0; i < 20; ++i) { if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess) { @@ -364,7 +355,7 @@ int calllog_run_main(int argc, char** argv) { } usleep(2000); } - if (reloadLogsIfNeeded() || filterTab != 0 || selected != 0) { + if (reloadLogsIfNeeded() || themeChanged || filterTab != 0 || selected != 0) { filterTab = 0; selected = 0; applyFilter(); @@ -373,7 +364,7 @@ int calllog_run_main(int argc, char** argv) { XFlush(dpy); writeIntFile(activePidPath, static_cast(::getpid())); } else { - if (reloadLogsIfNeeded()) { + if (reloadLogsIfNeeded() || themeChanged) { render(); } } @@ -428,6 +419,10 @@ int calllog_run_main(int argc, char** argv) { hideWindow(false); } else if (cmd.rfind("OPEN", 0) == 0) { showWindow(); + } else if (cmd == "RELOAD_THEME") { + theme.load(dpy, screen, visual, cmap, layoutMain, layoutSmall, + layoutIcon, nullptr, win); + render(); } } @@ -614,9 +609,6 @@ int calllog_run_main(int argc, char** argv) { ::unlink(getPluginFifoPath("calllog").c_str()); ::unlink(daemonPidPath.c_str()); - pango_font_description_free(descIcon); - pango_font_description_free(descSmall); - pango_font_description_free(descMain); g_object_unref(layoutIcon); g_object_unref(layoutSmall); g_object_unref(layoutMain); diff --git a/src/dwm-plugins/camera_plugin.cpp b/src/dwm-plugins/camera_plugin.cpp index 516e5a4..9136b53 100644 --- a/src/dwm-plugins/camera_plugin.cpp +++ b/src/dwm-plugins/camera_plugin.cpp @@ -198,10 +198,12 @@ int runCameraApp(int argc, char** argv) { Visual* visual = DefaultVisual(dpy, screen); Colormap cmap = DefaultColormap(dpy, screen); + ThemeSpec initSpec = loadThemeSpec(dpy, screen); + XSetWindowAttributes swa {}; swa.override_redirect = True; - swa.background_pixel = 0x181818; - swa.border_pixel = 0x005577; + swa.background_pixel = initSpec.bgMain; + swa.border_pixel = initSpec.selBg; swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | VisibilityChangeMask | StructureNotifyMask; @@ -223,37 +225,18 @@ int runCameraApp(int argc, char** argv) { GC gc = XCreateGC(dpy, pix, 0, nullptr); XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap); - PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen); - PangoContext* pangoCtx = pango_font_map_create_context(fontMap); - - PangoLayout* layoutMain = pango_layout_new(pangoCtx); - PangoFontDescription* descMain = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 15px"); - pango_layout_set_font_description(layoutMain, descMain); - - PangoLayout* layoutSmall = pango_layout_new(pangoCtx); - PangoFontDescription* descSmall = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 12px"); - pango_layout_set_font_description(layoutSmall, descSmall); - - PangoLayout* layoutIcon = pango_layout_new(pangoCtx); - PangoFontDescription* descIcon = - pango_font_description_from_string("DejaVuSansM Nerd Font, Fira Code 16px"); - pango_layout_set_font_description(layoutIcon, descIcon); - - auto allocColor = [&](const char* hex) { - XftColor col {}; - XftColorAllocName(dpy, visual, cmap, hex, &col); - return col; - }; - - XftColor colWhite = allocColor("#ffffff"); - XftColor colLight = allocColor("#dddddd"); - XftColor colMuted = allocColor("#999999"); - XftColor colYellow = allocColor("#f4d03f"); - XftColor colGreen = allocColor("#2ecc71"); - XftColor colBlue = allocColor("#5dade2"); - XftColor colRed = allocColor("#e74c3c"); + PluginTheme pt; + pt.init(dpy, screen, visual, cmap, win); + auto& layoutMain = pt.layoutMain; + auto& layoutSmall = pt.layoutSmall; + auto& layoutIcon = pt.layoutIcon; + auto& colWhite = pt.colWhite; + auto& colLight = pt.colLight; + auto& colMuted = pt.colMuted; + auto& colYellow = pt.colYellow; + auto& colGreen = pt.colGreen; + auto& colBlue = pt.colBlue; + auto& colRed = pt.colRed; std::vector vfFront(kVfW * kVfH, 0x101010); std::vector vfBack(kVfW * kVfH, 0x101010); @@ -524,9 +507,9 @@ int runCameraApp(int argc, char** argv) { const std::string& centerLabel, const std::string& rightLabel) { const int barY = kWinHeight - 26; - XSetForeground(dpy, gc, 0x141414); + XSetForeground(dpy, gc, pt.spec.bgFooter); XFillRectangle(dpy, pix, gc, 0, barY, kWinWidth, 26); - XSetForeground(dpy, gc, 0x2d2d2d); + XSetForeground(dpy, gc, pt.spec.divider); XDrawLine(dpy, pix, gc, 0, barY, kWinWidth, barY); drawPango(layoutMain, &colBlue, 6, barY, 26, leftLabel); @@ -549,14 +532,15 @@ int runCameraApp(int argc, char** argv) { auto drawUI = [&]() { if (!visible) return; + pt.checkAndReload(); int64_t tNow = nowMs(); - XSetForeground(dpy, gc, 0x181818); + XSetForeground(dpy, gc, pt.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); if (mode == ViewMode::Viewfinder) { // Top status bar (28px) - XSetForeground(dpy, gc, 0x141414); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 28); drawPango(layoutIcon, &colGreen, 6, 0, 28, kIconCamera); drawPango(layoutMain, &colWhite, 28, 0, 28, "800x600", 85); @@ -590,7 +574,7 @@ int runCameraApp(int argc, char** argv) { drawPango(layoutSmall, &colLight, 16, vfY + 78, 24, errMsg, 208); } else { // Subtle framing reticle in center of viewfinder - XSetForeground(dpy, gc, 0x0088bb); + XSetForeground(dpy, gc, pt.spec.accentAlt); int cx = kWinWidth / 2; int cy = vfY + kVfH / 2; XDrawLine(dpy, pix, gc, cx - 10, cy, cx - 3, cy); @@ -600,14 +584,14 @@ int runCameraApp(int argc, char** argv) { } if (tNow < shutterFlashUntilMs) { - XSetForeground(dpy, gc, 0xffffff); + XSetForeground(dpy, gc, pt.spec.fgBright); XDrawRectangle(dpy, pix, gc, 0, vfY, kVfW - 1, kVfH - 1); XDrawRectangle(dpy, pix, gc, 1, vfY + 1, kVfW - 3, kVfH - 3); } if (countdownActive) { int remSec = std::max(1, static_cast((countdownTargetMs - tNow + 999) / 1000)); - XSetForeground(dpy, gc, 0x005577); + XSetForeground(dpy, gc, pt.spec.selBg); XFillRectangle(dpy, pix, gc, 78, vfY + 66, 84, 44); drawPango(layoutMain, &colWhite, 92, vfY + 66, 44, "Timer: " + std::to_string(remSec)); @@ -615,31 +599,34 @@ int runCameraApp(int argc, char** argv) { // Camera control strip (y = 208..270, 62px) const int ctlY = 208; - XSetForeground(dpy, gc, 0x1e1e1e); + XSetForeground(dpy, gc, pt.spec.bgAltOdd); XFillRectangle(dpy, pix, gc, 0, ctlY, kWinWidth, 62); - XSetForeground(dpy, gc, 0x333333); + XSetForeground(dpy, gc, pt.spec.border); XDrawLine(dpy, pix, gc, 0, ctlY, kWinWidth, ctlY); // Pill 1: Flash (* or 1) - XSetForeground(dpy, gc, fl ? 0x004466 : 0x282828); + XSetForeground(dpy, gc, fl ? pt.spec.bgInputFocus : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 6, ctlY + 5, 110, 24); drawPango(layoutSmall, fl ? &colYellow : &colLight, 10, ctlY + 5, 24, std::string("[*] Flash:") + (fl ? "ON" : "OFF"), 102); // Pill 2: Torch (0 or 2) - XSetForeground(dpy, gc, torchOn ? 0x004466 : 0x282828); + XSetForeground(dpy, gc, + torchOn ? pt.spec.bgInputFocus : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 124, ctlY + 5, 110, 24); drawPango(layoutSmall, torchOn ? &colYellow : &colLight, 128, ctlY + 5, 24, std::string("[0] Torch:") + (torchOn ? "ON" : "OFF"), 102); // Pill 3: Zoom (Up/Dn) - XSetForeground(dpy, gc, zf > 1 ? 0x004466 : 0x282828); + XSetForeground(dpy, gc, + zf > 1 ? pt.spec.bgInputFocus : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 6, ctlY + 33, 110, 24); drawPango(layoutSmall, &colBlue, 10, ctlY + 33, 24, "[U/D] Zoom:" + std::to_string(zf) + "x", 102); // Pill 4: Timer (# or 3) - XSetForeground(dpy, gc, timerEnabled ? 0x004466 : 0x282828); + XSetForeground(dpy, gc, + timerEnabled ? pt.spec.bgInputFocus : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 124, ctlY + 33, 110, 24); drawPango(layoutSmall, timerEnabled ? &colYellow : &colLight, 128, ctlY + 33, 24, @@ -648,7 +635,7 @@ int runCameraApp(int argc, char** argv) { drawSoftkeyBar("Gallery", "SNAP", "Close"); } else if (mode == ViewMode::Gallery) { - XSetForeground(dpy, gc, 0x141414); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 28); drawPango(layoutIcon, &colBlue, 6, 0, 28, kIconImage); drawPango(layoutMain, &colWhite, 28, 0, 28, "Gallery", 110); @@ -684,7 +671,7 @@ int runCameraApp(int argc, char** argv) { } else if (mode == ViewMode::ConfirmDelete && !photos.empty()) { const auto& pe = photos[selectedPhoto]; - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36); drawPango(layoutIcon, &colRed, 8, 0, 36, kIconTrash); drawPango(layoutMain, &colWhite, 30, 0, 36, "Delete Photo?", 200); @@ -696,8 +683,10 @@ int runCameraApp(int argc, char** argv) { for (int i = 0; i < 2; ++i) { int ry = 156 + i * 40; bool isSel = (confirmSelected == i); - XSetForeground(dpy, gc, - isSel ? (i == 1 ? 0x922b21 : 0x005577) : 0x2a2a2a); + XSetForeground( + dpy, gc, + isSel ? (i == 1 ? pt.spec.dangerBg : pt.spec.selBg) + : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 16, ry, kWinWidth - 32, 34); drawPango(layoutMain, &colWhite, 28, ry, 34, i == 0 ? "No, Keep Photo" : "Yes, Delete Photo", 184); @@ -708,9 +697,9 @@ int runCameraApp(int argc, char** argv) { { std::lock_guard lk(frameMutex); if (!statusBanner.empty() && tNow < statusBannerUntilMs) { - XSetForeground(dpy, gc, 0x102535); + XSetForeground(dpy, gc, pt.spec.bgToast); XFillRectangle(dpy, pix, gc, 8, 174, kWinWidth - 16, 28); - XSetForeground(dpy, gc, 0x2ecc71); + XSetForeground(dpy, gc, pt.spec.success); XDrawRectangle(dpy, pix, gc, 8, 174, kWinWidth - 17, 27); drawPango(layoutSmall, &colWhite, 14, 174, 28, statusBanner, kWinWidth - 28); @@ -814,6 +803,9 @@ int runCameraApp(int argc, char** argv) { for (const auto& cmd : readFifoLines(fifoFd)) { if (cmd == "NOP") { continue; + } else if (cmd == "RELOAD_THEME") { + pt.reload(); + if (visible) drawUI(); } else if (cmd == "CLOSE") { hideWindow(); } else if (cmd == "OPEN") { @@ -993,7 +985,10 @@ int runCameraApp(int argc, char** argv) { if (pr > 0) { if (fifoIdx >= 0 && (pfds[fifoIdx].revents & POLLIN)) { for (const auto& cmd : readFifoLines(fifoFd)) { - if (cmd == "CLOSE") { + if (cmd == "RELOAD_THEME") { + pt.reload(); + if (visible) drawUI(); + } else if (cmd == "CLOSE") { hideWindow(); } else if (cmd == "OPEN") { showWindow(""); diff --git a/src/dwm-plugins/contacts_plugin.cpp b/src/dwm-plugins/contacts_plugin.cpp index 6222b31..6a76e9f 100644 --- a/src/dwm-plugins/contacts_plugin.cpp +++ b/src/dwm-plugins/contacts_plugin.cpp @@ -130,33 +130,19 @@ int runContactsApp(int argc, char** argv) { PangoContext* pangoCtx = pango_font_map_create_context(fontMap); PangoLayout* layoutMain = pango_layout_new(pangoCtx); - PangoFontDescription* descMain = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 15px"); - pango_layout_set_font_description(layoutMain, descMain); - PangoLayout* layoutSmall = pango_layout_new(pangoCtx); - PangoFontDescription* descSmall = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 12px"); - pango_layout_set_font_description(layoutSmall, descSmall); - PangoLayout* layoutIcon = pango_layout_new(pangoCtx); - PangoFontDescription* descIcon = - pango_font_description_from_string("DejaVuSansM Nerd Font, Fira Code 16px"); - pango_layout_set_font_description(layoutIcon, descIcon); - auto allocColor = [&](const char* hex) { - XftColor col{}; - XftColorAllocName(dpy, visual, cmap, hex, &col); - return col; - }; - - XftColor colWhite = allocColor("#ffffff"); - XftColor colLight = allocColor("#dddddd"); - XftColor colMuted = allocColor("#999999"); - XftColor colYellow = allocColor("#f4d03f"); - XftColor colGreen = allocColor("#2ecc71"); - XftColor colBlue = allocColor("#5dade2"); - XftColor colRed = allocColor("#e74c3c"); + PluginTheme theme; + theme.load(dpy, screen, visual, cmap, layoutMain, layoutSmall, layoutIcon, + nullptr, win); + XftColor& colWhite = theme.colWhite; + XftColor& colLight = theme.colLight; + XftColor& colMuted = theme.colMuted; + XftColor& colYellow = theme.colYellow; + XftColor& colGreen = theme.colGreen; + XftColor& colBlue = theme.colBlue; + XftColor& colRed = theme.colRed; ContactBook book; std::vector filtered; @@ -277,9 +263,9 @@ int runContactsApp(int argc, char** argv) { const std::string& rightLabel, XftColor* centerCol = nullptr) { int sbY = kWinHeight - kSoftbarH; - XSetForeground(dpy, gc, 0x181818); + XSetForeground(dpy, gc, theme.spec.bgFooter); XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, kSoftbarH); - XSetForeground(dpy, gc, 0x444444); + XSetForeground(dpy, gc, theme.spec.border); XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, 1); if (!leftLabel.empty()) { @@ -298,14 +284,14 @@ int runContactsApp(int argc, char** argv) { }; auto render = [&]() { - XSetForeground(dpy, gc, 0x222222); + XSetForeground(dpy, gc, theme.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); if (mode == ViewMode::List || mode == ViewMode::Options) { // Header bar with live search / count - XSetForeground(dpy, gc, 0x1c1c1c); + XSetForeground(dpy, gc, theme.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH); - XSetForeground(dpy, gc, 0x444444); + XSetForeground(dpy, gc, theme.spec.border); XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1); if (searchQuery.empty()) { @@ -347,10 +333,10 @@ int runContactsApp(int argc, char** argv) { bool isSel = (idx == selected); XSetForeground(dpy, gc, - isSel ? 0x005577 : ((i % 2 == 0) ? 0x282828 : 0x2e2e2e)); + isSel ? theme.spec.selBg : ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd)); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH); if (isSel) { - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH); } @@ -367,7 +353,7 @@ int runContactsApp(int argc, char** argv) { int sbH = std::max(16, (kListH * kVisibleRows) / total); int sbY = kListY + ((kListH - sbH) * scrollOffset) / (total - kVisibleRows); - XSetForeground(dpy, gc, 0x666666); + XSetForeground(dpy, gc, theme.spec.divider); XFillRectangle(dpy, pix, gc, kWinWidth - 3, sbY, 3, sbH); } } @@ -394,18 +380,18 @@ int runContactsApp(int argc, char** argv) { int menuRowH = 34; int menuH = optCount * menuRowH + 4; int menuY = kWinHeight - kSoftbarH - menuH; - XSetForeground(dpy, gc, 0x181818); + XSetForeground(dpy, gc, theme.spec.bgFooter); XFillRectangle(dpy, pix, gc, 8, menuY, kWinWidth - 16, menuH); - XSetForeground(dpy, gc, 0x005577); + XSetForeground(dpy, gc, theme.spec.selBg); XDrawRectangle(dpy, pix, gc, 8, menuY, kWinWidth - 17, menuH - 1); for (int i = 0; i < optCount; ++i) { int ry = menuY + 2 + i * menuRowH; bool isSel = (i == optionSelected); if (isSel) { - XSetForeground(dpy, gc, 0x005577); + XSetForeground(dpy, gc, theme.spec.selBg); XFillRectangle(dpy, pix, gc, 10, ry, kWinWidth - 20, menuRowH); - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 10, ry, 4, menuRowH); } XftColor* icCol = (i == 5) ? &colRed : (isSel ? &colYellow : &colGreen); @@ -424,11 +410,11 @@ int runContactsApp(int argc, char** argv) { c = filtered[selected]; } constexpr int kCardH = 56; - XSetForeground(dpy, gc, 0x1b3b4b); + XSetForeground(dpy, gc, theme.spec.bgInputFocus); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kCardH); - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 0, 0, 4, kCardH); - XSetForeground(dpy, gc, 0x005577); + XSetForeground(dpy, gc, theme.spec.selBg); XFillRectangle(dpy, pix, gc, 0, kCardH - 2, kWinWidth, 2); drawPango(layoutIcon, &colYellow, 10, 6, 24, kIconUser); @@ -469,10 +455,10 @@ int runContactsApp(int argc, char** argv) { bool isSel = (idx == detailSelected); XSetForeground(dpy, gc, - isSel ? 0x005577 : ((i % 2 == 0) ? 0x282828 : 0x2e2e2e)); + isSel ? theme.spec.selBg : ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd)); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH); if (isSel) { - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH); } XftColor* icCol = @@ -485,9 +471,9 @@ int runContactsApp(int argc, char** argv) { drawSoftbar("Edit", "Select", "Back", &colGreen); } else if (mode == ViewMode::Edit) { - XSetForeground(dpy, gc, 0x1c1c1c); + XSetForeground(dpy, gc, theme.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH); - XSetForeground(dpy, gc, 0x444444); + XSetForeground(dpy, gc, theme.spec.border); XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1); drawPango(layoutIcon, &colGreen, 8, 0, kHeaderH, @@ -505,9 +491,9 @@ int runContactsApp(int argc, char** argv) { // Field 0: Name box (y = 44..108) auto drawFieldBox = [&](int y, int h, bool active, const char* label, const std::string& val, const char* placeholder) { - XSetForeground(dpy, gc, active ? 0x1b3b4b : 0x2a2a2a); + XSetForeground(dpy, gc, active ? theme.spec.bgInputFocus : theme.spec.bgButton); XFillRectangle(dpy, pix, gc, 6, y, kWinWidth - 12, h); - XSetForeground(dpy, gc, active ? 0xf4d03f : 0x444444); + XSetForeground(dpy, gc, active ? theme.spec.accent : theme.spec.border); XDrawRectangle(dpy, pix, gc, 6, y, kWinWidth - 13, h - 1); drawPango(layoutSmall, active ? &colYellow : &colMuted, 12, y + 3, 18, label); @@ -533,10 +519,10 @@ int runContactsApp(int argc, char** argv) { // Button Row 2: Pick from Recent Numbers (y = 168..208) auto drawBtnRow = [&](int y, int h, bool active, const char* icon, const char* label, XftColor* icCol) { - XSetForeground(dpy, gc, active ? 0x005577 : 0x2c2c2c); + XSetForeground(dpy, gc, active ? theme.spec.selBg : theme.spec.bgButton); XFillRectangle(dpy, pix, gc, 6, y, kWinWidth - 12, h); if (active) { - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 6, y, 4, h); } drawPango(layoutIcon, active ? &colYellow : icCol, 14, y, h, icon); @@ -555,9 +541,9 @@ int runContactsApp(int argc, char** argv) { drawSoftbar("Save", (editField >= 2) ? "Select" : "Next", canClearChar ? "Clear" : "Cancel", &colGreen); } else if (mode == ViewMode::PickRecent) { - XSetForeground(dpy, gc, 0x1c1c1c); + XSetForeground(dpy, gc, theme.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH); - XSetForeground(dpy, gc, 0x444444); + XSetForeground(dpy, gc, theme.spec.border); XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1); drawPango(layoutIcon, &colBlue, 8, 0, kHeaderH, kIconClock); drawPango(layoutMain, &colWhite, 32, 0, kHeaderH, "Recent Numbers", 200); @@ -579,10 +565,10 @@ int runContactsApp(int argc, char** argv) { int rowH = nextRy - ry; bool isSel = (idx == recentSelected); XSetForeground(dpy, gc, - isSel ? 0x005577 : ((i % 2 == 0) ? 0x282828 : 0x2e2e2e)); + isSel ? theme.spec.selBg : ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd)); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH); if (isSel) { - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH); } drawPango(layoutIcon, isSel ? &colYellow : &colGreen, 10, ry, rowH, @@ -593,7 +579,7 @@ int runContactsApp(int argc, char** argv) { } drawSoftbar("Use", "Select", "Back", &colGreen); } else if (mode == ViewMode::ConfirmDelete) { - XSetForeground(dpy, gc, 0x1c1c1c); + XSetForeground(dpy, gc, theme.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH); drawPango(layoutIcon, &colRed, 8, 0, kHeaderH, kIconTrash); drawPango(layoutMain, &colWhite, 32, 0, kHeaderH, "Delete Contact?", 200); @@ -609,10 +595,10 @@ int runContactsApp(int argc, char** argv) { for (int i = 0; i < 2; ++i) { int ry = 130 + i * 50; bool isSel = (i == confirmSelected); - XSetForeground(dpy, gc, isSel ? 0x005577 : 0x2c2c2c); + XSetForeground(dpy, gc, isSel ? theme.spec.selBg : theme.spec.bgButton); XFillRectangle(dpy, pix, gc, 8, ry, kWinWidth - 16, 44); if (isSel) { - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 8, ry, 4, 44); } drawPango(layoutIcon, (i == 1) ? &colRed : &colGreen, 16, ry, 44, @@ -691,6 +677,9 @@ int runContactsApp(int argc, char** argv) { auto showWindow = [&](const std::string& arg) { closeOtherPlugins("contacts"); + bool themeChanged = theme.checkAndReload( + dpy, screen, visual, cmap, layoutMain, layoutSmall, layoutIcon, + nullptr, win); bool openNewMode = (arg.rfind("new:", 0) == 0); std::string prefill = openNewMode ? arg.substr(4) : ""; @@ -703,7 +692,7 @@ int runContactsApp(int argc, char** argv) { zeroHeld = false; visible = true; XMapRaised(dpy, win); - if (!openNewMode) { + if (!openNewMode && !themeChanged) { XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); } for (int i = 0; i < 20; ++i) { @@ -717,8 +706,8 @@ int runContactsApp(int argc, char** argv) { if (openNewMode) { beginNewContact(prefill); render(); - } else if (reloaded || mode != ViewMode::List || !searchQuery.empty() || - selected != 0) { + } else if (reloaded || themeChanged || mode != ViewMode::List || + !searchQuery.empty() || selected != 0) { mode = ViewMode::List; searchQuery.clear(); selected = 0; @@ -918,6 +907,10 @@ int runContactsApp(int argc, char** argv) { } else { showWindow(""); } + } else if (cmd == "RELOAD_THEME") { + theme.load(dpy, screen, visual, cmap, layoutMain, layoutSmall, + layoutIcon, nullptr, win); + render(); } else if (cmd == "OPEN") { showWindow(""); } else if (cmd.rfind("OPEN ", 0) == 0) { @@ -1443,9 +1436,6 @@ int runContactsApp(int argc, char** argv) { ::unlink(getPluginFifoPath("contacts").c_str()); ::unlink(daemonPidPath.c_str()); - pango_font_description_free(descIcon); - pango_font_description_free(descSmall); - pango_font_description_free(descMain); g_object_unref(layoutIcon); g_object_unref(layoutSmall); g_object_unref(layoutMain); diff --git a/src/dwm-plugins/dialer_plugin.cpp b/src/dwm-plugins/dialer_plugin.cpp index 71bee84..9050b7c 100644 --- a/src/dwm-plugins/dialer_plugin.cpp +++ b/src/dwm-plugins/dialer_plugin.cpp @@ -106,37 +106,19 @@ int dialer_run_main(int argc, char** argv) { PangoContext* pangoCtx = pango_font_map_create_context(fontMap); PangoLayout* layoutNum = pango_layout_new(pangoCtx); - PangoFontDescription* descNum = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font Bold 18px"); - pango_layout_set_font_description(layoutNum, descNum); - PangoLayout* layoutMain = pango_layout_new(pangoCtx); - PangoFontDescription* descMain = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 15px"); - pango_layout_set_font_description(layoutMain, descMain); - PangoLayout* layoutSmall = pango_layout_new(pangoCtx); - PangoFontDescription* descSmall = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 12px"); - pango_layout_set_font_description(layoutSmall, descSmall); - PangoLayout* layoutIcon = pango_layout_new(pangoCtx); - PangoFontDescription* descIcon = - pango_font_description_from_string("DejaVuSansM Nerd Font, Fira Code 16px"); - pango_layout_set_font_description(layoutIcon, descIcon); - auto allocColor = [&](const char* hex) { - XftColor col{}; - XftColorAllocName(dpy, visual, cmap, hex, &col); - return col; - }; - - XftColor colWhite = allocColor("#ffffff"); - XftColor colLight = allocColor("#dddddd"); - XftColor colMuted = allocColor("#999999"); - XftColor colYellow = allocColor("#f4d03f"); - XftColor colGreen = allocColor("#2ecc71"); - XftColor colBlue = allocColor("#5dade2"); + PluginTheme theme; + theme.load(dpy, screen, visual, cmap, layoutMain, layoutSmall, layoutIcon, + layoutNum, win); + XftColor& colWhite = theme.colWhite; + XftColor& colLight = theme.colLight; + XftColor& colMuted = theme.colMuted; + XftColor& colYellow = theme.colYellow; + XftColor& colGreen = theme.colGreen; + XftColor& colBlue = theme.colBlue; ContactBook book; std::vector allContacts; @@ -241,17 +223,17 @@ int dialer_run_main(int argc, char** argv) { constexpr int kVisibleRows = 5; auto render = [&]() { - XSetForeground(dpy, gc, 0x222222); + XSetForeground(dpy, gc, theme.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); bool topSel = (selected < 0); - XSetForeground(dpy, gc, topSel ? 0x1b3b4b : 0x1c1c1c); + XSetForeground(dpy, gc, topSel ? theme.spec.bgInputFocus : theme.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH); if (topSel) { - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 0, 0, 4, kHeaderH); } - XSetForeground(dpy, gc, topSel ? 0x005577 : 0x444444); + XSetForeground(dpy, gc, topSel ? theme.spec.selBg : theme.spec.border); XFillRectangle(dpy, pix, gc, 0, kHeaderH - 2, kWinWidth, 2); drawPango(layoutIcon, &colGreen, 10, 4, 30, kIconPhone); @@ -315,11 +297,11 @@ int dialer_run_main(int argc, char** argv) { bool isSel = (idx == selected); XSetForeground(dpy, gc, - isSel ? 0x005577 : ((i % 2 == 0) ? 0x282828 : 0x2e2e2e)); + isSel ? theme.spec.selBg : ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd)); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH); if (isSel) { - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH); } @@ -345,15 +327,15 @@ int dialer_run_main(int argc, char** argv) { int sbH = std::max(16, (kListH * kVisibleRows) / total); int sbY = kListY + ((kListH - sbH) * scrollOffset) / (total - kVisibleRows); - XSetForeground(dpy, gc, 0x666666); + XSetForeground(dpy, gc, theme.spec.divider); XFillRectangle(dpy, pix, gc, kWinWidth - 3, sbY, 3, sbH); } } int sbY = kWinHeight - kSoftbarH; - XSetForeground(dpy, gc, 0x181818); + XSetForeground(dpy, gc, theme.spec.bgFooter); XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, kSoftbarH); - XSetForeground(dpy, gc, 0x444444); + XSetForeground(dpy, gc, theme.spec.border); XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, 1); std::string leftLabel = number.empty() ? "Call Log" : "+Sign"; @@ -387,6 +369,9 @@ int dialer_run_main(int argc, char** argv) { auto showWindow = [&](const std::string& arg) { closeOtherPlugins("dialer"); + bool themeChanged = theme.checkAndReload( + dpy, screen, visual, cmap, layoutMain, layoutSmall, layoutIcon, + layoutNum, win); std::string cleanArg = normalizePhone(arg); if (!visible) { openStartMs = nowMs(); @@ -398,7 +383,7 @@ int dialer_run_main(int argc, char** argv) { zeroPressMs = zeroHeld ? openStartMs : 0; visible = true; XMapRaised(dpy, win); - if (cleanArg.empty()) { + if (cleanArg.empty() && !themeChanged) { XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); } for (int i = 0; i < 20; ++i) { @@ -409,7 +394,7 @@ int dialer_run_main(int argc, char** argv) { usleep(2000); } bool reloaded = reloadContactsIfNeeded(); - if (!cleanArg.empty() || reloaded || number != cleanArg) { + if (!cleanArg.empty() || reloaded || themeChanged || number != cleanArg) { number = cleanArg; selected = number.empty() ? (allContacts.empty() ? -1 : 0) : -1; updateFilter(); @@ -494,6 +479,10 @@ int dialer_run_main(int argc, char** argv) { showWindow(""); } else if (cmd.rfind("OPEN ", 0) == 0) { showWindow(cmd.substr(5)); + } else if (cmd == "RELOAD_THEME") { + theme.load(dpy, screen, visual, cmap, layoutMain, layoutSmall, + layoutIcon, layoutNum, win); + render(); } } @@ -723,10 +712,6 @@ int dialer_run_main(int argc, char** argv) { ::unlink(getPluginFifoPath("dialer").c_str()); ::unlink(daemonPidPath.c_str()); - pango_font_description_free(descIcon); - pango_font_description_free(descSmall); - pango_font_description_free(descMain); - pango_font_description_free(descNum); g_object_unref(layoutIcon); g_object_unref(layoutSmall); g_object_unref(layoutMain); diff --git a/src/dwm-plugins/dwm_plugin_runner.c b/src/dwm-plugins/dwm_plugin_runner.c index 2e00058..157000a 100644 --- a/src/dwm-plugins/dwm_plugin_runner.c +++ b/src/dwm-plugins/dwm_plugin_runner.c @@ -127,7 +127,10 @@ static int try_dmenu_socket_client(int sub_argc, char **sub_argv, int *exit_code } static int try_send_plugin_fifo(const char *plugin_name, int sub_argc, char **sub_argv) { - if (sub_argc >= 2 && sub_argv[1] && strcmp(sub_argv[1], "--preload") == 0) { + if (sub_argc >= 2 && sub_argv[1] && + (strcmp(sub_argv[1], "--preload") == 0 || + strcmp(sub_argv[1], "--apply") == 0 || + strcmp(sub_argv[1], "--export-default") == 0)) { return 0; } char pid_path[128]; @@ -212,6 +215,9 @@ static const char *resolve_plugin_name_from_argv0(const char *argv0) { if (strcasecmp(base, "xcamera") == 0 || strcasecmp(base, "camera") == 0) { return "camera"; } + if (strcasecmp(base, "xthemes") == 0 || strcasecmp(base, "themes") == 0) { + return "themes"; + } return NULL; } diff --git a/src/dwm-plugins/files_plugin.cpp b/src/dwm-plugins/files_plugin.cpp index 80e40e1..10422c9 100644 --- a/src/dwm-plugins/files_plugin.cpp +++ b/src/dwm-plugins/files_plugin.cpp @@ -236,10 +236,12 @@ int runFilesApp(int argc, char** argv) { Visual* visual = DefaultVisual(dpy, screen); Colormap cmap = DefaultColormap(dpy, screen); + ThemeSpec initSpec = loadThemeSpec(dpy, screen); + XSetWindowAttributes swa {}; swa.override_redirect = True; - swa.background_pixel = 0x222222; - swa.border_pixel = 0x005577; + swa.background_pixel = initSpec.bgMain; + swa.border_pixel = initSpec.selBg; swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | VisibilityChangeMask | StructureNotifyMask; @@ -261,38 +263,19 @@ int runFilesApp(int argc, char** argv) { GC gc = XCreateGC(dpy, pix, 0, nullptr); XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap); - PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen); - PangoContext* pangoCtx = pango_font_map_create_context(fontMap); - - PangoLayout* layoutMain = pango_layout_new(pangoCtx); - PangoFontDescription* descMain = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 15px"); - pango_layout_set_font_description(layoutMain, descMain); - - PangoLayout* layoutSmall = pango_layout_new(pangoCtx); - PangoFontDescription* descSmall = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 12px"); - pango_layout_set_font_description(layoutSmall, descSmall); - - PangoLayout* layoutIcon = pango_layout_new(pangoCtx); - PangoFontDescription* descIcon = - pango_font_description_from_string("DejaVuSansM Nerd Font, Fira Code 16px"); - pango_layout_set_font_description(layoutIcon, descIcon); - - auto allocColor = [&](const char* hex) { - XftColor col {}; - XftColorAllocName(dpy, visual, cmap, hex, &col); - return col; - }; - - XftColor colWhite = allocColor("#ffffff"); - XftColor colLight = allocColor("#dddddd"); - XftColor colMuted = allocColor("#999999"); - XftColor colYellow = allocColor("#f4d03f"); - XftColor colGreen = allocColor("#2ecc71"); - XftColor colBlue = allocColor("#5dade2"); - XftColor colOrange = allocColor("#e67e22"); - XftColor colRed = allocColor("#e74c3c"); + PluginTheme pt; + pt.init(dpy, screen, visual, cmap, win); + auto& layoutMain = pt.layoutMain; + auto& layoutSmall = pt.layoutSmall; + auto& layoutIcon = pt.layoutIcon; + auto& colWhite = pt.colWhite; + auto& colLight = pt.colLight; + auto& colMuted = pt.colMuted; + auto& colYellow = pt.colYellow; + auto& colGreen = pt.colGreen; + auto& colBlue = pt.colBlue; + auto& colOrange = pt.colOrange; + auto& colRed = pt.colRed; const char* homeEnv = std::getenv("HOME"); std::string homeDir = (homeEnv && homeEnv[0]) ? homeEnv : "/home/user"; @@ -599,9 +582,9 @@ int runFilesApp(int argc, char** argv) { const std::string& centerLabel, const std::string& rightLabel) { const int barY = kWinHeight - 26; - XSetForeground(dpy, gc, 0x141414); + XSetForeground(dpy, gc, pt.spec.bgFooter); XFillRectangle(dpy, pix, gc, 0, barY, kWinWidth, 26); - XSetForeground(dpy, gc, 0x2d2d2d); + XSetForeground(dpy, gc, pt.spec.divider); XDrawLine(dpy, pix, gc, 0, barY, kWinWidth, barY); drawPango(layoutMain, &colBlue, 6, barY, 26, leftLabel); @@ -624,14 +607,15 @@ int runFilesApp(int argc, char** argv) { auto drawUI = [&]() { if (!visible) return; + pt.checkAndReload(); - XSetForeground(dpy, gc, 0x222222); + XSetForeground(dpy, gc, pt.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); if (mode == ViewMode::Browser) { - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 38); - XSetForeground(dpy, gc, 0x333333); + XSetForeground(dpy, gc, pt.spec.border); XDrawLine(dpy, pix, gc, 0, 37, kWinWidth, 37); if (searchQuery.empty()) { @@ -681,12 +665,13 @@ int runFilesApp(int argc, char** argv) { bool isSel = (idx == selected); if (isSel) { - XSetForeground(dpy, gc, 0x005577); + XSetForeground(dpy, gc, pt.spec.selBg); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); - XSetForeground(dpy, gc, 0x0088bb); + XSetForeground(dpy, gc, pt.spec.accentAlt); XDrawLine(dpy, pix, gc, 0, ry, kWinWidth, ry); } else { - XSetForeground(dpy, gc, (i % 2 == 0) ? 0x222222 : 0x1d1d1d); + XSetForeground(dpy, gc, + (i % 2 == 0) ? pt.spec.bgAltEven : pt.spec.bgAltOdd); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); } @@ -738,7 +723,7 @@ int runFilesApp(int argc, char** argv) { searchQuery.empty() ? "Close" : "Clear"); } else if (mode == ViewMode::Actions) { - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34); drawPango(layoutIcon, &colBlue, 6, 0, 34, kIconList); drawPango(layoutMain, &colWhite, 28, 0, 34, "File Actions", 200); @@ -755,7 +740,7 @@ int runFilesApp(int argc, char** argv) { if (idx >= static_cast(actionsList.size())) break; int ry = listY + i * rowH; bool isSel = (idx == actionSelected); - XSetForeground(dpy, gc, isSel ? 0x005577 : 0x222222); + XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); drawPango(layoutIcon, isSel ? &colYellow : &colBlue, 8, ry, rowH, actionsList[idx].icon); @@ -765,7 +750,7 @@ int runFilesApp(int argc, char** argv) { drawSoftkeyBar("Select", "Select", "Back"); } else if (mode == ViewMode::TextView) { - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 32); drawPango(layoutIcon, &colBlue, 6, 0, 32, kIconFileText); drawPango(layoutMain, &colWhite, 28, 0, 32, viewingFileName, 150); @@ -791,7 +776,7 @@ int runFilesApp(int argc, char** argv) { drawSoftkeyBar("Edit", "Down", "Back"); } else if (mode == ViewMode::ImageView) { - XSetForeground(dpy, gc, 0x161616); + 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); @@ -807,7 +792,7 @@ int runFilesApp(int argc, char** argv) { } else if (mode == ViewMode::PropsView && !filteredItems.empty()) { const auto& it = filteredItems[selected]; - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34); drawPango(layoutIcon, &colBlue, 6, 0, 34, kIconInfo); drawPango(layoutMain, &colWhite, 28, 0, 34, "Properties", 200); @@ -838,7 +823,7 @@ int runFilesApp(int argc, char** argv) { drawSoftkeyBar("Back", "Back", "Back"); } else if (mode == ViewMode::InputModal) { - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36); drawPango(layoutIcon, &colBlue, 6, 0, 36, kIconEdit); drawPango(layoutMain, &colWhite, 28, 0, 36, inputTitle, 145); @@ -849,9 +834,9 @@ int runFilesApp(int argc, char** argv) { else if (caseMode == TextCaseMode::Num) mBadge = "[123]"; drawPango(layoutSmall, &colYellow, kWinWidth - 46, 0, 36, mBadge); - XSetForeground(dpy, gc, 0x182630); + XSetForeground(dpy, gc, pt.spec.bgInputFocus); XFillRectangle(dpy, pix, gc, 10, 74, kWinWidth - 20, 38); - XSetForeground(dpy, gc, 0x0088bb); + XSetForeground(dpy, gc, pt.spec.accentAlt); XDrawRectangle(dpy, pix, gc, 10, 74, kWinWidth - 21, 37); drawPango(layoutMain, &colWhite, 16, 74, 38, inputValue + "|", kWinWidth - 32); @@ -863,7 +848,7 @@ int runFilesApp(int argc, char** argv) { } else if (mode == ViewMode::ConfirmDelete && !filteredItems.empty()) { const auto& it = filteredItems[selected]; - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36); drawPango(layoutIcon, &colRed, 8, 0, 36, kIconTrash); drawPango(layoutMain, &colWhite, 30, 0, 36, "Delete Item?", 200); @@ -878,8 +863,10 @@ int runFilesApp(int argc, char** argv) { for (int i = 0; i < 2; ++i) { int ry = 164 + i * 40; bool isSel = (confirmSelected == i); - XSetForeground(dpy, gc, - isSel ? (i == 1 ? 0x922b21 : 0x005577) : 0x2a2a2a); + XSetForeground( + dpy, gc, + isSel ? (i == 1 ? pt.spec.dangerBg : pt.spec.selBg) + : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 16, ry, kWinWidth - 32, 34); drawPango(layoutMain, &colWhite, 28, ry, 34, i == 0 ? "No, Keep Item" : "Yes, Delete", 184); @@ -889,9 +876,9 @@ int runFilesApp(int argc, char** argv) { int64_t tNow = nowMs(); if (!statusBanner.empty() && tNow < statusBannerUntilMs) { - XSetForeground(dpy, gc, 0x102535); + XSetForeground(dpy, gc, pt.spec.bgToast); XFillRectangle(dpy, pix, gc, 8, kWinHeight - 58, kWinWidth - 16, 28); - XSetForeground(dpy, gc, 0x5dade2); + XSetForeground(dpy, gc, pt.spec.info); XDrawRectangle(dpy, pix, gc, 8, kWinHeight - 58, kWinWidth - 17, 27); drawPango(layoutSmall, &colWhite, 14, kWinHeight - 58, 28, statusBanner, kWinWidth - 28); @@ -1151,6 +1138,9 @@ int runFilesApp(int argc, char** argv) { for (const auto& cmd : readFifoLines(fifoFd)) { if (cmd == "NOP") { continue; + } else if (cmd == "RELOAD_THEME") { + pt.reload(); + if (visible) drawUI(); } else if (cmd == "CLOSE") { hideWindow(); } else if (cmd == "OPEN") { @@ -1384,7 +1374,10 @@ int runFilesApp(int argc, char** argv) { int pr = ::poll(pfds, nfds, timeoutMs); if (pr > 0 && fifoIdx >= 0 && (pfds[fifoIdx].revents & POLLIN)) { for (const auto& cmd : readFifoLines(fifoFd)) { - if (cmd == "CLOSE") { + if (cmd == "RELOAD_THEME") { + pt.reload(); + if (visible) drawUI(); + } else if (cmd == "CLOSE") { hideWindow(); } else if (cmd == "OPEN") { showWindow(""); diff --git a/src/dwm-plugins/phone_data.h b/src/dwm-plugins/phone_data.h index 90346d0..0894122 100644 --- a/src/dwm-plugins/phone_data.h +++ b/src/dwm-plugins/phone_data.h @@ -8,6 +8,8 @@ #define _EVDEVK(_v) (0x10081000 + (_v)) #endif +#include "theme.h" + #include #include #include @@ -69,6 +71,7 @@ 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 +constexpr const char* kIconPalette = "\xef\x87\xbc"; // U+F1FC paint-brush / theme inline int64_t nowMs() { struct timespec ts {}; @@ -962,6 +965,7 @@ inline void closeOtherPlugins(const char* selfPluginName) { {"xsms.pid", "sms"}, {"xfiles.pid", "files"}, {"xcamera.pid", "camera"}, + {"xthemes.pid", "themes"}, }; for (const auto& p : kAllPlugins) { if (selfPluginName && std::strcmp(p.name, selfPluginName) == 0) { @@ -976,6 +980,32 @@ inline void closeOtherPlugins(const char* selfPluginName) { } } +inline void broadcastThemeReload(const char* selfPluginName = nullptr) { + static const char* kPlugins[] = { + "dmenu", + "xdrawer", + "dialer", + "calllog", + "contacts", + "sms", + "files", + "camera", + "themes", + }; + for (const char* name : kPlugins) { + if (selfPluginName && std::strcmp(name, selfPluginName) == 0) { + continue; + } + std::string fifoPath = getPluginFifoPath(name); + int fd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC); + if (fd >= 0) { + (void)::write(fd, "RELOAD_THEME\n", 13); + ::close(fd); + } + } + runAsyncCmd("pkill -USR1 -x st >/dev/null 2>&1"); +} + inline std::string getModemLogDirPath() { const char* env = std::getenv("SXMO_LOGDIR"); if (env && env[0]) { diff --git a/src/dwm-plugins/sms_plugin.cpp b/src/dwm-plugins/sms_plugin.cpp index 956cf7e..2acec93 100644 --- a/src/dwm-plugins/sms_plugin.cpp +++ b/src/dwm-plugins/sms_plugin.cpp @@ -130,10 +130,12 @@ int runSmsApp(int argc, char** argv) { Visual* visual = DefaultVisual(dpy, screen); Colormap cmap = DefaultColormap(dpy, screen); + ThemeSpec initSpec = loadThemeSpec(dpy, screen); + XSetWindowAttributes swa {}; swa.override_redirect = True; - swa.background_pixel = 0x222222; - swa.border_pixel = 0x005577; + swa.background_pixel = initSpec.bgMain; + swa.border_pixel = initSpec.selBg; swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | VisibilityChangeMask | StructureNotifyMask; @@ -155,37 +157,18 @@ int runSmsApp(int argc, char** argv) { GC gc = XCreateGC(dpy, pix, 0, nullptr); XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap); - PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen); - PangoContext* pangoCtx = pango_font_map_create_context(fontMap); - - PangoLayout* layoutMain = pango_layout_new(pangoCtx); - PangoFontDescription* descMain = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 15px"); - pango_layout_set_font_description(layoutMain, descMain); - - PangoLayout* layoutSmall = pango_layout_new(pangoCtx); - PangoFontDescription* descSmall = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 12px"); - pango_layout_set_font_description(layoutSmall, descSmall); - - PangoLayout* layoutIcon = pango_layout_new(pangoCtx); - PangoFontDescription* descIcon = - pango_font_description_from_string("DejaVuSansM Nerd Font, Fira Code 16px"); - pango_layout_set_font_description(layoutIcon, descIcon); - - auto allocColor = [&](const char* hex) { - XftColor col {}; - XftColorAllocName(dpy, visual, cmap, hex, &col); - return col; - }; - - XftColor colWhite = allocColor("#ffffff"); - XftColor colLight = allocColor("#dddddd"); - XftColor colMuted = allocColor("#999999"); - XftColor colYellow = allocColor("#f4d03f"); - XftColor colGreen = allocColor("#2ecc71"); - XftColor colBlue = allocColor("#5dade2"); - XftColor colRed = allocColor("#e74c3c"); + PluginTheme pt; + pt.init(dpy, screen, visual, cmap, win); + auto& layoutMain = pt.layoutMain; + auto& layoutSmall = pt.layoutSmall; + auto& layoutIcon = pt.layoutIcon; + auto& colWhite = pt.colWhite; + auto& colLight = pt.colLight; + auto& colMuted = pt.colMuted; + auto& colYellow = pt.colYellow; + auto& colGreen = pt.colGreen; + auto& colBlue = pt.colBlue; + auto& colRed = pt.colRed; ContactBook book; std::vector allThreads; @@ -442,9 +425,9 @@ int runSmsApp(int argc, char** argv) { const std::string& centerLabel, const std::string& rightLabel) { const int barY = kWinHeight - 26; - XSetForeground(dpy, gc, 0x141414); + XSetForeground(dpy, gc, pt.spec.bgFooter); XFillRectangle(dpy, pix, gc, 0, barY, kWinWidth, 26); - XSetForeground(dpy, gc, 0x2d2d2d); + XSetForeground(dpy, gc, pt.spec.divider); XDrawLine(dpy, pix, gc, 0, barY, kWinWidth, barY); drawPango(layoutMain, &colBlue, 6, barY, 26, leftLabel); @@ -467,14 +450,15 @@ int runSmsApp(int argc, char** argv) { auto drawUI = [&]() { if (!visible) return; + pt.checkAndReload(); - XSetForeground(dpy, gc, 0x222222); + XSetForeground(dpy, gc, pt.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); if (mode == ViewMode::Threads) { - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 38); - XSetForeground(dpy, gc, 0x333333); + XSetForeground(dpy, gc, pt.spec.border); XDrawLine(dpy, pix, gc, 0, 37, kWinWidth, 37); if (searchQuery.empty()) { @@ -521,12 +505,13 @@ int runSmsApp(int argc, char** argv) { bool isSel = (idx == selectedThread); if (isSel) { - XSetForeground(dpy, gc, 0x005577); + XSetForeground(dpy, gc, pt.spec.selBg); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); - XSetForeground(dpy, gc, 0x0088bb); + XSetForeground(dpy, gc, pt.spec.accentAlt); XDrawLine(dpy, pix, gc, 0, ry, kWinWidth, ry); } else { - XSetForeground(dpy, gc, (i % 2 == 0) ? 0x222222 : 0x1d1d1d); + XSetForeground(dpy, gc, + (i % 2 == 0) ? pt.spec.bgAltEven : pt.spec.bgAltOdd); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); } @@ -562,9 +547,9 @@ int runSmsApp(int argc, char** argv) { searchQuery.empty() ? "Close" : "Clear"); } else if (mode == ViewMode::ThreadView) { - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36); - XSetForeground(dpy, gc, 0x333333); + XSetForeground(dpy, gc, pt.spec.border); XDrawLine(dpy, pix, gc, 0, 35, kWinWidth, 35); std::string cname = book.resolveName(activeNumber); @@ -640,13 +625,15 @@ int runSmsApp(int argc, char** argv) { int cardH = std::min(cg.h, areaH - 4); unsigned long bg = isSel - ? 0x004466 - : (cg.incoming ? 0x262d32 : 0x1f2d38); + ? pt.spec.bgInputFocus + : (cg.incoming ? pt.spec.bgBubbleIn + : pt.spec.bgBubbleOut); XSetForeground(dpy, gc, bg); XFillRectangle(dpy, pix, gc, cardX, curY, cardW, cardH); - unsigned long accent = cg.incoming ? 0x2ecc71 : 0x5dade2; - XSetForeground(dpy, gc, isSel ? 0xf4d03f : accent); + unsigned long accent = + cg.incoming ? pt.spec.success : pt.spec.info; + XSetForeground(dpy, gc, isSel ? pt.spec.accent : accent); if (cg.incoming) { XFillRectangle(dpy, pix, gc, cardX, curY, 3, cardH); } else { @@ -672,9 +659,9 @@ int runSmsApp(int argc, char** argv) { drawSoftkeyBar("Options", "Reply", "Back"); } else if (mode == ViewMode::Compose) { - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 32); - XSetForeground(dpy, gc, 0x333333); + XSetForeground(dpy, gc, pt.spec.border); XDrawLine(dpy, pix, gc, 0, 31, kWinWidth, 31); drawPango(layoutIcon, &colBlue, 6, 0, 30, kIconSend); @@ -703,9 +690,10 @@ int runSmsApp(int argc, char** argv) { // Field 0: Recipient bool toSel = (composeField == 0); - XSetForeground(dpy, gc, toSel ? 0x004466 : 0x1a1a1a); + XSetForeground(dpy, gc, + toSel ? pt.spec.bgInputFocus : pt.spec.bgInput); XFillRectangle(dpy, pix, gc, 6, 36, kWinWidth - 12, 32); - XSetForeground(dpy, gc, toSel ? 0x0088bb : 0x383838); + XSetForeground(dpy, gc, toSel ? pt.spec.accentAlt : pt.spec.border); XDrawRectangle(dpy, pix, gc, 6, 36, kWinWidth - 13, 31); std::string cname = book.resolveName(composeRecipient); @@ -719,9 +707,10 @@ int runSmsApp(int argc, char** argv) { // Field 1: Message Body bool msgSel = (composeField == 1); - XSetForeground(dpy, gc, msgSel ? 0x182630 : 0x181818); + XSetForeground(dpy, gc, + msgSel ? pt.spec.bgInputFocus : pt.spec.bgInput); XFillRectangle(dpy, pix, gc, 6, 72, kWinWidth - 12, 156); - XSetForeground(dpy, gc, msgSel ? 0x0088bb : 0x383838); + XSetForeground(dpy, gc, msgSel ? pt.spec.accentAlt : pt.spec.border); XDrawRectangle(dpy, pix, gc, 6, 72, kWinWidth - 13, 155); std::string bodyDisp = composeText; @@ -752,15 +741,15 @@ int runSmsApp(int argc, char** argv) { // Field 2 & 3: Send & Save Draft buttons bool sendSel = (composeField == 2); bool draftSel = (composeField == 3); - XSetForeground(dpy, gc, sendSel ? 0x005577 : 0x262626); + XSetForeground(dpy, gc, sendSel ? pt.spec.selBg : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 6, 234, 118, 30); - XSetForeground(dpy, gc, sendSel ? 0x0088bb : 0x444444); + XSetForeground(dpy, gc, sendSel ? pt.spec.accentAlt : pt.spec.border); XDrawRectangle(dpy, pix, gc, 6, 234, 117, 29); drawPango(layoutMain, &colGreen, 18, 234, 30, "Send SMS", 100); - XSetForeground(dpy, gc, draftSel ? 0x005577 : 0x262626); + XSetForeground(dpy, gc, draftSel ? pt.spec.selBg : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 130, 234, 104, 30); - XSetForeground(dpy, gc, draftSel ? 0x0088bb : 0x444444); + XSetForeground(dpy, gc, draftSel ? pt.spec.accentAlt : pt.spec.border); XDrawRectangle(dpy, pix, gc, 130, 234, 103, 29); drawPango(layoutMain, &colYellow, 146, 234, 30, "Draft", 84); @@ -778,7 +767,7 @@ int runSmsApp(int argc, char** argv) { } } else if (mode == ViewMode::PickContact) { - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34); drawPango(layoutIcon, &colBlue, 8, 0, 34, kIconBook); drawPango(layoutMain, &colWhite, 30, 0, 34, "Select Recipient", 200); @@ -799,7 +788,7 @@ int runSmsApp(int argc, char** argv) { const auto& c = pickerContacts[idx]; int ry = listY + i * rowH; bool isSel = (idx == selectedPicker); - XSetForeground(dpy, gc, isSel ? 0x005577 : 0x222222); + XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); drawPango(layoutIcon, c.isRecent ? &colYellow : &colBlue, 6, ry + 1, 20, c.isRecent ? kIconClock : kIconUser); @@ -811,7 +800,7 @@ int runSmsApp(int argc, char** argv) { drawSoftkeyBar("Select", "Select", "Back"); } else if (mode == ViewMode::Options) { - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34); drawPango(layoutIcon, &colBlue, 8, 0, 34, kIconList); drawPango(layoutMain, &colWhite, 30, 0, 34, "Message Options", 200); @@ -821,7 +810,7 @@ int runSmsApp(int argc, char** argv) { for (size_t i = 0; i < optionsList.size(); ++i) { int ry = listY + static_cast(i) * rowH; bool isSel = (static_cast(i) == selectedOption); - XSetForeground(dpy, gc, isSel ? 0x005577 : 0x222222); + XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); drawPango(layoutIcon, isSel ? &colYellow : &colBlue, 10, ry, rowH, optionsList[i].icon); @@ -831,7 +820,7 @@ int runSmsApp(int argc, char** argv) { drawSoftkeyBar("Select", "Select", "Back"); } else if (mode == ViewMode::ConfirmDelete) { - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, pt.spec.bgHeader); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36); drawPango(layoutIcon, &colRed, 8, 0, 36, kIconTrash); drawPango(layoutMain, &colWhite, 30, 0, 36, "Delete Thread?", 200); @@ -844,7 +833,10 @@ int runSmsApp(int argc, char** argv) { for (int i = 0; i < 2; ++i) { int ry = 164 + i * 40; bool isSel = (confirmSelected == i); - XSetForeground(dpy, gc, isSel ? (i == 1 ? 0x922b21 : 0x005577) : 0x2a2a2a); + XSetForeground( + dpy, gc, + isSel ? (i == 1 ? pt.spec.dangerBg : pt.spec.selBg) + : pt.spec.bgButton); XFillRectangle(dpy, pix, gc, 16, ry, kWinWidth - 32, 34); drawPango(layoutMain, &colWhite, 28, ry, 34, i == 0 ? "No, Keep Thread" : "Yes, Delete Thread", 184); @@ -856,9 +848,9 @@ int runSmsApp(int argc, char** argv) { { std::lock_guard lk(sendMutex); if (!statusBanner.empty() && tNow < statusBannerUntilMs) { - XSetForeground(dpy, gc, 0x102535); + XSetForeground(dpy, gc, pt.spec.bgToast); XFillRectangle(dpy, pix, gc, 8, kWinHeight - 58, kWinWidth - 16, 28); - XSetForeground(dpy, gc, 0x5dade2); + XSetForeground(dpy, gc, pt.spec.info); XDrawRectangle(dpy, pix, gc, 8, kWinHeight - 58, kWinWidth - 17, 27); drawPango(layoutSmall, &colWhite, 14, kWinHeight - 58, 28, statusBanner, kWinWidth - 28); @@ -1106,6 +1098,9 @@ int runSmsApp(int argc, char** argv) { for (const auto& cmd : readFifoLines(fifoFd)) { if (cmd == "NOP") { continue; + } else if (cmd == "RELOAD_THEME") { + pt.reload(); + if (visible) drawUI(); } else if (cmd == "CLOSE") { hideWindow(); } else if (cmd == "OPEN") { @@ -1416,7 +1411,10 @@ int runSmsApp(int argc, char** argv) { if (pr > 0) { if (fifoIdx >= 0 && (pfds[fifoIdx].revents & POLLIN)) { for (const auto& cmd : readFifoLines(fifoFd)) { - if (cmd == "CLOSE") { + if (cmd == "RELOAD_THEME") { + pt.reload(); + if (visible) drawUI(); + } else if (cmd == "CLOSE") { hideWindow(); } else if (cmd == "OPEN") { showWindow(""); diff --git a/src/dwm-plugins/theme.h b/src/dwm-plugins/theme.h new file mode 100644 index 0000000..0b8fad5 --- /dev/null +++ b/src/dwm-plugins/theme.h @@ -0,0 +1,636 @@ +#ifndef DWM_PLUGINS_THEME_H +#define DWM_PLUGINS_THEME_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace bananarchy { + +inline std::string themeTrim(const std::string& s) { + size_t b = 0; + while (b < s.size() && std::isspace(static_cast(s[b]))) { + ++b; + } + size_t e = s.size(); + while (e > b && std::isspace(static_cast(s[e - 1]))) { + --e; + } + if (e >= b + 2 && + ((s[b] == '"' && s[e - 1] == '"') || (s[b] == '\'' && s[e - 1] == '\''))) { + return s.substr(b + 1, e - b - 2); + } + return s.substr(b, e - b); +} + +inline int themeHexNibble(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); + if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); + return -1; +} + +inline bool parseHexRgb(const std::string& raw, uint32_t* outRgb) { + std::string s = themeTrim(raw); + if (s.rfind("0x", 0) == 0 || s.rfind("0X", 0) == 0) { + s = "#" + s.substr(2); + } + if (s.size() == 7 && s[0] == '#') { + uint32_t v = 0; + for (int i = 1; i < 7; ++i) { + int n = themeHexNibble(s[i]); + if (n < 0) return false; + v = (v << 4) | static_cast(n); + } + *outRgb = v; + return true; + } + if (s.size() == 4 && s[0] == '#') { + int r = themeHexNibble(s[1]); + int g = themeHexNibble(s[2]); + int b = themeHexNibble(s[3]); + if (r < 0 || g < 0 || b < 0) return false; + *outRgb = static_cast(((r * 17) << 16) | ((g * 17) << 8) | (b * 17)); + return true; + } + return false; +} + +inline std::string formatHexRgb(uint32_t rgb) { + char buf[16]; + std::snprintf(buf, sizeof(buf), "#%06x", static_cast(rgb & 0xffffffu)); + return buf; +} + +inline uint32_t adjustRgb(uint32_t rgb, int delta) { + int r = std::clamp(static_cast((rgb >> 16) & 0xff) + delta, 0, 255); + int g = std::clamp(static_cast((rgb >> 8) & 0xff) + delta, 0, 255); + int b = std::clamp(static_cast(rgb & 0xff) + delta, 0, 255); + return static_cast((r << 16) | (g << 8) | b); +} + +inline uint32_t blendRgb(uint32_t c1, uint32_t c2, int weight2Pct) { + int w2 = std::clamp(weight2Pct, 0, 100); + int w1 = 100 - w2; + int r = (((c1 >> 16) & 0xff) * w1 + ((c2 >> 16) & 0xff) * w2) / 100; + int g = (((c1 >> 8) & 0xff) * w1 + ((c2 >> 8) & 0xff) * w2) / 100; + int b = ((c1 & 0xff) * w1 + (c2 & 0xff) * w2) / 100; + return static_cast((std::clamp(r, 0, 255) << 16) | + (std::clamp(g, 0, 255) << 8) | + std::clamp(b, 0, 255)); +} + +inline std::string getHomeDirPath() { + const char* h = std::getenv("HOME"); + return (h && h[0]) ? h : "/home/user"; +} + +inline std::string getXresourcesPath() { + return getHomeDirPath() + "/.Xresources"; +} + +inline std::string getThemesDirPath() { + return getHomeDirPath() + "/.xthemes"; +} + +struct ThemeSpec { + std::string name = "Default"; + std::string fontFace = "Fira Code"; + std::string iconFontFace = "DejaVuSansM Nerd Font"; + int baseFontSize = 15; + + // Core colors (24-bit RGB) + uint32_t bgMain = 0x222222; + uint32_t bgBar = 0x323232; + uint32_t bgHeader = 0x1c1c1c; + uint32_t bgFooter = 0x181818; + uint32_t bgOverlay = 0x161616; + uint32_t bgAltEven = 0x282828; + uint32_t bgAltOdd = 0x2e2e2e; + uint32_t bgInput = 0x1a1a1a; + uint32_t bgInputFocus = 0x1b3b4b; + uint32_t bgButton = 0x2a2a2a; + uint32_t bgSearch = 0x383838; + uint32_t bgBubbleIn = 0x262d32; + uint32_t bgBubbleOut = 0x1f2d38; + uint32_t bgToast = 0x102535; + + uint32_t fgMain = 0xdddddd; + uint32_t fgBright = 0xffffff; + uint32_t fgMuted = 0x999999; + + uint32_t selBg = 0x005577; + uint32_t selFg = 0xffffff; + uint32_t accent = 0xf4d03f; + uint32_t accentAlt = 0x0088bb; + uint32_t highlight = 0xffc978; + uint32_t border = 0x444444; + uint32_t divider = 0x666666; + + uint32_t success = 0x2ecc71; + uint32_t info = 0x5dade2; + uint32_t warning = 0xe67e22; + uint32_t danger = 0xe74c3c; + uint32_t dangerBg = 0x922b21; + + int sizeSmall() const { + return std::max(8, static_cast(std::lround(baseFontSize * 12.0 / 15.0))); + } + int sizeMain() const { + return std::clamp(baseFontSize, 10, 24); + } + int sizeIcon() const { + return std::max(10, static_cast(std::lround(baseFontSize * 16.0 / 15.0))); + } + int sizeLarge() const { + return std::max(11, static_cast(std::lround(baseFontSize * 18.0 / 15.0))); + } + + std::string pangoMainFontStr() const { + return fontFace + ", " + iconFontFace + " " + std::to_string(sizeMain()) + "px"; + } + std::string pangoSmallFontStr() const { + return fontFace + ", " + iconFontFace + " " + std::to_string(sizeSmall()) + "px"; + } + std::string pangoIconFontStr() const { + return iconFontFace + ", " + fontFace + " " + std::to_string(sizeIcon()) + "px"; + } + std::string pangoLargeBoldFontStr() const { + return fontFace + ", " + iconFontFace + " Bold " + std::to_string(sizeLarge()) + "px"; + } + std::string xftFontStr(int pxSize) const { + return fontFace + ":pixelsize=" + std::to_string(pxSize) + + ":style=Regular:antialias=true:autohint=true"; + } + std::string xftIconFontStr(int pxSize) const { + return iconFontFace + ":pixelsize=" + std::to_string(pxSize) + + ":style=Regular:antialias=true:autohint=true"; + } + + void recomputeDerivedFromCore() { + int luma = (((bgMain >> 16) & 0xff) * 299 + + ((bgMain >> 8) & 0xff) * 587 + + (bgMain & 0xff) * 114) / 1000; + int dir = (luma > 140) ? -1 : 1; + + bgBar = adjustRgb(bgMain, dir * 16); + bgHeader = adjustRgb(bgMain, -dir * 6); + bgFooter = adjustRgb(bgMain, -dir * 10); + bgOverlay = adjustRgb(bgMain, -dir * 12); + bgAltEven = adjustRgb(bgMain, dir * 6); + bgAltOdd = adjustRgb(bgMain, dir * 12); + bgInput = adjustRgb(bgMain, -dir * 8); + bgInputFocus = blendRgb(bgMain, selBg, 40); + bgButton = adjustRgb(bgMain, dir * 8); + bgSearch = adjustRgb(bgMain, dir * 22); + bgBubbleIn = blendRgb(bgMain, success, 14); + bgBubbleOut = blendRgb(bgMain, selBg, 26); + bgToast = blendRgb(bgOverlay, selBg, 35); + + int fgLuma = (((fgMain >> 16) & 0xff) * 299 + + ((fgMain >> 8) & 0xff) * 587 + + (fgMain & 0xff) * 114) / 1000; + fgBright = (fgLuma > 128) ? 0xffffff : 0x000000; + fgMuted = blendRgb(fgMain, bgMain, 45); + + accentAlt = adjustRgb(selBg, 48); + highlight = blendRgb(accent, 0xffffff, 30); + border = adjustRgb(bgMain, dir * 34); + divider = adjustRgb(bgMain, dir * 68); + } + + void parseFromMap(const std::unordered_map& kv) { + auto getStr = [&](const char* k1, const char* k2 = nullptr, + const char* k3 = nullptr) -> std::string { + auto it = kv.find(k1); + if (it != kv.end() && !it->second.empty()) return it->second; + if (k2) { + it = kv.find(k2); + if (it != kv.end() && !it->second.empty()) return it->second; + } + if (k3) { + it = kv.find(k3); + if (it != kv.end() && !it->second.empty()) return it->second; + } + return ""; + }; + + std::string nm = getStr("theme.name", "*themeName"); + if (!nm.empty()) name = nm; + + std::string ff = getStr("theme.fontFace", "*fontFace"); + if (!ff.empty()) { + fontFace = ff; + } else { + std::string dwmFont = getStr("dwm.font"); + if (!dwmFont.empty()) { + size_t comma = dwmFont.find(','); + if (comma != std::string::npos) { + fontFace = themeTrim(dwmFont.substr(0, comma)); + } + } + } + + std::string iff = getStr("theme.iconFontFace", "*iconFontFace"); + if (!iff.empty()) iconFontFace = iff; + + std::string bfs = getStr("theme.baseFontSize", "*baseFontSize"); + if (!bfs.empty()) { + int sz = std::atoi(bfs.c_str()); + if (sz >= 10 && sz <= 24) baseFontSize = sz; + } else { + std::string dwmFont = getStr("dwm.font"); + size_t pxPos = dwmFont.rfind("px"); + if (pxPos != std::string::npos && pxPos > 0) { + size_t sp = pxPos; + while (sp > 0 && std::isdigit(static_cast(dwmFont[sp - 1]))) { + --sp; + } + if (sp < pxPos) { + int sz = std::atoi(dwmFont.substr(sp, pxPos - sp).c_str()); + if (sz >= 10 && sz <= 24) baseFontSize = sz; + } + } + } + + auto getCol = [&](uint32_t* dst, const char* k1, const char* k2 = nullptr, + const char* k3 = nullptr) -> bool { + std::string s = getStr(k1, k2, k3); + if (s.empty()) return false; + uint32_t v = 0; + if (parseHexRgb(s, &v)) { + *dst = v; + return true; + } + return false; + }; + + bool hasCoreBg = getCol(&bgMain, "theme.bgMain", "*background", "dmenu.background"); + bool hasCoreFg = getCol(&fgMain, "theme.fgMain", "*foreground", "dwm.normfgcolor"); + bool hasSelBg = getCol(&selBg, "theme.selBg", "dmenu.selbackground", "dwm.selbgcolor"); + bool hasSelFg = getCol(&selFg, "theme.selFg", "dmenu.selforeground", "dwm.selfgcolor"); + bool hasAccent = getCol(&accent, "theme.accent", "*accentColor", "st.cursorColor"); + (void)hasCoreBg; + (void)hasCoreFg; + (void)hasSelBg; + (void)hasSelFg; + (void)hasAccent; + + // Derive defaults from core colors first, then override with any explicit tokens + recomputeDerivedFromCore(); + + getCol(&bgBar, "theme.bgBar", "dwm.background", "dwm.normbgcolor"); + getCol(&bgHeader, "theme.bgHeader", "dmenu.background", "st.background"); + getCol(&bgFooter, "theme.bgFooter"); + getCol(&bgOverlay, "theme.bgOverlay"); + getCol(&bgAltEven, "theme.bgAltEven"); + getCol(&bgAltOdd, "theme.bgAltOdd"); + getCol(&bgInput, "theme.bgInput"); + getCol(&bgInputFocus, "theme.bgInputFocus"); + getCol(&bgButton, "theme.bgButton"); + getCol(&bgSearch, "theme.bgSearch", "dmenu.outbackground"); + getCol(&bgBubbleIn, "theme.bgBubbleIn"); + getCol(&bgBubbleOut, "theme.bgBubbleOut"); + getCol(&bgToast, "theme.bgToast"); + + getCol(&fgBright, "theme.fgBright", "dmenu.selforeground"); + getCol(&fgMuted, "theme.fgMuted"); + + getCol(&accentAlt, "theme.accentAlt"); + getCol(&highlight, "theme.highlight", "dmenu.highlight"); + getCol(&border, "theme.border", "dwm.border", "dwm.normbordercolor"); + getCol(÷r, "theme.divider"); + + getCol(&success, "theme.success"); + getCol(&info, "theme.info"); + getCol(&warning, "theme.warning"); + getCol(&danger, "theme.danger"); + getCol(&dangerBg, "theme.dangerBg"); + } + + static void parseLinesIntoMap(const std::string& content, + std::unordered_map& kv) { + std::istringstream ss(content); + std::string line; + while (std::getline(ss, line)) { + std::string t = themeTrim(line); + if (t.empty() || t[0] == '!' || t[0] == '#') continue; + size_t colon = t.find(':'); + if (colon == std::string::npos) continue; + std::string key = themeTrim(t.substr(0, colon)); + std::string val = themeTrim(t.substr(colon + 1)); + if (!key.empty() && !val.empty()) { + kv[key] = val; + } + } + } + + bool loadFromFile(const std::string& path) { + std::ifstream in(path); + if (!in.is_open()) return false; + std::ostringstream ss; + ss << in.rdbuf(); + std::unordered_map kv; + parseLinesIntoMap(ss.str(), kv); + parseFromMap(kv); + return true; + } + + void loadActive(Display* dpy, int screen) { + std::unordered_map kv; + if (dpy) { + Atom actualType = None; + int actualFormat = 0; + unsigned long nitems = 0, bytesAfter = 0; + unsigned char* prop = nullptr; + if (XGetWindowProperty(dpy, RootWindow(dpy, screen), XA_RESOURCE_MANAGER, + 0L, 65536L, False, XA_STRING, &actualType, + &actualFormat, &nitems, &bytesAfter, + &prop) == Success && + prop) { + if (actualType == XA_STRING && actualFormat == 8 && nitems > 0) { + std::string resStr(reinterpret_cast(prop), nitems); + parseLinesIntoMap(resStr, kv); + } + XFree(prop); + } + } + // Also read ~/.Xresources so direct file edits always take effect + std::ifstream in(getXresourcesPath()); + if (in.is_open()) { + std::ostringstream ss; + ss << in.rdbuf(); + parseLinesIntoMap(ss.str(), kv); + } + if (!kv.empty()) { + parseFromMap(kv); + } + } + + std::string serializeXresources() const { + std::ostringstream out; + out << "! Space to setup configversion\n"; + out << "! configversion: 7bff124b2de16292fe6e7a7e5d3322ae\n"; + out << "! Bananarchy Unified Theme: " << name << "\n\n"; + + out << "theme.name: " << name << "\n"; + out << "theme.fontFace: " << fontFace << "\n"; + out << "theme.iconFontFace: " << iconFontFace << "\n"; + out << "theme.baseFontSize: " << sizeMain() << "\n\n"; + + out << "theme.bgMain: " << formatHexRgb(bgMain) << "\n"; + out << "theme.bgBar: " << formatHexRgb(bgBar) << "\n"; + out << "theme.bgHeader: " << formatHexRgb(bgHeader) << "\n"; + out << "theme.bgFooter: " << formatHexRgb(bgFooter) << "\n"; + out << "theme.bgOverlay: " << formatHexRgb(bgOverlay) << "\n"; + out << "theme.bgAltEven: " << formatHexRgb(bgAltEven) << "\n"; + out << "theme.bgAltOdd: " << formatHexRgb(bgAltOdd) << "\n"; + out << "theme.bgInput: " << formatHexRgb(bgInput) << "\n"; + out << "theme.bgInputFocus: " << formatHexRgb(bgInputFocus) << "\n"; + out << "theme.bgButton: " << formatHexRgb(bgButton) << "\n"; + out << "theme.bgSearch: " << formatHexRgb(bgSearch) << "\n"; + out << "theme.bgBubbleIn: " << formatHexRgb(bgBubbleIn) << "\n"; + out << "theme.bgBubbleOut: " << formatHexRgb(bgBubbleOut) << "\n"; + out << "theme.bgToast: " << formatHexRgb(bgToast) << "\n\n"; + + out << "theme.fgMain: " << formatHexRgb(fgMain) << "\n"; + out << "theme.fgBright: " << formatHexRgb(fgBright) << "\n"; + out << "theme.fgMuted: " << formatHexRgb(fgMuted) << "\n\n"; + + out << "theme.selBg: " << formatHexRgb(selBg) << "\n"; + out << "theme.selFg: " << formatHexRgb(selFg) << "\n"; + out << "theme.accent: " << formatHexRgb(accent) << "\n"; + out << "theme.accentAlt: " << formatHexRgb(accentAlt) << "\n"; + out << "theme.highlight: " << formatHexRgb(highlight) << "\n"; + out << "theme.border: " << formatHexRgb(border) << "\n"; + out << "theme.divider: " << formatHexRgb(divider) << "\n\n"; + + out << "theme.success: " << formatHexRgb(success) << "\n"; + out << "theme.info: " << formatHexRgb(info) << "\n"; + out << "theme.warning: " << formatHexRgb(warning) << "\n"; + out << "theme.danger: " << formatHexRgb(danger) << "\n"; + out << "theme.dangerBg: " << formatHexRgb(dangerBg) << "\n\n"; + + out << "dmenu.font: " << xftFontStr(sizeLarge()) << "\n"; + out << "dmenu.background: " << formatHexRgb(bgHeader) << "\n"; + out << "dmenu.foreground: " << formatHexRgb(fgBright) << "\n"; + out << "dmenu.selbackground: " << formatHexRgb(selBg) << "\n"; + out << "dmenu.selforeground: " << formatHexRgb(selFg) << "\n"; + out << "dmenu.outbackground: " << formatHexRgb(bgSearch) << "\n"; + out << "dmenu.outforeground: " << formatHexRgb(fgBright) << "\n"; + out << "dmenu.highlight: " << formatHexRgb(highlight) << "\n\n"; + + out << "dwm.font: " << pangoMainFontStr() << "\n"; + out << "dwm.background: " << formatHexRgb(bgBar) << "\n"; + out << "dwm.foreground: " << formatHexRgb(fgMain) << "\n"; + out << "dwm.border: " << formatHexRgb(border) << "\n"; + out << "dwm.selbackground: " << formatHexRgb(selBg) << "\n"; + out << "dwm.selforeground: " << formatHexRgb(selFg) << "\n"; + out << "dwm.selborder: " << formatHexRgb(selBg) << "\n"; + out << "dwm.normbgcolor: " << formatHexRgb(bgBar) << "\n"; + out << "dwm.normbordercolor: " << formatHexRgb(border) << "\n"; + out << "dwm.normfgcolor: " << formatHexRgb(fgMain) << "\n"; + out << "dwm.selbgcolor: " << formatHexRgb(selBg) << "\n"; + out << "dwm.selbordercolor: " << formatHexRgb(selBg) << "\n"; + out << "dwm.selfgcolor: " << formatHexRgb(selFg) << "\n\n"; + + out << "st.font: " << xftFontStr(sizeLarge()) << "\n"; + out << "st.termname: xterm-256color\n"; + out << "st.background: " << formatHexRgb(bgHeader) << "\n"; + out << "st.foreground: " << formatHexRgb(fgMain) << "\n"; + out << "st.cursorColor: " << formatHexRgb(accent) << "\n\n"; + + out << "svkbd.font: " << fontFace << ":pixelsize=" << sizeSmall() << "\n"; + return out.str(); + } + + bool saveToFile(const std::string& path) const { + std::ofstream out(path, std::ios::trunc); + if (!out.is_open()) return false; + out << serializeXresources(); + return true; + } +}; + +inline ThemeSpec loadThemeSpec(Display* dpy = nullptr, int screen = 0) { + ThemeSpec spec; + spec.loadActive(dpy, screen); + return spec; +} + +struct PluginTheme { + ThemeSpec spec; + time_t lastXresMtime = -1; + off_t lastXresSize = -1; + bool colorsAllocated = false; + + Display* dpy_ = nullptr; + int screen_ = 0; + Visual* visual_ = nullptr; + Colormap cmap_ = 0; + Window win_ = None; + PangoContext* pangoCtx = nullptr; + PangoLayout* layoutMain = nullptr; + PangoLayout* layoutSmall = nullptr; + PangoLayout* layoutIcon = nullptr; + PangoLayout* layoutNum = nullptr; + + XftColor colWhite{}; + XftColor colLight{}; + XftColor colMuted{}; + XftColor colYellow{}; + XftColor colGreen{}; + XftColor colBlue{}; + XftColor colOrange{}; + XftColor colRed{}; + + void allocXft(Display* dpy, Visual* visual, Colormap cmap, uint32_t rgb, + XftColor* dst) { + std::string hex = formatHexRgb(rgb); + XftColorAllocName(dpy, visual, cmap, hex.c_str(), dst); + } + + void init(Display* dpy, int screen, Visual* visual, Colormap cmap, + Window win = None) { + dpy_ = dpy; + screen_ = screen; + visual_ = visual; + cmap_ = cmap; + win_ = win; + if (!pangoCtx && dpy) { + PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen); + pangoCtx = pango_font_map_create_context(fontMap); + layoutMain = pango_layout_new(pangoCtx); + layoutSmall = pango_layout_new(pangoCtx); + layoutIcon = pango_layout_new(pangoCtx); + layoutNum = pango_layout_new(pangoCtx); + } + reload(); + } + + void reload() { + if (dpy_ && visual_) { + load(dpy_, screen_, visual_, cmap_, layoutMain, layoutSmall, layoutIcon, + layoutNum, win_); + } + } + + void load(Display* dpy, int screen, Visual* visual, Colormap cmap, + PangoLayout* lMain = nullptr, + PangoLayout* lSmall = nullptr, + PangoLayout* lIcon = nullptr, + PangoLayout* lNum = nullptr, + Window win = None) { + dpy_ = dpy; + screen_ = screen; + visual_ = visual; + cmap_ = cmap; + if (win != None) win_ = win; + if (lMain) layoutMain = lMain; + if (lSmall) layoutSmall = lSmall; + if (lIcon) layoutIcon = lIcon; + if (lNum) layoutNum = lNum; + + struct stat st {}; + if (::stat(getXresourcesPath().c_str(), &st) == 0) { + lastXresMtime = st.st_mtim.tv_sec; + lastXresSize = st.st_size; + } + + spec = ThemeSpec{}; + spec.loadActive(dpy, screen); + + if (colorsAllocated) { + XftColorFree(dpy, visual, cmap, &colWhite); + XftColorFree(dpy, visual, cmap, &colLight); + XftColorFree(dpy, visual, cmap, &colMuted); + XftColorFree(dpy, visual, cmap, &colYellow); + XftColorFree(dpy, visual, cmap, &colGreen); + XftColorFree(dpy, visual, cmap, &colBlue); + XftColorFree(dpy, visual, cmap, &colOrange); + XftColorFree(dpy, visual, cmap, &colRed); + } + + allocXft(dpy, visual, cmap, spec.fgBright, &colWhite); + allocXft(dpy, visual, cmap, spec.fgMain, &colLight); + allocXft(dpy, visual, cmap, spec.fgMuted, &colMuted); + allocXft(dpy, visual, cmap, spec.accent, &colYellow); + allocXft(dpy, visual, cmap, spec.success, &colGreen); + allocXft(dpy, visual, cmap, spec.info, &colBlue); + allocXft(dpy, visual, cmap, spec.warning, &colOrange); + allocXft(dpy, visual, cmap, spec.danger, &colRed); + colorsAllocated = true; + + if (layoutMain) { + PangoFontDescription* d = + pango_font_description_from_string(spec.pangoMainFontStr().c_str()); + pango_layout_set_font_description(layoutMain, d); + pango_font_description_free(d); + } + if (layoutSmall) { + PangoFontDescription* d = + pango_font_description_from_string(spec.pangoSmallFontStr().c_str()); + pango_layout_set_font_description(layoutSmall, d); + pango_font_description_free(d); + } + if (layoutIcon) { + PangoFontDescription* d = + pango_font_description_from_string(spec.pangoIconFontStr().c_str()); + pango_layout_set_font_description(layoutIcon, d); + pango_font_description_free(d); + } + if (layoutNum) { + PangoFontDescription* d = pango_font_description_from_string( + spec.pangoLargeBoldFontStr().c_str()); + pango_layout_set_font_description(layoutNum, d); + pango_font_description_free(d); + } + if (win_ != None) { + XSetWindowBackground(dpy, win_, spec.bgMain); + XSetWindowBorder(dpy, win_, spec.selBg); + } + } + + bool checkAndReload() { + if (!dpy_ || !visual_) return false; + return checkAndReload(dpy_, screen_, visual_, cmap_, layoutMain, + layoutSmall, layoutIcon, layoutNum, win_); + } + + bool checkAndReload(Display* dpy, int screen, Visual* visual, Colormap cmap, + PangoLayout* lMain = nullptr, + PangoLayout* lSmall = nullptr, + PangoLayout* lIcon = nullptr, + PangoLayout* lNum = nullptr, + Window win = None) { + struct stat st {}; + if (::stat(getXresourcesPath().c_str(), &st) == 0) { + if (st.st_mtim.tv_sec != lastXresMtime || st.st_size != lastXresSize) { + load(dpy, screen, visual, cmap, lMain, lSmall, lIcon, lNum, win); + return true; + } + } + return false; + } +}; + +} // namespace bananarchy + +#endif // DWM_PLUGINS_THEME_H diff --git a/src/dwm-plugins/themes_plugin.cpp b/src/dwm-plugins/themes_plugin.cpp new file mode 100644 index 0000000..5ee11bf --- /dev/null +++ b/src/dwm-plugins/themes_plugin.cpp @@ -0,0 +1,1380 @@ +#include "phone_data.h" +#include "../sxmo-dwm/dwm_plugin.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace bananarchy; + +volatile sig_atomic_t gRunning = 1; + +void handleSignal(int) { + gRunning = 0; +} + +enum class ViewMode { + Browser = 0, + Actions = 1, + Customize = 2, + HexModal = 3, + NameModal = 4, + ConfirmDelete = 5, +}; + +enum class NameModalAction { + SaveCurrentAs = 0, + DuplicateTheme = 1, +}; + +enum class TextCaseMode { + Title = 0, // Abc + Lower = 1, // abc + Upper = 2, // ABC + Num = 3, // 123 +}; + +const char* kMultiTapTheme[10] = { + " _-0", // 0 + ".-_1", // 1 + "abc2", // 2 + "def3", // 3 + "ghi4", // 4 + "jkl5", // 5 + "mno6", // 6 + "pqrs7", // 7 + "tuv8", // 8 + "wxyz9" // 9 +}; + +const char* kHexKeyMap[10] = { + "0", // 0 + "1", // 1 + "2abc", // 2 + "3def", // 3 + "4", // 4 + "5", // 5 + "6", // 6 + "7", // 7 + "8", // 8 + "9" // 9 +}; + +const char* kFontPresets[] = { + "Fira Code", + "DejaVuSansM Nerd Font", + "DejaVu Sans Mono", + "DejaVu Sans", + "monospace", + "sans-serif" +}; +constexpr int kNumFontPresets = + static_cast(sizeof(kFontPresets) / sizeof(kFontPresets[0])); + +const char* kColorPresets[] = { + "#222222", "#181818", "#141414", "#101418", "#0d1117", "#1e1e2e", + "#282a36", "#2e3440", "#002b36", "#1d2021", "#2b1b17", "#121b18", + "#f5f5f0", "#ebdbb2", "#ffffff", "#dddddd", "#bbbbbb", "#999999", + "#005577", "#0088bb", "#1f6feb", "#3d59a1", "#6272a4", "#5e81ac", + "#b57614", "#2ecc71", "#27ae60", "#8ec07c", "#a6e3a1", "#5dade2", + "#89b4fa", "#88c0d0", "#f4d03f", "#fabd2f", "#f9e2af", "#e67e22", + "#fe8019", "#e74c3c", "#fb4934", "#f38ba8", "#bd93f9", "#cba6f7" +}; +constexpr int kNumColorPresets = + static_cast(sizeof(kColorPresets) / sizeof(kColorPresets[0])); + +struct ThemeFileEntry { + std::string name; // basename without .xth + std::string fullPath; // ~/.xthemes/.xth + ThemeSpec spec; + bool isActive = false; +}; + +void popUtf8(std::string& s) { + if (s.empty()) return; + while (!s.empty() && (static_cast(s.back()) & 0xC0) == 0x80) { + s.pop_back(); + } + if (!s.empty()) s.pop_back(); +} + +std::string getXthemesDir() { + const char* home = std::getenv("HOME"); + std::string dir = + std::string((home && home[0]) ? home : "/home/user") + "/.xthemes"; + ::mkdir(dir.c_str(), 0755); + return dir; +} + +bool writeTextFile(const std::string& path, const std::string& content) { + ContactBook::ensureParentDir(path); + std::string tmpPath = path + ".tmp"; + { + std::ofstream out(tmpPath, std::ios::trunc); + if (!out.is_open()) { + return false; + } + out << content; + out.flush(); + if (!out.good()) { + ::unlink(tmpPath.c_str()); + return false; + } + } + return ::rename(tmpPath.c_str(), path.c_str()) == 0; +} + +void updateRootResourceManager(const std::string& xresContent, + Display* existingDpy = nullptr) { + Display* dpy = existingDpy ? existingDpy : XOpenDisplay(nullptr); + if (!dpy) return; + Window root = DefaultRootWindow(dpy); + XChangeProperty(dpy, root, XA_RESOURCE_MANAGER, XA_STRING, 8, PropModeReplace, + reinterpret_cast(xresContent.data()), + static_cast(xresContent.size())); + XFlush(dpy); + if (!existingDpy) { + XCloseDisplay(dpy); + } +} + +void ensureDefaultThemesSeeded() { + std::string dir = getXthemesDir(); + std::string defPath = dir + "/Default.xth"; + if (::access(defPath.c_str(), R_OK) != 0) { + if (::access("/etc/skel/.xthemes/Default.xth", R_OK) == 0) { + ThemeSpec skelSpec; + skelSpec.name = "Default"; + skelSpec.loadFromFile("/etc/skel/.xthemes/Default.xth"); + writeTextFile(defPath, skelSpec.serializeXresources()); + } else { + ThemeSpec defSpec; + defSpec.name = "Default"; + writeTextFile(defPath, defSpec.serializeXresources()); + } + } + + // Also seed any additional prepackaged themes from /etc/skel/.xthemes/ + DIR* skelD = ::opendir("/etc/skel/.xthemes"); + if (skelD) { + struct dirent* ent; + while ((ent = ::readdir(skelD)) != nullptr) { + std::string fname = ent->d_name; + if (fname.size() > 4 && fname.substr(fname.size() - 4) == ".xth") { + std::string target = dir + "/" + fname; + if (::access(target.c_str(), R_OK) != 0) { + ThemeSpec ts; + ts.name = fname.substr(0, fname.size() - 4); + if (ts.loadFromFile("/etc/skel/.xthemes/" + fname)) { + ts.name = fname.substr(0, fname.size() - 4); + writeTextFile(target, ts.serializeXresources()); + } + } + } + } + ::closedir(skelD); + } + + std::string xresPath = getXresourcesPath(); + if (::access(xresPath.c_str(), R_OK) != 0) { + ThemeSpec defSpec; + defSpec.loadFromFile(defPath); + writeTextFile(xresPath, defSpec.serializeXresources()); + } +} + +bool applyThemeSpecSystemWide(const ThemeSpec& spec, + Display* existingDpy = nullptr, + const char* skipPlugin = "") { + std::string xresPath = getXresourcesPath(); + std::string content = spec.serializeXresources(); + if (!writeTextFile(xresPath, content)) { + return false; + } + updateRootResourceManager(content, existingDpy); + broadcastThemeReload(skipPlugin ? skipPlugin : ""); + return true; +} + +std::vector loadInstalledThemes(const ThemeSpec& activeSpec) { + ensureDefaultThemesSeeded(); + std::vector list; + std::string dir = getXthemesDir(); + DIR* d = ::opendir(dir.c_str()); + if (!d) return list; + + struct dirent* ent; + while ((ent = ::readdir(d)) != nullptr) { + std::string fname = ent->d_name; + if (fname.size() <= 4 || fname.substr(fname.size() - 4) != ".xth") { + continue; + } + ThemeFileEntry item; + item.name = fname.substr(0, fname.size() - 4); + item.fullPath = dir + "/" + fname; + item.spec.name = item.name; + item.spec.loadFromFile(item.fullPath); + item.spec.name = item.name; + + bool nameMatch = + (toLowerStr(activeSpec.name) == toLowerStr(item.name)); + bool colorMatch = + (activeSpec.bgMain == item.spec.bgMain && + activeSpec.selBg == item.spec.selBg && + activeSpec.accent == item.spec.accent && + activeSpec.fgMain == item.spec.fgMain && + activeSpec.baseFontSize == item.spec.baseFontSize && + toLowerStr(activeSpec.fontFace) == toLowerStr(item.spec.fontFace)); + item.isActive = nameMatch || colorMatch; + list.push_back(item); + } + ::closedir(d); + + std::sort(list.begin(), list.end(), + [](const ThemeFileEntry& a, const ThemeFileEntry& b) { + if (a.name == "Default" && b.name != "Default") return true; + if (b.name == "Default" && a.name != "Default") return false; + return toLowerStr(a.name) < toLowerStr(b.name); + }); + return list; +} + +int runThemesApp(int argc, char** argv) { + gRunning = 1; + std::setlocale(LC_ALL, ""); + XSetLocaleModifiers(""); + + bool isPreload = false; + std::string applyArg; + bool exportDefaultOnly = false; + std::string openArg; + + for (int i = 1; i < argc; ++i) { + if (!argv[i]) continue; + if (std::strcmp(argv[i], "--preload") == 0) { + isPreload = true; + } else if (std::strcmp(argv[i], "--export-default") == 0) { + exportDefaultOnly = true; + } else if (std::strcmp(argv[i], "--apply") == 0 && i + 1 < argc && + argv[i + 1]) { + applyArg = argv[++i]; + } else if (argv[i][0] != '-' && openArg.empty()) { + openArg = argv[i]; + } + } + + ensureDefaultThemesSeeded(); + + if (exportDefaultOnly) { + ThemeSpec defSpec; + defSpec.name = "Default"; + std::string defPath = getXthemesDir() + "/Default.xth"; + writeTextFile(defPath, defSpec.serializeXresources()); + return 0; + } + + if (!applyArg.empty()) { + std::string path = applyArg; + if (path.find('/') == std::string::npos) { + if (path.size() <= 4 || path.substr(path.size() - 4) != ".xth") { + path += ".xth"; + } + path = getXthemesDir() + "/" + path; + } + ThemeSpec ts; + if (!ts.loadFromFile(path)) { + return 1; + } + return applyThemeSpecSystemWide(ts) ? 0 : 1; + } + + std::string daemonPidPath = getPluginDaemonPidPath("themes"); + std::string activePidPath = getRuntimeFile("xthemes.pid"); + + if (isPluginDaemonAlive("themes")) { + if (!isPreload) { + sendPluginCommand("themes", + openArg.empty() ? "OPEN" : ("OPEN " + openArg)); + } + return 0; + } + + writeIntFile(daemonPidPath, static_cast(::getpid())); + int fifoFd = setupPluginDaemonFifo("themes"); + + struct sigaction sa {}; + sa.sa_handler = handleSignal; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, nullptr); + sigaction(SIGTERM, &sa, nullptr); + + Display* dpy = XOpenDisplay(nullptr); + if (!dpy) { + if (fifoFd >= 0) ::close(fifoFd); + ::unlink(daemonPidPath.c_str()); + return 1; + } + + int screen = DefaultScreen(dpy); + Window root = RootWindow(dpy, screen); + Visual* visual = DefaultVisual(dpy, screen); + Colormap cmap = DefaultColormap(dpy, screen); + + ThemeSpec initSpec; + initSpec.loadActive(dpy, screen); + + XSetWindowAttributes swa {}; + swa.override_redirect = True; + swa.background_pixel = initSpec.bgMain; + swa.border_pixel = initSpec.selBg; + swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | + VisibilityChangeMask | StructureNotifyMask; + + Window win = XCreateWindow( + dpy, root, kWinX, kWinY, kWinWidth, kWinHeight, 0, + DefaultDepth(dpy, screen), CopyFromParent, visual, + CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask, &swa); + + XClassHint ch {}; + char resName[] = "xthemes"; + char resClass[] = "XThemes"; + ch.res_name = resName; + ch.res_class = resClass; + XSetClassHint(dpy, win, &ch); + XStoreName(dpy, win, "Themes"); + + Pixmap pix = XCreatePixmap(dpy, win, kWinWidth, kWinHeight, + DefaultDepth(dpy, screen)); + GC gc = XCreateGC(dpy, pix, 0, nullptr); + XftDraw* xftDraw = XftDrawCreate(dpy, pix, visual, cmap); + + PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen); + PangoContext* pangoCtx = pango_font_map_create_context(fontMap); + PangoLayout* layoutMain = pango_layout_new(pangoCtx); + PangoLayout* layoutSmall = pango_layout_new(pangoCtx); + PangoLayout* layoutIcon = pango_layout_new(pangoCtx); + + PluginTheme pt; + pt.load(dpy, screen, visual, cmap, layoutMain, layoutSmall, layoutIcon, + nullptr, win); + auto& colWhite = pt.colWhite; + auto& colLight = pt.colLight; + auto& colMuted = pt.colMuted; + auto& colYellow = pt.colYellow; + auto& colGreen = pt.colGreen; + auto& colBlue = pt.colBlue; + auto& colRed = pt.colRed; + + ViewMode mode = ViewMode::Browser; + std::vector themes; + int selected = 0; + int actionSelected = 0; + int confirmSelected = 0; + + // Customize state + ThemeSpec editSpec; + std::string editThemeName; + int editField = 0; // 0..9 + constexpr int kNumEditFields = 10; + + // Hex color input modal state + std::string hexInput; + int hexTargetField = 2; + int lastTapDigit = -1; + int lastTapCycle = 0; + int64_t lastTapMs = 0; + + // Name input modal state + NameModalAction nameAction = NameModalAction::SaveCurrentAs; + std::string nameTitle; + std::string nameInput; + TextCaseMode caseMode = TextCaseMode::Title; + + std::string statusBanner; + int64_t statusBannerUntilMs = 0; + + struct ActionEntry { + int id; + std::string icon; + std::string label; + }; + std::vector actionsList; + + bool visible = false; + int64_t openStartMs = 0; + int64_t startupIgnoreUntilMs = 0; + bool initialSoftkeysReleased = true; + int softkeyPending = 0; + int64_t softkeyDeadlineMs = 0; + + auto refreshThemes = [&](const std::string& selectName = "") { + pt.load(dpy, screen, visual, cmap, layoutMain, layoutSmall, layoutIcon, + nullptr, win); + themes = loadInstalledThemes(pt.spec); + if (!selectName.empty()) { + for (size_t i = 0; i < themes.size(); ++i) { + if (toLowerStr(themes[i].name) == toLowerStr(selectName)) { + selected = static_cast(i); + break; + } + } + } + if (selected >= static_cast(themes.size())) { + selected = std::max(0, static_cast(themes.size()) - 1); + } + }; + + refreshThemes(); + + auto getEditFieldColorPtr = [&](int fieldIdx) -> uint32_t* { + switch (fieldIdx) { + case 2: return &editSpec.bgMain; + case 3: return &editSpec.bgBar; + case 4: return &editSpec.bgHeader; + case 5: return &editSpec.selBg; + case 6: return &editSpec.selFg; + case 7: return &editSpec.accent; + case 8: return &editSpec.fgMain; + case 9: return &editSpec.fgMuted; + default: return nullptr; + } + }; + + auto syncCustomColorsToDerived = [&](bool keepBarAndHeader = true) { + uint32_t savedBar = editSpec.bgBar; + uint32_t savedHdr = editSpec.bgHeader; + uint32_t savedMuted = editSpec.fgMuted; + editSpec.recomputeDerivedFromCore(); + if (keepBarAndHeader) { + editSpec.bgBar = savedBar; + editSpec.bgHeader = savedHdr; + } + editSpec.fgMuted = savedMuted; + }; + + auto cycleEditField = [&](int fieldIdx, int dir) { + if (fieldIdx == 0) { + int curIdx = 0; + for (int i = 0; i < kNumFontPresets; ++i) { + if (toLowerStr(editSpec.fontFace) == toLowerStr(kFontPresets[i])) { + curIdx = i; + break; + } + } + curIdx = (curIdx + dir + kNumFontPresets) % kNumFontPresets; + editSpec.fontFace = kFontPresets[curIdx]; + } else if (fieldIdx == 1) { + editSpec.baseFontSize = std::clamp(editSpec.baseFontSize + dir, 11, 22); + } else { + uint32_t* colPtr = getEditFieldColorPtr(fieldIdx); + if (!colPtr) return; + int curIdx = -1; + for (int i = 0; i < kNumColorPresets; ++i) { + uint32_t p = 0; + if (parseHexRgb(kColorPresets[i], &p) && p == *colPtr) { + curIdx = i; + break; + } + } + if (curIdx < 0) { + curIdx = (dir > 0) ? 0 : (kNumColorPresets - 1); + } else { + curIdx = (curIdx + dir + kNumColorPresets) % kNumColorPresets; + } + uint32_t nextCol = *colPtr; + if (parseHexRgb(kColorPresets[curIdx], &nextCol)) { + *colPtr = nextCol; + syncCustomColorsToDerived(fieldIdx != 2); + } + } + }; + + auto buildActionsList = [&]() { + actionsList.clear(); + if (!themes.empty()) { + actionsList.push_back({1, kIconCheck, "Apply Selected Theme"}); + actionsList.push_back({2, kIconPalette, "Customize Theme Colors/Font"}); + } + actionsList.push_back({3, kIconPlus, "Save Current as New Theme"}); + if (!themes.empty()) { + actionsList.push_back({4, kIconCopy, "Duplicate Theme"}); + actionsList.push_back({5, kIconEdit, "Edit .xth in Nano"}); + } + actionsList.push_back({6, kIconSort, "Reset Default Theme"}); + if (!themes.empty() && themes[selected].name != "Default") { + actionsList.push_back({7, kIconTrash, "Delete Theme"}); + } + actionSelected = 0; + }; + + auto applySelectedTheme = [&]() { + if (themes.empty()) return; + ThemeSpec spec = themes[selected].spec; + spec.name = themes[selected].name; + if (applyThemeSpecSystemWide(spec, dpy, "themes")) { + refreshThemes(spec.name); + statusBanner = "Applied theme: " + spec.name; + statusBannerUntilMs = nowMs() + 2000; + } else { + statusBanner = "Failed to write ~/.Xresources"; + statusBannerUntilMs = nowMs() + 2000; + } + }; + + auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h, + const std::string& text, int maxW = -1) { + pango_layout_set_text(layout, text.c_str(), static_cast(text.size())); + if (maxW > 0) { + pango_layout_set_width(layout, maxW * PANGO_SCALE); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + } else { + pango_layout_set_width(layout, -1); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); + } + int tw = 0, th = 0; + pango_layout_get_pixel_size(layout, &tw, &th); + int dy = y + std::max(0, (h - th) / 2); + pango_xft_render_layout(xftDraw, color, layout, x * PANGO_SCALE, + dy * PANGO_SCALE); + return tw; + }; + + auto drawSwatch = [&](int x, int y, int w, int h, unsigned long color) { + XSetForeground(dpy, gc, color); + XFillRectangle(dpy, pix, gc, x, y, w, h); + XSetForeground(dpy, gc, pt.spec.border); + XDrawRectangle(dpy, pix, gc, x, y, w - 1, h - 1); + }; + + auto drawSoftkeyBar = [&](const std::string& leftLabel, + const std::string& centerLabel, + const std::string& rightLabel) { + const int barY = kWinHeight - 26; + XSetForeground(dpy, gc, pt.spec.bgFooter); + XFillRectangle(dpy, pix, gc, 0, barY, kWinWidth, 26); + XSetForeground(dpy, gc, pt.spec.divider); + XDrawLine(dpy, pix, gc, 0, barY, kWinWidth, barY); + + drawPango(layoutMain, &colBlue, 6, barY, 26, leftLabel); + + pango_layout_set_width(layoutMain, -1); + pango_layout_set_text(layoutMain, centerLabel.c_str(), + static_cast(centerLabel.size())); + int cw = 0, chh = 0; + pango_layout_get_pixel_size(layoutMain, &cw, &chh); + drawPango(layoutMain, &colWhite, (kWinWidth - cw) / 2, barY, 26, + centerLabel); + + pango_layout_set_width(layoutMain, -1); + pango_layout_set_text(layoutMain, rightLabel.c_str(), + static_cast(rightLabel.size())); + int rw = 0, rhh = 0; + pango_layout_get_pixel_size(layoutMain, &rw, &rhh); + drawPango(layoutMain, &colLight, kWinWidth - rw - 6, barY, 26, rightLabel); + }; + + auto drawUI = [&]() { + if (!visible) return; + if (pt.checkAndReload(dpy, screen, visual, cmap, layoutMain, layoutSmall, + layoutIcon, nullptr, win)) { + themes = loadInstalledThemes(pt.spec); + if (selected >= static_cast(themes.size())) { + selected = std::max(0, static_cast(themes.size()) - 1); + } + } + + XSetForeground(dpy, gc, pt.spec.bgMain); + XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); + + if (mode == ViewMode::Browser) { + XSetForeground(dpy, gc, pt.spec.bgHeader); + XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36); + XSetForeground(dpy, gc, pt.spec.border); + XDrawLine(dpy, pix, gc, 0, 35, kWinWidth, 35); + + drawPango(layoutIcon, &colYellow, 6, 1, 34, kIconPalette); + drawPango(layoutMain, &colWhite, 26, 1, 34, "Themes (~/.xthemes)", 180); + + std::string cnt = "[" + std::to_string(themes.size()) + "]"; + pango_layout_set_width(layoutSmall, -1); + pango_layout_set_text(layoutSmall, cnt.c_str(), + static_cast(cnt.size())); + int cw = 0, chh = 0; + pango_layout_get_pixel_size(layoutSmall, &cw, &chh); + drawPango(layoutSmall, &colMuted, kWinWidth - cw - 6, 1, 34, cnt); + + const int listY = 36; + const int rowH = 34; + const int visibleRows = 5; + + if (themes.empty()) { + drawPango(layoutMain, &colMuted, 16, 100, 24, "No themes found", 208); + } else { + int scroll = 0; + if (selected >= visibleRows) { + scroll = selected - visibleRows + 1; + } + for (int i = 0; i < visibleRows; ++i) { + int idx = scroll + i; + if (idx >= static_cast(themes.size())) break; + const auto& th = themes[idx]; + int ry = listY + i * rowH; + bool isSel = (idx == selected); + + if (isSel) { + XSetForeground(dpy, gc, pt.spec.selBg); + XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); + XSetForeground(dpy, gc, pt.spec.accentAlt); + XDrawLine(dpy, pix, gc, 0, ry, kWinWidth, ry); + } else { + XSetForeground(dpy, gc, + (i % 2 == 0) ? pt.spec.bgAltEven : pt.spec.bgAltOdd); + XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); + } + + drawPango(layoutIcon, + th.isActive ? &colGreen : (isSel ? &colYellow : &colBlue), + 6, ry, rowH, th.isActive ? kIconCheck : kIconPalette); + + std::string label = th.name + (th.isActive ? " *" : ""); + drawPango(layoutMain, &colWhite, 28, ry, rowH, label, 130); + + // Draw 4 color swatches on the right side of the row + int swX = kWinWidth - 72; + int swY = ry + (rowH - 14) / 2; + drawSwatch(swX, swY, 14, 14, th.spec.bgMain); + drawSwatch(swX + 16, swY, 14, 14, th.spec.selBg); + drawSwatch(swX + 32, swY, 14, 14, th.spec.accent); + drawSwatch(swX + 48, swY, 14, 14, th.spec.fgMain); + } + + // Bottom live preview card of the selected theme (y = 208..268) + const auto& selTh = themes[selected]; + const int cardY = 208; + const int cardH = 58; + XSetForeground(dpy, gc, selTh.spec.bgHeader); + XFillRectangle(dpy, pix, gc, 4, cardY, kWinWidth - 8, cardH); + XSetForeground(dpy, gc, selTh.spec.selBg); + XDrawRectangle(dpy, pix, gc, 4, cardY, kWinWidth - 9, cardH - 1); + + std::string line1 = selTh.name + ".xth | " + selTh.spec.fontFace + + " " + std::to_string(selTh.spec.baseFontSize) + "px"; + drawPango(layoutSmall, &colYellow, 10, cardY + 3, 18, line1, + kWinWidth - 20); + + std::string line2 = + "BG:" + formatHexRgb(selTh.spec.bgMain) + + " SEL:" + formatHexRgb(selTh.spec.selBg) + + " ACC:" + formatHexRgb(selTh.spec.accent); + drawPango(layoutSmall, &colLight, 10, cardY + 21, 18, line2, + kWinWidth - 20); + + std::string line3 = + "FG:" + formatHexRgb(selTh.spec.fgMain) + + " Font:" + std::to_string(selTh.spec.sizeSmall()) + "/" + + std::to_string(selTh.spec.sizeMain()) + "/" + + std::to_string(selTh.spec.sizeIcon()) + "/" + + std::to_string(selTh.spec.sizeLarge()) + "px"; + drawPango(layoutSmall, &colMuted, 10, cardY + 38, 18, line3, + kWinWidth - 20); + } + + drawSoftkeyBar("Options", "Apply", "Close"); + + } else if (mode == ViewMode::Actions) { + XSetForeground(dpy, gc, pt.spec.bgHeader); + XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34); + drawPango(layoutIcon, &colBlue, 6, 0, 34, kIconList); + drawPango(layoutMain, &colWhite, 28, 0, 34, "Theme Options", 200); + + const int listY = 36; + const int rowH = 33; + const int visibleRows = 7; + int scroll = 0; + if (actionSelected >= visibleRows) { + scroll = actionSelected - visibleRows + 1; + } + for (int i = 0; i < visibleRows; ++i) { + int idx = scroll + i; + if (idx >= static_cast(actionsList.size())) break; + int ry = listY + i * rowH; + bool isSel = (idx == actionSelected); + XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain); + XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); + drawPango(layoutIcon, isSel ? &colYellow : &colBlue, 8, ry, rowH, + actionsList[idx].icon); + drawPango(layoutMain, &colWhite, 32, ry, rowH, actionsList[idx].label, + 202); + } + drawSoftkeyBar("Select", "Select", "Back"); + + } else if (mode == ViewMode::Customize) { + XSetForeground(dpy, gc, pt.spec.bgHeader); + XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 32); + drawPango(layoutIcon, &colYellow, 6, 0, 32, kIconPalette); + drawPango(layoutMain, &colWhite, 28, 0, 32, + "Edit: " + editThemeName, 204); + + struct FieldRow { + const char* label; + std::string value; + bool hasSwatch; + unsigned long swatchColor; + }; + FieldRow rows[kNumEditFields] = { + {"Font Face", editSpec.fontFace, false, 0}, + {"Base Size", + std::to_string(editSpec.baseFontSize) + "px (" + + std::to_string(editSpec.sizeSmall()) + "/" + + std::to_string(editSpec.sizeMain()) + "/" + + std::to_string(editSpec.sizeLarge()) + ")", + false, 0}, + {"Main BG", formatHexRgb(editSpec.bgMain), true, editSpec.bgMain}, + {"Bar BG", formatHexRgb(editSpec.bgBar), true, editSpec.bgBar}, + {"Header BG", formatHexRgb(editSpec.bgHeader), true, editSpec.bgHeader}, + {"Selected BG", formatHexRgb(editSpec.selBg), true, editSpec.selBg}, + {"Selected FG", formatHexRgb(editSpec.selFg), true, editSpec.selFg}, + {"Accent", formatHexRgb(editSpec.accent), true, editSpec.accent}, + {"Font Color", formatHexRgb(editSpec.fgMain), true, editSpec.fgMain}, + {"Muted Color", formatHexRgb(editSpec.fgMuted), true, editSpec.fgMuted} + }; + + const int listY = 34; + const int rowH = 29; + const int visibleRows = 8; + int scroll = 0; + if (editField >= visibleRows) { + scroll = editField - visibleRows + 1; + } + for (int i = 0; i < visibleRows; ++i) { + int idx = scroll + i; + if (idx >= kNumEditFields) break; + int ry = listY + i * rowH; + bool isSel = (idx == editField); + XSetForeground( + dpy, gc, + isSel ? pt.spec.selBg + : ((i % 2 == 0) ? pt.spec.bgAltEven : pt.spec.bgAltOdd)); + XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1); + + drawPango(layoutSmall, isSel ? &colYellow : &colLight, 6, ry, rowH, + rows[idx].label, 88); + + if (rows[idx].hasSwatch) { + drawSwatch(98, ry + (rowH - 14) / 2, 16, 14, rows[idx].swatchColor); + drawPango(layoutMain, &colWhite, 120, ry, rowH, + "< " + rows[idx].value + " >", 114); + } else { + drawPango(layoutSmall, &colWhite, 96, ry, rowH, + "< " + rows[idx].value + " >", 138); + } + } + + drawSoftkeyBar("Save", editField >= 2 ? "Hex#" : "Next", "Back"); + + } else if (mode == ViewMode::HexModal) { + XSetForeground(dpy, gc, pt.spec.bgHeader); + XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36); + drawPango(layoutIcon, &colYellow, 6, 0, 36, kIconPalette); + drawPango(layoutMain, &colWhite, 28, 0, 36, "Enter Hex Color", 200); + + XSetForeground(dpy, gc, pt.spec.bgInputFocus); + XFillRectangle(dpy, pix, gc, 12, 66, kWinWidth - 24, 38); + XSetForeground(dpy, gc, pt.spec.accentAlt); + XDrawRectangle(dpy, pix, gc, 12, 66, kWinWidth - 25, 37); + + std::string disp = "#" + hexInput + "|"; + drawPango(layoutMain, &colWhite, 20, 66, 38, disp, 140); + + uint32_t previewCol = 0; + std::string candidate = "#" + hexInput; + if (parseHexRgb(candidate, &previewCol)) { + drawSwatch(kWinWidth - 60, 71, 40, 28, previewCol); + } + + drawPango(layoutSmall, &colLight, 12, 118, 20, + "Keys 0-9: digits / hex A-F", 216); + drawPango(layoutSmall, &colMuted, 12, 140, 20, + "2: 2/A/B/C 3: 3/D/E/F", 216); + drawSoftkeyBar("OK", "OK", hexInput.empty() ? "Cancel" : "Del"); + + } else if (mode == ViewMode::NameModal) { + XSetForeground(dpy, gc, pt.spec.bgHeader); + XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36); + drawPango(layoutIcon, &colBlue, 6, 0, 36, kIconEdit); + drawPango(layoutMain, &colWhite, 28, 0, 36, nameTitle, 145); + + const char* mBadge = "[Abc]"; + if (caseMode == TextCaseMode::Lower) mBadge = "[abc]"; + else if (caseMode == TextCaseMode::Upper) mBadge = "[ABC]"; + else if (caseMode == TextCaseMode::Num) mBadge = "[123]"; + drawPango(layoutSmall, &colYellow, kWinWidth - 46, 0, 36, mBadge); + + XSetForeground(dpy, gc, pt.spec.bgInputFocus); + XFillRectangle(dpy, pix, gc, 10, 74, kWinWidth - 20, 38); + XSetForeground(dpy, gc, pt.spec.accentAlt); + XDrawRectangle(dpy, pix, gc, 10, 74, kWinWidth - 21, 37); + drawPango(layoutMain, &colWhite, 16, 74, 38, nameInput + "|", + kWinWidth - 32); + + drawPango(layoutSmall, &colMuted, 10, 124, 20, + "Saves to ~/.xthemes/.xth", 220); + drawPango(layoutSmall, &colMuted, 10, 146, 20, + "0-9: multi-tap #: cycle case", 220); + drawSoftkeyBar("Save", "Save", nameInput.empty() ? "Cancel" : "Del"); + + } else if (mode == ViewMode::ConfirmDelete && !themes.empty()) { + const auto& th = themes[selected]; + XSetForeground(dpy, gc, pt.spec.bgHeader); + XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36); + drawPango(layoutIcon, &colRed, 8, 0, 36, kIconTrash); + drawPango(layoutMain, &colWhite, 30, 0, 36, "Delete Theme?", 200); + + drawPango(layoutMain, &colWhite, 14, 68, 26, th.name + ".xth", 212); + drawPango(layoutSmall, &colMuted, 14, 98, 22, + "Remove this theme file from", 212); + drawPango(layoutSmall, &colMuted, 14, 118, 22, "~/.xthemes/?", 212); + + for (int i = 0; i < 2; ++i) { + int ry = 164 + i * 40; + bool isSel = (confirmSelected == i); + XSetForeground( + dpy, gc, + isSel ? (i == 1 ? pt.spec.dangerBg : pt.spec.selBg) + : pt.spec.bgButton); + XFillRectangle(dpy, pix, gc, 16, ry, kWinWidth - 32, 34); + drawPango(layoutMain, &colWhite, 28, ry, 34, + i == 0 ? "No, Keep Theme" : "Yes, Delete Theme", 184); + } + drawSoftkeyBar("Select", "Confirm", "Cancel"); + } + + int64_t tNow = nowMs(); + if (!statusBanner.empty() && tNow < statusBannerUntilMs) { + XSetForeground(dpy, gc, pt.spec.bgToast); + XFillRectangle(dpy, pix, gc, 8, kWinHeight - 58, kWinWidth - 16, 28); + XSetForeground(dpy, gc, pt.spec.accent); + XDrawRectangle(dpy, pix, gc, 8, kWinHeight - 58, kWinWidth - 17, 27); + drawPango(layoutSmall, &colWhite, 14, kWinHeight - 58, 28, statusBanner, + kWinWidth - 28); + } + + XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0); + XFlush(dpy); + }; + + auto showWindow = [&](const std::string& argStr) { + closeOtherPlugins("themes"); + mode = ViewMode::Browser; + refreshThemes(argStr); + writeIntFile(activePidPath, static_cast(::getpid())); + if (!visible) { + visible = true; + XMapRaised(dpy, win); + for (int i = 0; i < 20; ++i) { + if (XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, + CurrentTime) == GrabSuccess) { + break; + } + usleep(2000); + } + } else { + XRaiseWindow(dpy, win); + } + XSetInputFocus(dpy, win, RevertToPointerRoot, CurrentTime); + openStartMs = nowMs(); + startupIgnoreUntilMs = openStartMs + 180; + initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0); + softkeyPending = 0; + drawUI(); + }; + + auto hideWindow = [&]() { + if (!visible) return; + writeSoftkeySuppress(nowMs() + 450); + XUngrabKeyboard(dpy, CurrentTime); + XUnmapWindow(dpy, win); + XFlush(dpy); + visible = false; + ::unlink(activePidPath.c_str()); + }; + + auto commitNameTap = [&](int digit) { + const char* chars = kMultiTapTheme[digit]; + int len = static_cast(std::strlen(chars)); + if (len <= 0) return; + + int64_t t = nowMs(); + if (caseMode == TextCaseMode::Num) { + nameInput.push_back(static_cast('0' + digit)); + lastTapDigit = -1; + return; + } + + bool sameKey = (lastTapDigit == digit && (t - lastTapMs) <= 850 && + !nameInput.empty()); + if (sameKey) { + lastTapCycle = (lastTapCycle + 1) % len; + popUtf8(nameInput); + } else { + lastTapDigit = digit; + lastTapCycle = 0; + } + lastTapMs = t; + + char ch = chars[lastTapCycle]; + if (std::isalpha(static_cast(ch))) { + if (caseMode == TextCaseMode::Upper) { + ch = static_cast(std::toupper(static_cast(ch))); + } else if (caseMode == TextCaseMode::Title && nameInput.empty()) { + ch = static_cast(std::toupper(static_cast(ch))); + } + } + nameInput.push_back(ch); + }; + + auto commitHexTap = [&](int digit) { + const char* chars = kHexKeyMap[digit]; + int len = static_cast(std::strlen(chars)); + if (len <= 0) return; + + int64_t t = nowMs(); + bool sameKey = (lastTapDigit == digit && (t - lastTapMs) <= 800 && + len > 1 && !hexInput.empty()); + if (sameKey) { + lastTapCycle = (lastTapCycle + 1) % len; + hexInput.pop_back(); + } else { + if (hexInput.size() >= 6) return; + lastTapDigit = digit; + lastTapCycle = 0; + } + lastTapMs = t; + hexInput.push_back(chars[lastTapCycle]); + }; + + auto executeAction = [&](int actId) { + if (actId == 1) { + mode = ViewMode::Browser; + applySelectedTheme(); + } else if (actId == 2 && !themes.empty()) { + editSpec = themes[selected].spec; + editThemeName = themes[selected].name; + editField = 0; + mode = ViewMode::Customize; + } else if (actId == 3) { + nameAction = NameModalAction::SaveCurrentAs; + nameTitle = "Save Theme As"; + nameInput = "Custom"; + lastTapDigit = -1; + mode = ViewMode::NameModal; + } else if (actId == 4 && !themes.empty()) { + nameAction = NameModalAction::DuplicateTheme; + nameTitle = "Duplicate Theme"; + nameInput = themes[selected].name + "_Copy"; + lastTapDigit = -1; + mode = ViewMode::NameModal; + } else if (actId == 5 && !themes.empty()) { + std::string p = themes[selected].fullPath; + hideWindow(); + runAsyncCmd("st -e nano \"" + p + "\""); + } else if (actId == 6) { + ThemeSpec defSpec; + defSpec.name = "Default"; + writeTextFile(getXthemesDir() + "/Default.xth", + defSpec.serializeXresources()); + applyThemeSpecSystemWide(defSpec, dpy); + refreshThemes("Default"); + statusBanner = "Reset & applied Default theme"; + statusBannerUntilMs = nowMs() + 2000; + mode = ViewMode::Browser; + } else if (actId == 7 && !themes.empty()) { + confirmSelected = 0; + mode = ViewMode::ConfirmDelete; + } + }; + + auto handleSoftkeyAction = [&](bool isLeft) { + if (mode == ViewMode::Browser) { + if (isLeft) { + buildActionsList(); + mode = ViewMode::Actions; + } else { + hideWindow(); + return; + } + } else if (mode == ViewMode::Actions) { + if (isLeft) { + if (!actionsList.empty()) { + executeAction(actionsList[actionSelected].id); + } + } else { + mode = ViewMode::Browser; + } + } else if (mode == ViewMode::Customize) { + if (isLeft) { + editSpec.name = editThemeName; + std::string targetPath = getXthemesDir() + "/" + editThemeName + ".xth"; + writeTextFile(targetPath, editSpec.serializeXresources()); + applyThemeSpecSystemWide(editSpec, dpy); + refreshThemes(editThemeName); + statusBanner = "Saved & applied " + editThemeName; + statusBannerUntilMs = nowMs() + 2000; + mode = ViewMode::Browser; + } else { + mode = ViewMode::Browser; + } + } else if (mode == ViewMode::HexModal) { + if (isLeft) { + uint32_t parsed = 0; + if (parseHexRgb("#" + hexInput, &parsed)) { + uint32_t* colPtr = getEditFieldColorPtr(hexTargetField); + if (colPtr) { + *colPtr = parsed; + syncCustomColorsToDerived(hexTargetField != 2); + } + mode = ViewMode::Customize; + } else { + statusBanner = "Enter 6 hex digits (0-9, a-f)"; + statusBannerUntilMs = nowMs() + 1800; + } + } else { + if (!hexInput.empty()) { + hexInput.pop_back(); + lastTapDigit = -1; + } else { + mode = ViewMode::Customize; + } + } + } else if (mode == ViewMode::NameModal) { + if (isLeft) { + std::string cleanName = trimStr(nameInput); + // Strip any slash or .xth suffix + if (cleanName.size() > 4 && + cleanName.substr(cleanName.size() - 4) == ".xth") { + cleanName = cleanName.substr(0, cleanName.size() - 4); + } + for (char& c : cleanName) { + if (c == '/' || c == '\\' || c == ':') c = '_'; + } + if (!cleanName.empty()) { + ThemeSpec toSave = + (nameAction == NameModalAction::DuplicateTheme && !themes.empty()) + ? themes[selected].spec + : pt.spec; + toSave.name = cleanName; + std::string outPath = getXthemesDir() + "/" + cleanName + ".xth"; + if (writeTextFile(outPath, toSave.serializeXresources())) { + refreshThemes(cleanName); + statusBanner = "Saved " + cleanName + ".xth"; + statusBannerUntilMs = nowMs() + 2000; + } + } + mode = ViewMode::Browser; + } else { + if (!nameInput.empty()) { + popUtf8(nameInput); + lastTapDigit = -1; + } else { + mode = ViewMode::Browser; + } + } + } else if (mode == ViewMode::ConfirmDelete) { + if (isLeft) { + if (confirmSelected == 1 && !themes.empty() && + themes[selected].name != "Default") { + std::string delName = themes[selected].name; + ::unlink(themes[selected].fullPath.c_str()); + refreshThemes(); + statusBanner = "Deleted " + delName + ".xth"; + statusBannerUntilMs = nowMs() + 1800; + } + mode = ViewMode::Browser; + } else { + mode = ViewMode::Browser; + } + } + drawUI(); + }; + + if (!isPreload) { + showWindow(openArg); + } + + int xfd = ConnectionNumber(dpy); + while (gRunning) { + for (const auto& cmd : readFifoLines(fifoFd)) { + if (cmd == "NOP") { + continue; + } else if (cmd == "RELOAD_THEME") { + refreshThemes(); + if (visible) drawUI(); + } else if (cmd == "CLOSE") { + hideWindow(); + } else if (cmd == "OPEN") { + showWindow(""); + } else if (cmd.rfind("OPEN ", 0) == 0) { + showWindow(trimStr(cmd.substr(5))); + } + } + + while (XPending(dpy) > 0) { + XEvent ev; + XNextEvent(dpy, &ev); + if (!visible) continue; + + if (ev.type == Expose && ev.xexpose.count == 0) { + drawUI(); + } else if (ev.type == KeyPress) { + int64_t tNow = nowMs(); + unsigned int kc = ev.xkey.keycode; + KeySym sym = XLookupKeysym(&ev.xkey, 0); + + bool isLeftSk = + (kc == 135 || sym == XK_Menu || sym == XF86XK_KbdLcdMenu1 || + sym == _EVDEVK(0x246) || sym == XK_F1); + bool isRightSk = + (kc == 166 || sym == XF86XK_Back || sym == XF86XK_KbdLcdMenu2 || + sym == _EVDEVK(0x247) || sym == XK_F2); + + if (!initialSoftkeysReleased) { + if (querySoftkeysDown(dpy) == 0) { + initialSoftkeysReleased = true; + } else if (isLeftSk || isRightSk) { + continue; + } + } + if (tNow < startupIgnoreUntilMs && (isLeftSk || isRightSk)) { + continue; + } + + if (isLeftSk || isRightSk) { + int bit = isLeftSk ? 1 : 2; + softkeyPending |= bit; + if (softkeyPending == 3 || querySoftkeysDown(dpy) == 3) { + softkeyPending = 0; + sendPluginCommand("xdrawer", "TOGGLE"); + continue; + } + softkeyDeadlineMs = tNow + 35; + continue; + } + + if (sym == XK_Escape || kc == 124 || sym == XF86XK_PowerOff) { + hideWindow(); + continue; + } + + if (sym == XK_BackSpace || sym == XK_Delete) { + handleSoftkeyAction(false); + continue; + } + + bool isCallKey = + (kc == 177 || sym == XF86XK_PickupPhone || sym == XF86XK_Phone || + sym == _EVDEVK(0x1bd) || sym == _EVDEVK(0x0a9)); + + if (mode == ViewMode::Browser) { + if (sym == XK_Up || sym == XK_KP_Up) { + if (!themes.empty()) { + selected = + (selected - 1 + static_cast(themes.size())) % + static_cast(themes.size()); + drawUI(); + } + } else if (sym == XK_Down || sym == XK_KP_Down) { + if (!themes.empty()) { + selected = (selected + 1) % static_cast(themes.size()); + drawUI(); + } + } else if (sym == XK_Return || sym == XK_KP_Enter || isCallKey) { + applySelectedTheme(); + drawUI(); + } else { + char d = mapDialKey(sym, kc, ev.xkey.state); + if (d == '*' && !themes.empty()) { + editSpec = themes[selected].spec; + editThemeName = themes[selected].name; + editField = 0; + mode = ViewMode::Customize; + drawUI(); + } else if (d == '#') { + nameAction = NameModalAction::SaveCurrentAs; + nameTitle = "Save Theme As"; + nameInput = "Custom"; + lastTapDigit = -1; + mode = ViewMode::NameModal; + drawUI(); + } else if (d >= '1' && d <= '9') { + int idx = (d - '1'); + if (idx < static_cast(themes.size())) { + selected = idx; + applySelectedTheme(); + drawUI(); + } + } + } + } else if (mode == ViewMode::Actions) { + if (sym == XK_Up || sym == XK_KP_Up) { + if (!actionsList.empty()) { + actionSelected = + (actionSelected - 1 + static_cast(actionsList.size())) % + static_cast(actionsList.size()); + drawUI(); + } + } else if (sym == XK_Down || sym == XK_KP_Down) { + if (!actionsList.empty()) { + actionSelected = + (actionSelected + 1) % static_cast(actionsList.size()); + drawUI(); + } + } else if (sym == XK_Return || sym == XK_KP_Enter) { + handleSoftkeyAction(true); + } + } else if (mode == ViewMode::Customize) { + if (sym == XK_Up || sym == XK_KP_Up) { + editField = (editField - 1 + kNumEditFields) % kNumEditFields; + drawUI(); + } else if (sym == XK_Down || sym == XK_KP_Down) { + editField = (editField + 1) % kNumEditFields; + drawUI(); + } else if (sym == XK_Left || sym == XK_KP_Left) { + cycleEditField(editField, -1); + drawUI(); + } else if (sym == XK_Right || sym == XK_KP_Right) { + cycleEditField(editField, 1); + drawUI(); + } else if (sym == XK_Return || sym == XK_KP_Enter) { + if (editField >= 2) { + uint32_t* colPtr = getEditFieldColorPtr(editField); + if (colPtr) { + hexTargetField = editField; + hexInput = formatHexRgb(*colPtr).substr(1); + lastTapDigit = -1; + mode = ViewMode::HexModal; + } + } else { + cycleEditField(editField, 1); + } + drawUI(); + } + } else if (mode == ViewMode::HexModal) { + if (sym == XK_Return || sym == XK_KP_Enter) { + handleSoftkeyAction(true); + } else if (sym == XK_Right || sym == XK_KP_Right) { + lastTapDigit = -1; + } else { + char d = mapDialKey(sym, kc, ev.xkey.state); + if (d >= '0' && d <= '9') { + commitHexTap(d - '0'); + drawUI(); + } + } + } else if (mode == ViewMode::NameModal) { + if (sym == XK_Return || sym == XK_KP_Enter) { + handleSoftkeyAction(true); + } else if (sym == XK_Right || sym == XK_KP_Right) { + lastTapDigit = -1; + } else { + char d = mapDialKey(sym, kc, ev.xkey.state); + if (d == '#') { + lastTapDigit = -1; + caseMode = static_cast( + (static_cast(caseMode) + 1) % 4); + drawUI(); + } else if (d == '*') { + commitNameTap(1); + drawUI(); + } else if (d >= '0' && d <= '9') { + commitNameTap(d - '0'); + drawUI(); + } + } + } else if (mode == ViewMode::ConfirmDelete) { + if (sym == XK_Up || sym == XK_Down || sym == XK_KP_Up || + sym == XK_KP_Down) { + confirmSelected = 1 - confirmSelected; + drawUI(); + } else if (sym == XK_Return || sym == XK_KP_Enter) { + handleSoftkeyAction(true); + } + } + } + } + + int64_t tNow = nowMs(); + if (visible && softkeyPending != 0 && tNow >= softkeyDeadlineMs) { + if (querySoftkeysDown(dpy) == 3) { + softkeyPending = 0; + sendPluginCommand("xdrawer", "TOGGLE"); + } else { + bool isLeft = (softkeyPending == 1); + softkeyPending = 0; + handleSoftkeyAction(isLeft); + } + } + + int timeoutMs = -1; + if (visible && softkeyPending != 0) { + timeoutMs = std::max(1, static_cast(softkeyDeadlineMs - tNow)); + } + + struct pollfd pfds[2]; + int nfds = 0; + pfds[nfds++] = {xfd, POLLIN, 0}; + int fifoIdx = -1; + if (fifoFd >= 0) { + fifoIdx = nfds; + pfds[nfds++] = {fifoFd, POLLIN, 0}; + } + + int pr = ::poll(pfds, nfds, timeoutMs); + if (pr > 0 && fifoIdx >= 0 && (pfds[fifoIdx].revents & POLLIN)) { + for (const auto& cmd : readFifoLines(fifoFd)) { + if (cmd == "RELOAD_THEME") { + refreshThemes(); + if (visible) drawUI(); + } else if (cmd == "CLOSE") { + hideWindow(); + } else if (cmd == "OPEN") { + showWindow(""); + } else if (cmd.rfind("OPEN ", 0) == 0) { + showWindow(trimStr(cmd.substr(5))); + } + } + } + } + + hideWindow(); + if (fifoFd >= 0) ::close(fifoFd); + ::unlink(daemonPidPath.c_str()); + return 0; +} + +int themes_plugin_init(const DwmHostApi* host) { + if (host && host->spawn_plugin) { + host->spawn_plugin("themes", "--preload"); + } + return 0; +} + +} // namespace + +extern "C" __attribute__((visibility("default"))) const DwmPlugin dwm_plugin_entry = { + DWM_PLUGIN_API_VERSION, + "themes", + "Xresources theme manager for ~/.xthemes (*.xth) with live system-wide reload", + themes_plugin_init, + nullptr, + nullptr, + nullptr, + nullptr, + runThemesApp +}; diff --git a/src/sxmo-dmenu/dmenu.c b/src/sxmo-dmenu/dmenu.c index 66826d1..7af4697 100644 --- a/src/sxmo-dmenu/dmenu.c +++ b/src/sxmo-dmenu/dmenu.c @@ -107,6 +107,10 @@ static Clr *scheme[SchemeLast]; static char *colors[SchemeLast][2]; static const char *fonts[3] = { NULL, NULL, NULL }; +static char xres_font_main[256] = ""; +static char xres_font_icon[256] = ""; +static time_t last_xres_mtime = 0; +static long last_xres_nsec = 0; #include "config.h" @@ -151,11 +155,14 @@ static const char *ICON_ZMI = "\xf3\xb0\x9b\xad"; static const char *ICON_ZMO = "\xf3\xb0\x9b\xac"; static const char *ICON_A2X = "\xe2\x87\x84"; static const char *ICON_KBD = "\xef\x84\x9c"; +static const char *ICON_THM = "\xf3\xb0\x8f\x98"; static void drawmenu(void); static void show_menu(void); static void hide_menu(void); static void open_builtin_menu(const char *menu_name, int keep_idx); +static void load_dmenu_xresources(void); +static void reload_dmenu_theme(void); static char * cistrstr(const char *s, const char *sub) @@ -1711,6 +1718,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 Themes", ICON_THM); + add_menu_item(label, "@plugin:themes", 0); + char bl_buf[32] = ""; if (read_first_line("/sys/class/leds/white:torch/brightness", bl_buf, sizeof(bl_buf)) || read_first_line("/sys/class/leds/white:flash/brightness", bl_buf, sizeof(bl_buf))) { @@ -1742,6 +1752,8 @@ open_builtin_menu(const char *menu_name, int keep_idx) } else if (strcmp(menu_name, "applications") == 0) { reset_session_defaults(1, "Apps"); append_active_call_entries(); + snprintf(label, sizeof(label), "%s Themes", ICON_THM); + add_menu_item(label, "@plugin:themes", 0); load_cache_or_hook("sxmo_apps.cache", "sxmo_hook_apps.sh"); snprintf(label, sizeof(label), "%s System Menu", ICON_MNU); add_menu_item(label, "@menu:sys", 1); @@ -1776,6 +1788,8 @@ open_builtin_menu(const char *menu_name, int keep_idx) home = "/home/user"; char pbuf[256]; + snprintf(label, sizeof(label), "%s Themes", ICON_THM); + add_menu_item(label, "@plugin:themes", 0); snprintf(label, sizeof(label), "%s Brightness", ICON_ARU); add_menu_item(label, "@toggle:bright_up", 1); snprintf(label, sizeof(label), "%s Brightness", ICON_ARD); @@ -1942,6 +1956,7 @@ close_other_active_plugins(void) { "xsms.pid", "/tmp/dwm_plugin_sms.fifo" }, { "xfiles.pid", "/tmp/dwm_plugin_files.fifo" }, { "xcamera.pid", "/tmp/dwm_plugin_camera.fifo" }, + { "xthemes.pid", "/tmp/dwm_plugin_themes.fifo" }, }; const char *rdir = getenv("XDG_RUNTIME_DIR"); const char *base = (rdir && rdir[0]) ? rdir : "/run/user/10000"; @@ -1960,12 +1975,185 @@ close_other_active_plugins(void) } } +static const char * +dmenu_xrm_lookup(XrmDatabase xdb, const char *k1, const char *k2, const char *k3) +{ + char *type = NULL; + XrmValue xval; + + if (k1 && XrmGetResource(xdb, k1, "*", &type, &xval) && xval.addr && xval.addr[0]) + return xval.addr; + if (k2 && XrmGetResource(xdb, k2, "*", &type, &xval) && xval.addr && xval.addr[0]) + return xval.addr; + if (k3 && XrmGetResource(xdb, k3, "*", &type, &xval) && xval.addr && xval.addr[0]) + return xval.addr; + return NULL; +} + +static void +dmenu_set_color(int scm, int col, const char *val) +{ + if (!val || !val[0]) + return; + free(colors[scm][col]); + colors[scm][col] = strdup(val); +} + +static void +load_dmenu_xresources(void) +{ + XrmInitialize(); + + const char *home = getenv("HOME"); + char path[512]; + snprintf(path, sizeof(path), "%s/.Xresources", (home && home[0]) ? home : "/home/user"); + struct stat st; + if (stat(path, &st) == 0) { + last_xres_mtime = st.st_mtim.tv_sec; + last_xres_nsec = st.st_mtim.tv_nsec; + } + + XrmDatabase xdb = XrmGetFileDatabase(path); + Atom actual_type = None; + int actual_format = 0; + unsigned long nitems = 0, bytes_after = 0; + unsigned char *prop = NULL; + + if (!xdb && XGetWindowProperty(dpy, root, XA_RESOURCE_MANAGER, 0L, 65536L, False, + XA_STRING, &actual_type, &actual_format, + &nitems, &bytes_after, &prop) == Success && prop) { + if (actual_type == XA_STRING && actual_format == 8 && nitems > 0) + xdb = XrmGetStringDatabase((const char *)prop); + XFree(prop); + } + if (!xdb) { + char *xrm = XResourceManagerString(dpy); + if (xrm) + xdb = XrmGetStringDatabase(xrm); + } + + if (xdb) { + const char *ff = dmenu_xrm_lookup(xdb, "theme.fontFace", NULL, NULL); + const char *icf = dmenu_xrm_lookup(xdb, "theme.iconFontFace", NULL, NULL); + const char *bsz = dmenu_xrm_lookup(xdb, "theme.baseFontSize", NULL, NULL); + const char *dmf = dmenu_xrm_lookup(xdb, "dmenu.font", NULL, NULL); + + if (ff || bsz) { + int base_sz = bsz ? atoi(bsz) : 15; + if (base_sz < 8 || base_sz > 32) + base_sz = 15; + int large_sz = (base_sz * 18 + 7) / 15; + snprintf(xres_font_main, sizeof(xres_font_main), + "%s:pixelsize=%d:style=Regular:antialias=true:autohint=true", + ff ? ff : "Fira Code", large_sz); + snprintf(xres_font_icon, sizeof(xres_font_icon), + "%s:pixelsize=%d:style=Regular:antialias=true:autohint=true", + icf ? icf : "DejaVuSansM Nerd Font", large_sz); + fonts[0] = xres_font_main; + fonts[1] = xres_font_icon; + } else if (dmf) { + snprintf(xres_font_main, sizeof(xres_font_main), "%s", dmf); + fonts[0] = xres_font_main; + fonts[1] = defaultfonts[1]; + } + + dmenu_set_color(SchemeNorm, ColBg, + dmenu_xrm_lookup(xdb, "theme.bgHeader", "theme.bgMain", "dmenu.background")); + dmenu_set_color(SchemeNorm, ColFg, + dmenu_xrm_lookup(xdb, "theme.fgBright", "theme.fgMain", "dmenu.foreground")); + + dmenu_set_color(SchemeSel, ColBg, + dmenu_xrm_lookup(xdb, "theme.selBg", "dmenu.selbackground", NULL)); + dmenu_set_color(SchemeSel, ColFg, + dmenu_xrm_lookup(xdb, "theme.selFg", "dmenu.selforeground", NULL)); + + dmenu_set_color(SchemeOut, ColBg, + dmenu_xrm_lookup(xdb, "theme.bgSearch", "dmenu.outbackground", "theme.bgInputFocus")); + dmenu_set_color(SchemeOut, ColFg, + dmenu_xrm_lookup(xdb, "theme.fgBright", "dmenu.outforeground", "theme.fgMain")); + + dmenu_set_color(SchemeNormHighlight, ColBg, + dmenu_xrm_lookup(xdb, "theme.bgHeader", "theme.bgMain", "dmenu.background")); + dmenu_set_color(SchemeNormHighlight, ColFg, + dmenu_xrm_lookup(xdb, "theme.highlight", "theme.accent", "dmenu.normhlforeground")); + + dmenu_set_color(SchemeSelHighlight, ColBg, + dmenu_xrm_lookup(xdb, "theme.selBg", "dmenu.selbackground", NULL)); + dmenu_set_color(SchemeSelHighlight, ColFg, + dmenu_xrm_lookup(xdb, "theme.highlight", "theme.accent", "dmenu.selhlforeground")); + + XrmDestroyDatabase(xdb); + } +} + +static void +reload_dmenu_theme(void) +{ + int i; + + load_dmenu_xresources(); + for (i = 0; i < SchemeLast; ++i) { + if (!colors[i][ColBg]) + colors[i][ColBg] = strdup(defaultcolors[i][ColBg]); + if (!colors[i][ColFg]) + colors[i][ColFg] = strdup(defaultcolors[i][ColFg]); + } + + if (drw->fonts) { + drw_fontset_free(drw->fonts); + drw->fonts = NULL; + } + if (!fonts[0]) { + fonts[0] = defaultfonts[0]; + fonts[1] = defaultfonts[1]; + } + if (!drw_fontset_create(drw, fonts, 2)) + drw_fontset_create(drw, defaultfonts, 2); + + lrpad = drw->fonts->h / 2; + bh = drw->fonts->h + 2; + bh = MAX(bh, (int)lineheight); + default_lines = MAX(1, (avail_h_cached / (int)bh) - 1); + + for (i = 0; i < SchemeLast; i++) { + free(scheme[i]); + scheme[i] = drw_scm_create(drw, (const char **)colors[i], 2); + } + for (i = 0; i < SchemeLast; ++i) { + free(colors[i][ColFg]); + free(colors[i][ColBg]); + colors[i][ColFg] = NULL; + colors[i][ColBg] = NULL; + } + + if (win) { + XSetWindowBackground(dpy, win, scheme[SchemeNorm][ColBg].pixel); + XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + } + if (menu_active) + drawmenu(); +} + +static void +check_dmenu_xres_changed(void) +{ + const char *home = getenv("HOME"); + char path[512]; + snprintf(path, sizeof(path), "%s/.Xresources", (home && home[0]) ? home : "/home/user"); + struct stat st; + if (stat(path, &st) == 0) { + if (st.st_mtim.tv_sec != last_xres_mtime || st.st_mtim.tv_nsec != last_xres_nsec) + reload_dmenu_theme(); + } +} static void show_menu(void) { const int bar_h = 24; int target_y; + check_dmenu_xres_changed(); + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; if (lines > 0) { lines = default_lines; @@ -2242,6 +2430,8 @@ handle_fifo_commands(int fifo_fd) if (strcmp(line, "CLOSE") == 0) { if (menu_active) cancel_session(); + } else if (strcmp(line, "RELOAD_THEME") == 0) { + reload_dmenu_theme(); } else if (strcmp(line, "OPEN") == 0) { if (active_client_fd >= 0) finish_client_session(1, NULL); @@ -2469,6 +2659,8 @@ setup(int map_immediately) for (j = 0; j < SchemeLast; ++j) { free(colors[j][ColFg]); free(colors[j][ColBg]); + colors[j][ColFg] = NULL; + colors[j][ColBg] = NULL; } clip = XInternAtom(dpy, "CLIPBOARD", False); @@ -2789,6 +2981,8 @@ dmenu_run_main(int argc, char *argv[]) die("could not get embedding window attributes: 0x%lx", parentwin); drw = drw_create(dpy, screen, root, wa.width, wa.height); + load_dmenu_xresources(); + if (!fonts[0]) { fonts[0] = defaultfonts[0]; fonts[1] = defaultfonts[1]; diff --git a/src/sxmo-dwm/dwm.c b/src/sxmo-dwm/dwm.c index 4141da6..349f2a7 100644 --- a/src/sxmo-dwm/dwm.c +++ b/src/sxmo-dwm/dwm.c @@ -272,7 +272,8 @@ static void clienttagpush(const Arg *arg); static void shiftview(const Arg *arg); static void pushup(const Arg *arg); static void pushdown(const Arg *arg); -static void readxresources(); +static void readxresources(void); +static void reload_dwm_theme(void); static void load_plugins(void); static void unload_plugins(void); @@ -320,6 +321,7 @@ static Window root, wmcheckwin; /* Empty arrays to be filled from command line, Xresources, and defaults, in decreasing order of precedence */ static char *colors[4][3]; /* 4 schemes, 3 colors each */ +static char active_font[256]; static xcb_connection_t *xcon; @@ -2258,6 +2260,8 @@ propertynotify(XEvent *e) if (ev->window == root) { if (ev->atom == XA_WM_NAME) updatestatus(); + else if (ev->atom == XA_RESOURCE_MANAGER) + reload_dwm_theme(); else drawbars(); } else if (ev->state == PropertyDelete) @@ -2657,6 +2661,7 @@ setup(void) root = RootWindow(dpy, screen); drw = drw_create(dpy, screen, root, sw, sh); + snprintf(active_font, sizeof(active_font), "%s", font); readxresources(); /* Apply defaults to font and colors*/ for (i = 0; i <= SchemeTabInactive; ++i){ @@ -2666,7 +2671,7 @@ setup(void) } } - if (!drw_font_create(drw, font)) + if (!drw_font_create(drw, active_font)) die("no fonts could be loaded."); lrpad = 6; bh = MAX(drw->font->h + 4, 24); @@ -2697,6 +2702,10 @@ setup(void) for (j = 0; j <= SchemeTabInactive; ++j) { free(colors[j][ColFg]); free(colors[j][ColBg]); + free(colors[j][ColBorder]); + colors[j][ColFg] = NULL; + colors[j][ColBg] = NULL; + colors[j][ColBorder] = NULL; } /* init bars */ updatebars(); @@ -3429,46 +3438,155 @@ zoom(const Arg *arg) } +static void +set_color_slot(int scm, int col, const char *val) +{ + if (!val || !val[0]) + return; + free(colors[scm][col]); + colors[scm][col] = strdup(val); +} + +static const char * +xrm_lookup_any(XrmDatabase xdb, const char *k1, const char *k2, const char *k3) +{ + char *type = NULL; + XrmValue xval; + + if (k1 && XrmGetResource(xdb, k1, "*", &type, &xval) && xval.addr && xval.addr[0]) + return xval.addr; + if (k2 && XrmGetResource(xdb, k2, "*", &type, &xval) && xval.addr && xval.addr[0]) + return xval.addr; + if (k3 && XrmGetResource(xdb, k3, "*", &type, &xval) && xval.addr && xval.addr[0]) + return xval.addr; + return NULL; +} + void -readxresources() { - XrmInitialize(); +readxresources(void) +{ + XrmInitialize(); - char* xrm; - if ((xrm = XResourceManagerString(drw->dpy))) { - char *type; - XrmDatabase xdb = XrmGetStringDatabase(xrm); - XrmValue xval; + XrmDatabase xdb = NULL; + Atom actual_type = None; + int actual_format = 0; + unsigned long nitems = 0, bytes_after = 0; + unsigned char *prop = NULL; - if (XrmGetResource(xdb, "dwm.background", "*", &type, &xval) && !colors[SchemeNorm][ColBg] ) - colors[SchemeNorm][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dwm.foreground", "*", &type, &xval) && !colors[SchemeNorm][ColFg] ) - colors[SchemeNorm][ColFg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dwm.border", "*", &type, &xval) && !colors[SchemeNorm][ColBorder] ) - colors[SchemeNorm][ColBorder] = strdup(xval.addr); + const char *home = getenv("HOME"); + char path[512]; + snprintf(path, sizeof(path), "%s/.Xresources", (home && home[0]) ? home : "/home/user"); + xdb = XrmGetFileDatabase(path); - if (XrmGetResource(xdb, "dwm.selbackground", "*", &type, &xval) && !colors[SchemeSel][ColBg] ) - colors[SchemeSel][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dwm.selforeground", "*", &type, &xval) && !colors[SchemeSel][ColFg] ) - colors[SchemeSel][ColFg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dwm.selborder", "*", &type, &xval) && !colors[SchemeSel][ColBorder] ) - colors[SchemeSel][ColBorder] = strdup(xval.addr); + if (!xdb && XGetWindowProperty(dpy, root, XA_RESOURCE_MANAGER, 0L, 65536L, False, + XA_STRING, &actual_type, &actual_format, + &nitems, &bytes_after, &prop) == Success && prop) { + if (actual_type == XA_STRING && actual_format == 8 && nitems > 0) + xdb = XrmGetStringDatabase((const char *)prop); + XFree(prop); + } + if (!xdb) { + char *xrm = XResourceManagerString(drw->dpy); + if (xrm) + xdb = XrmGetStringDatabase(xrm); + } - if (XrmGetResource(xdb, "dwm.tabactivebackground", "*", &type, &xval) && !colors[SchemeTabActive][ColBg] ) - colors[SchemeTabActive][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dwm.tabactiveforeground", "*", &type, &xval) && !colors[SchemeTabActive][ColFg] ) - colors[SchemeTabActive][ColFg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dwm.tabactiveborder", "*", &type, &xval) && !colors[SchemeTabActive][ColBorder] ) - colors[SchemeTabActive][ColBorder] = strdup(xval.addr); + if (xdb) { + const char *ff = xrm_lookup_any(xdb, "theme.fontFace", NULL, NULL); + const char *icf = xrm_lookup_any(xdb, "theme.iconFontFace", NULL, NULL); + const char *bsz = xrm_lookup_any(xdb, "theme.baseFontSize", NULL, NULL); + const char *dwmf = xrm_lookup_any(xdb, "dwm.font", NULL, NULL); - if (XrmGetResource(xdb, "dwm.tabinactivebackground", "*", &type, &xval) && !colors[SchemeTabInactive][ColBg] ) - colors[SchemeTabInactive][ColBg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dwm.tabinactiveforeground", "*", &type, &xval) && !colors[SchemeTabInactive][ColFg] ) - colors[SchemeTabInactive][ColFg] = strdup(xval.addr); - if (XrmGetResource(xdb, "dwm.tabinactiveborder", "*", &type, &xval) && !colors[SchemeTabInactive][ColBorder] ) - colors[SchemeTabInactive][ColBorder] = strdup(xval.addr); + if (ff || bsz) { + int sz = bsz ? atoi(bsz) : 15; + if (sz < 8 || sz > 32) + sz = 15; + snprintf(active_font, sizeof(active_font), "%s, %s %dpx", + ff ? ff : "Fira Code", + icf ? icf : "DejaVuSansM Nerd Font", + sz); + } else if (dwmf) { + snprintf(active_font, sizeof(active_font), "%s", dwmf); + } else { + snprintf(active_font, sizeof(active_font), "%s", font); + } - XrmDestroyDatabase(xdb); - } + set_color_slot(SchemeNorm, ColBg, + xrm_lookup_any(xdb, "theme.bgBar", "dwm.background", "dwm.normbgcolor")); + set_color_slot(SchemeNorm, ColFg, + xrm_lookup_any(xdb, "theme.fgMain", "dwm.foreground", "dwm.normfgcolor")); + set_color_slot(SchemeNorm, ColBorder, + xrm_lookup_any(xdb, "theme.border", "dwm.border", "dwm.normbordercolor")); + + set_color_slot(SchemeSel, ColBg, + xrm_lookup_any(xdb, "theme.selBg", "dwm.selbackground", "dwm.selbgcolor")); + set_color_slot(SchemeSel, ColFg, + xrm_lookup_any(xdb, "theme.selFg", "dwm.selforeground", "dwm.selfgcolor")); + set_color_slot(SchemeSel, ColBorder, + xrm_lookup_any(xdb, "theme.accent", "dwm.selborder", "dwm.selbordercolor")); + + set_color_slot(SchemeTabActive, ColBg, + xrm_lookup_any(xdb, "theme.selBg", "dwm.tabactivebackground", "dwm.selbgcolor")); + set_color_slot(SchemeTabActive, ColFg, + xrm_lookup_any(xdb, "theme.selFg", "dwm.tabactiveforeground", "dwm.selfgcolor")); + set_color_slot(SchemeTabActive, ColBorder, + xrm_lookup_any(xdb, "theme.accent", "dwm.tabactiveborder", "dwm.selbordercolor")); + + set_color_slot(SchemeTabInactive, ColBg, + xrm_lookup_any(xdb, "theme.bgBar", "dwm.tabinactivebackground", "dwm.normbgcolor")); + set_color_slot(SchemeTabInactive, ColFg, + xrm_lookup_any(xdb, "theme.fgMain", "dwm.tabinactiveforeground", "dwm.normfgcolor")); + set_color_slot(SchemeTabInactive, ColBorder, + xrm_lookup_any(xdb, "theme.border", "dwm.tabinactiveborder", "dwm.normbordercolor")); + + XrmDestroyDatabase(xdb); + } +} + +static void +reload_dwm_theme(void) +{ + int i, j; + Monitor *m; + Client *c; + + readxresources(); + for (i = 0; i <= SchemeTabInactive; ++i) { + for (j = 0; j <= ColBorder; ++j) { + if (!colors[i][j]) + colors[i][j] = strdup(defaultcolors[i][j]); + } + } + + if (drw->font) { + drw_font_free(drw->font); + drw->font = NULL; + } + if (!drw_font_create(drw, active_font)) + drw_font_create(drw, font); + + for (i = 0; i < LENGTH(colors); i++) { + free(scheme[i]); + scheme[i] = drw_scm_create(drw, (const char **)colors[i], 3); + } + for (j = 0; j <= SchemeTabInactive; ++j) { + free(colors[j][ColFg]); + free(colors[j][ColBg]); + free(colors[j][ColBorder]); + colors[j][ColFg] = NULL; + colors[j][ColBg] = NULL; + colors[j][ColBorder] = NULL; + } + + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) { + if (c == m->sel) + XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); + else + XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); + } + } + drawbars(); } int diff --git a/src/sxmo-st/config.def.h b/src/sxmo-st/config.def.h index 9914b9d..eba7d25 100644 --- a/src/sxmo-st/config.def.h +++ b/src/sxmo-st/config.def.h @@ -118,7 +118,9 @@ static const char *colorname[] = { [255] = 0, /* more colors can be added after 255 to use with DefaultXX */ - "#cccccc", + "#161616", + "#dddddd", + "#f4d03f", "#555555", }; @@ -127,10 +129,10 @@ static const char *colorname[] = { * Default colors (colorname index) * foreground, background, cursor, reverse cursor */ -unsigned int defaultfg = 7; -unsigned int defaultbg = 0; -static unsigned int defaultcs = 256; -static unsigned int defaultrcs = 257; +unsigned int defaultfg = 257; +unsigned int defaultbg = 256; +static unsigned int defaultcs = 258; +static unsigned int defaultrcs = 259; /* * Default shape of cursor diff --git a/src/sxmo-st/config.h b/src/sxmo-st/config.h index 9914b9d..eba7d25 100644 --- a/src/sxmo-st/config.h +++ b/src/sxmo-st/config.h @@ -118,7 +118,9 @@ static const char *colorname[] = { [255] = 0, /* more colors can be added after 255 to use with DefaultXX */ - "#cccccc", + "#161616", + "#dddddd", + "#f4d03f", "#555555", }; @@ -127,10 +129,10 @@ static const char *colorname[] = { * Default colors (colorname index) * foreground, background, cursor, reverse cursor */ -unsigned int defaultfg = 7; -unsigned int defaultbg = 0; -static unsigned int defaultcs = 256; -static unsigned int defaultrcs = 257; +unsigned int defaultfg = 257; +unsigned int defaultbg = 256; +static unsigned int defaultcs = 258; +static unsigned int defaultrcs = 259; /* * Default shape of cursor diff --git a/src/sxmo-st/x.c b/src/sxmo-st/x.c index 376b59a..a653fe6 100644 --- a/src/sxmo-st/x.c +++ b/src/sxmo-st/x.c @@ -1942,6 +1942,21 @@ resize(XEvent *e) cresize(e->xconfigure.width, e->xconfigure.height); } +static volatile sig_atomic_t st_reload_requested = 0; +static char st_theme_font[256] = ""; +static char st_theme_bg[32] = ""; +static char st_theme_fg[32] = ""; +static char st_theme_cs[32] = ""; + +static void +st_sigusr1(int unused) +{ + (void)unused; + st_reload_requested = 1; +} + +void config_init(void); + void run(void) { @@ -1970,8 +1985,19 @@ run(void) ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); cresize(w, h); + signal(SIGUSR1, st_sigusr1); for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + if (st_reload_requested) { + st_reload_requested = 0; + config_init(); + xloadcols(); + xunloadfonts(); + xloadfonts(opt_font ? opt_font : font, 0); + cresize(win.w, win.h); + redraw(); + XFlush(xw.dpy); + } FD_ZERO(&rfd); FD_SET(ttyfd, &rfd); FD_SET(xfd, &rfd); @@ -2084,18 +2110,71 @@ resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) void config_init(void) { - char *resm; - XrmDatabase db; + XrmDatabase db = NULL; ResourcePref *p; + Atom actual_type = None; + int actual_format = 0; + unsigned long nitems = 0, bytes_after = 0; + unsigned char *prop = NULL; + char *type = NULL; + XrmValue ret; XrmInitialize(); - resm = XResourceManagerString(xw.dpy); - if (!resm) + { + const char *home = getenv("HOME"); + char path[512]; + snprintf(path, sizeof(path), "%s/.Xresources", (home && home[0]) ? home : "/home/user"); + db = XrmGetFileDatabase(path); + } + if (!db && XGetWindowProperty(xw.dpy, DefaultRootWindow(xw.dpy), XA_RESOURCE_MANAGER, + 0L, 65536L, False, XA_STRING, &actual_type, + &actual_format, &nitems, &bytes_after, &prop) == Success && prop) { + if (actual_type == XA_STRING && actual_format == 8 && nitems > 0) + db = XrmGetStringDatabase((const char *)prop); + XFree(prop); + } + if (!db) { + char *resm = XResourceManagerString(xw.dpy); + if (resm) + db = XrmGetStringDatabase(resm); + } + if (!db) return; - db = XrmGetStringDatabase(resm); for (p = resources; p < resources + LEN(resources); p++) resource_load(db, p->name, p->type, p->dst); + + /* Also honor unified theme.* keys */ + const char *ff = NULL, *bsz = NULL; + if (XrmGetResource(db, "theme.fontFace", "*", &type, &ret) && ret.addr && ret.addr[0]) + ff = ret.addr; + if (XrmGetResource(db, "theme.baseFontSize", "*", &type, &ret) && ret.addr && ret.addr[0]) + bsz = ret.addr; + if (ff || bsz) { + int base_sz = bsz ? atoi(bsz) : 15; + if (base_sz < 8 || base_sz > 32) + base_sz = 15; + int large_sz = (base_sz * 18 + 7) / 15; + snprintf(st_theme_font, sizeof(st_theme_font), + "%s:pixelsize=%d:style=Regular:antialias=true:autohint=true", + ff ? ff : "Fira Code", large_sz); + font = st_theme_font; + } + if (XrmGetResource(db, "theme.bgHeader", "*", &type, &ret) && ret.addr && ret.addr[0]) { + snprintf(st_theme_bg, sizeof(st_theme_bg), "%s", ret.addr); + colorname[256] = st_theme_bg; + } else if (XrmGetResource(db, "theme.bgMain", "*", &type, &ret) && ret.addr && ret.addr[0]) { + snprintf(st_theme_bg, sizeof(st_theme_bg), "%s", ret.addr); + colorname[256] = st_theme_bg; + } + if (XrmGetResource(db, "theme.fgMain", "*", &type, &ret) && ret.addr && ret.addr[0]) { + snprintf(st_theme_fg, sizeof(st_theme_fg), "%s", ret.addr); + colorname[257] = st_theme_fg; + } + if (XrmGetResource(db, "theme.accent", "*", &type, &ret) && ret.addr && ret.addr[0]) { + snprintf(st_theme_cs, sizeof(st_theme_cs), "%s", ret.addr); + colorname[258] = st_theme_cs; + } } void diff --git a/src/xdrawer/xdrawer.cpp b/src/xdrawer/xdrawer.cpp index 571d01a..d576258 100644 --- a/src/xdrawer/xdrawer.cpp +++ b/src/xdrawer/xdrawer.cpp @@ -15,6 +15,7 @@ #include #include "../sxmo-dwm/dwm_plugin.h" +#include "../dwm-plugins/theme.h" #include #include @@ -532,31 +533,17 @@ static int xdrawer_run_main(int argc, char** argv) { PangoFontMap* fontMap = pango_xft_get_font_map(dpy, screen); PangoContext* pangoCtx = pango_font_map_create_context(fontMap); PangoLayout* layoutMain = pango_layout_new(pangoCtx); - PangoFontDescription* descMain = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 15px"); - pango_layout_set_font_description(layoutMain, descMain); - PangoLayout* layoutIcon = pango_layout_new(pangoCtx); - PangoFontDescription* descIcon = - pango_font_description_from_string("DejaVuSansM Nerd Font, Fira Code 16px"); - pango_layout_set_font_description(layoutIcon, descIcon); - PangoLayout* layoutSmall = pango_layout_new(pangoCtx); - PangoFontDescription* descSmall = - pango_font_description_from_string("Fira Code, DejaVuSansM Nerd Font 12px"); - pango_layout_set_font_description(layoutSmall, descSmall); - auto allocColor = [&](const char* hex) { - XftColor col{}; - XftColorAllocName(dpy, visual, cmap, hex, &col); - return col; - }; - - XftColor colWhite = allocColor("#ffffff"); - XftColor colLight = allocColor("#dddddd"); - XftColor colMuted = allocColor("#aaaaaa"); - XftColor colYellow = allocColor("#f4d03f"); - XftColor colGreen = allocColor("#2ecc71"); + bananarchy::PluginTheme theme; + theme.load(dpy, screen, visual, cmap, layoutMain, layoutSmall, layoutIcon, + nullptr, win); + XftColor& colWhite = theme.colWhite; + XftColor& colLight = theme.colLight; + XftColor& colMuted = theme.colMuted; + XftColor& colYellow = theme.colYellow; + XftColor& colGreen = theme.colGreen; PhoneController controller; size_t selected = 0; @@ -591,7 +578,7 @@ static int xdrawer_run_main(int argc, char** argv) { }; auto render = [&]() { - XSetForeground(dpy, gc, 0x222222); + XSetForeground(dpy, gc, theme.spec.bgMain); XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight); const auto& items = controller.items(); @@ -601,11 +588,11 @@ static int xdrawer_run_main(int argc, char** argv) { int rowH = nextRy - ry; bool isSel = (i == selected); - XSetForeground(dpy, gc, isSel ? 0x005577 : ((i % 2 == 0) ? 0x282828 : 0x2e2e2e)); + XSetForeground(dpy, gc, isSel ? theme.spec.selBg : ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd)); XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH); if (isSel) { - XSetForeground(dpy, gc, 0xf4d03f); + XSetForeground(dpy, gc, theme.spec.accent); XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH); } @@ -635,19 +622,19 @@ static int xdrawer_run_main(int argc, char** argv) { int barH = 12; int barY = ry + (rowH - barH) / 2; - XSetForeground(dpy, gc, 0x161616); + XSetForeground(dpy, gc, theme.spec.bgOverlay); XFillRectangle(dpy, pix, gc, barX, barY, barW, barH); - XSetForeground(dpy, gc, isSel ? 0xdddddd : 0x555555); + XSetForeground(dpy, gc, isSel ? theme.spec.fgMain : theme.spec.border); XDrawRectangle(dpy, pix, gc, barX, barY, barW - 1, barH - 1); int fillW = ((barW - 2) * pct) / 100; if (fillW > 0) { - XSetForeground(dpy, gc, isSel ? 0xf4d03f : 0x3498db); + XSetForeground(dpy, gc, isSel ? theme.spec.accent : theme.spec.info); XFillRectangle(dpy, pix, gc, barX + 1, barY + 1, fillW, barH - 2); } int knobX = barX + std::clamp(fillW - 2, 0, barW - 5); - XSetForeground(dpy, gc, 0xffffff); + XSetForeground(dpy, gc, theme.spec.fgBright); XFillRectangle(dpy, pix, gc, knobX, barY - 2, 5, barH + 4); char pctBuf[16]; @@ -727,6 +714,9 @@ static int xdrawer_run_main(int argc, char** argv) { closeOtherPlugin("xsms.pid", "/tmp/dwm_plugin_sms.fifo"); closeOtherPlugin("xfiles.pid", "/tmp/dwm_plugin_files.fifo"); closeOtherPlugin("xcamera.pid", "/tmp/dwm_plugin_camera.fifo"); + closeOtherPlugin("xthemes.pid", "/tmp/dwm_plugin_themes.fifo"); + theme.checkAndReload(dpy, screen, visual, cmap, layoutMain, layoutSmall, + layoutIcon, nullptr, win); openStartMs = nowMs(); startupIgnoreUntilMs = openStartMs + 180; initialKeysReleased = (querySoftkeysDown(dpy) == 0); @@ -802,6 +792,10 @@ static int xdrawer_run_main(int argc, char** argv) { showWindow(); } else if (cmd == "CLOSE") { hideWindow(false); + } else if (cmd == "RELOAD_THEME") { + theme.load(dpy, screen, visual, cmap, layoutMain, layoutSmall, + layoutIcon, nullptr, win); + render(); } } } @@ -928,9 +922,6 @@ static int xdrawer_run_main(int argc, char** argv) { ::unlink(fifoPath.c_str()); ::unlink(daemonPidPath.c_str()); - pango_font_description_free(descSmall); - pango_font_description_free(descIcon); - pango_font_description_free(descMain); g_object_unref(layoutSmall); g_object_unref(layoutIcon); g_object_unref(layoutMain);