x11: Implement various window functions (#12070)
This (mostly) allows the CSD added in https://github.com/zed-industries/zed/pull/11525 to work in X11. It's still a bit buggy as it detects a second window drag right after the first one finishes, but it's probably better to change the way window drags are detected in the title bar itself (as that causes other issues). The CSD can be tested by changing the return value of `should_render_window_controls` to true. Also fixes F11 crashing. Release Notes: - N/A
This commit is contained in:
parent
c0259a448d
commit
d8605c8614
4 changed files with 195 additions and 35 deletions
|
@ -16,9 +16,12 @@ use util::ResultExt;
|
|||
use x11rb::{
|
||||
connection::{Connection as _, RequestConnection as _},
|
||||
protocol::{
|
||||
render::{self, ConnectionExt as _},
|
||||
render,
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xproto::{self, ConnectionExt as _, CreateWindowAux},
|
||||
xproto::{
|
||||
self, Atom, ClientMessageEvent, ConnectionExt as _, CreateWindowAux, EventMask,
|
||||
TranslateCoordinatesReply,
|
||||
},
|
||||
},
|
||||
resource_manager::Database,
|
||||
wrapper::ConnectionExt as _,
|
||||
|
@ -38,17 +41,23 @@ use std::{
|
|||
sync::{self, Arc},
|
||||
};
|
||||
|
||||
use super::X11Display;
|
||||
use super::{X11Display, XINPUT_MASTER_DEVICE};
|
||||
|
||||
x11rb::atom_manager! {
|
||||
pub XcbAtoms: AtomsCookie {
|
||||
UTF8_STRING,
|
||||
WM_PROTOCOLS,
|
||||
WM_DELETE_WINDOW,
|
||||
WM_CHANGE_STATE,
|
||||
_NET_WM_NAME,
|
||||
_NET_WM_STATE,
|
||||
_NET_WM_STATE_MAXIMIZED_VERT,
|
||||
_NET_WM_STATE_MAXIMIZED_HORZ,
|
||||
_NET_WM_STATE_FULLSCREEN,
|
||||
_NET_WM_STATE_HIDDEN,
|
||||
_NET_WM_STATE_FOCUSED,
|
||||
_NET_WM_MOVERESIZE,
|
||||
_GTK_SHOW_WINDOW_MENU,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,6 +167,7 @@ pub(crate) struct X11WindowState {
|
|||
client: X11ClientStatePtr,
|
||||
executor: ForegroundExecutor,
|
||||
atoms: XcbAtoms,
|
||||
x_root_window: xproto::Window,
|
||||
raw: RawWindow,
|
||||
bounds: Bounds<i32>,
|
||||
scale_factor: f32,
|
||||
|
@ -311,7 +321,7 @@ impl X11WindowState {
|
|||
.xinput_xi_select_events(
|
||||
x_window,
|
||||
&[xinput::EventMask {
|
||||
deviceid: 1,
|
||||
deviceid: XINPUT_MASTER_DEVICE,
|
||||
mask: vec![
|
||||
xinput::XIEventMask::MOTION
|
||||
| xinput::XIEventMask::BUTTON_PRESS
|
||||
|
@ -359,6 +369,7 @@ impl X11WindowState {
|
|||
executor,
|
||||
display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
|
||||
raw,
|
||||
x_root_window: visual_set.root,
|
||||
bounds: params.bounds.map(|v| v.0),
|
||||
scale_factor,
|
||||
renderer: BladeRenderer::new(gpu, config),
|
||||
|
@ -403,6 +414,12 @@ impl Drop for X11Window {
|
|||
}
|
||||
}
|
||||
|
||||
enum WmHintPropertyState {
|
||||
Remove = 0,
|
||||
Add = 1,
|
||||
Toggle = 2,
|
||||
}
|
||||
|
||||
impl X11Window {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
|
@ -431,6 +448,63 @@ impl X11Window {
|
|||
x_window,
|
||||
})
|
||||
}
|
||||
|
||||
fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
|
||||
let state = self.0.state.borrow();
|
||||
let message = ClientMessageEvent::new(
|
||||
32,
|
||||
self.0.x_window,
|
||||
state.atoms._NET_WM_STATE,
|
||||
[wm_hint_property_state as u32, prop1, prop2, 1, 0],
|
||||
);
|
||||
self.0
|
||||
.xcb_connection
|
||||
.send_event(
|
||||
false,
|
||||
state.x_root_window,
|
||||
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
message,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn get_wm_hints(&self) -> Vec<u32> {
|
||||
let reply = self
|
||||
.0
|
||||
.xcb_connection
|
||||
.get_property(
|
||||
false,
|
||||
self.0.x_window,
|
||||
self.0.state.borrow().atoms._NET_WM_STATE,
|
||||
xproto::AtomEnum::ATOM,
|
||||
0,
|
||||
u32::MAX,
|
||||
)
|
||||
.unwrap()
|
||||
.reply()
|
||||
.unwrap();
|
||||
// Reply is in u8 but atoms are represented as u32
|
||||
reply
|
||||
.value
|
||||
.chunks_exact(4)
|
||||
.map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_root_position(&self, position: Point<Pixels>) -> TranslateCoordinatesReply {
|
||||
let state = self.0.state.borrow();
|
||||
self.0
|
||||
.xcb_connection
|
||||
.translate_coordinates(
|
||||
self.0.x_window,
|
||||
state.x_root_window,
|
||||
(position.x.0 * state.scale_factor) as i16,
|
||||
(position.y.0 * state.scale_factor) as i16,
|
||||
)
|
||||
.unwrap()
|
||||
.reply()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl X11WindowStatePtr {
|
||||
|
@ -555,12 +629,15 @@ impl PlatformWindow for X11Window {
|
|||
self.0.state.borrow().bounds.map(|v| v.into())
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn is_maximized(&self) -> bool {
|
||||
false
|
||||
let state = self.0.state.borrow();
|
||||
let wm_hints = self.get_wm_hints();
|
||||
// A maximized window that gets minimized will still retain its maximized state.
|
||||
!wm_hints.contains(&state.atoms._NET_WM_STATE_HIDDEN)
|
||||
&& wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_VERT)
|
||||
&& wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_HORZ)
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn window_bounds(&self) -> WindowBounds {
|
||||
let state = self.0.state.borrow();
|
||||
WindowBounds::Windowed(state.bounds.map(|p| DevicePixels(p)))
|
||||
|
@ -630,9 +707,10 @@ impl PlatformWindow for X11Window {
|
|||
.log_err();
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn is_active(&self) -> bool {
|
||||
false
|
||||
let state = self.0.state.borrow();
|
||||
self.get_wm_hints()
|
||||
.contains(&state.atoms._NET_WM_STATE_FOCUSED)
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
|
@ -665,13 +743,16 @@ impl PlatformWindow for X11Window {
|
|||
data.push(b'\0');
|
||||
data.extend(app_id.bytes()); // class
|
||||
|
||||
self.0.xcb_connection.change_property8(
|
||||
xproto::PropMode::REPLACE,
|
||||
self.0.x_window,
|
||||
xproto::AtomEnum::WM_CLASS,
|
||||
xproto::AtomEnum::STRING,
|
||||
&data,
|
||||
);
|
||||
self.0
|
||||
.xcb_connection
|
||||
.change_property8(
|
||||
xproto::PropMode::REPLACE,
|
||||
self.0.x_window,
|
||||
xproto::AtomEnum::WM_CLASS,
|
||||
xproto::AtomEnum::STRING,
|
||||
&data,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
|
@ -693,24 +774,48 @@ impl PlatformWindow for X11Window {
|
|||
unimplemented!()
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn minimize(&self) {
|
||||
unimplemented!()
|
||||
let state = self.0.state.borrow();
|
||||
const WINDOW_ICONIC_STATE: u32 = 3;
|
||||
let message = ClientMessageEvent::new(
|
||||
32,
|
||||
self.0.x_window,
|
||||
state.atoms.WM_CHANGE_STATE,
|
||||
[WINDOW_ICONIC_STATE, 0, 0, 0, 0],
|
||||
);
|
||||
self.0
|
||||
.xcb_connection
|
||||
.send_event(
|
||||
false,
|
||||
state.x_root_window,
|
||||
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
message,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn zoom(&self) {
|
||||
unimplemented!()
|
||||
let state = self.0.state.borrow();
|
||||
self.set_wm_hints(
|
||||
WmHintPropertyState::Toggle,
|
||||
state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
|
||||
state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
|
||||
);
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn toggle_fullscreen(&self) {
|
||||
unimplemented!()
|
||||
let state = self.0.state.borrow();
|
||||
self.set_wm_hints(
|
||||
WmHintPropertyState::Toggle,
|
||||
state.atoms._NET_WM_STATE_FULLSCREEN,
|
||||
xproto::AtomEnum::NONE.into(),
|
||||
);
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
false
|
||||
let state = self.0.state.borrow();
|
||||
self.get_wm_hints()
|
||||
.contains(&state.atoms._NET_WM_STATE_FULLSCREEN)
|
||||
}
|
||||
|
||||
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
|
||||
|
@ -755,11 +860,64 @@ impl PlatformWindow for X11Window {
|
|||
inner.renderer.sprite_atlas().clone()
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn show_window_menu(&self, _position: Point<Pixels>) {}
|
||||
fn show_window_menu(&self, position: Point<Pixels>) {
|
||||
let state = self.0.state.borrow();
|
||||
let coords = self.get_root_position(position);
|
||||
let message = ClientMessageEvent::new(
|
||||
32,
|
||||
self.0.x_window,
|
||||
state.atoms._GTK_SHOW_WINDOW_MENU,
|
||||
[
|
||||
XINPUT_MASTER_DEVICE as u32,
|
||||
coords.dst_x as u32,
|
||||
coords.dst_y as u32,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
);
|
||||
self.0
|
||||
.xcb_connection
|
||||
.send_event(
|
||||
false,
|
||||
state.x_root_window,
|
||||
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
message,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn start_system_move(&self) {}
|
||||
fn start_system_move(&self) {
|
||||
let state = self.0.state.borrow();
|
||||
let pointer = self
|
||||
.0
|
||||
.xcb_connection
|
||||
.query_pointer(self.0.x_window)
|
||||
.unwrap()
|
||||
.reply()
|
||||
.unwrap();
|
||||
const MOVERESIZE_MOVE: u32 = 8;
|
||||
let message = ClientMessageEvent::new(
|
||||
32,
|
||||
self.0.x_window,
|
||||
state.atoms._NET_WM_MOVERESIZE,
|
||||
[
|
||||
pointer.root_x as u32,
|
||||
pointer.root_y as u32,
|
||||
MOVERESIZE_MOVE,
|
||||
1, // Left mouse button
|
||||
1,
|
||||
],
|
||||
);
|
||||
self.0
|
||||
.xcb_connection
|
||||
.send_event(
|
||||
false,
|
||||
state.x_root_window,
|
||||
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
message,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn should_render_window_controls(&self) -> bool {
|
||||
false
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue