Compare commits

...

3 Commits

Author SHA1 Message Date
Dustin 38e826b454 marionette: Pass params to NewWindow
The `NewWindow` Marionette command actually takes two arguments.  They
are optional, and without them, Firefox will open a new *tab* instead
of a new *window*.  Since we obviously want windows rather than tabs, so
as to place them on separate monitors, we need to explicitly specify
this when we execute the command.
2023-01-05 22:23:22 -06:00
Dustin ebb5318390 Move windows to their monitors
When we create Firefox windows for each monitor, we need to move them to
their respective screens.  The Marionette protocol provides a
`SetWindowRect` command that *should* move the active window (it doesn't
work for all window managers, notably *i3*).
2023-01-05 22:22:51 -06:00
Dustin 311e73e097 Fullscreen windows at startup
We don't want the browser chrome showing on heads-up displays.
2023-01-05 14:10:20 -06:00
5 changed files with 118 additions and 8 deletions

View File

@ -90,6 +90,30 @@ pub struct CloseWindowParams {
pub handle: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
#[allow(dead_code)]
pub enum WindowType {
Window,
Tab,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
#[allow(dead_code)]
pub struct NewWindowParams {
#[serde(rename = "type")]
pub window_type: WindowType,
pub focus: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(dead_code)]
pub struct NewWindowResponse {
pub handle: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
#[allow(dead_code)]
@ -98,6 +122,16 @@ pub struct SwitchToWindowParams {
pub focus: bool,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[allow(dead_code)]
pub struct WindowRect {
pub x: Option<i32>,
pub y: Option<i32>,
pub height: Option<u32>,
pub width: Option<u32>,
}
#[derive(Debug, Serialize)]
#[serde(tag = "command", content = "params")]
pub enum Command {
@ -114,7 +148,11 @@ pub enum Command {
#[serde(rename = "WebDriver:CloseWindow")]
CloseWindow(CloseWindowParams),
#[serde(rename = "WebDriver:NewWindow")]
NewWindow,
NewWindow(NewWindowParams),
#[serde(rename = "WebDriver:SwitchToWindow")]
SwitchToWindow(SwitchToWindowParams),
#[serde(rename = "WebDriver:FullscreenWindow")]
FullscreenWindow,
#[serde(rename = "WebDriver:SetWindowRect")]
SetWindowRect(WindowRect),
}

View File

@ -20,7 +20,8 @@ pub use error::{CommandError, ConnectionError, ErrorResponse, MessageError};
use message::{
CloseWindowParams, Command, GetCurrentUrlResponse, GetTitleResponse,
Hello, NavigateParams, NewSessionParams, NewSessionResponse,
SwitchToWindowParams,
NewWindowParams, NewWindowResponse, SwitchToWindowParams, WindowRect,
WindowType,
};
#[derive(Debug, Deserialize, Serialize)]
@ -183,6 +184,16 @@ impl Marionette {
Ok(())
}
pub async fn fullscreen(&mut self) -> Result<(), CommandError> {
let res: serde_json::Value = self
.conn
.send_message(Command::FullscreenWindow)
.await?
.unwrap();
debug!("Received message: {:?}", res);
Ok(())
}
pub async fn get_title(&mut self) -> Result<String, CommandError> {
let res: GetTitleResponse =
self.conn.send_message(Command::GetTitle).await?.unwrap();
@ -240,9 +251,40 @@ impl Marionette {
Ok(res)
}
pub async fn new_window(&mut self) -> Result<String, CommandError> {
let res: String =
self.conn.send_message(Command::NewWindow).await?.unwrap();
pub async fn new_window(
&mut self,
window_type: WindowType,
focus: bool,
) -> Result<String, CommandError> {
let res: NewWindowResponse = self
.conn
.send_message(Command::NewWindow(NewWindowParams {
window_type,
focus,
}))
.await?
.unwrap();
debug!("Received message: {:?}", res);
Ok(res.handle)
}
pub async fn set_window_rect(
&mut self,
x: Option<i32>,
y: Option<i32>,
height: Option<u32>,
width: Option<u32>,
) -> Result<WindowRect, CommandError> {
let res: WindowRect = self
.conn
.send_message(Command::SetWindowRect(WindowRect {
x,
y,
height,
width,
}))
.await?
.unwrap();
debug!("Received message: {:?}", res);
Ok(res)
}

View File

@ -3,5 +3,6 @@ pub struct Monitor {
pub name: String,
pub width: u32,
pub height: u32,
pub x: i32,
pub y: i32,
}

View File

@ -6,6 +6,7 @@ use tracing::{debug, error, info, trace, warn};
use crate::browser::{Browser, BrowserError};
use crate::config::Configuration;
use crate::marionette::error::{CommandError, ConnectionError};
use crate::marionette::message::WindowType;
use crate::marionette::{Marionette, MarionetteConnection};
use crate::monitor::Monitor;
use crate::mqtt::{Message, MqttClient, MqttPublisher};
@ -140,10 +141,28 @@ impl Session {
let mut windowmap = HashMap::new();
let mut window = Some(handles.remove(handles.len() - 1));
for monitor in monitors {
debug!(
"Creating window for monitor {}: {}x{} ({}, {})",
monitor.name,
monitor.width,
monitor.height,
monitor.x,
monitor.y
);
if window.is_none() {
window = Some(self.marionette.new_window().await?);
window = Some(
self.marionette
.new_window(WindowType::Window, false)
.await?,
);
}
windowmap.insert(monitor.name, window.take().unwrap());
let w = window.take().unwrap();
self.marionette.switch_to_window(w.clone(), false).await?;
self.marionette
.set_window_rect(Some(monitor.x), Some(monitor.y), None, None)
.await?;
self.marionette.fullscreen().await?;
windowmap.insert(monitor.name, w);
}
trace!("Built window map: {:?}", windowmap);
Ok(windowmap)
@ -215,6 +234,8 @@ fn get_monitors() -> Vec<Monitor> {
name: m.name().unwrap().to_string(),
width: m.width(),
height: m.height(),
x: m.x(),
y: m.y(),
})
.collect()
}

View File

@ -29,6 +29,14 @@ impl XMonitor {
pub fn name(&self) -> Option<&str> {
Some(&self.name)
}
pub fn x(&self) -> i32 {
self.monitor.x as i32
}
pub fn y(&self) -> i32 {
self.monitor.y as i32
}
}
/// Return information about the monitors attached to an X display