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