diff --git a/TODO.MD b/TODO.MD new file mode 100644 index 0000000..b597f71 --- /dev/null +++ b/TODO.MD @@ -0,0 +1,5 @@ + + +- change icon on taskbar. +- fix dark theme. +- check for duplicates commands in all scripts and cloud init during deployment. diff --git a/emmc-provisioning/cloud-init/01-set-rotation-once.sh b/emmc-provisioning/cloud-init/01-set-rotation-once.sh index 8da3be8..712a692 100644 --- a/emmc-provisioning/cloud-init/01-set-rotation-once.sh +++ b/emmc-provisioning/cloud-init/01-set-rotation-once.sh @@ -1,30 +1,44 @@ #!/bin/bash -# One-shot: set reTerminal DM (labwc/Wayland) rotation to Left via wlr-randr, then remove self. +# One-shot: set screen rotation via kanshi (same as Control Center), then remove self. +# Reads video=DSI-1:rotate=N from kernel cmdline and writes ~/.config/kanshi/config. # Runs once as user pi at first login; deletes its autostart and this script so it never runs again. # Logs to /var/log/first-boot.log. FIRST_BOOT_LOG="/var/log/first-boot.log" BASE="$(basename "$0" .sh)" log() { echo "[$(date -Iseconds)] [$BASE] $*" >> "$FIRST_BOOT_LOG" 2>/dev/null || true; } -log "started (labwc/wlr-randr)" -log "waiting 5s for compositor ..." -sleep 5 +ROTATE="270" +for f in /boot/firmware/cmdline.txt /boot/cmdline.txt; do + if [[ -f "$f" ]]; then + val=$(grep -o 'video=DSI-1:rotate=[0-9]*' "$f" 2>/dev/null | head -1) + val="${val#*rotate=}" + if [[ "$val" =~ ^(90|180|270)$ ]]; then ROTATE="$val"; break; fi + fi +done +log "writing kanshi config with transform $ROTATE (from cmdline)" -OUTPUT="" -if command -v wlr-randr &>/dev/null; then - OUTPUT=$(wlr-randr 2>/dev/null | awk '/^[A-Za-z0-9_-]+ /{print $1; exit}') -fi -if [[ -z "$OUTPUT" ]]; then - OUTPUT="DSI-1" - log "using default output: $OUTPUT" -fi +KANSHI_DIR="$HOME/.config/kanshi" +KANSHI_CONFIG="$KANSHI_DIR/config" +mkdir -p "$KANSHI_DIR" +cat > "$KANSHI_CONFIG" << EOF +profile { + output DSI-1 enable scale 1.000000 mode 800x1280@60.000 position 0,0 transform $ROTATE +} +EOF +log "kanshi config written to $KANSHI_CONFIG" -if [[ -n "$OUTPUT" ]] && command -v wlr-randr &>/dev/null; then - log "applying rotation left (transform 270) on $OUTPUT" - wlr-randr --output "$OUTPUT" --transform 270 2>&1 | while read -r line; do log "$line"; done +# Set GTK dark theme (same as first-boot step 08) +GTK_THEME_NAME="PiXnoir" +[[ -d /usr/share/themes/Adwaita-dark ]] && ! [[ -d /usr/share/themes/PiXnoir ]] && GTK_THEME_NAME="Adwaita-dark" +GTK_SETTINGS="$HOME/.config/gtk-3.0/settings.ini" +mkdir -p "$(dirname "$GTK_SETTINGS")" +if [[ ! -f "$GTK_SETTINGS" ]]; then + printf '%s\n' '[Settings]' 'gtk-application-prefer-dark-theme=1' "gtk-theme-name=$GTK_THEME_NAME" > "$GTK_SETTINGS" else - log "WARNING: wlr-randr not found or no output" + grep -q '^gtk-application-prefer-dark-theme=' "$GTK_SETTINGS" && sed -i 's/^gtk-application-prefer-dark-theme=.*/gtk-application-prefer-dark-theme=1/' "$GTK_SETTINGS" || echo 'gtk-application-prefer-dark-theme=1' >> "$GTK_SETTINGS" + grep -q '^gtk-theme-name=' "$GTK_SETTINGS" && sed -i "s/^gtk-theme-name=.*/gtk-theme-name=$GTK_THEME_NAME/" "$GTK_SETTINGS" || echo "gtk-theme-name=$GTK_THEME_NAME" >> "$GTK_SETTINGS" fi +log "Set dark theme ($GTK_THEME_NAME) in gtk-3.0/settings.ini" log "removing one-shot desktop and script" rm -f "$HOME/.config/autostart/${BASE}.desktop" "$HOME/${BASE}.sh" diff --git a/emmc-provisioning/cloud-init/TASKBAR-ICON.md b/emmc-provisioning/cloud-init/TASKBAR-ICON.md new file mode 100644 index 0000000..01ce980 --- /dev/null +++ b/emmc-provisioning/cloud-init/TASKBAR-ICON.md @@ -0,0 +1,73 @@ +# Taskbar (start menu) icon + +The icon you see in the taskbar that opens the application menu is the **start-here** icon from the current icon theme. On Raspberry Pi OS with rpd-labwc (wf-panel-pi + wfplug-menu), that icon is resolved by name: **`start-here`** in the **places** context. + +On the default PiXtrix theme, `start-here` is a symlink to the Raspberry Pi logo (`rpi.png`). To show a different icon, replace **start-here** with your own image in the same theme (or in a custom theme that overrides it). + +--- + +## How to change it + +### Option 1: Replace on the device (one-off) + +After first-boot, SSH in and replace the icon files. The panel uses **PiXtrix** by default; start-here there is under **places**: + +- Paths: `/usr/share/icons/PiXtrix//places/start-here.png` +- Sizes present on Raspberry Pi OS: **16, 24, 32, 48, 64, 96** (and optionally 22 for legacy). + +**Steps (as root):** + +```bash +# Backup originals (currently symlinks to rpi.png) +for s in 16 24 32 48 64 96; do + [ -L /usr/share/icons/PiXtrix/${s}x${s}/places/start-here.png ] && \ + cp -P /usr/share/icons/PiXtrix/${s}x${s}/places/start-here.png /usr/share/icons/PiXtrix/${s}x${s}/places/start-here.png.bak +done + +# Replace with your PNG (one size – e.g. 32x32 – then scale for others, or use a scalable SVG and generate PNGs) +# Example: copy your icon to 32x32 (panel default icon_size is 32) +cp /path/to/your-icon.png /usr/share/icons/PiXtrix/32x32/places/start-here.png +# Remove symlink first if it was a link: +rm -f /usr/share/icons/PiXtrix/32x32/places/start-here.png +cp /path/to/your-icon.png /usr/share/icons/PiXtrix/32x32/places/start-here.png + +# Repeat for other sizes, or use ImageMagick to scale: +for s in 16 24 48 64 96; do + convert /path/to/your-icon.png -resize ${s}x${s} /usr/share/icons/PiXtrix/${s}x${s}/places/start-here.png +done +``` + +Then restart the panel (or log out and back in): + +```bash +pkill wf-panel-pi +# Panel is usually restarted by the session; if not, log out and in again. +``` + +### Option 2: Deploy via first-boot (file server) + +**first-boot.sh** (step 05) already does this: if **`start-here.png`** is present in the first-boot folder on the file server (same URL as splash.png, e.g. `http://10.20.50.1:5000/files/first-boot/start-here.png`), it is downloaded and installed as the taskbar start button icon in PiXtrix (all sizes 16, 24, 32, 48, 64, 96). If ImageMagick (`convert`) is installed, the icon is resized per size; otherwise the same image is copied to each size. Place **start-here.png** in your portal first-boot folder (e.g. next to splash.png) and re-provision or run first-boot; no extra config needed. + +--- + +## Specs for the new icon + +So that it looks correct in the taskbar and in the menu, use these specs: + +| Property | Value | +|----------------|--------| +| **Icon name** | Must be installed as **start-here** (in **places** context) so the panel finds it. | +| **Format** | PNG (preferred for raster), or SVG if you generate PNGs per size. | +| **Sizes** | 16×16, 24×24, 32×32, 48×48, 64×64, 96×96 px (panel default is 32px). | +| **Aspect** | Square (1∶1). The panel shows it in a square cell. | +| **Background** | Transparent (PNG with alpha) so it fits any panel background. | +| **Content** | Simple, recognizable at 16–32 px (logo or symbol). Avoid fine detail. | +| **Theme path** | `PiXtrix/x/places/start-here.png` (or override via a custom theme). | + +### Recommendation + +- **Source asset**: One PNG at **32×32** or **64×64** (or one SVG), then generate the other sizes to avoid scaling artifacts. +- **Design**: High contrast, clear at 24–32 px; works on both light and dark panel (or match your panel colour). +- **File server name**: e.g. `taskbar-icon.png` or `start-here.png`; first-boot script would download it and install to the paths above. + +If you want, the next step is to add a small script (and optional first-boot wiring) that downloads `taskbar-icon.png` from the file server and installs it as **start-here** in all required sizes. diff --git a/emmc-provisioning/cloud-init/TASKBAR-THEME.md b/emmc-provisioning/cloud-init/TASKBAR-THEME.md new file mode 100644 index 0000000..5587c98 --- /dev/null +++ b/emmc-provisioning/cloud-init/TASKBAR-THEME.md @@ -0,0 +1,155 @@ +# Taskbar (wf-panel-pi) editing and custom theme + +You can edit the taskbar to your liking and create a custom theme. The panel is **wf-panel-pi** (GTK3); configuration is in **`~/.config/wf-panel-pi/wf-panel-pi.ini`** and optional **custom CSS** via the **css_path** option. + +--- + +## 1. Editing the taskbar (INI config) + +Config file: **`~/.config/wf-panel-pi/wf-panel-pi.ini`** (user `pi`). If the file is empty or missing, the panel uses built-in defaults. Options (from `panel-pi.xml`): + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| **css_path** | string | (empty) | Path to a custom CSS file for the panel (GTK CSS). | +| **widgets_left** | string | `smenu spacing0 spacing4 launchers spacing8 window-list` | Widget names for the left section (space-separated). | +| **widgets_center** | string | `none` | Widgets in the center. | +| **widgets_right** | string | `tray power ejecter updater ... clock ... batt squeek` | Widgets on the right. | +| **icon_size** | int | 32 | Icon size in pixels. | +| **minimal_height** | int | 24 | Panel height (min). | +| **autohide** | bool | false | Hide panel until cursor touches edge. | +| **autohide_duration** | int | 300 | Animation duration (ms) when showing/hiding. | +| **position** | string | top | `top` or `bottom`. | +| **background_color** | string | #EDECEBFF | Panel background (ARGB hex). | +| **layer** | string | bottom | `top`, `bottom`, `overlay`, `background`. | +| **monitor** | string | 0 | Monitor index. | +| **edge_offset** | int | 20 | Pixels from edge to trigger autohide. | +| **gestures_touch_only** | bool | false | Restrict gestures to touch. | + +### Widget names (for widgets_left / _center / _right) + +- **Left:** `smenu` (start menu), `spacing0` … `spacing8` (spacers), `launchers`, `window-list` +- **Right:** `tray`, `power`, `ejecter`, `updater`, `connect`, `bluetooth`, `netman`, `volumepulse`, `clock`, `batt`, `squeek`, plus `spacing2` etc. + +Example: minimal left (only menu + window list), right (clock + tray only): + +```ini +[panel] +widgets_left = smenu spacing4 window-list +widgets_center = none +widgets_right = spacing2 clock spacing2 tray +icon_size = 28 +minimal_height = 28 +position = bottom +background_color = #2D2D2DFF +``` + +Save as `~/.config/wf-panel-pi/wf-panel-pi.ini` for user `pi`, then restart the panel: `pkill wf-panel-pi` (session will respawn it, or log out/in). + +--- + +## 2. Custom theme (CSS + INI) + +The panel supports **GTK 3 CSS** via **css_path**. You can: + +1. Set **css_path** in `wf-panel-pi.ini` to a full path to your `.css` file (e.g. `~/.config/wf-panel-pi/panel-theme.css`). +2. Use GTK CSS to style the panel window and its children (colors, borders, font, padding, etc.). + +### INI + +```ini +[panel] +css_path = /home/pi/.config/wf-panel-pi/panel-theme.css +background_color = #1E1E1EFF +icon_size = 28 +minimal_height = 28 +``` + +### Example CSS (panel-theme.css) + +GTK panel often uses a single top-level window; you can target it and inner boxes. Example (dark, rounded, with padding): + +```css +/* Custom taskbar theme – dark, subtle border */ +window { + background-color: transparent; +} + +/* Main panel box */ +window box { + background-color: rgba(30, 30, 30, 0.95); + border-radius: 12px; + margin: 4px; + padding: 2px 8px; + border: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4); +} + +/* Buttons / plugin containers */ +button, +button:hover, +button:active { + background: transparent; + border: none; + border-radius: 6px; + padding: 4px; + color: #e0e0e0; +} + +button:hover { + background: rgba(255, 255, 255, 0.08); +} + +/* Labels (e.g. clock) */ +label { + color: #e0e0e0; + font-size: 11pt; +} +``` + +Paths: use an **absolute path** in **css_path** (e.g. `/home/pi/.config/wf-panel-pi/panel-theme.css`). Expand `~` yourself when writing the INI (the panel may not expand it). + +--- + +## 3. Deploying a custom theme via first-boot + +To apply a custom taskbar theme on every provisioned device: + +1. **On the file server** (e.g. first-boot folder): add: + - **wf-panel-pi.ini** – INI with your `[panel]` options and `css_path` pointing to the CSS file path on the device. + - **panel-theme.css** – your GTK CSS. + +2. **In first-boot** (or a one-shot script): for user `pi`: + - Create `~/.config/wf-panel-pi/` if needed. + - Copy or download **wf-panel-pi.ini** to `~/.config/wf-panel-pi/wf-panel-pi.ini`. + - Copy or download **panel-theme.css** to e.g. `~/.config/wf-panel-pi/panel-theme.css`. + - In the INI, set `css_path = /home/pi/.config/wf-panel-pi/panel-theme.css` (or use `$PI_HOME` in the script). + - `chown -R pi:pi ~/.config/wf-panel-pi`. + +3. **Restart panel** after first login (or rely on next login): `pkill wf-panel-pi` (optional in script; session often restarts it). + +Example first-boot snippet (run as root, after `$PI_HOME` is set): + +```bash +PANEL_CONF="$PI_HOME/.config/wf-panel-pi" +mkdir -p "$PANEL_CONF" +curl -fsSL "${FILE_SERVER}/wf-panel-pi.ini" -o "$PANEL_CONF/wf-panel-pi.ini" +curl -fsSL "${FILE_SERVER}/panel-theme.css" -o "$PANEL_CONF/panel-theme.css" +sed -i "s|/home/pi|$PI_HOME|g" "$PANEL_CONF/wf-panel-pi.ini" +chown -R "$PI_USER:$PI_USER" "$PANEL_CONF" +``` + +Ensure the INI uses the same path as where you save the CSS (e.g. `css_path = /home/pi/.config/wf-panel-pi/panel-theme.css` or `$PI_HOME/...` if you substitute in the script). + +**Example theme files** in this repo: `config-files/wf-panel-pi.ini` and `config-files/panel-theme.css` (dark, rounded panel). Host them on the file server and deploy with the snippet above. + +--- + +## 4. Summary + +| Goal | Where | How | +|------|--------|-----| +| Change layout (widgets, position, size) | `~/.config/wf-panel-pi/wf-panel-pi.ini` | Edit `[panel]` options. | +| Change colors / style | Same INI + custom CSS | Set **css_path** and write GTK CSS. | +| Custom theme on all devices | File server + first-boot | Deploy **wf-panel-pi.ini** and **panel-theme.css** and copy to `~/.config/wf-panel-pi/` for `pi`. | + +After any change, restart the panel (`pkill wf-panel-pi`) or log out and back in so the new theme is applied. diff --git a/emmc-provisioning/cloud-init/config-files/README.md b/emmc-provisioning/cloud-init/config-files/README.md index e02ec04..2e4e4b1 100644 --- a/emmc-provisioning/cloud-init/config-files/README.md +++ b/emmc-provisioning/cloud-init/config-files/README.md @@ -8,5 +8,7 @@ first-boot.sh downloads these from `FILE_SERVER` (e.g. `http://10.20.50.1:5000/f | 99-wallpaper.conf | /etc/lightdm/lightdm.conf.d/99-wallpaper.conf | | 99-default-session.conf | /etc/lightdm/lightdm.conf.d/99-default-session.conf (rpd-labwc) | | maliit-keyboard.desktop | /home/pi/.config/autostart/maliit-keyboard.desktop | -| 01-set-rotation-once.desktop | /home/pi/.config/autostart/01-set-rotation-once.desktop (with 01-set-rotation-once.sh) | +| 01-set-rotation-once.desktop | /home/pi/.config/autostart/01-set-rotation-once.desktop (with 01-set-rotation-once.sh; writes kanshi config + dark theme) | | 02-set-wallpaper-once.desktop | /home/pi/.config/autostart/02-set-wallpaper-once.desktop (with 02-set-wallpaper-once.sh). Wallpaper is also set during first-boot via pcmanfm. | +| wf-panel-pi.ini | Optional: /home/pi/.config/wf-panel-pi/wf-panel-pi.ini (custom taskbar layout and css_path). See TASKBAR-THEME.md. | +| panel-theme.css | Optional: /home/pi/.config/wf-panel-pi/panel-theme.css (custom taskbar CSS). Deploy with wf-panel-pi.ini; see TASKBAR-THEME.md. | diff --git a/emmc-provisioning/cloud-init/config-files/panel-theme.css b/emmc-provisioning/cloud-init/config-files/panel-theme.css new file mode 100644 index 0000000..e4834e8 --- /dev/null +++ b/emmc-provisioning/cloud-init/config-files/panel-theme.css @@ -0,0 +1,44 @@ +/* Custom wf-panel-pi theme – dark, rounded panel + * Place at ~/.config/wf-panel-pi/panel-theme.css + * Set css_path in wf-panel-pi.ini to this file (absolute path). + */ + +/* Panel window */ +window { + background-color: transparent; +} + +/* Main panel box – dark with slight rounding and shadow */ +window box { + background-color: rgba(45, 45, 45, 0.96); + border-radius: 10px; + margin: 4px; + padding: 2px 8px; + border: 1px solid rgba(255, 255, 255, 0.06); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35); +} + +/* Plugin / launcher buttons */ +button, +button:hover, +button:active { + background: transparent; + border: none; + border-radius: 6px; + padding: 4px; + color: #e0e0e0; +} + +button:hover { + background: rgba(255, 255, 255, 0.08); +} + +button:active { + background: rgba(255, 255, 255, 0.12); +} + +/* Labels (e.g. clock text) */ +label { + color: #e0e0e0; + font-size: 11pt; +} diff --git a/emmc-provisioning/cloud-init/config-files/wf-panel-pi.ini b/emmc-provisioning/cloud-init/config-files/wf-panel-pi.ini new file mode 100644 index 0000000..00e78e9 --- /dev/null +++ b/emmc-provisioning/cloud-init/config-files/wf-panel-pi.ini @@ -0,0 +1,21 @@ +# wf-panel-pi custom theme example +# Copy to ~/.config/wf-panel-pi/wf-panel-pi.ini (user pi) +# Optional: copy panel-theme.css to ~/.config/wf-panel-pi/panel-theme.css +# and ensure css_path below points to it. Restart panel: pkill wf-panel-pi + +[panel] +# Custom CSS (use absolute path; replace /home/pi with actual $PI_HOME if different) +css_path = /home/pi/.config/wf-panel-pi/panel-theme.css +# Layout +widgets_left = smenu spacing0 spacing4 launchers spacing8 window-list +widgets_center = none +widgets_right = tray power ejecter updater spacing2 connect spacing2 bluetooth spacing2 netman spacing2 volumepulse spacing2 clock spacing2 batt spacing2 squeek +# Size and position +icon_size = 28 +minimal_height = 28 +position = top +# Dark background (ARGB) +background_color = #2D2D2DFF +# Optional: autohide +autohide = false +autohide_duration = 300 diff --git a/emmc-provisioning/cloud-init/files-from-guard/README.md b/emmc-provisioning/cloud-init/files-from-guard/README.md index 1a5276b..952d88e 100644 --- a/emmc-provisioning/cloud-init/files-from-guard/README.md +++ b/emmc-provisioning/cloud-init/files-from-guard/README.md @@ -14,7 +14,7 @@ first-boot.sh downloads from **`.../files/first-boot/`** (e.g. `http://10.20.50. | **99-wallpaper.conf** | LightDM greeter wallpaper (from `config-files/`). | | **99-default-session.conf** | LightDM default session rpd-labwc (from `config-files/`). | | **maliit-keyboard.desktop** | Maliit on-screen keyboard autostart (from `config-files/`). | -| **01-set-rotation-once.sh** + **.desktop** | One-shot: wlr-randr rotation (Left) at first login. | +| **01-set-rotation-once.sh** + **.desktop** | One-shot: writes ~/.config/kanshi/config with rotation from cmdline and sets GTK dark theme at first login. | Desktop wallpaper is set once during first-boot via pcmanfm config (first-boot.sh). Optional one-shot: **02-set-wallpaper-once.sh**. diff --git a/emmc-provisioning/cloud-init/first-boot.conf.example b/emmc-provisioning/cloud-init/first-boot.conf.example index bf5b59c..f500346 100644 --- a/emmc-provisioning/cloud-init/first-boot.conf.example +++ b/emmc-provisioning/cloud-init/first-boot.conf.example @@ -42,7 +42,8 @@ # WALLPAPER_MODE="crop" # --- Display (reTerminal DM) --- -# Kernel cmdline: DSI rotation. 90 = 90° clockwise; use 180 or 270 for other orientations. +# Kernel cmdline: DSI rotation. 90 = 90° clockwise; 180 or 270 for other orientations. +# At login, ~/.config/kanshi/config is written with this transform (same as Control Center). # DSI_ROTATE="270" # Kernel cmdline: swiotlb size (for vc4-drm/DSI). Leave empty to skip. diff --git a/emmc-provisioning/cloud-init/first-boot.md b/emmc-provisioning/cloud-init/first-boot.md index 80e7edb..e4630f8 100644 --- a/emmc-provisioning/cloud-init/first-boot.md +++ b/emmc-provisioning/cloud-init/first-boot.md @@ -62,7 +62,7 @@ Installs the software needed for the rest of the script and for the kiosk: | **wmctrl** | Window control; used to force Chromium into fullscreen. | | **openssh-server** | SSH access (often also enabled in user-data). | | **swaybg** | Wallpaper for labwc (Wayland); used by one-shot and labwc autostart. | -| **wlr-randr** | Display rotation for wlroots/labwc; one-shot sets “Left” (transform 270). | +| **kanshi** | Display rotation: ~/.config/kanshi/config is written at login with transform from cmdline (same as Control Center). The same login scripts also set GTK dark theme (~/.config/gtk-3.0/settings.ini). | | **maliit-keyboard** | On-screen keyboard for touch input. | | **xinput-calibrator** | Touchscreen calibration (optional; run manually if needed). | @@ -84,7 +84,7 @@ Downloads from `FILE_SERVER` (no local creation): Ensure the `.desktop` file on the server has `Exec=/home/pi/start-chromium.sh` (or the path you use on the device). -**Touch in Chromium:** Long-press on the touchscreen to open the context menu (right-click). This works when Chromium runs as a Wayland client (default under rpd-labwc). If you ever run under pure X11, long-press may not trigger the context menu; in that case you can use **evdev-right-click-emulation** (see e.g. [evdev-right-click-emulation](https://github.com/PeterCxy/evdev-right-click-emulation)) to inject right-click on long-press at the input layer. +**Touch long-press → right-click in Chromium:** Under Wayland (rpd-labwc), long-press may work for right-click elsewhere (e.g. desktop) but **not inside Chromium**—Chromium gets touch events directly and does not implement long-press-as-right-click. To get context menu on long-press in the browser (and everywhere), use **evdev-right-click-emulation** at the input layer so long-press is converted to right-click before any app sees it: [evdev-right-click-emulation](https://github.com/PeterCxy/evdev-right-click-emulation). Build, install the binary (e.g. to `/usr/local/bin/evdev-rce`), and run it as a systemd service at boot (works on both X11 and Wayland). --- @@ -134,7 +134,7 @@ The reTerminal DM default is portrait. Rotation is set **persistently** via the - **Kernel cmdline** — First-boot appends **`video=DSI-1:rotate=90`** to **`/boot/firmware/cmdline.txt`** (or `/boot/cmdline.txt`). The file must remain **one single line** with no line breaks. - **90° clockwise** — The value `90` gives 90° clockwise rotation. Other valid values: `180`, `270` (90° counter-clockwise). -- **Effect** — Rotation is applied by the kernel at boot; no one-shot script or wlr-randr needed. +- **Effect** — Kernel cmdline rotates at boot; at login ~/.config/kanshi/config is written with the same transform (kanshi applies it; same as Control Center). --- diff --git a/emmc-provisioning/cloud-init/first-boot.sh b/emmc-provisioning/cloud-init/first-boot.sh index 9542ab3..b4c88af 100644 --- a/emmc-provisioning/cloud-init/first-boot.sh +++ b/emmc-provisioning/cloud-init/first-boot.sh @@ -134,8 +134,26 @@ step_02_packages() { log "Packages installed successfully" } -# --- Step 03: Kiosk files from file server --- -step_03_kiosk_files() { +# --- Step 03: reTerminal DM drivers (Seeed) --- +step_03_reterminal_drivers() { + if [[ -z "$RETERMINAL_REPO_URL" ]]; then + log "Skipping reTerminal drivers (RETERMINAL_REPO_URL not set)" + return 0 + fi + REPO_DIR="/tmp/seeed-linux-dtoverlays" + log "Cloning seeed-linux-dtoverlays to $REPO_DIR ..." + git clone --depth 1 "$RETERMINAL_REPO_URL" "$REPO_DIR" + log "Running reTerminal.sh --device $RETERMINAL_DEVICE from $REPO_DIR ..." + if ( cd "$REPO_DIR" && "$REPO_DIR/scripts/reTerminal.sh" --device "$RETERMINAL_DEVICE" ); then + log "reTerminal DM drivers installed (reboot will apply)" + else + log "WARNING: reTerminal.sh failed (see log above). Display/touch may still work; you can retry later with: cd $REPO_DIR && sudo ./scripts/reTerminal.sh --device $RETERMINAL_DEVICE" + fi + rm -rf "$REPO_DIR" +} + +# --- Step 04: Kiosk files from file server --- +step_04_kiosk_files() { log "Creating $AUTOSTART" mkdir -p "$AUTOSTART" log "Downloading start-chromium.sh from ${FILE_SERVER}/start-chromium.sh" @@ -147,8 +165,8 @@ step_03_kiosk_files() { log "Kiosk files installed under $PI_HOME and $AUTOSTART" } -# --- Step 04: Boot splash and wallpaper --- -step_04_splash_wallpaper() { +# --- Step 05: Boot splash and wallpaper --- +step_05_splash_wallpaper() { log "Creating $PLYMOUTH_DIR and /usr/share/rpd-wallpaper" mkdir -p "$PLYMOUTH_DIR" /usr/share/rpd-wallpaper if curl -fsSL "${FILE_SERVER}/splash.png" -o "$PLYMOUTH_DIR/splash.png"; then @@ -183,10 +201,32 @@ step_04_splash_wallpaper() { else log "WARNING: Could not download splash.png" fi + # Optional: taskbar start button icon from file server (start-here.png) + if curl -fsSL "${FILE_SERVER}/start-here.png" -o /tmp/start-here.png; then + PIXTRIX_PLACES="/usr/share/icons/PiXtrix" + if [[ -d "$PIXTRIX_PLACES/32x32/places" ]]; then + for s in 16 24 32 48 64 96; do + DEST="$PIXTRIX_PLACES/${s}x${s}/places/start-here.png" + if [[ -d "$(dirname "$DEST")" ]]; then + rm -f "$DEST" + if command -v convert >/dev/null 2>&1; then + convert /tmp/start-here.png -resize "${s}x${s}" "$DEST" 2>/dev/null && true + else + cp /tmp/start-here.png "$DEST" + fi + chmod 644 "$DEST" + fi + done + log "Taskbar start button icon (start-here.png) installed from file server" + else + log "WARNING: PiXtrix theme places dir not found; skipped taskbar icon" + fi + rm -f /tmp/start-here.png + fi } -# --- Step 05: LightDM session --- -step_05_lightdm() { +# --- Step 06: LightDM session --- +step_06_lightdm() { mkdir -p /etc/lightdm/lightdm.conf.d if curl -fsSL "${FILE_SERVER}/99-default-session.conf" -o /etc/lightdm/lightdm.conf.d/99-default-session.conf 2>/dev/null; then log "99-default-session.conf installed" @@ -198,16 +238,36 @@ step_05_lightdm() { sed -i "s/^autologin-session=.*/autologin-session=$LIGHTDM_SESSION/" /etc/lightdm/lightdm.conf log "Patched /etc/lightdm/lightdm.conf to use $LIGHTDM_SESSION" fi + # Delay LightDM on first boot after provisioning so reTerminal DM DSI panel has time to init (avoids black screen on first reboot) + mkdir -p /etc/systemd/system/lightdm.service.d + cat > /etc/systemd/system/cm4-await-display.service << 'AWAITSVC' +[Unit] +Description=Wait for reTerminal DM display on first boot after provisioning +Before=lightdm.service +DefaultDependencies=no + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/sh -c 'if [ -f /var/lib/cm4-provisioning/await-display ]; then echo "Waiting 18s for DSI panel..."; sleep 18; rm -f /var/lib/cm4-provisioning/await-display; fi' + +[Install] +WantedBy=graphical.target +AWAITSVC + printf '%s\n' '[Unit]' 'After=cm4-await-display.service' > /etc/systemd/system/lightdm.service.d/99-await-display.conf + systemctl daemon-reload + systemctl enable cm4-await-display.service 2>/dev/null || true + log "Installed cm4-await-display.service (delays LightDM on first reboot after provisioning)" } -# --- Step 06: Maliit on-screen keyboard --- -step_06_maliit() { +# --- Step 07: Maliit on-screen keyboard --- +step_07_maliit() { mkdir -p "$AUTOSTART" "$PI_HOME/.config" curl -fsSL "${FILE_SERVER}/maliit-keyboard.desktop" -o "$AUTOSTART/maliit-keyboard.desktop" 2>/dev/null && log "maliit-keyboard.desktop installed" || log "WARNING: Could not download maliit-keyboard.desktop" } -# --- Step 07: Dark theme (GTK) --- -step_07_dark_theme() { +# --- Step 08: Dark theme (GTK) --- +step_08_dark_theme() { GTK_SETTINGS="$PI_HOME/.config/gtk-3.0/settings.ini" mkdir -p "$(dirname "$GTK_SETTINGS")" if [[ ! -f "$GTK_SETTINGS" ]]; then @@ -220,24 +280,6 @@ step_07_dark_theme() { chown -R "$PI_USER:$PI_USER" "$PI_HOME/.config" } -# --- Step 08: reTerminal DM drivers (Seeed) --- -step_08_reterminal_drivers() { - if [[ -z "$RETERMINAL_REPO_URL" ]]; then - log "Skipping reTerminal drivers (RETERMINAL_REPO_URL not set)" - return 0 - fi - REPO_DIR="/tmp/seeed-linux-dtoverlays" - log "Cloning seeed-linux-dtoverlays to $REPO_DIR ..." - git clone --depth 1 "$RETERMINAL_REPO_URL" "$REPO_DIR" - log "Running reTerminal.sh --device $RETERMINAL_DEVICE from $REPO_DIR ..." - if ( cd "$REPO_DIR" && "$REPO_DIR/scripts/reTerminal.sh" --device "$RETERMINAL_DEVICE" ); then - log "reTerminal DM drivers installed (reboot will apply)" - else - log "WARNING: reTerminal.sh failed (see log above). Display/touch may still work; you can retry later with: cd $REPO_DIR && sudo ./scripts/reTerminal.sh --device $RETERMINAL_DEVICE" - fi - rm -rf "$REPO_DIR" -} - # --- Step 09: Re-apply splash and Plymouth theme --- step_09_reapply_splash() { CFG_PATH="/boot/firmware/config.txt" @@ -277,6 +319,17 @@ step_10_cmdline() { step_11_oneshots() { if [[ -n "$DSI_ROTATE" ]]; then log "Rotation is set via kernel cmdline (video=DSI-1:rotate=$DSI_ROTATE)" + # Install set-rotation-at-login to write ~/.config/kanshi/config at every login (same as Control Center) + if curl -fsSL "${FILE_SERVER}/set-rotation-at-login.sh" -o "$PI_HOME/set-rotation-at-login.sh" 2>/dev/null; then + chmod 755 "$PI_HOME/set-rotation-at-login.sh" + chown "$PI_USER:$PI_USER" "$PI_HOME/set-rotation-at-login.sh" + if curl -fsSL "${FILE_SERVER}/set-rotation-at-login.desktop" -o /tmp/set-rotation-at-login.desktop 2>/dev/null; then + sed "s|/home/pi|$PI_HOME|g" /tmp/set-rotation-at-login.desktop > "$AUTOSTART/set-rotation-at-login.desktop" + chown "$PI_USER:$PI_USER" "$AUTOSTART/set-rotation-at-login.desktop" + log "Installed set-rotation-at-login (re-applies rotation from cmdline every login)" + fi + rm -f /tmp/set-rotation-at-login.desktop + fi fi if [[ -n "$ONESHOT_SCRIPTS" ]]; then for _name in $ONESHOT_SCRIPTS; do @@ -298,6 +351,9 @@ step_13_reboot() { DEVICE_IP="$(hostname -I 2>/dev/null | awk '{print $1}')" report_status "done" "First-boot complete" "13" "reboot" "$DEVICE_IP" log "Device IP: ${DEVICE_IP:-unknown}" + # Flag for cm4-await-display.service: on next boot delay LightDM so DSI panel can init (avoids black screen) + mkdir -p /var/lib/cm4-provisioning + touch /var/lib/cm4-provisioning/await-display log "=== first-boot.sh finished, rebooting ===" reboot } @@ -305,12 +361,12 @@ step_13_reboot() { # --- Main: run steps in order --- run_step 01 hostname run_step 02 packages -run_step 03 kiosk_files -run_step 04 splash_wallpaper -run_step 05 lightdm -run_step 06 maliit -run_step 07 dark_theme -run_step 08 reterminal_drivers +run_step 03 reterminal_drivers +run_step 04 kiosk_files +run_step 05 splash_wallpaper +run_step 06 lightdm +run_step 07 maliit +run_step 08 dark_theme run_step 09 reapply_splash run_step 10 cmdline run_step 11 oneshots diff --git a/emmc-provisioning/cloud-init/set-rotation-at-login.sh b/emmc-provisioning/cloud-init/set-rotation-at-login.sh index e422877..4a15030 100644 --- a/emmc-provisioning/cloud-init/set-rotation-at-login.sh +++ b/emmc-provisioning/cloud-init/set-rotation-at-login.sh @@ -1,13 +1,31 @@ #!/bin/bash -# Set reTerminal DM (labwc/Wayland) rotation to Left at every login. -# Runs from autostart when user pi logs in; does not remove itself. -# Use this when wlr-randr transform does not persist across reboots. -sleep 5 -OUTPUT="" -if command -v wlr-randr &>/dev/null; then - OUTPUT=$(wlr-randr 2>/dev/null | awk '/^[A-Za-z0-9_-]+ /{print $1; exit}') -fi -[[ -z "$OUTPUT" ]] && OUTPUT="DSI-1" -if [[ -n "$OUTPUT" ]] && command -v wlr-randr &>/dev/null; then - wlr-randr --output "$OUTPUT" --transform 270 +# Set screen rotation via kanshi (same as Control Center). Reads video=DSI-1:rotate=N +# from kernel cmdline and writes ~/.config/kanshi/config. Kanshi auto-reloads when the file changes. +# Also sets GTK dark theme (PiXnoir / Adwaita-dark). Runs from autostart when user pi logs in; does not remove itself. +ROTATE="270" +for f in /boot/firmware/cmdline.txt /boot/cmdline.txt; do + [[ -f "$f" ]] || continue + val=$(grep -o 'video=DSI-1:rotate=[0-9]*' "$f" 2>/dev/null | head -1) + val="${val#*rotate=}" + [[ "$val" =~ ^(90|180|270)$ ]] && ROTATE="$val" && break +done +KANSHI_DIR="$HOME/.config/kanshi" +KANSHI_CONFIG="$KANSHI_DIR/config" +mkdir -p "$KANSHI_DIR" +cat > "$KANSHI_CONFIG" << EOF +profile { + output DSI-1 enable scale 1.000000 mode 800x1280@60.000 position 0,0 transform $ROTATE +} +EOF + +# Set GTK dark theme (same as first-boot step 08) +GTK_THEME_NAME="PiXnoir" +[[ -d /usr/share/themes/Adwaita-dark ]] && ! [[ -d /usr/share/themes/PiXnoir ]] && GTK_THEME_NAME="Adwaita-dark" +GTK_SETTINGS="$HOME/.config/gtk-3.0/settings.ini" +mkdir -p "$(dirname "$GTK_SETTINGS")" +if [[ ! -f "$GTK_SETTINGS" ]]; then + printf '%s\n' '[Settings]' 'gtk-application-prefer-dark-theme=1' "gtk-theme-name=$GTK_THEME_NAME" > "$GTK_SETTINGS" +else + grep -q '^gtk-application-prefer-dark-theme=' "$GTK_SETTINGS" && sed -i 's/^gtk-application-prefer-dark-theme=.*/gtk-application-prefer-dark-theme=1/' "$GTK_SETTINGS" || echo 'gtk-application-prefer-dark-theme=1' >> "$GTK_SETTINGS" + grep -q '^gtk-theme-name=' "$GTK_SETTINGS" && sed -i "s/^gtk-theme-name=.*/gtk-theme-name=$GTK_THEME_NAME/" "$GTK_SETTINGS" || echo "gtk-theme-name=$GTK_THEME_NAME" >> "$GTK_SETTINGS" fi diff --git a/emmc-provisioning/cloud-init/start-chromium.sh b/emmc-provisioning/cloud-init/start-chromium.sh index ef7d1c3..25ba0e0 100755 --- a/emmc-provisioning/cloud-init/start-chromium.sh +++ b/emmc-provisioning/cloud-init/start-chromium.sh @@ -1,56 +1,42 @@ #!/bin/bash +# Start Chromium in app mode. Optional env vars: +# CHROMIUM_APP_URL - URL to open (default: http://127.0.0.1:8080) +# CHROMIUM_MODE - "fullscreen" or "kiosk" (default: fullscreen) +# +# Touch long-press → right-click: In Chromium on Wayland, long-press often does *not* +# open the context menu (Chromium handles touch itself). Elsewhere (e.g. desktop) it may +# work. For long-press right-click *inside* Chromium, use evdev-right-click-emulation +# at the input layer so all apps (including Chromium) receive real right-click events: +# https://github.com/PeterCxy/evdev-right-click-emulation +# # Disable keyring prompts export GNOME_KEYRING_CONTROL="" -# Prefer Wayland when available so touch long-press produces right-click (context menu) -# like the rest of the desktop. X11/XWayland does not get that behavior for Chromium. -USE_WAYLAND=0 -if [ -n "$WAYLAND_DISPLAY" ] && [ -S "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/$WAYLAND_DISPLAY" ]; then - USE_WAYLAND=1 -fi +# Wayland + labwc compositor. +export GDK_BACKEND=wayland +# Wait for compositor +for i in {1..60}; do + if [ -S "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/$WAYLAND_DISPLAY" ]; then + pgrep -x labwc >/dev/null 2>&1 && break + fi + sleep 0.5 +done +# URL to open in Chromium (app mode) +#CHROMIUM_APP_URL="${CHROMIUM_APP_URL:-http://127.0.0.1:8080}" +CHROMIUM_APP_URL="${CHROMIUM_APP_URL:-https://tototheo.com}" -if [ "$USE_WAYLAND" -eq 1 ]; then - # Native Wayland: fullscreen + touch-friendly (long-press = right-click) - export GDK_BACKEND=wayland - # Wait for compositor - for i in {1..60}; do - if [ -S "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/$WAYLAND_DISPLAY" ]; then - pgrep -x labwc >/dev/null 2>&1 && break - fi - sleep 0.5 - done - sleep 3 - /usr/bin/chromium --start-fullscreen --noerrdialogs --disable-infobars --disable-session-crashed-bubble --disable-restore-session-state --no-first-run --password-store=basic --use-mock-keychain --ozone-platform=wayland --enable-features=WaylandWindowDecorations --disable-features=UseChromeOSDirectVideoDecoder --app=http://127.0.0.1:8080 & -else - # Fallback: X11 (e.g. no Wayland session) - export DISPLAY=:0 - export GDK_BACKEND=x11 - unset WAYLAND_DISPLAY - for i in {1..60}; do - if xset q >/dev/null 2>&1 || [ -n "$DISPLAY" ]; then - if pgrep -x pcmanfm >/dev/null 2>&1 || pgrep -x lxsession >/dev/null 2>&1 || pgrep -x xfdesktop >/dev/null 2>&1; then - break - fi - fi - sleep 0.5 - done - sleep 5 - /usr/bin/chromium --start-fullscreen --noerrdialogs --disable-infobars --disable-session-crashed-bubble --disable-restore-session-state --no-first-run --password-store=basic --use-mock-keychain --ozone-platform=x11 --disable-features=UseChromeOSDirectVideoDecoder --app=http://127.0.0.1:8080 & - sleep 3 - for i in {1..10}; do - WINDOW_ID=$(wmctrl -l 2>/dev/null | grep -i chromium | head -1 | awk '{print $1}') - if [ -n "$WINDOW_ID" ]; then - wmctrl -i -r "$WINDOW_ID" -b add,fullscreen 2>/dev/null - break - fi - sleep 0.5 - done -fi +# Mode: "fullscreen" or "kiosk" +CHROMIUM_MODE="${CHROMIUM_MODE:-fullscreen}" +CHROMIUM_OPTS="--noerrdialogs --disable-infobars --disable-session-crashed-bubble --disable-restore-session-state --no-first-run --password-store=basic --use-mock-keychain --ozone-platform=wayland --enable-features=WaylandWindowDecorations --disable-features=UseChromeOSDirectVideoDecoder --app=${CHROMIUM_APP_URL}" +case "${CHROMIUM_MODE}" in + kiosk) CHROMIUM_OPTS="--kiosk ${CHROMIUM_OPTS}" ;; + *) CHROMIUM_OPTS="--start-fullscreen ${CHROMIUM_OPTS}" ;; +esac + +sleep 3 +/usr/bin/chromium $CHROMIUM_OPTS & # Keep script running wait - -# Kiosk mode (commented out - uncomment to use instead of fullscreen) -# /usr/bin/chromium --kiosk --noerrdialogs --disable-infobars --disable-session-crashed-bubble --disable-restore-session-state --no-first-run --password-store=basic --use-mock-keychain --ozone-platform=x11 --app=http://127.0.0.1:8080