diff --git a/shell.nix b/shell.nix index d31055d..2ef9082 100644 --- a/shell.nix +++ b/shell.nix @@ -19,6 +19,7 @@ pkgs.mkShell { libXi openssl alsa-lib + python3 ]; shellHook = '' diff --git a/src/config.rs b/src/config.rs index 34a8fc5..f2e17f8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,6 +20,8 @@ pub enum ConfigError { pub struct Config { pub token: Option, pub home_assistant_url: String, + /// Port for the HTTP listener that accepts `POST /close-hass` from HASS. + pub hass_api_port: u16, pub cashcode_serial_port: String, pub cctalk_serial_port: String, /// Override the value for specific coin positions (channels). @@ -36,6 +38,7 @@ impl Default for Config { Self { token: None, home_assistant_url: "https://ha.hackem.cc/web-dramma/0?BrowserID=dramma".to_string(), + hass_api_port: 8321, cashcode_serial_port: "/dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller_D-if00-port0" .to_string(), diff --git a/src/home_assistant.rs b/src/home_assistant.rs index 143ab35..6d12ad7 100644 --- a/src/home_assistant.rs +++ b/src/home_assistant.rs @@ -1,5 +1,8 @@ use log::{error, info}; +use std::io::{Read, Write}; +use std::net::TcpListener; use std::process::{Child, Command}; +use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; /// Manages a Chromium subprocess for displaying Home Assistant @@ -30,7 +33,7 @@ impl ChromiumManager { // Try chromium first, then chromium-browser as fallback (different Debian versions) let command_result = Command::new("chromium") .arg("--app=".to_string() + url) - .arg("--window-size=1280,940") + .arg("--start-fullscreen") .arg("--window-position=0,0") .arg("--disable-infobars") .arg("--noerrdialogs") @@ -50,7 +53,7 @@ impl ChromiumManager { // Fallback to chromium-browser Command::new("chromium-browser") .arg("--app=".to_string() + url) - .arg("--window-size=1280,940") + .arg("--start-fullscreen") .arg("--window-position=0,0") .arg("--disable-infobars") .arg("--noerrdialogs") @@ -108,3 +111,45 @@ impl Drop for ChromiumManager { self.close(); } } + +/// Starts a simple HTTP listener for remote control from Home Assistant. +/// When a `POST /close-hass` request is received, sends a signal through `tx`. +pub fn start_close_listener(port: u16, tx: Sender<()>) { + let addr = format!("0.0.0.0:{}", port); + let listener = match TcpListener::bind(&addr) { + Ok(l) => l, + Err(e) => { + error!("Failed to bind HASS close listener on {}: {}", addr, e); + return; + } + }; + info!("šŸ  Home Assistant close listener on port {}", port); + + for stream in listener.incoming() { + let Ok(mut stream) = stream else { + continue; + }; + let mut buf = [0u8; 512]; + let Ok(n) = stream.read(&mut buf) else { + continue; + }; + let request = String::from_utf8_lossy(&buf[..n]); + let first_line = request.lines().next().unwrap_or(""); + + if first_line.starts_with("POST /close-hass") { + info!("šŸ  Received remote close-hass request"); + let _ = tx.send(()); + let _ = stream.write_all( + b"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nContent-Length: 2\r\n\r\nOK", + ); + } else if first_line.starts_with("OPTIONS") { + // CORS preflight + let _ = stream.write_all( + b"HTTP/1.1 204 No Content\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: POST, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type\r\n\r\n", + ); + } else { + let _ = + stream.write_all(b"HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\n\r\nNot Found"); + } + } +} diff --git a/src/main.rs b/src/main.rs index 1c50bd3..d0cb415 100644 --- a/src/main.rs +++ b/src/main.rs @@ -623,5 +623,24 @@ mod home_assistant_handler { info!("Hiding Home Assistant page, closing Chromium"); chromium_hide.close(); }); + + // HTTP listener so HASS can POST /close-hass to dismiss its own page + let (tx, rx) = std::sync::mpsc::channel::<()>(); + let port = config.hass_api_port; + thread::spawn(move || { + home_assistant::start_close_listener(port, tx); + }); + + let weak = app.as_weak(); + thread::spawn(move || { + while rx.recv().is_ok() { + let weak = weak.clone(); + let _ = slint::invoke_from_event_loop(move || { + if let Some(window) = weak.upgrade() { + window.invoke_close_hass_remote(); + } + }); + } + }); } } diff --git a/ui/main_window.slint b/ui/main_window.slint index 04d6349..8546397 100644 --- a/ui/main_window.slint +++ b/ui/main_window.slint @@ -47,6 +47,12 @@ export component MainWindow inherits Window { callback fetch-usernames(); // fetches available-usernames for autocomplete callback confetti-started(); // tells rust to start confetti dismiss timer + /// Called from Rust when HASS sends a POST /close-hass request. + public function close-hass-remote() { + root.hide-home-assistant(); + root.current-page = Page.Main; + } + title: "Donation Machine"; width: 1280px; height: 1024px; diff --git a/ui/pages/home_assistant.slint b/ui/pages/home_assistant.slint index ac4b6a1..10e43d5 100644 --- a/ui/pages/home_assistant.slint +++ b/ui/pages/home_assistant.slint @@ -2,82 +2,88 @@ import { Button, Palette } from "std-widgets.slint"; export component HomeAssistant inherits Rectangle { in-out property home-assistant-url: ""; - + callback back-clicked(); - + background: Palette.background; - + VerticalLayout { alignment: start; padding: 16px; spacing: 16px; - + // back button HorizontalLayout { alignment: start; - + Button { text: "← Back"; width: 150px; height: 60px; - + clicked => { root.back-clicked(); } } } - - // hass iframe placeholder - // until slint supports html iframes + + // status panel behind fullscreen Chromium Rectangle { background: #1a1a1a; border-width: 2px; border-color: #333333; border-radius: 8px; vertical-stretch: 1; - + VerticalLayout { alignment: center; padding: 32px; - + spacing: 12px; + + Text { + text: "šŸ "; + font-size: 64px; + horizontal-alignment: center; + } + Text { - text: "Home Assistant"; + text: "Home Assistant Opened!"; font-size: 28px; font-weight: 700; - color: #ffffff; + color: #4caf50; horizontal-alignment: center; } - - Rectangle { - height: 20px; - } - + Text { - text: root.home-assistant-url != "" ? "Connected to: " + root.home-assistant-url : "No URL configured"; + text: "Chromium is running in fullscreen"; font-size: 16px; color: #cccccc; horizontal-alignment: center; - wrap: word-wrap; } - - Rectangle { - height: 20px; + + Rectangle { height: 8px; } + + Text { + text: root.home-assistant-url != "" ? root.home-assistant-url : "No URL configured"; + font-size: 13px; + color: #888888; + horizontal-alignment: center; + wrap: word-wrap; } - + + Rectangle { height: 24px; } + Text { - text: "āš ļø Web view integration needed"; + text: "If you see this screen instead of Home Assistant:"; font-size: 14px; + font-weight: 600; color: #ff9800; horizontal-alignment: center; } - - Rectangle { - height: 10px; - } - + Text { - text: "if hass did not started,\nconfigure the iframe URL in .config/dramma.toml\nor install chromium huh"; - font-size: 12px; + text: "• Make sure chromium is installed\n• Check .config/dramma.toml has the correct home_assistant_url\n• You can close this page with POST /close-hass"; + font-size: 13px; color: #999999; horizontal-alignment: center; wrap: word-wrap;