Compare commits

..

No commits in common. "master" and "dev/ci" have entirely different histories.

4 changed files with 39 additions and 132 deletions

View File

@ -41,7 +41,7 @@ pub struct Capabilities {
pub webdriver_click: bool,
pub page_load_strategy: String,
pub platform_name: String,
pub platform_version: Option<String>,
pub platform_version: String,
// proxy:
pub set_window_rect: bool,
pub timeouts: Timeouts,
@ -155,6 +155,4 @@ pub enum Command {
FullscreenWindow,
#[serde(rename = "WebDriver:SetWindowRect")]
SetWindowRect(WindowRect),
#[serde(rename = "WebDriver:Refresh")]
Refresh,
}

View File

@ -271,13 +271,6 @@ impl Marionette {
Ok(res.handle)
}
pub async fn refresh(&mut self) -> Result<(), CommandError> {
let res: serde_json::Value =
self.conn.send_message(Command::Refresh).await?.unwrap();
debug!("Reeived message: {:?}", res);
Ok(())
}
pub async fn set_window_rect(
&mut self,
x: Option<i32>,

View File

@ -15,26 +15,16 @@ use tracing::{debug, error, info, trace, warn};
use crate::config::Configuration;
use crate::hass::{self, HassConfig};
/// Callback methods invoked to hanle incoming MQTT messages
#[async_trait]
pub trait MessageHandler {
/// Navigate to the specified URL
async fn navigate(&mut self, publisher: &MqttPublisher, msg: &Message);
/// Turn on or off the display(s)
async fn power(&mut self, publisher: &MqttPublisher, msg: &Message);
/// Refresh the current page
async fn refresh(&mut self, publisher: &MqttPublisher, msg: &Message);
}
/// MQTT message types
#[derive(Debug)]
pub enum MessageType {
/// Navigate to the specified URL
Navigate,
/// Turn on or off the display(s)
PowerState,
/// Refresh the current page
Refresh,
}
pub struct MqttClient<'a> {
@ -82,13 +72,10 @@ impl<'a> MqttClient<'a> {
let client = self.client.lock().await;
let prefix = &self.config.mqtt.topic_prefix;
let t_nav = format!("{}/+/navigate", prefix);
let t_ref = format!("{}/+/refresh", prefix);
let t_power = format!("{}/power", prefix);
client.subscribe(&t_nav, 0).await?;
client.subscribe(&t_ref, 0).await?;
client.subscribe(&t_power, 0).await?;
self.topics.insert(t_nav, MessageType::Navigate);
self.topics.insert(t_ref, MessageType::Refresh);
self.topics.insert(t_power, MessageType::PowerState);
Ok(())
}
@ -138,9 +125,6 @@ impl<'a> MqttClient<'a> {
MessageType::PowerState => {
handler.power(&publisher, &msg).await;
}
MessageType::Refresh => {
handler.refresh(&publisher, &msg).await;
}
}
}
}
@ -285,27 +269,6 @@ impl<'a> MqttPublisher<'a> {
trace!("Publishing message: {:?}", msg);
self.client.lock().await.publish(msg).await?;
let command_topic = Some(format!("{}/{}/refresh", prefix, screen));
let name = format!("Refresh {}", screen);
let unique_id = format!("button.{}_refresh", key);
let object_id = unique_id.clone();
let config = HassConfig {
command_topic,
name,
unique_id,
object_id,
state_topic: "".into(),
icon: "mdi:refresh".into(),
..config
};
let msg = Message::new_retained(
format!("homeassistant/button/{}_refresh/config", key),
serde_json::to_string(&config).unwrap(),
0,
);
trace!("Publishing message: {:?}", msg);
self.client.lock().await.publish(msg).await?;
let unique_id = format!("light.{}", key);
let object_id = unique_id.clone();
let command_topic = Some(format!("{}/power", prefix));
@ -317,7 +280,6 @@ impl<'a> MqttPublisher<'a> {
name,
unique_id,
object_id,
icon: "mdi:monitor".into(),
..config
};
let msg = Message::new_retained(

View File

@ -181,36 +181,38 @@ pub struct MessageHandler<'a> {
windows: HashMap<String, String>,
}
impl<'a> MessageHandler<'a> {
/// Switch to the window shown on the specified screen
///
/// This method switches to the active window of the Marionette
/// session to the one displayed on the specified screen. It must
/// be called before performing any screen-specific operation,
/// such as navigating to a URL or refreshing the page.
async fn switch_window(&mut self, screen: &str) -> Result<(), String> {
let Some(window) = self.windows.get(screen) else {
return Err(format!("Unknown screen {}", screen));
#[async_trait::async_trait]
impl<'a> crate::mqtt::MessageHandler for MessageHandler<'a> {
async fn navigate(&mut self, publisher: &MqttPublisher, msg: &Message) {
let url = msg.payload_str();
let parts: Vec<&str> = msg.topic().split('/').rev().collect();
let screen = match parts.get(1) {
Some(&"") | None => {
warn!("Invalid navigate request: no screen");
return;
}
Some(s) => s,
};
debug!("Switching to window {}", window);
if let Err(e) =
self.marionette.switch_to_window(window.into(), false).await
{
return Err(e.to_string());
if let Some(window) = self.windows.get(*screen) {
debug!("Switching to window {}", window);
if let Err(e) =
self.marionette.switch_to_window(window.into(), false).await
{
error!(
"Failed to switch to window on screen {}: {}",
screen, e
);
return;
}
} else {
error!("Invalid navigate request: unknown screen {}", screen);
return;
}
debug!("Handling navigate request: {}", url);
info!("Navigate screen {} to {}", screen, url);
if let Err(e) = self.marionette.navigate(url.to_string()).await {
error!("Failed to navigate: {}", e);
}
Ok(())
}
/// Publish the current page info via MQTT
///
/// This method retrieves the current title and URL of the page
/// displayed in the window visible on the specified screen, then
/// publishes the values to their respective MQTT topics.
async fn publish_info(
&mut self,
screen: &str,
publisher: &MqttPublisher<'_>,
) {
match self.marionette.get_current_url().await {
Ok(u) => {
if let Err(e) = publisher.publish_url(screen, &u).await {
@ -228,27 +230,6 @@ impl<'a> MessageHandler<'a> {
Err(e) => error!("Error getting title: {}", e),
}
}
}
#[async_trait::async_trait]
impl<'a> crate::mqtt::MessageHandler for MessageHandler<'a> {
async fn navigate(&mut self, publisher: &MqttPublisher, msg: &Message) {
let url = msg.payload_str();
let Some(screen) = screen_from_topic(msg.topic()) else {
warn!("Invalid navigate request: no screen");
return;
};
if let Err(e) = self.switch_window(screen).await {
error!("Failed to switch to window on screen {}: {}", screen, e);
return;
}
debug!("Handling navigate request: {}", url);
info!("Navigate screen {} to {}", screen, url);
if let Err(e) = self.marionette.navigate(url.to_string()).await {
error!("Failed to navigate: {}", e);
}
self.publish_info(screen, publisher).await;
}
async fn power(&mut self, publisher: &MqttPublisher, msg: &Message) {
match msg.payload_str().as_ref() {
@ -262,30 +243,6 @@ impl<'a> crate::mqtt::MessageHandler for MessageHandler<'a> {
error!("Failed to publish power state: {}", e);
}
}
/// Refresh the page currently displayd on the specified screen
///
/// This method is called by the MQTT receiver when a
/// [`crate::mqtt::MessageType::Refresh`] message is received from
/// the broker. It calls `WebDriver:Refresh` for the window
/// displayed on the screen specified in the message topic, then
/// publishes the page URL and title, after the page load completes.
async fn refresh(&mut self, publisher: &MqttPublisher, msg: &Message) {
let Some(screen) = screen_from_topic(msg.topic()) else {
warn!("Invalid refresh request: no screen");
return;
};
if let Err(e) = self.switch_window(screen).await {
error!("Failed to switch to window on screen {}: {}", screen, e);
return;
}
info!("Refreshing screen: {}", screen);
if let Err(e) = self.marionette.refresh().await {
error!("Failed to refresh page: {}", e);
return;
}
self.publish_info(screen, publisher).await;
}
}
#[cfg(unix)]
@ -315,7 +272,10 @@ fn turn_screen_on() {
error!("Failed to turn on display \"{}\"", Display::name());
}
if !dpms::disable(&display) {
error!("Failed to disable DPMS on display \"{}\"", Display::name());
error!(
"Failed to disable DPMS on display \"{}\"",
Display::name()
);
}
}
@ -329,7 +289,10 @@ fn turn_screen_off() {
}
};
if !dpms::enable(&display) {
error!("Failed to enable DPMS on display \"{}\"", Display::name());
error!(
"Failed to enable DPMS on display \"{}\"",
Display::name()
);
}
if !dpms::force_level(&display, dpms::DpmsPowerLevel::Off) {
error!("Failed to turn off display \"{}\"", Display::name());
@ -353,12 +316,3 @@ fn is_screen_on() -> bool {
}
true
}
/// Extract the screen name from the topic
///
/// The screen name is the penultimate segment of the topic path, e.g.
/// .../HDMI-1/navigate (HDMI-1) or .../eDP-1/refresh (eDP-1).
fn screen_from_topic(topic: &str) -> Option<&str> {
let topic = topic.get(..topic.rfind('/')?)?;
topic.get(topic.rfind('/')? + 1..)
}