install.sh 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. #!/bin/bash
  2. # Dune Weaver Touch - One-Command Installer
  3. # This script sets up everything needed to run Dune Weaver Touch on boot
  4. set -e
  5. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  6. ACTUAL_USER="${SUDO_USER:-$USER}"
  7. USER_HOME=$(eval echo ~$ACTUAL_USER)
  8. # Detect Raspberry Pi model
  9. PI_MODEL=$(cat /proc/device-tree/model 2>/dev/null | tr -d '\0' || echo "unknown")
  10. IS_PI5=false
  11. if [[ "$PI_MODEL" == *"Pi 5"* ]]; then
  12. IS_PI5=true
  13. fi
  14. echo "🎯 Dune Weaver Touch - Complete Installation"
  15. echo "============================================="
  16. echo "App directory: $SCRIPT_DIR"
  17. echo "User: $ACTUAL_USER"
  18. echo "Detected: $PI_MODEL"
  19. echo ""
  20. # Check if running as root
  21. if [ "$EUID" -ne 0 ]; then
  22. echo "❌ This installer must be run with sudo privileges"
  23. echo ""
  24. echo "Usage: sudo ./install.sh"
  25. echo ""
  26. exit 1
  27. fi
  28. echo "🔧 Installing system components..."
  29. echo ""
  30. # Function to install system scripts
  31. install_scripts() {
  32. echo "📄 Installing system scripts..."
  33. local scripts=("screen-on" "screen-off" "touch-monitor")
  34. for script in "${scripts[@]}"; do
  35. local source_path="$SCRIPT_DIR/scripts/$script"
  36. local target_path="/usr/local/bin/$script"
  37. if [ -f "$source_path" ]; then
  38. cp "$source_path" "$target_path"
  39. chmod 755 "$target_path"
  40. chown root:root "$target_path"
  41. echo " ✅ $script → $target_path"
  42. else
  43. echo " ⚠️ $script not found at $source_path"
  44. fi
  45. done
  46. echo " 📄 System scripts installed"
  47. }
  48. # Function to setup systemd service
  49. setup_systemd() {
  50. echo "🚀 Setting up systemd service..."
  51. local SERVICE_FILE="/etc/systemd/system/dune-weaver-touch.service"
  52. # Generate service file with EGLFS backend (GPU-accelerated, proper compositing)
  53. # Pi 5 rotation is handled in QML since EGLFS rotation may not work on Pi 5
  54. echo " ℹ️ Using EGLFS backend (GPU-accelerated)"
  55. cat > "$SERVICE_FILE" << EOF
  56. [Unit]
  57. Description=Dune Weaver Touch Interface
  58. After=multi-user.target network-online.target
  59. Wants=network-online.target
  60. # Wait for DRM/KMS devices to be ready
  61. After=systemd-udev-settle.service
  62. Wants=systemd-udev-settle.service
  63. [Service]
  64. Type=simple
  65. User=$ACTUAL_USER
  66. Group=$ACTUAL_USER
  67. WorkingDirectory=$SCRIPT_DIR
  68. Environment=QT_QPA_PLATFORM=eglfs
  69. Environment=QT_QPA_EGLFS_ALWAYS_SET_MODE=1
  70. Environment=QT_QPA_EGLFS_HIDECURSOR=1
  71. Environment=QT_QPA_EGLFS_INTEGRATION=eglfs_kms
  72. Environment=QT_QPA_EGLFS_KMS_ATOMIC=1
  73. ExecStart=$SCRIPT_DIR/venv/bin/python $SCRIPT_DIR/main.py
  74. Restart=always
  75. RestartSec=10
  76. StartLimitInterval=200
  77. StartLimitBurst=5
  78. [Install]
  79. WantedBy=multi-user.target
  80. EOF
  81. # Enable service
  82. systemctl daemon-reload
  83. systemctl enable dune-weaver-touch.service
  84. echo " 🚀 Systemd service installed and enabled"
  85. }
  86. # Function to configure boot settings for DSI display
  87. configure_boot_settings() {
  88. echo "🖥️ Configuring boot settings for DSI display..."
  89. local CONFIG_FILE="/boot/firmware/config.txt"
  90. # Fallback to old path if new path doesn't exist
  91. [ ! -f "$CONFIG_FILE" ] && CONFIG_FILE="/boot/config.txt"
  92. if [ ! -f "$CONFIG_FILE" ]; then
  93. echo " ⚠️ config.txt not found, skipping boot configuration"
  94. return
  95. fi
  96. # Backup config.txt
  97. cp "$CONFIG_FILE" "${CONFIG_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
  98. echo " ✅ Backed up config.txt"
  99. # Remove old/conflicting KMS settings
  100. sed -i '/dtoverlay=vc4-fkms-v3d/d' "$CONFIG_FILE"
  101. sed -i '/dtoverlay=vc4-xfkms-v3d/d' "$CONFIG_FILE"
  102. if [ "$IS_PI5" = true ]; then
  103. # Pi 5: KMS is enabled by default, gpu_mem is ignored (dedicated VRAM)
  104. echo " ℹ️ Pi 5 detected - KMS enabled by default, skipping vc4-kms-v3d overlay"
  105. echo " ℹ️ Pi 5 has dedicated VRAM, skipping gpu_mem setting"
  106. else
  107. # Pi 3/4: Add full KMS if not present
  108. if ! grep -q "dtoverlay=vc4-kms-v3d" "$CONFIG_FILE"; then
  109. # Find [all] section or add at end
  110. if grep -q "^\[all\]" "$CONFIG_FILE"; then
  111. sed -i '/^\[all\]/a dtoverlay=vc4-kms-v3d' "$CONFIG_FILE"
  112. else
  113. echo -e "\n[all]\ndtoverlay=vc4-kms-v3d" >> "$CONFIG_FILE"
  114. fi
  115. echo " ✅ Enabled full KMS (vc4-kms-v3d) for eglfs support"
  116. else
  117. echo " ℹ️ Full KMS already enabled"
  118. fi
  119. # Add GPU memory if not present (only for Pi 3/4 with shared memory)
  120. if ! grep -q "gpu_mem=" "$CONFIG_FILE"; then
  121. echo "gpu_mem=128" >> "$CONFIG_FILE"
  122. echo " ✅ Set GPU memory to 128MB"
  123. else
  124. echo " ℹ️ GPU memory already configured"
  125. fi
  126. fi
  127. # Disable splash screens for cleaner boot (all Pi models)
  128. if ! grep -q "disable_splash=1" "$CONFIG_FILE"; then
  129. echo "disable_splash=1" >> "$CONFIG_FILE"
  130. echo " ✅ Disabled rainbow splash"
  131. else
  132. echo " ℹ️ Rainbow splash already disabled"
  133. fi
  134. echo " 🖥️ Boot configuration updated"
  135. }
  136. # Function to setup touch rotation via udev rule
  137. setup_touch_rotation() {
  138. echo "👆 Setting up touchscreen rotation..."
  139. local UDEV_RULE_FILE="/etc/udev/rules.d/99-ft5x06-rotate.rules"
  140. # Pi 5 with linuxfb: QML handles rotation (including touch transform)
  141. # Pi 3/4: Use udev rule for touch rotation to match kernel framebuffer rotation
  142. if [ "$IS_PI5" = true ]; then
  143. echo " ℹ️ Pi 5 detected - touch rotation handled by QML, skipping udev rule"
  144. # Remove any existing touch rotation rule
  145. if [ -f "$UDEV_RULE_FILE" ]; then
  146. rm -f "$UDEV_RULE_FILE"
  147. udevadm control --reload-rules
  148. udevadm trigger
  149. echo " ✅ Removed existing touch rotation udev rule"
  150. fi
  151. else
  152. # Create udev rule for FT5x06 touch controller (180° rotation)
  153. cat > "$UDEV_RULE_FILE" << 'EOF'
  154. # Rotate FT5x06 touchscreen 180 degrees using libinput calibration matrix
  155. # Matrix format: a b c d e f 0 0 1
  156. # For 180° rotation: -1 0 1 0 -1 1 0 0 1
  157. # This inverts both X and Y axes (equivalent to 180° rotation)
  158. SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="*generic ft5x06*", \
  159. ENV{LIBINPUT_CALIBRATION_MATRIX}="-1 0 1 0 -1 1 0 0 1"
  160. EOF
  161. chmod 644 "$UDEV_RULE_FILE"
  162. echo " ✅ Touch rotation udev rule created: $UDEV_RULE_FILE"
  163. # Reload udev rules
  164. udevadm control --reload-rules
  165. udevadm trigger
  166. echo " ✅ Udev rules reloaded"
  167. echo " 👆 Touch rotation configured (180°)"
  168. fi
  169. }
  170. # Function to hide mouse cursor
  171. hide_mouse_cursor() {
  172. echo "🖱️ Configuring mouse cursor hiding..."
  173. # Install unclutter for hiding mouse cursor when idle
  174. echo " 📦 Installing unclutter..."
  175. apt install -y unclutter > /dev/null 2>&1
  176. # Create autostart directory if it doesn't exist
  177. local AUTOSTART_DIR="$USER_HOME/.config/autostart"
  178. mkdir -p "$AUTOSTART_DIR"
  179. chown -R "$ACTUAL_USER:$ACTUAL_USER" "$USER_HOME/.config"
  180. # Create unclutter autostart entry
  181. cat > "$AUTOSTART_DIR/unclutter.desktop" << 'EOF'
  182. [Desktop Entry]
  183. Type=Application
  184. Name=Unclutter
  185. Comment=Hide mouse cursor when idle
  186. Exec=unclutter -idle 0.1 -root
  187. Hidden=false
  188. NoDisplay=false
  189. X-GNOME-Autostart-enabled=true
  190. EOF
  191. chown "$ACTUAL_USER:$ACTUAL_USER" "$AUTOSTART_DIR/unclutter.desktop"
  192. echo " 🖱️ Mouse cursor hiding configured"
  193. }
  194. # Function to setup kiosk optimizations
  195. setup_kiosk_optimizations() {
  196. echo "🖥️ Setting up kiosk optimizations..."
  197. local CMDLINE_FILE="/boot/firmware/cmdline.txt"
  198. [ ! -f "$CMDLINE_FILE" ] && CMDLINE_FILE="/boot/cmdline.txt"
  199. # Determine DSI connector name based on Pi model
  200. # Pi 5 uses DSI-2 (separate DRM device), Pi 3/4 use DSI-1
  201. local DSI_CONNECTOR="DSI-1"
  202. if [ "$IS_PI5" = true ]; then
  203. DSI_CONNECTOR="DSI-2"
  204. echo " ℹ️ Pi 5 detected - using DSI-2 connector"
  205. fi
  206. # Configure cmdline.txt for display and boot
  207. if [ -f "$CMDLINE_FILE" ]; then
  208. cp "$CMDLINE_FILE" "${CMDLINE_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
  209. # Add video parameter for DSI display with rotation
  210. # Check for any existing DSI video configuration
  211. if ! grep -q "video=DSI-[0-9]:800x480@60,rotate=180" "$CMDLINE_FILE"; then
  212. sed -i "s/$/ video=${DSI_CONNECTOR}:800x480@60,rotate=180/" "$CMDLINE_FILE"
  213. echo " ✅ DSI display configuration added (${DSI_CONNECTOR}:800x480@60, rotated 180°)"
  214. else
  215. echo " ℹ️ DSI display configuration already present"
  216. fi
  217. # Add quiet splash for cleaner boot
  218. if ! grep -q "quiet splash" "$CMDLINE_FILE"; then
  219. sed -i 's/$/ quiet splash/' "$CMDLINE_FILE"
  220. echo " ✅ Boot splash enabled"
  221. else
  222. echo " ℹ️ Boot splash already enabled"
  223. fi
  224. # Disable console cursor (the blinking underscore on fbcon)
  225. if ! grep -q "vt.global_cursor_default=0" "$CMDLINE_FILE"; then
  226. sed -i 's/$/ vt.global_cursor_default=0/' "$CMDLINE_FILE"
  227. echo " ✅ Console cursor disabled"
  228. else
  229. echo " ℹ️ Console cursor already disabled"
  230. fi
  231. fi
  232. echo " 🖥️ Kiosk optimizations applied"
  233. }
  234. # Function to create eglfs configuration for Qt
  235. create_eglfs_config() {
  236. echo "🖥️ Creating EGLFS configuration..."
  237. local CONFIG_FILE="$SCRIPT_DIR/eglfs_config.json"
  238. # Find the correct DRM device with DSI connector
  239. local DRM_DEVICE="/dev/dri/card0"
  240. local DSI_CONNECTOR="DSI-1"
  241. # Check each card for DSI connector
  242. for card in /sys/class/drm/card*/; do
  243. card_name=$(basename "$card")
  244. if [ -d "$card/${card_name}-DSI-1" ]; then
  245. DRM_DEVICE="/dev/dri/$card_name"
  246. DSI_CONNECTOR="DSI-1"
  247. echo " ✅ Found DSI-1 on $card_name"
  248. break
  249. elif [ -d "$card/${card_name}-DSI-2" ]; then
  250. DRM_DEVICE="/dev/dri/$card_name"
  251. DSI_CONNECTOR="DSI-2"
  252. echo " ✅ Found DSI-2 on $card_name"
  253. break
  254. fi
  255. done
  256. cat > "$CONFIG_FILE" << EOF
  257. {
  258. "device": "$DRM_DEVICE",
  259. "hwcursor": false,
  260. "pbuffers": true,
  261. "separateScreens": false,
  262. "outputs": [
  263. {
  264. "name": "$DSI_CONNECTOR",
  265. "mode": "800x480"
  266. }
  267. ]
  268. }
  269. EOF
  270. chown "$ACTUAL_USER:$ACTUAL_USER" "$CONFIG_FILE"
  271. chmod 644 "$CONFIG_FILE"
  272. echo " ✅ EGLFS config created: $CONFIG_FILE"
  273. echo " 📟 Device: $DRM_DEVICE, Connector: $DSI_CONNECTOR"
  274. }
  275. # Function to setup user groups for DRM/input access
  276. setup_user_groups() {
  277. echo "👥 Setting up user groups for hardware access..."
  278. local REQUIRED_GROUPS=("video" "render" "input" "gpio")
  279. for group in "${REQUIRED_GROUPS[@]}"; do
  280. if getent group "$group" > /dev/null 2>&1; then
  281. if id -nG "$ACTUAL_USER" | grep -qw "$group"; then
  282. echo " ℹ️ User already in '$group' group"
  283. else
  284. usermod -aG "$group" "$ACTUAL_USER"
  285. echo " ✅ Added user to '$group' group"
  286. fi
  287. else
  288. echo " ⚠️ Group '$group' does not exist, skipping"
  289. fi
  290. done
  291. echo " 👥 User groups configured"
  292. }
  293. # Function to setup console auto-login
  294. setup_console_autologin() {
  295. echo "🔑 Setting up console auto-login..."
  296. local OVERRIDE_DIR="/etc/systemd/system/getty@tty1.service.d"
  297. local OVERRIDE_FILE="$OVERRIDE_DIR/autologin.conf"
  298. # Create override directory
  299. mkdir -p "$OVERRIDE_DIR"
  300. # Create autologin override for getty@tty1
  301. cat > "$OVERRIDE_FILE" << EOF
  302. [Service]
  303. ExecStart=
  304. ExecStart=-/sbin/agetty --autologin $ACTUAL_USER --noclear %I \$TERM
  305. EOF
  306. chmod 644 "$OVERRIDE_FILE"
  307. echo " ✅ Getty autologin override created: $OVERRIDE_FILE"
  308. # Reload systemd to pick up changes
  309. systemctl daemon-reload
  310. echo " ✅ Systemd daemon reloaded"
  311. echo " 🔑 Console auto-login configured for user: $ACTUAL_USER"
  312. }
  313. # Function to setup Python virtual environment and install dependencies
  314. setup_python_environment() {
  315. echo "🐍 Setting up Python virtual environment..."
  316. # Install system Qt6 and PySide6 packages for full eglfs support
  317. echo " 📦 Installing system Qt6 and PySide6 packages..."
  318. apt update
  319. apt install -y \
  320. python3-pyside6.qtcore \
  321. python3-pyside6.qtgui \
  322. python3-pyside6.qtqml \
  323. python3-pyside6.qtquick \
  324. python3-pyside6.qtquickcontrols2 \
  325. python3-pyside6.qtquickwidgets \
  326. python3-pyside6.qtwebsockets \
  327. python3-pyside6.qtnetwork \
  328. qml6-module-qtquick \
  329. qml6-module-qtquick-controls \
  330. qml6-module-qtquick-layouts \
  331. qml6-module-qtquick-window \
  332. qml6-module-qtquick-dialogs \
  333. qml6-module-qt-labs-qmlmodels \
  334. qt6-virtualkeyboard-plugin \
  335. qml6-module-qtquick-virtualkeyboard \
  336. qt6-base-dev \
  337. qt6-declarative-dev \
  338. libqt6opengl6 \
  339. libqt6core5compat6 \
  340. libqt6network6 \
  341. libqt6websockets6 > /dev/null 2>&1
  342. echo " ✅ System Qt6/PySide6 packages installed"
  343. # Create virtual environment with system site packages
  344. if [ ! -d "$SCRIPT_DIR/venv" ]; then
  345. echo " 📦 Creating virtual environment with system site packages..."
  346. python3 -m venv --system-site-packages "$SCRIPT_DIR/venv" || {
  347. echo " ⚠️ Could not create virtual environment. Installing python3-venv..."
  348. apt install -y python3-venv python3-full
  349. python3 -m venv --system-site-packages "$SCRIPT_DIR/venv"
  350. }
  351. else
  352. echo " ℹ️ Virtual environment already exists"
  353. fi
  354. # Install non-Qt dependencies from requirements.txt
  355. echo " 📦 Installing Python dependencies..."
  356. "$SCRIPT_DIR/venv/bin/python" -m pip install --upgrade pip > /dev/null 2>&1
  357. "$SCRIPT_DIR/venv/bin/pip" install -r "$SCRIPT_DIR/requirements.txt" > /dev/null 2>&1
  358. # Change ownership to the actual user (not root)
  359. chown -R "$ACTUAL_USER:$ACTUAL_USER" "$SCRIPT_DIR/venv"
  360. echo " 🐍 Python virtual environment ready"
  361. }
  362. # Main installation process
  363. echo "Starting complete installation..."
  364. echo ""
  365. # Install everything
  366. setup_user_groups
  367. create_eglfs_config
  368. setup_python_environment
  369. install_scripts
  370. setup_systemd
  371. configure_boot_settings
  372. setup_touch_rotation
  373. hide_mouse_cursor
  374. setup_kiosk_optimizations
  375. setup_console_autologin
  376. echo ""
  377. echo "🎉 Installation Complete!"
  378. echo "========================"
  379. echo ""
  380. echo "📟 Detected: $PI_MODEL"
  381. if [ "$IS_PI5" = true ]; then
  382. echo " └─ Using Pi 5 optimized settings (DSI-2, dedicated VRAM)"
  383. fi
  384. echo ""
  385. echo "✅ Python virtual environment created at: $SCRIPT_DIR/venv"
  386. echo "✅ System scripts installed in /usr/local/bin/"
  387. echo "✅ Systemd service configured for auto-start"
  388. echo "✅ Mouse cursor hiding configured (Qt + unclutter)"
  389. echo "✅ Kiosk optimizations applied"
  390. echo "✅ Console auto-login configured"
  391. echo "✅ User groups configured (video, render, input)"
  392. echo "✅ EGLFS display config created"
  393. echo ""
  394. echo "🔧 Service Management:"
  395. echo " Start now: sudo systemctl start dune-weaver-touch"
  396. echo " Stop: sudo systemctl stop dune-weaver-touch"
  397. echo " Status: sudo systemctl status dune-weaver-touch"
  398. echo " Logs: sudo journalctl -u dune-weaver-touch -f"
  399. echo ""
  400. echo "💡 To start the service now without rebooting:"
  401. echo " sudo systemctl start dune-weaver-touch"
  402. echo ""
  403. echo "🛠️ For development/testing (run manually):"
  404. echo " cd $SCRIPT_DIR"
  405. echo " ./run.sh"
  406. echo ""
  407. echo "⚙️ To change boot/login settings later:"
  408. echo " sudo ./configure-boot.sh"
  409. echo ""
  410. # Ask if user wants to start now
  411. read -p "🤔 Would you like to start the service now? (y/N): " -n 1 -r
  412. echo
  413. if [[ $REPLY =~ ^[Yy]$ ]]; then
  414. echo "🚀 Starting Dune Weaver Touch service..."
  415. systemctl start dune-weaver-touch
  416. sleep 2
  417. systemctl status dune-weaver-touch --no-pager -l
  418. echo ""
  419. echo "✅ Service started! Check the status above."
  420. fi
  421. echo ""
  422. echo "🚀 Next Steps:"
  423. echo " 1. ⚠️ REBOOT REQUIRED for config.txt changes to take effect"
  424. echo " 2. After reboot, the app will start automatically on boot via systemd service"
  425. echo " 3. Check the logs if you encounter any issues: sudo journalctl -u dune-weaver-touch -f"
  426. echo ""
  427. echo "🎯 Installation completed successfully!"