Compare commits

...

2 Commits

Author SHA1 Message Date
nearxos
8b4930d4b9 Refactor screen rotation setup and GTK theme configuration in first-boot process
Update the one-shot script to set screen rotation using kanshi based on kernel command line parameters, replacing the previous wlr-randr method. The script now writes the configuration to ~/.config/kanshi/config and sets the GTK dark theme (PiXnoir or Adwaita-dark) at first login. Additionally, enhance documentation to reflect these changes and clarify the role of the new script in the first-boot process.
2026-02-23 09:59:32 +02:00
nearxos
fd4e54f125 Enhance first-boot configuration with step enable flags and improved documentation
Add step enable flags to the first-boot configuration, allowing users to control the execution of each step during the initial setup. Update the first-boot script to load these flags from the configuration file, ensuring flexibility in customization. Revise the example configuration file to clarify the default behavior and provide guidance on disabling specific steps. This improves user experience by offering more granular control over the first-boot process.
2026-02-23 09:59:18 +02:00
14 changed files with 536 additions and 129 deletions

5
TODO.MD Normal file
View File

@@ -0,0 +1,5 @@
- change icon on taskbar.
- fix dark theme.
- check for duplicates commands in all scripts and cloud init during deployment.

View File

@@ -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
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"
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)"
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
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"
# 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"

View File

@@ -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/<size>/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 (11). The panel shows it in a square cell. |
| **Background** | Transparent (PNG with alpha) so it fits any panel background. |
| **Content** | Simple, recognizable at 1632 px (logo or symbol). Avoid fine detail. |
| **Theme path** | `PiXtrix/<size>x<size>/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 2432 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.

View File

@@ -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.

View File

@@ -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. |

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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**.

View File

@@ -58,3 +58,18 @@ RETERMINAL_REPO_URL="https://github.com/Seeed-Studio/seeed-linux-dtoverlays"
# --- One-shots ---
# Space-separated names of one-shot scripts (numbered = run order at first login). Leave empty for none.
ONESHOT_SCRIPTS="01-set-rotation-once 02-set-wallpaper-once"
# --- Step enable flags (1 = run, 0 = skip). All enabled below; set to 0 to disable a step. ---
ENABLE_STEP_01=1
ENABLE_STEP_02=1
ENABLE_STEP_03=1
ENABLE_STEP_04=1
ENABLE_STEP_05=1
ENABLE_STEP_06=1
ENABLE_STEP_07=1
ENABLE_STEP_08=1
ENABLE_STEP_09=1
ENABLE_STEP_10=1
ENABLE_STEP_11=1
ENABLE_STEP_12=1
ENABLE_STEP_13=1

View File

@@ -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.
@@ -61,8 +62,9 @@
# Example: "01-set-rotation-once 02-set-wallpaper-once"
# ONESHOT_SCRIPTS="01-set-rotation-once 02-set-wallpaper-once"
# --- Step enable flags (1 = run, 0 = skip). Default: all 1. Set in config to disable a step. ---
# --- Step enable flags (1 = run, 0 = skip). All steps enabled by default. Set ENABLE_STEP_NN=0 to disable. ---
# 01=hostname, 02=packages, 03=kiosk_files, 04=splash_wallpaper, 05=lightdm, 06=maliit,
# 07=dark_theme, 08=reterminal_drivers, 09=reapply_splash, 10=cmdline, 11=oneshots, 12=log_permissions, 13=reboot
# Example: uncomment to disable a step
# ENABLE_STEP_08=0
# ENABLE_STEP_13=0

View File

@@ -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).
---

View File

@@ -25,7 +25,19 @@ SWIOTLB_SIZE="${SWIOTLB_SIZE:-65536}"
RETERMINAL_DEVICE="${RETERMINAL_DEVICE:-reTerminal-DM}"
RETERMINAL_REPO_URL="${RETERMINAL_REPO_URL:-https://github.com/Seeed-Studio/seeed-linux-dtoverlays}"
ONESHOT_SCRIPTS="${ONESHOT_SCRIPTS:-}"
# Step enable flags (1 = run, 0 = skip). Default all enabled.
# --- Load config file first (first found); then defaults apply only to unset vars ---
FIRST_BOOT_CONF=""
for _f in "$SCRIPT_DIR/first-boot.conf" /tmp/first-boot.conf /etc/cm4-provisioning/first-boot.conf; do
if [[ -f "$_f" ]]; then
# shellcheck source=first-boot.conf.example
set -a && source "$_f" && set +a
FIRST_BOOT_CONF="$_f"
break
fi
done
# Step enable flags (1 = run, 0 = skip). Default all enabled; only set if not already set by config.
ENABLE_STEP_01="${ENABLE_STEP_01:-1}"
ENABLE_STEP_02="${ENABLE_STEP_02:-1}"
ENABLE_STEP_03="${ENABLE_STEP_03:-1}"
@@ -40,17 +52,6 @@ ENABLE_STEP_11="${ENABLE_STEP_11:-1}"
ENABLE_STEP_12="${ENABLE_STEP_12:-1}"
ENABLE_STEP_13="${ENABLE_STEP_13:-1}"
# --- Load config file (first found) ---
FIRST_BOOT_CONF=""
for _f in "$SCRIPT_DIR/first-boot.conf" /tmp/first-boot.conf /etc/cm4-provisioning/first-boot.conf; do
if [[ -f "$_f" ]]; then
# shellcheck source=first-boot.conf.example
set -a && source "$_f" && set +a
FIRST_BOOT_CONF="$_f"
break
fi
done
# --- Derived paths ---
PI_HOME="/home/$PI_USER"
AUTOSTART="$PI_HOME/.config/autostart"
@@ -78,10 +79,14 @@ report_status() {
}
report_status "started" "First-boot started" "" "" ""
# --- Helper: run step if enabled ---
# --- Helper: run step if enabled (accepts "1" or "0"; strips CR/LF/whitespace) ---
run_step() {
local n="$1" name="$2" enable_var="ENABLE_STEP_${n}"
if [[ "${!enable_var}" == "1" ]]; then
local n="$1" name="$2"
local enable_var="ENABLE_STEP_${n}" val
val="${!enable_var}"
val="${val//[^01]/}"
val="${val:0:1}"
if [[ "$val" == "1" ]]; then
log "--- Step $n: $name ---"
"step_${n}_${name}" || return $?
report_status "running" "Step $n: $name completed" "$n" "$name" ""
@@ -129,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"
@@ -142,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
@@ -178,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"
@@ -193,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
@@ -215,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"
@@ -272,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
@@ -293,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
}
@@ -300,14 +361,25 @@ 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
run_step 12 log_permissions
run_step 13 reboot
# If reboot was disabled, still report done and device IP so the portal shows completion
_step13_val="${ENABLE_STEP_13:-1}"
_step13_val="${_step13_val//[^01]/}"
_step13_val="${_step13_val:0:1}"
if [[ "$_step13_val" != "1" ]]; then
DEVICE_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
report_status "done" "First-boot complete (reboot disabled)" "13" "reboot" "$DEVICE_IP"
log "Device IP: ${DEVICE_IP:-unknown}"
log "=== first-boot.sh finished (reboot disabled) ==="
fi

View File

@@ -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

View File

@@ -1,16 +1,18 @@
#!/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
if [ "$USE_WAYLAND" -eq 1 ]; then
# Native Wayland: fullscreen + touch-friendly (long-press = right-click)
# Wayland + labwc compositor.
export GDK_BACKEND=wayland
# Wait for compositor
for i in {1..60}; do
@@ -19,38 +21,22 @@ if [ "$USE_WAYLAND" -eq 1 ]; then
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
# 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}"
# 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