1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 21:32:13 +03:00

add WindowOps::focus, ActivateWindow, window:focus()

Only implemented on X11 so far.
Note that Wayland doesn't support this action at all.

refs: https://github.com/wez/wezterm/issues/2973
This commit is contained in:
Wez Furlong 2023-01-18 20:50:24 -07:00
parent 4f1e2604eb
commit b01aa129f7
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
18 changed files with 352 additions and 0 deletions

View File

@ -534,6 +534,9 @@ pub enum KeyAssignment {
ResetTerminal,
OpenUri(String),
ActivateCommandPalette,
ActivateWindow(usize),
ActivateWindowRelative(isize),
ActivateWindowRelativeNoWrap(isize),
}
impl_lua_conversion_dynamic!(KeyAssignment);

View File

@ -48,6 +48,10 @@ As features stabilize some brief notes about them will accumulate here.
[rose-pine](colorschemes/r/index.md#rose-pine),
[rose-pine-dawn](colorschemes/r/index.md#rose-pine-dawn),
[rose-pine-moon](colorschemes/r/index.md#rose-pine-moon)
* [window:focus()](config/lua/window/focus.md),
[ActivateWindow](config/lua/keyassignment/ActivateWindow.md),
[ActivateWindowRelative](config/lua/keyassignment/ActivateWindowRelative.md),
[ActivateWindowRelativeNoWrap](config/lua/keyassignment/ActivateWindowRelativeNoWrap.md)
#### Fixed
* X11: hanging or killing the IME could hang wezterm

View File

@ -0,0 +1,37 @@
# ActivateWindow(n)
*since: nightly builds only*
Activates the *nth* GUI window, zero-based.
Performing this action is equivalent to executing this lua code fragment:
```lua
wezterm.gui.gui_windows()[n + 1]:focus()
```
Here's an example of setting up hotkeys to activate specific windows:
```lua
local wezterm = require 'wezterm'
local act = wezterm.action
local mykeys = {}
for i = 1, 8 do
-- CMD+ALT + number to activate that window
table.insert(mykeys, {
key = tostring(i),
mods = 'CMD|ALT',
action = act.ActivateWindow(i - 1),
})
end
return {
keys = mykeys,
}
```
See also
[ActivateWindowRelative](ActivateWindowRelative.md),
[ActivateWindowRelativeNoWrap](ActivateWindowRelativeNoWrap.md).

View File

@ -0,0 +1,29 @@
# ActivateWindowRelative(delta)
*since: nightly builds only*
Activates a GUI window relative to the current window.
`ActivateWindowRelative(1)` activates the next window, while
`ActivateWindowRelative(-1)` activates the previous window.
This action will wrap around and activate the appropriate window
at the start/end.
Here's an example of setting up (not very useful) hotkeys to cycle between
windows:
```lua
local wezterm = require 'wezterm'
local act = wezterm.action
return {
keys = {
{ key = 'r', mods = 'ALT', action = act.ActivateWindowRelative(1) },
{ key = 'e', mods = 'ALT', action = act.ActivateWindowRelative(-1) },
},
}
```
See also [ActivateWindowRelativeNoWrap](ActivateWindowRelativeNoWrap.md),
[ActivateWindow](ActivateWindow.md).

View File

@ -0,0 +1,28 @@
# ActivateWindowRelative(delta)
*since: nightly builds only*
Activates a GUI window relative to the current window.
`ActivateWindowRelativeNoWrap(1)` activates the next window, while
`ActivateWindowRelativeNoWrap(-1)` activates the previous window.
This action will NOT wrap around; if the current window is the first/last, then this action will not change the current window.
Here's an example of setting up (not very useful) hotkeys to cycle between
windows:
```lua
local wezterm = require 'wezterm'
local act = wezterm.action
return {
keys = {
{ key = 'r', mods = 'ALT', action = act.ActivateWindowRelativeNoWrap(1) },
{ key = 'e', mods = 'ALT', action = act.ActivateWindowRelativeNoWrap(-1) },
},
}
```
See also [ActivateWindowRelative](ActivateWindowRelative.md),
[ActivateWindow](ActivateWindow.md).

View File

@ -0,0 +1,7 @@
# `wezterm.gui.gui_windows()`
*Since: nightly builds only*
Returns an array table listing all GUI [Window](../window/index.md) objects in
a stable/consistent order.

View File

@ -0,0 +1,13 @@
# window:focus()
*Since: nightly builds only*
Attempts to focus and activate the window.
|OS |Supported?|
|---------------|------------------------|
|macOS |Not yet implemented |
|Windows |Not yet implemented |
|X11 |Yes |
|Wayland |Wayland does not allow this action|

View File

@ -947,6 +947,94 @@ pub fn derive_command_from_key_assignment(action: &KeyAssignment) -> Option<Comm
menubar: &[],
icon: Some("mdi_close_box_outline"),
},
ActivateWindow(n) => {
let n = *n;
let ordinal = english_ordinal(n as isize + 1);
CommandDef {
brief: format!("Activate {ordinal} Window").into(),
doc: format!("Activates the {ordinal} window").into(),
keys: vec![],
args: &[ArgType::ActiveWindow],
menubar: &["Window", "Select Window"],
icon: None,
}
}
ActivateWindowRelative(-1) => CommandDef {
brief: "Activate the preceeding window".into(),
doc: "Activates the preceeding window. If this is the first \
window then cycles around and activates last window"
.into(),
keys: vec![],
args: &[ArgType::ActiveWindow],
menubar: &["Window", "Select Window"],
icon: None,
},
ActivateWindowRelative(1) => CommandDef {
brief: "Activate the next window".into(),
doc: "Activates the next window. If this is the last \
window then cycles around and activates first window"
.into(),
keys: vec![],
args: &[ArgType::ActiveWindow],
menubar: &["Window", "Select Window"],
icon: None,
},
ActivateWindowRelative(n) => {
let (direction, amount) = if *n < 0 {
("backwards", -n)
} else {
("forwards", *n)
};
let ordinal = english_ordinal(amount + 1);
CommandDef {
brief: format!("Activate the {ordinal} window {direction}").into(),
doc: format!(
"Activates the {ordinal} window, moving {direction}. \
Wraps around to the other end"
)
.into(),
keys: vec![],
args: &[ArgType::ActiveWindow],
menubar: &[],
icon: None,
}
}
ActivateWindowRelativeNoWrap(-1) => CommandDef {
brief: "Activate the preceeding window".into(),
doc: "Activates the preceeding window, stopping at the first \
window"
.into(),
keys: vec![],
args: &[ArgType::ActiveWindow],
menubar: &["Window", "Select Window"],
icon: None,
},
ActivateWindowRelativeNoWrap(1) => CommandDef {
brief: "Activate the next window".into(),
doc: "Activates the next window, stopping at the last \
window"
.into(),
keys: vec![],
args: &[ArgType::ActiveWindow],
menubar: &["Window", "Select Window"],
icon: None,
},
ActivateWindowRelativeNoWrap(n) => {
let (direction, amount) = if *n < 0 {
("backwards", -n)
} else {
("forwards", *n)
};
let ordinal = english_ordinal(amount + 1);
CommandDef {
brief: format!("Activate the {ordinal} window {direction}").into(),
doc: format!("Activates the {ordinal} window, moving {direction}.").into(),
keys: vec![],
args: &[ArgType::ActiveWindow],
menubar: &[],
icon: None,
}
}
ActivateTabRelative(-1) => CommandDef {
brief: "Activate the tab to the left".into(),
doc: "Activates the tab to the left. If this is the left-most \

View File

@ -269,6 +269,19 @@ impl GuiFrontEnd {
.context("running message loop")
}
pub fn gui_windows(&self) -> Vec<GuiWin> {
let windows = self.known_windows.borrow();
let mut windows: Vec<GuiWin> = windows
.iter()
.map(|(window, &mux_window_id)| GuiWin {
mux_window_id,
window: window.clone(),
})
.collect();
windows.sort_by(|a, b| a.window.cmp(&b.window));
windows
}
pub fn reconcile_workspace(&self) -> Future<()> {
let mut promise = Promise::new();
let mux = Mux::get();

View File

@ -70,6 +70,10 @@ impl UserData for GuiWin {
this.window.toggle_fullscreen();
Ok(())
});
methods.add_method("focus", |_, this, _: ()| {
this.window.focus();
Ok(())
});
methods.add_method(
"toast_notification",
|_, _, (title, message, url, timeout): (String, String, Option<String>, Option<u64>)| {

View File

@ -47,6 +47,15 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> {
keys
}
window_mod.set(
"gui_windows",
lua.create_function(|_, _: ()| {
let fe =
try_front_end().ok_or_else(|| mlua::Error::external("not called on gui thread"))?;
Ok(fe.gui_windows())
})?,
)?;
window_mod.set(
"default_keys",
lua.create_function(|lua, _: ()| {

View File

@ -1929,6 +1929,47 @@ impl TermWindow {
}
}
fn activate_window(&mut self, window_idx: usize) -> anyhow::Result<()> {
let windows = front_end().gui_windows();
if let Some(win) = windows.get(window_idx) {
win.window.focus();
}
Ok(())
}
fn activate_window_relative(&mut self, delta: isize, wrap: bool) -> anyhow::Result<()> {
let windows = front_end().gui_windows();
let my_idx = windows
.iter()
.position(|w| Some(&w.window) == self.window.as_ref())
.ok_or_else(|| anyhow!("I'm not in the window list!?"))?;
let idx = my_idx as isize + delta;
let idx = if wrap {
let idx = if idx < 0 {
windows.len() as isize + idx
} else {
idx
};
idx as usize % windows.len()
} else {
if idx < 0 {
0
} else if idx >= windows.len() as isize {
windows.len().saturating_sub(1)
} else {
idx as usize
}
};
if let Some(win) = windows.get(idx) {
win.window.focus();
}
Ok(())
}
fn activate_tab(&mut self, tab_idx: isize) -> anyhow::Result<()> {
let mux = Mux::get();
let mut window = mux
@ -2367,6 +2408,15 @@ impl TermWindow {
ActivateTab(n) => {
self.activate_tab(*n)?;
}
ActivateWindow(n) => {
self.activate_window(*n)?;
}
ActivateWindowRelative(n) => {
self.activate_window_relative(*n, true)?;
}
ActivateWindowRelativeNoWrap(n) => {
self.activate_window_relative(*n, false)?;
}
SendString(s) => pane.writer().write_all(s.as_bytes())?,
SendKey(key) => {
use keyevent::{window_mods_to_termwiz_mods, Key};

View File

@ -296,6 +296,7 @@ pub trait WindowOps {
fn maximize(&self) {}
fn restore(&self) {}
fn focus(&self) {}
fn toggle_fullscreen(&self) {}

View File

@ -636,6 +636,13 @@ impl WindowOps for Window {
});
}
fn focus(&self) {
Connection::with_window_inner(self.id, |inner| {
inner.focus();
Ok(())
});
}
fn hide(&self) {
Connection::with_window_inner(self.id, |inner| {
inner.hide();
@ -987,6 +994,8 @@ impl WindowInner {
}
}
fn focus(&mut self) {}
fn hide(&mut self) {
unsafe {
NSWindow::miniaturize_(*self.window, *self.window);

View File

@ -906,6 +906,13 @@ impl WindowOps for WaylandWindow {
});
}
fn focus(&self) {
WaylandConnection::with_window_inner(self.0, |inner| {
inner.focus();
Ok(())
});
}
fn show(&self) {
WaylandConnection::with_window_inner(self.0, |inner| {
inner.show();
@ -1065,6 +1072,10 @@ impl WaylandWindowInner {
}
}
fn focus(&mut self) {
log::debug!("Wayland doesn't support applications changing focus");
}
fn show(&mut self) {
if self.window.is_none() {
return;

View File

@ -51,6 +51,7 @@ pub struct XConnection {
pub atom_net_wm_moveresize: Atom,
pub atom_net_supported: Atom,
pub atom_net_supporting_wm_check: Atom,
pub atom_net_active_window: Atom,
pub(crate) xrm: RefCell<HashMap<String, String>>,
pub(crate) windows: RefCell<HashMap<xcb::x::Window, Arc<Mutex<XWindowInner>>>>,
should_terminate: RefCell<bool>,
@ -583,6 +584,7 @@ impl XConnection {
let atom_net_wm_moveresize = Self::intern_atom(&conn, "_NET_WM_MOVERESIZE")?;
let atom_net_supported = Self::intern_atom(&conn, "_NET_SUPPORTED")?;
let atom_net_supporting_wm_check = Self::intern_atom(&conn, "_NET_SUPPORTING_WM_CHECK")?;
let atom_net_active_window = Self::intern_atom(&conn, "_NET_ACTIVE_WINDOW")?;
let has_randr = conn.active_extensions().any(|e| e == xcb::Extension::RandR);
@ -691,6 +693,7 @@ impl XConnection {
atom_net_wm_moveresize,
atom_net_supported,
atom_net_supporting_wm_check,
atom_net_active_window,
atom_net_wm_icon,
keyboard,
kbd_ev,

View File

@ -1240,6 +1240,34 @@ impl XWindowInner {
});
}
fn focus(&mut self) {
let conn = self.conn();
conn.send_request_no_reply_log(&xcb::x::SendEvent {
propagate: true,
destination: xcb::x::SendEventDest::Window(conn.root),
event_mask: xcb::x::EventMask::SUBSTRUCTURE_REDIRECT
| xcb::x::EventMask::SUBSTRUCTURE_NOTIFY,
event: &xcb::x::ClientMessageEvent::new(
self.window_id,
conn.atom_net_active_window,
xcb::x::ClientMessageData::Data32([
1,
// You'd think that self.copy_and_paste.time would
// be the thing to use, but Mutter ignored this request
// until I switched to CURRENT_TIME
xcb::x::CURRENT_TIME,
0,
0,
0,
]),
),
});
if let Err(err) = conn.flush() {
log::error!("Error flushing: {err:#}");
}
}
fn invalidate(&mut self) {
self.queue_pending(WindowEvent::NeedRepaint);
self.dispatch_pending_events().ok();
@ -1581,6 +1609,13 @@ impl WindowOps for XWindow {
});
}
fn focus(&self) {
XConnection::with_window_inner(self.0, |inner| {
inner.focus();
Ok(())
});
}
fn show(&self) {
XConnection::with_window_inner(self.0, |inner| {
inner.show();

View File

@ -266,6 +266,14 @@ impl WindowOps for Window {
}
}
fn focus(&self) {
match self {
Self::X11(x) => x.focus(),
#[cfg(feature = "wayland")]
Self::Wayland(w) => w.focus(),
}
}
fn toggle_fullscreen(&self) {
match self {
Self::X11(x) => x.toggle_fullscreen(),