Linux window decorations (#13611)
This PR adds support for full client side decorations on X11 and Wayland TODO: - [x] Adjust GPUI APIs to expose CSD related information - [x] Implement remaining CSD features (Resizing, window border, window shadow) - [x] Integrate with existing background appearance and window transparency - [x] Figure out how to check if the window is tiled on X11 - [x] Implement in Zed - [x] Repeatedly maximizing and unmaximizing can panic - [x] Resizing is strangely slow - [x] X11 resizing and movement doesn't work for this: https://discord.com/channels/869392257814519848/1204679850208657418/1256816908519604305 - [x] The top corner can clip with current styling - [x] Pressing titlebar buttons doesn't work - [x] Not showing maximize / unmaximize buttons - [x] Noisy transparency logs / surface transparency problem https://github.com/zed-industries/zed/pull/13611#issuecomment-2201685030 - [x] Strange offsets when dragging the project panel https://github.com/zed-industries/zed/pull/13611#pullrequestreview-2154606261 - [x] Shadow inset with `_GTK_FRAME_EXTENTS` doesn't respect tiling on X11 (observe by snapping an X11 window in any direction) Release Notes: - N/A --------- Co-authored-by: conrad <conrad@zed.dev> Co-authored-by: Owen Law <81528246+someone13574@users.noreply.github.com> Co-authored-by: apricotbucket28 <71973804+apricotbucket28@users.noreply.github.com> Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
98699a65c1
commit
47aa761ca9
28 changed files with 1633 additions and 540 deletions
|
@ -2,10 +2,11 @@ use anyhow::Context;
|
|||
|
||||
use crate::{
|
||||
platform::blade::{BladeRenderer, BladeSurfaceConfig},
|
||||
px, size, AnyWindowHandle, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels,
|
||||
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
|
||||
PromptLevel, Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
|
||||
WindowKind, WindowParams, X11ClientStatePtr,
|
||||
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, Modifiers,
|
||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
||||
Point, PromptLevel, ResizeEdge, Scene, Size, Tiling, WindowAppearance,
|
||||
WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowParams,
|
||||
X11ClientStatePtr,
|
||||
};
|
||||
|
||||
use blade_graphics as gpu;
|
||||
|
@ -15,24 +16,17 @@ use x11rb::{
|
|||
connection::Connection,
|
||||
protocol::{
|
||||
randr::{self, ConnectionExt as _},
|
||||
sync,
|
||||
xinput::{self, ConnectionExt as _},
|
||||
xproto::{
|
||||
self, ClientMessageEvent, ConnectionExt as _, EventMask, TranslateCoordinatesReply,
|
||||
},
|
||||
xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply},
|
||||
},
|
||||
wrapper::ConnectionExt as _,
|
||||
xcb_ffi::XCBConnection,
|
||||
};
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::c_void,
|
||||
num::NonZeroU32,
|
||||
ops::Div,
|
||||
ptr::NonNull,
|
||||
rc::Rc,
|
||||
sync::{self, Arc},
|
||||
time::Duration,
|
||||
cell::RefCell, ffi::c_void, mem::size_of, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,
|
||||
sync::Arc, time::Duration,
|
||||
};
|
||||
|
||||
use super::{X11Display, XINPUT_MASTER_DEVICE};
|
||||
|
@ -50,10 +44,16 @@ x11rb::atom_manager! {
|
|||
_NET_WM_STATE_HIDDEN,
|
||||
_NET_WM_STATE_FOCUSED,
|
||||
_NET_ACTIVE_WINDOW,
|
||||
_NET_WM_SYNC_REQUEST,
|
||||
_NET_WM_SYNC_REQUEST_COUNTER,
|
||||
_NET_WM_BYPASS_COMPOSITOR,
|
||||
_NET_WM_MOVERESIZE,
|
||||
_NET_WM_WINDOW_TYPE,
|
||||
_NET_WM_WINDOW_TYPE_NOTIFICATION,
|
||||
_NET_WM_SYNC,
|
||||
_MOTIF_WM_HINTS,
|
||||
_GTK_SHOW_WINDOW_MENU,
|
||||
_GTK_FRAME_EXTENTS,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,6 +70,21 @@ fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window)
|
|||
}
|
||||
}
|
||||
|
||||
impl ResizeEdge {
|
||||
fn to_moveresize(&self) -> u32 {
|
||||
match self {
|
||||
ResizeEdge::TopLeft => 0,
|
||||
ResizeEdge::Top => 1,
|
||||
ResizeEdge::TopRight => 2,
|
||||
ResizeEdge::Right => 3,
|
||||
ResizeEdge::BottomRight => 4,
|
||||
ResizeEdge::Bottom => 5,
|
||||
ResizeEdge::BottomLeft => 6,
|
||||
ResizeEdge::Left => 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Visual {
|
||||
id: xproto::Visualid,
|
||||
|
@ -166,6 +181,8 @@ pub struct X11WindowState {
|
|||
executor: ForegroundExecutor,
|
||||
atoms: XcbAtoms,
|
||||
x_root_window: xproto::Window,
|
||||
pub(crate) counter_id: sync::Counter,
|
||||
pub(crate) last_sync_counter: Option<sync::Int64>,
|
||||
_raw: RawWindow,
|
||||
bounds: Bounds<Pixels>,
|
||||
scale_factor: f32,
|
||||
|
@ -173,7 +190,22 @@ pub struct X11WindowState {
|
|||
display: Rc<dyn PlatformDisplay>,
|
||||
input_handler: Option<PlatformInputHandler>,
|
||||
appearance: WindowAppearance,
|
||||
background_appearance: WindowBackgroundAppearance,
|
||||
maximized_vertical: bool,
|
||||
maximized_horizontal: bool,
|
||||
hidden: bool,
|
||||
active: bool,
|
||||
fullscreen: bool,
|
||||
decorations: WindowDecorations,
|
||||
pub handle: AnyWindowHandle,
|
||||
last_insets: [u32; 4],
|
||||
}
|
||||
|
||||
impl X11WindowState {
|
||||
fn is_transparent(&self) -> bool {
|
||||
self.decorations == WindowDecorations::Client
|
||||
|| self.background_appearance != WindowBackgroundAppearance::Opaque
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -230,19 +262,11 @@ impl X11WindowState {
|
|||
.map_or(x_main_screen_index, |did| did.0 as usize);
|
||||
|
||||
let visual_set = find_visuals(&xcb_connection, x_screen_index);
|
||||
let visual_maybe = match params.window_background {
|
||||
WindowBackgroundAppearance::Opaque => visual_set.opaque,
|
||||
WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
|
||||
visual_set.transparent
|
||||
}
|
||||
};
|
||||
let visual = match visual_maybe {
|
||||
|
||||
let visual = match visual_set.transparent {
|
||||
Some(visual) => visual,
|
||||
None => {
|
||||
log::warn!(
|
||||
"Unable to find a matching visual for {:?}",
|
||||
params.window_background
|
||||
);
|
||||
log::warn!("Unable to find a transparent visual",);
|
||||
visual_set.inherit
|
||||
}
|
||||
};
|
||||
|
@ -269,7 +293,8 @@ impl X11WindowState {
|
|||
| xproto::EventMask::STRUCTURE_NOTIFY
|
||||
| xproto::EventMask::FOCUS_CHANGE
|
||||
| xproto::EventMask::KEY_PRESS
|
||||
| xproto::EventMask::KEY_RELEASE,
|
||||
| xproto::EventMask::KEY_RELEASE
|
||||
| EventMask::PROPERTY_CHANGE,
|
||||
);
|
||||
|
||||
let mut bounds = params.bounds.to_device_pixels(scale_factor);
|
||||
|
@ -349,7 +374,26 @@ impl X11WindowState {
|
|||
x_window,
|
||||
atoms.WM_PROTOCOLS,
|
||||
xproto::AtomEnum::ATOM,
|
||||
&[atoms.WM_DELETE_WINDOW],
|
||||
&[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sync::initialize(xcb_connection, 3, 1).unwrap();
|
||||
let sync_request_counter = xcb_connection.generate_id().unwrap();
|
||||
sync::create_counter(
|
||||
xcb_connection,
|
||||
sync_request_counter,
|
||||
sync::Int64 { lo: 0, hi: 0 },
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
xcb_connection
|
||||
.change_property32(
|
||||
xproto::PropMode::REPLACE,
|
||||
x_window,
|
||||
atoms._NET_WM_SYNC_REQUEST_COUNTER,
|
||||
xproto::AtomEnum::CARDINAL,
|
||||
&[sync_request_counter],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -396,7 +440,8 @@ impl X11WindowState {
|
|||
// Note: this has to be done after the GPU init, or otherwise
|
||||
// the sizes are immediately invalidated.
|
||||
size: query_render_extent(xcb_connection, x_window),
|
||||
transparent: params.window_background != WindowBackgroundAppearance::Opaque,
|
||||
// In case we have window decorations to render
|
||||
transparent: true,
|
||||
};
|
||||
xcb_connection.map_window(x_window).unwrap();
|
||||
|
||||
|
@ -438,9 +483,19 @@ impl X11WindowState {
|
|||
renderer: BladeRenderer::new(gpu, config),
|
||||
atoms: *atoms,
|
||||
input_handler: None,
|
||||
active: false,
|
||||
fullscreen: false,
|
||||
maximized_vertical: false,
|
||||
maximized_horizontal: false,
|
||||
hidden: false,
|
||||
appearance,
|
||||
handle,
|
||||
background_appearance: WindowBackgroundAppearance::Opaque,
|
||||
destroyed: false,
|
||||
decorations: WindowDecorations::Server,
|
||||
last_insets: [0, 0, 0, 0],
|
||||
counter_id: sync_request_counter,
|
||||
last_sync_counter: None,
|
||||
refresh_rate,
|
||||
})
|
||||
}
|
||||
|
@ -511,7 +566,7 @@ impl X11Window {
|
|||
scale_factor: f32,
|
||||
appearance: WindowAppearance,
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(Self(X11WindowStatePtr {
|
||||
let ptr = X11WindowStatePtr {
|
||||
state: Rc::new(RefCell::new(X11WindowState::new(
|
||||
handle,
|
||||
client,
|
||||
|
@ -527,7 +582,12 @@ impl X11Window {
|
|||
callbacks: Rc::new(RefCell::new(Callbacks::default())),
|
||||
xcb_connection: xcb_connection.clone(),
|
||||
x_window,
|
||||
}))
|
||||
};
|
||||
|
||||
let state = ptr.state.borrow_mut();
|
||||
ptr.set_wm_properties(state);
|
||||
|
||||
Ok(Self(ptr))
|
||||
}
|
||||
|
||||
fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
|
||||
|
@ -549,29 +609,6 @@ impl X11Window {
|
|||
.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
|
||||
|
@ -586,6 +623,48 @@ impl X11Window {
|
|||
.reply()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn send_moveresize(&self, flag: u32) {
|
||||
let state = self.0.state.borrow();
|
||||
|
||||
self.0
|
||||
.xcb_connection
|
||||
.ungrab_pointer(x11rb::CURRENT_TIME)
|
||||
.unwrap()
|
||||
.check()
|
||||
.unwrap();
|
||||
|
||||
let pointer = self
|
||||
.0
|
||||
.xcb_connection
|
||||
.query_pointer(self.0.x_window)
|
||||
.unwrap()
|
||||
.reply()
|
||||
.unwrap();
|
||||
let message = ClientMessageEvent::new(
|
||||
32,
|
||||
self.0.x_window,
|
||||
state.atoms._NET_WM_MOVERESIZE,
|
||||
[
|
||||
pointer.root_x as u32,
|
||||
pointer.root_y as u32,
|
||||
flag,
|
||||
0, // Left mouse button
|
||||
0,
|
||||
],
|
||||
);
|
||||
self.0
|
||||
.xcb_connection
|
||||
.send_event(
|
||||
false,
|
||||
state.x_root_window,
|
||||
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
message,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.0.xcb_connection.flush().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl X11WindowStatePtr {
|
||||
|
@ -600,6 +679,54 @@ impl X11WindowStatePtr {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if event.atom == state.atoms._NET_WM_STATE {
|
||||
self.set_wm_properties(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_wm_properties(&self, mut state: std::cell::RefMut<X11WindowState>) {
|
||||
let reply = self
|
||||
.xcb_connection
|
||||
.get_property(
|
||||
false,
|
||||
self.x_window,
|
||||
state.atoms._NET_WM_STATE,
|
||||
xproto::AtomEnum::ATOM,
|
||||
0,
|
||||
u32::MAX,
|
||||
)
|
||||
.unwrap()
|
||||
.reply()
|
||||
.unwrap();
|
||||
|
||||
let atoms = reply
|
||||
.value
|
||||
.chunks_exact(4)
|
||||
.map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
|
||||
|
||||
state.active = false;
|
||||
state.fullscreen = false;
|
||||
state.maximized_vertical = false;
|
||||
state.maximized_horizontal = false;
|
||||
state.hidden = true;
|
||||
|
||||
for atom in atoms {
|
||||
if atom == state.atoms._NET_WM_STATE_FOCUSED {
|
||||
state.active = true;
|
||||
} else if atom == state.atoms._NET_WM_STATE_FULLSCREEN {
|
||||
state.fullscreen = true;
|
||||
} else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_VERT {
|
||||
state.maximized_vertical = true;
|
||||
} else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_HORZ {
|
||||
state.maximized_horizontal = true;
|
||||
} else if atom == state.atoms._NET_WM_STATE_HIDDEN {
|
||||
state.hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
let mut callbacks = self.callbacks.borrow_mut();
|
||||
if let Some(fun) = callbacks.close.take() {
|
||||
|
@ -715,6 +842,9 @@ impl X11WindowStatePtr {
|
|||
));
|
||||
resize_args = Some((state.content_size(), state.scale_factor));
|
||||
}
|
||||
if let Some(value) = state.last_sync_counter.take() {
|
||||
sync::set_counter(&self.xcb_connection, state.counter_id, value).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let mut callbacks = self.callbacks.borrow_mut();
|
||||
|
@ -737,8 +867,12 @@ impl X11WindowStatePtr {
|
|||
}
|
||||
|
||||
pub fn set_appearance(&mut self, appearance: WindowAppearance) {
|
||||
self.state.borrow_mut().appearance = appearance;
|
||||
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.appearance = appearance;
|
||||
let is_transparent = state.is_transparent();
|
||||
state.renderer.update_transparency(is_transparent);
|
||||
state.appearance = appearance;
|
||||
drop(state);
|
||||
let mut callbacks = self.callbacks.borrow_mut();
|
||||
if let Some(ref mut fun) = callbacks.appearance_changed {
|
||||
(fun)()
|
||||
|
@ -757,11 +891,9 @@ impl PlatformWindow for X11Window {
|
|||
|
||||
fn is_maximized(&self) -> bool {
|
||||
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)
|
||||
!state.hidden && state.maximized_vertical && state.maximized_horizontal
|
||||
}
|
||||
|
||||
fn window_bounds(&self) -> WindowBounds {
|
||||
|
@ -862,9 +994,7 @@ impl PlatformWindow for X11Window {
|
|||
}
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
let state = self.0.state.borrow();
|
||||
self.get_wm_hints()
|
||||
.contains(&state.atoms._NET_WM_STATE_FOCUSED)
|
||||
self.0.state.borrow().active
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
|
@ -913,10 +1043,11 @@ impl PlatformWindow for X11Window {
|
|||
log::info!("ignoring macOS specific set_edited");
|
||||
}
|
||||
|
||||
fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
|
||||
let mut inner = self.0.state.borrow_mut();
|
||||
let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
|
||||
inner.renderer.update_transparency(transparent);
|
||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
||||
let mut state = self.0.state.borrow_mut();
|
||||
state.background_appearance = background_appearance;
|
||||
let transparent = state.is_transparent();
|
||||
state.renderer.update_transparency(transparent);
|
||||
}
|
||||
|
||||
fn show_character_palette(&self) {
|
||||
|
@ -962,9 +1093,7 @@ impl PlatformWindow for X11Window {
|
|||
}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
let state = self.0.state.borrow();
|
||||
self.get_wm_hints()
|
||||
.contains(&state.atoms._NET_WM_STATE_FULLSCREEN)
|
||||
self.0.state.borrow().fullscreen
|
||||
}
|
||||
|
||||
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
|
||||
|
@ -1004,7 +1133,7 @@ impl PlatformWindow for X11Window {
|
|||
inner.renderer.draw(scene);
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||
let inner = self.0.state.borrow();
|
||||
inner.renderer.sprite_atlas().clone()
|
||||
}
|
||||
|
@ -1035,41 +1164,109 @@ impl PlatformWindow for X11Window {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
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();
|
||||
fn start_window_move(&self) {
|
||||
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();
|
||||
self.send_moveresize(MOVERESIZE_MOVE);
|
||||
}
|
||||
|
||||
fn should_render_window_controls(&self) -> bool {
|
||||
false
|
||||
fn start_window_resize(&self, edge: ResizeEdge) {
|
||||
self.send_moveresize(edge.to_moveresize());
|
||||
}
|
||||
|
||||
fn window_decorations(&self) -> crate::Decorations {
|
||||
let state = self.0.state.borrow();
|
||||
|
||||
match state.decorations {
|
||||
WindowDecorations::Server => Decorations::Server,
|
||||
WindowDecorations::Client => {
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
|
||||
Decorations::Client {
|
||||
tiling: Tiling {
|
||||
top: state.maximized_vertical,
|
||||
bottom: state.maximized_vertical,
|
||||
left: state.maximized_horizontal,
|
||||
right: state.maximized_horizontal,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_client_inset(&self, inset: Pixels) {
|
||||
let mut state = self.0.state.borrow_mut();
|
||||
|
||||
let dp = (inset.0 * state.scale_factor) as u32;
|
||||
|
||||
let (left, right) = if state.maximized_horizontal {
|
||||
(0, 0)
|
||||
} else {
|
||||
(dp, dp)
|
||||
};
|
||||
let (top, bottom) = if state.maximized_vertical {
|
||||
(0, 0)
|
||||
} else {
|
||||
(dp, dp)
|
||||
};
|
||||
let insets = [left, right, top, bottom];
|
||||
|
||||
if state.last_insets != insets {
|
||||
state.last_insets = insets;
|
||||
|
||||
self.0
|
||||
.xcb_connection
|
||||
.change_property(
|
||||
xproto::PropMode::REPLACE,
|
||||
self.0.x_window,
|
||||
state.atoms._GTK_FRAME_EXTENTS,
|
||||
xproto::AtomEnum::CARDINAL,
|
||||
size_of::<u32>() as u8 * 8,
|
||||
4,
|
||||
bytemuck::cast_slice::<u32, u8>(&insets),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn request_decorations(&self, decorations: crate::WindowDecorations) {
|
||||
// https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
|
||||
let hints_data: [u32; 5] = match decorations {
|
||||
WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
|
||||
WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
|
||||
};
|
||||
|
||||
let mut state = self.0.state.borrow_mut();
|
||||
|
||||
self.0
|
||||
.xcb_connection
|
||||
.change_property(
|
||||
xproto::PropMode::REPLACE,
|
||||
self.0.x_window,
|
||||
state.atoms._MOTIF_WM_HINTS,
|
||||
state.atoms._MOTIF_WM_HINTS,
|
||||
std::mem::size_of::<u32>() as u8 * 8,
|
||||
5,
|
||||
bytemuck::cast_slice::<u32, u8>(&hints_data),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
match decorations {
|
||||
WindowDecorations::Server => {
|
||||
state.decorations = WindowDecorations::Server;
|
||||
let is_transparent = state.is_transparent();
|
||||
state.renderer.update_transparency(is_transparent);
|
||||
}
|
||||
WindowDecorations::Client => {
|
||||
state.decorations = WindowDecorations::Client;
|
||||
let is_transparent = state.is_transparent();
|
||||
state.renderer.update_transparency(is_transparent);
|
||||
}
|
||||
}
|
||||
|
||||
drop(state);
|
||||
let mut callbacks = self.0.callbacks.borrow_mut();
|
||||
if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
|
||||
appearance_changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue