mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 05:12:40 +03:00
lua: add wezterm.window.screens()
Currently implemented on X11 only, this function returns information about the geometry of the screen(s). This is taken from the same source of information we use for the `--position` CLI argument to `wezterm start`. ``` > wezterm.window.screens() { "by_name": { "DisplayPort-1": { "height": 2160, "name": "DisplayPort-1", "width": 3840, "x": 0, "y": 0, }, }, "main": { "height": 2160, "name": "DisplayPort-1", "width": 3840, "x": 0, "y": 0, }, "origin_x": 0, "origin_y": 0, "virtual_height": 2160, "virtual_width": 3840, } ```
This commit is contained in:
parent
082c61c2c3
commit
a6cf13e1e2
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -4811,6 +4811,7 @@ dependencies = [
|
||||
"wezterm-toast-notification",
|
||||
"winapi",
|
||||
"window",
|
||||
"window-funcs",
|
||||
"windows",
|
||||
]
|
||||
|
||||
@ -5060,6 +5061,17 @@ dependencies = [
|
||||
"xkbcommon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "window-funcs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"config",
|
||||
"luahelper",
|
||||
"wezterm-dynamic",
|
||||
"window",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.33.0"
|
||||
|
13
lua-api-crates/window-funcs/Cargo.toml
Normal file
13
lua-api-crates/window-funcs/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "window-funcs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
config = { path = "../../config" }
|
||||
luahelper = { path = "../../luahelper" }
|
||||
wezterm-dynamic = { path = "../../wezterm-dynamic" }
|
||||
window = {path="../../window"}
|
87
lua-api-crates/window-funcs/src/lib.rs
Normal file
87
lua-api-crates/window-funcs/src/lib.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use config::lua::get_or_create_module;
|
||||
use config::lua::mlua::{self, Lua};
|
||||
use luahelper::impl_lua_conversion_dynamic;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use wezterm_dynamic::{FromDynamic, ToDynamic};
|
||||
use window::{Connection, ConnectionOps};
|
||||
|
||||
fn get_conn() -> mlua::Result<Rc<Connection>> {
|
||||
Connection::get().ok_or_else(|| {
|
||||
mlua::Error::external("cannot get window Connection: not running on the gui thread?")
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromDynamic, ToDynamic)]
|
||||
pub struct ScreenInfo {
|
||||
pub name: String,
|
||||
pub x: isize,
|
||||
pub y: isize,
|
||||
pub width: isize,
|
||||
pub height: isize,
|
||||
}
|
||||
impl_lua_conversion_dynamic!(ScreenInfo);
|
||||
|
||||
#[derive(Debug, Clone, FromDynamic, ToDynamic)]
|
||||
pub struct Screens {
|
||||
pub main: ScreenInfo,
|
||||
pub by_name: HashMap<String, ScreenInfo>,
|
||||
pub origin_x: isize,
|
||||
pub origin_y: isize,
|
||||
pub virtual_width: isize,
|
||||
pub virtual_height: isize,
|
||||
}
|
||||
impl_lua_conversion_dynamic!(Screens);
|
||||
|
||||
impl From<window::screen::ScreenInfo> for ScreenInfo {
|
||||
fn from(info: window::screen::ScreenInfo) -> Self {
|
||||
Self {
|
||||
name: info.name,
|
||||
x: info.rect.min_x(),
|
||||
y: info.rect.min_y(),
|
||||
width: info.rect.width(),
|
||||
height: info.rect.height(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<window::screen::Screens> for Screens {
|
||||
fn from(screens: window::screen::Screens) -> Self {
|
||||
let origin_x = screens.virtual_rect.min_x();
|
||||
let origin_y = screens.virtual_rect.min_y();
|
||||
let virtual_width = screens.virtual_rect.width();
|
||||
let virtual_height = screens.virtual_rect.height();
|
||||
Self {
|
||||
main: screens.main.into(),
|
||||
by_name: screens
|
||||
.by_name
|
||||
.into_iter()
|
||||
.map(|(k, info)| (k, info.into()))
|
||||
.collect(),
|
||||
origin_x,
|
||||
origin_y,
|
||||
virtual_width,
|
||||
virtual_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(lua: &Lua) -> anyhow::Result<()> {
|
||||
let wezterm_mod = get_or_create_module(lua, "wezterm")?;
|
||||
let window_mod = lua.create_table()?;
|
||||
|
||||
window_mod.set(
|
||||
"screens",
|
||||
lua.create_function(|_, _: ()| {
|
||||
let conn = get_conn()?;
|
||||
let screens: Screens = conn
|
||||
.screens()
|
||||
.map_err(|err| mlua::Error::external(format!("{err:#}")))?
|
||||
.into();
|
||||
Ok(screens)
|
||||
})?,
|
||||
)?;
|
||||
|
||||
wezterm_mod.set("window", window_mod)?;
|
||||
Ok(())
|
||||
}
|
@ -82,6 +82,7 @@ wezterm-ssh = { path = "../wezterm-ssh" }
|
||||
wezterm-term = { path = "../term", features=["use_serde"] }
|
||||
wezterm-toast-notification = { path = "../wezterm-toast-notification" }
|
||||
window = { path = "../window" }
|
||||
window-funcs = { path = "../lua-api-crates/window-funcs" }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
shared_library = "0.1"
|
||||
|
@ -982,6 +982,10 @@ fn run() -> anyhow::Result<()> {
|
||||
};
|
||||
|
||||
env_bootstrap::bootstrap();
|
||||
// window_funcs is not set up by env_bootstrap as window_funcs is
|
||||
// GUI environment specific and env_bootstrap is used to setup the
|
||||
// headless mux server.
|
||||
config::lua::add_context_setup_func(window_funcs::register);
|
||||
|
||||
stats::Stats::init()?;
|
||||
let _saver = umask::UmaskSaver::new();
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::screen::Screens;
|
||||
use crate::{Appearance, Connection};
|
||||
use anyhow::Result as Fallible;
|
||||
use std::cell::RefCell;
|
||||
@ -48,4 +49,9 @@ pub trait ConnectionOps {
|
||||
|
||||
/// Perform the system beep/notification sound
|
||||
fn beep(&self) {}
|
||||
|
||||
/// Returns information about the screens
|
||||
fn screens(&self) -> anyhow::Result<Screens> {
|
||||
anyhow::bail!("Unable to query screen information");
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ pub use wezterm_color_types as color;
|
||||
mod configuration;
|
||||
pub mod connection;
|
||||
pub mod os;
|
||||
pub mod screen;
|
||||
mod spawn;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@ -57,6 +58,7 @@ pub type Rect = euclid::Rect<isize, PixelUnit>;
|
||||
pub type RectF = euclid::Rect<f32, PixelUnit>;
|
||||
pub type Size = euclid::Size2D<isize, PixelUnit>;
|
||||
pub type SizeF = euclid::Size2D<f32, PixelUnit>;
|
||||
pub type ScreenRect = euclid::Rect<isize, ScreenPixelUnit>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum MouseCursor {
|
||||
|
@ -3,8 +3,9 @@ use crate::connection::ConnectionOps;
|
||||
use crate::os::x11::window::XWindowInner;
|
||||
use crate::os::x11::xsettings::*;
|
||||
use crate::os::Connection;
|
||||
use crate::screen::{ScreenInfo, Screens};
|
||||
use crate::spawn::*;
|
||||
use crate::{Appearance, DeadKeyStatus};
|
||||
use crate::{Appearance, DeadKeyStatus, ScreenRect};
|
||||
use anyhow::{anyhow, bail, Context as _};
|
||||
use mio::event::Source;
|
||||
use mio::unix::SourceFd;
|
||||
@ -144,6 +145,69 @@ impl ConnectionOps for XConnection {
|
||||
}
|
||||
}
|
||||
|
||||
fn screens(&self) -> anyhow::Result<Screens> {
|
||||
if !self.has_randr {
|
||||
anyhow::bail!("XRANDR is not available, cannot query screen geometry");
|
||||
}
|
||||
|
||||
let res = self
|
||||
.send_and_wait_request(&xcb::randr::GetScreenResources { window: self.root })
|
||||
.context("get_screen_resources")?;
|
||||
|
||||
let mut virtual_rect: ScreenRect = euclid::rect(0, 0, 0, 0);
|
||||
let mut by_name = HashMap::new();
|
||||
|
||||
for &o in res.outputs() {
|
||||
let info = self
|
||||
.send_and_wait_request(&xcb::randr::GetOutputInfo {
|
||||
output: o,
|
||||
config_timestamp: res.config_timestamp(),
|
||||
})
|
||||
.context("get_output_info")?;
|
||||
let name = String::from_utf8_lossy(info.name()).to_string();
|
||||
let c = info.crtc();
|
||||
if let Ok(cinfo) = self.send_and_wait_request(&xcb::randr::GetCrtcInfo {
|
||||
crtc: c,
|
||||
config_timestamp: res.config_timestamp(),
|
||||
}) {
|
||||
let bounds = euclid::rect(
|
||||
cinfo.x() as isize,
|
||||
cinfo.y() as isize,
|
||||
cinfo.width() as isize,
|
||||
cinfo.height() as isize,
|
||||
);
|
||||
virtual_rect = virtual_rect.union(&bounds);
|
||||
let info = ScreenInfo {
|
||||
name: name.clone(),
|
||||
rect: bounds,
|
||||
};
|
||||
by_name.insert(name, info);
|
||||
}
|
||||
}
|
||||
|
||||
// The main screen is the one either at the origin of
|
||||
// the virtual area, or if that doesn't exist for some weird
|
||||
// reason, the screen closest to the origin.
|
||||
let main = by_name
|
||||
.values()
|
||||
.min_by_key(|screen| {
|
||||
screen
|
||||
.rect
|
||||
.origin
|
||||
.to_f32()
|
||||
.distance_to(euclid::Point2D::origin())
|
||||
.abs() as isize
|
||||
})
|
||||
.ok_or_else(|| anyhow::anyhow!("no screens were found"))?
|
||||
.clone();
|
||||
|
||||
Ok(Screens {
|
||||
main,
|
||||
by_name,
|
||||
virtual_rect,
|
||||
})
|
||||
}
|
||||
|
||||
fn run_message_loop(&self) -> anyhow::Result<()> {
|
||||
self.conn.flush()?;
|
||||
|
||||
|
@ -14,7 +14,6 @@ use promise::{Future, Promise};
|
||||
use raw_window_handle::unix::XcbHandle;
|
||||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::{Arc, Mutex};
|
||||
@ -1534,69 +1533,34 @@ fn resolve_geometry(
|
||||
conn: &XConnection,
|
||||
geometry: RequestedWindowGeometry,
|
||||
) -> anyhow::Result<ResolvedGeometry> {
|
||||
let bounds = if conn.has_randr {
|
||||
let res = conn
|
||||
.send_and_wait_request(&xcb::randr::GetScreenResources { window: conn.root })
|
||||
.context("get_screen_resources")?;
|
||||
let bounds = match conn.screens() {
|
||||
Ok(screens) => {
|
||||
log::trace!("{screens:?}");
|
||||
|
||||
let mut virtual_screen: Rect = euclid::rect(0, 0, 0, 0);
|
||||
let mut main_screen: Rect = euclid::rect(0, 0, 0, 0);
|
||||
let mut by_name = HashMap::new();
|
||||
|
||||
for &o in res.outputs() {
|
||||
let info = conn
|
||||
.send_and_wait_request(&xcb::randr::GetOutputInfo {
|
||||
output: o,
|
||||
config_timestamp: res.config_timestamp(),
|
||||
})
|
||||
.context("get_output_info")?;
|
||||
let name = String::from_utf8_lossy(info.name()).to_string();
|
||||
let c = info.crtc();
|
||||
if let Ok(cinfo) = conn.send_and_wait_request(&xcb::randr::GetCrtcInfo {
|
||||
crtc: c,
|
||||
config_timestamp: res.config_timestamp(),
|
||||
}) {
|
||||
let bounds = euclid::rect(
|
||||
cinfo.x() as isize,
|
||||
cinfo.y() as isize,
|
||||
cinfo.width() as isize,
|
||||
cinfo.height() as isize,
|
||||
);
|
||||
virtual_screen = virtual_screen.union(&bounds);
|
||||
if bounds.origin.x == 0 && bounds.origin.y == 0 {
|
||||
main_screen = bounds;
|
||||
match geometry.origin {
|
||||
GeometryOrigin::ScreenCoordinateSystem => screens.virtual_rect,
|
||||
GeometryOrigin::MainScreen => screens.main.rect,
|
||||
GeometryOrigin::ActiveScreen => {
|
||||
// TODO: find focused window and resolve it!
|
||||
// Maybe something like <https://stackoverflow.com/a/43666928/149111>
|
||||
// but ported to Rust?
|
||||
screens.main.rect
|
||||
}
|
||||
by_name.insert(name, bounds);
|
||||
}
|
||||
}
|
||||
log::trace!("{:?}", by_name);
|
||||
log::trace!("virtual: {:?}", virtual_screen);
|
||||
log::trace!("main: {:?}", main_screen);
|
||||
|
||||
match geometry.origin {
|
||||
GeometryOrigin::ScreenCoordinateSystem => virtual_screen,
|
||||
GeometryOrigin::MainScreen => main_screen,
|
||||
GeometryOrigin::ActiveScreen => {
|
||||
// TODO: find focused window and resolve it!
|
||||
// Maybe something like <https://stackoverflow.com/a/43666928/149111>
|
||||
// but ported to Rust?
|
||||
main_screen
|
||||
}
|
||||
GeometryOrigin::Named(name) => match by_name.get(&name) {
|
||||
Some(bounds) => bounds.clone(),
|
||||
None => {
|
||||
log::error!(
|
||||
"Requested display {} was not found; available displays are: {:?}. \
|
||||
GeometryOrigin::Named(name) => match screens.by_name.get(&name) {
|
||||
Some(info) => info.rect.clone(),
|
||||
None => {
|
||||
log::error!(
|
||||
"Requested display {} was not found; available displays are: {:?}. \
|
||||
Using primary display instead",
|
||||
name,
|
||||
by_name,
|
||||
);
|
||||
main_screen
|
||||
}
|
||||
},
|
||||
name,
|
||||
screens.by_name,
|
||||
);
|
||||
screens.main.rect
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
euclid::rect(0, 0, 65535, 65535)
|
||||
Err(_) => euclid::rect(0, 0, 65535, 65535),
|
||||
};
|
||||
|
||||
let dpi = conn.default_dpi();
|
||||
|
@ -5,6 +5,7 @@ use crate::connection::ConnectionOps;
|
||||
use crate::os::wayland::connection::WaylandConnection;
|
||||
#[cfg(feature = "wayland")]
|
||||
use crate::os::wayland::window::WaylandWindow;
|
||||
use crate::screen::Screens;
|
||||
use crate::os::x11::connection::XConnection;
|
||||
use crate::os::x11::window::XWindow;
|
||||
use crate::{
|
||||
@ -145,6 +146,14 @@ impl ConnectionOps for Connection {
|
||||
Self::Wayland(w) => w.beep(),
|
||||
}
|
||||
}
|
||||
|
||||
fn screens(&self) -> anyhow::Result<Screens> {
|
||||
match self {
|
||||
Self::X11(x) => x.screens(),
|
||||
#[cfg(feature = "wayland")]
|
||||
Self::Wayland(w) => w.screens(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
15
window/src/screen.rs
Normal file
15
window/src/screen.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use crate::ScreenRect;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Screens {
|
||||
pub main: ScreenInfo,
|
||||
pub by_name: HashMap<String, ScreenInfo>,
|
||||
pub virtual_rect: ScreenRect,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScreenInfo {
|
||||
pub name: String,
|
||||
pub rect: ScreenRect,
|
||||
}
|
Loading…
Reference in New Issue
Block a user