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:
parent
4f1e2604eb
commit
b01aa129f7
@ -534,6 +534,9 @@ pub enum KeyAssignment {
|
||||
ResetTerminal,
|
||||
OpenUri(String),
|
||||
ActivateCommandPalette,
|
||||
ActivateWindow(usize),
|
||||
ActivateWindowRelative(isize),
|
||||
ActivateWindowRelativeNoWrap(isize),
|
||||
}
|
||||
impl_lua_conversion_dynamic!(KeyAssignment);
|
||||
|
||||
|
@ -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
|
||||
|
37
docs/config/lua/keyassignment/ActivateWindow.md
Normal file
37
docs/config/lua/keyassignment/ActivateWindow.md
Normal 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).
|
29
docs/config/lua/keyassignment/ActivateWindowRelative.md
Normal file
29
docs/config/lua/keyassignment/ActivateWindowRelative.md
Normal 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).
|
@ -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).
|
7
docs/config/lua/wezterm.gui/gui_windows.md
Normal file
7
docs/config/lua/wezterm.gui/gui_windows.md
Normal 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.
|
||||
|
13
docs/config/lua/window/focus.md
Normal file
13
docs/config/lua/window/focus.md
Normal 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|
|
||||
|
@ -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 \
|
||||
|
@ -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();
|
||||
|
@ -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>)| {
|
||||
|
@ -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, _: ()| {
|
||||
|
@ -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};
|
||||
|
@ -296,6 +296,7 @@ pub trait WindowOps {
|
||||
|
||||
fn maximize(&self) {}
|
||||
fn restore(&self) {}
|
||||
fn focus(&self) {}
|
||||
|
||||
fn toggle_fullscreen(&self) {}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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(),
|
||||
|
Loading…
Reference in New Issue
Block a user