install.sh 16 KB

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