1
1
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:
Wez Furlong 2022-07-06 08:32:13 -07:00
parent 082c61c2c3
commit a6cf13e1e2
11 changed files with 238 additions and 61 deletions

12
Cargo.lock generated
View File

@ -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"

View 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"}

View 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(())
}

View File

@ -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"

View File

@ -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();

View File

@ -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");
}
}

View File

@ -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 {

View File

@ -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()?;

View File

@ -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();

View File

@ -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
View 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,
}