-
Notifications
You must be signed in to change notification settings - Fork 0
feat: allow HASS to close its own page via POST /close-hass and make Chromium fullscreen #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ pkgs.mkShell { | |
| libXi | ||
| openssl | ||
| alsa-lib | ||
| python3 | ||
| ]; | ||
|
|
||
| shellHook = '' | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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; | ||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+128
to
+135
|
||||||||||||||||||||||||||||||||||||||||||||
| 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") { | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+138
to
+145
|
||||||||||||||||||||||||||||||||||||||||||||
| 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") { | |
| let mut parts = first_line.split_whitespace(); | |
| let request_line = match (parts.next(), parts.next(), parts.next(), parts.next()) { | |
| (Some(method), Some(path), Some(version), None) => Some((method, path, version)), | |
| _ => None, | |
| }; | |
| if let Some(("POST", "/close-hass", _version)) = request_line { | |
| 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 let Some(("OPTIONS", "/close-hass", _version)) = request_line { |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,82 +2,88 @@ import { Button, Palette } from "std-widgets.slint"; | |||||
|
|
||||||
| export component HomeAssistant inherits Rectangle { | ||||||
| in-out property <string> 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"; | ||||||
|
||||||
| 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"; | |
| 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 http://localhost:<hass_api_port>/close-hass (default port 8321; configurable via hass_api_port)"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The close listener binds to
0.0.0.0, exposingPOST /close-hasson all interfaces with no authentication. This allows any host that can reach the device to remotely navigate the kiosk UI. If Home Assistant is the only intended caller, bind to127.0.0.1/localhostby default and/or require a shared secret (header/token) before accepting the request.