
Closes #31330 Second parts of https://github.com/zed-industries/zed/pull/31335 While the initial fix set the inset during drawing, that was after the window was resized, resulting in needing to manually resize the window for the change to properly take effect. I updated the code to not make the Wayland renderer rely on `client_inset` being updated by the API user to match with the decoration mode (given it is supposed to only be used when using CSD). I might later try to generalize that, and eventually make the client_inset only defined on window creation (instead of inside `client_side_decorations`, that would need testing on X) (and maybe also allow configuration for shadow, but it’s not something I need). Release Notes: - Fixed switching from client side decoration to server side decoration on Wayland
1216 lines
40 KiB
Rust
1216 lines
40 KiB
Rust
use std::{
|
|
cell::{Ref, RefCell, RefMut},
|
|
ffi::c_void,
|
|
ptr::NonNull,
|
|
rc::Rc,
|
|
sync::Arc,
|
|
};
|
|
|
|
use blade_graphics as gpu;
|
|
use collections::HashMap;
|
|
use futures::channel::oneshot::Receiver;
|
|
|
|
use raw_window_handle as rwh;
|
|
use wayland_backend::client::ObjectId;
|
|
use wayland_client::WEnum;
|
|
use wayland_client::{Proxy, protocol::wl_surface};
|
|
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1;
|
|
use wayland_protocols::wp::viewporter::client::wp_viewport;
|
|
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1;
|
|
use wayland_protocols::xdg::shell::client::xdg_surface;
|
|
use wayland_protocols::xdg::shell::client::xdg_toplevel::{self};
|
|
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
|
|
|
|
use crate::scene::Scene;
|
|
use crate::{
|
|
AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
|
|
PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
|
|
ResizeEdge, ScaledPixels, Size, Tiling, WaylandClientStatePtr, WindowAppearance,
|
|
WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowControls, WindowDecorations,
|
|
WindowParams, px, size,
|
|
};
|
|
use crate::{
|
|
Capslock,
|
|
platform::{
|
|
PlatformAtlas, PlatformInputHandler, PlatformWindow,
|
|
blade::{BladeContext, BladeRenderer, BladeSurfaceConfig},
|
|
linux::wayland::{display::WaylandDisplay, serial::SerialKind},
|
|
},
|
|
};
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct Callbacks {
|
|
request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
|
|
input: Option<Box<dyn FnMut(crate::PlatformInput) -> crate::DispatchEventResult>>,
|
|
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
|
hover_status_change: Option<Box<dyn FnMut(bool)>>,
|
|
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
|
moved: Option<Box<dyn FnMut()>>,
|
|
should_close: Option<Box<dyn FnMut() -> bool>>,
|
|
close: Option<Box<dyn FnOnce()>>,
|
|
appearance_changed: Option<Box<dyn FnMut()>>,
|
|
}
|
|
|
|
struct RawWindow {
|
|
window: *mut c_void,
|
|
display: *mut c_void,
|
|
}
|
|
|
|
impl rwh::HasWindowHandle for RawWindow {
|
|
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
|
let window = NonNull::new(self.window).unwrap();
|
|
let handle = rwh::WaylandWindowHandle::new(window);
|
|
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
|
|
}
|
|
}
|
|
impl rwh::HasDisplayHandle for RawWindow {
|
|
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
|
|
let display = NonNull::new(self.display).unwrap();
|
|
let handle = rwh::WaylandDisplayHandle::new(display);
|
|
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct InProgressConfigure {
|
|
size: Option<Size<Pixels>>,
|
|
fullscreen: bool,
|
|
maximized: bool,
|
|
resizing: bool,
|
|
tiling: Tiling,
|
|
}
|
|
|
|
pub struct WaylandWindowState {
|
|
xdg_surface: xdg_surface::XdgSurface,
|
|
acknowledged_first_configure: bool,
|
|
pub surface: wl_surface::WlSurface,
|
|
decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
|
|
app_id: Option<String>,
|
|
appearance: WindowAppearance,
|
|
blur: Option<org_kde_kwin_blur::OrgKdeKwinBlur>,
|
|
toplevel: xdg_toplevel::XdgToplevel,
|
|
viewport: Option<wp_viewport::WpViewport>,
|
|
outputs: HashMap<ObjectId, Output>,
|
|
display: Option<(ObjectId, Output)>,
|
|
globals: Globals,
|
|
renderer: BladeRenderer,
|
|
bounds: Bounds<Pixels>,
|
|
scale: f32,
|
|
input_handler: Option<PlatformInputHandler>,
|
|
decorations: WindowDecorations,
|
|
background_appearance: WindowBackgroundAppearance,
|
|
fullscreen: bool,
|
|
maximized: bool,
|
|
tiling: Tiling,
|
|
window_bounds: Bounds<Pixels>,
|
|
client: WaylandClientStatePtr,
|
|
handle: AnyWindowHandle,
|
|
active: bool,
|
|
hovered: bool,
|
|
in_progress_configure: Option<InProgressConfigure>,
|
|
resize_throttle: bool,
|
|
in_progress_window_controls: Option<WindowControls>,
|
|
window_controls: WindowControls,
|
|
client_inset: Option<Pixels>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct WaylandWindowStatePtr {
|
|
state: Rc<RefCell<WaylandWindowState>>,
|
|
callbacks: Rc<RefCell<Callbacks>>,
|
|
}
|
|
|
|
impl WaylandWindowState {
|
|
pub(crate) fn new(
|
|
handle: AnyWindowHandle,
|
|
surface: wl_surface::WlSurface,
|
|
xdg_surface: xdg_surface::XdgSurface,
|
|
toplevel: xdg_toplevel::XdgToplevel,
|
|
decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
|
|
appearance: WindowAppearance,
|
|
viewport: Option<wp_viewport::WpViewport>,
|
|
client: WaylandClientStatePtr,
|
|
globals: Globals,
|
|
gpu_context: &BladeContext,
|
|
options: WindowParams,
|
|
) -> anyhow::Result<Self> {
|
|
let renderer = {
|
|
let raw_window = RawWindow {
|
|
window: surface.id().as_ptr().cast::<c_void>(),
|
|
display: surface
|
|
.backend()
|
|
.upgrade()
|
|
.unwrap()
|
|
.display_ptr()
|
|
.cast::<c_void>(),
|
|
};
|
|
let config = BladeSurfaceConfig {
|
|
size: gpu::Extent {
|
|
width: options.bounds.size.width.0 as u32,
|
|
height: options.bounds.size.height.0 as u32,
|
|
depth: 1,
|
|
},
|
|
transparent: true,
|
|
};
|
|
BladeRenderer::new(gpu_context, &raw_window, config)?
|
|
};
|
|
|
|
Ok(Self {
|
|
xdg_surface,
|
|
acknowledged_first_configure: false,
|
|
surface,
|
|
decoration,
|
|
app_id: None,
|
|
blur: None,
|
|
toplevel,
|
|
viewport,
|
|
globals,
|
|
outputs: HashMap::default(),
|
|
display: None,
|
|
renderer,
|
|
bounds: options.bounds,
|
|
scale: 1.0,
|
|
input_handler: None,
|
|
decorations: WindowDecorations::Client,
|
|
background_appearance: WindowBackgroundAppearance::Opaque,
|
|
fullscreen: false,
|
|
maximized: false,
|
|
tiling: Tiling::default(),
|
|
window_bounds: options.bounds,
|
|
in_progress_configure: None,
|
|
resize_throttle: false,
|
|
client,
|
|
appearance,
|
|
handle,
|
|
active: false,
|
|
hovered: false,
|
|
in_progress_window_controls: None,
|
|
window_controls: WindowControls::default(),
|
|
client_inset: None,
|
|
})
|
|
}
|
|
|
|
pub fn is_transparent(&self) -> bool {
|
|
self.decorations == WindowDecorations::Client
|
|
|| self.background_appearance != WindowBackgroundAppearance::Opaque
|
|
}
|
|
|
|
pub fn primary_output_scale(&mut self) -> i32 {
|
|
let mut scale = 1;
|
|
let mut current_output = self.display.take();
|
|
for (id, output) in self.outputs.iter() {
|
|
if let Some((_, output_data)) = ¤t_output {
|
|
if output.scale > output_data.scale {
|
|
current_output = Some((id.clone(), output.clone()));
|
|
}
|
|
} else {
|
|
current_output = Some((id.clone(), output.clone()));
|
|
}
|
|
scale = scale.max(output.scale);
|
|
}
|
|
self.display = current_output;
|
|
scale
|
|
}
|
|
|
|
pub fn inset(&self) -> Pixels {
|
|
match self.decorations {
|
|
WindowDecorations::Server => px(0.0),
|
|
WindowDecorations::Client => self.client_inset.unwrap_or(px(0.0)),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) struct WaylandWindow(pub WaylandWindowStatePtr);
|
|
pub enum ImeInput {
|
|
InsertText(String),
|
|
SetMarkedText(String),
|
|
UnmarkText,
|
|
DeleteText,
|
|
}
|
|
|
|
impl Drop for WaylandWindow {
|
|
fn drop(&mut self) {
|
|
let mut state = self.0.state.borrow_mut();
|
|
let surface_id = state.surface.id();
|
|
let client = state.client.clone();
|
|
|
|
state.renderer.destroy();
|
|
if let Some(decoration) = &state.decoration {
|
|
decoration.destroy();
|
|
}
|
|
if let Some(blur) = &state.blur {
|
|
blur.release();
|
|
}
|
|
state.toplevel.destroy();
|
|
if let Some(viewport) = &state.viewport {
|
|
viewport.destroy();
|
|
}
|
|
state.xdg_surface.destroy();
|
|
state.surface.destroy();
|
|
|
|
let state_ptr = self.0.clone();
|
|
state
|
|
.globals
|
|
.executor
|
|
.spawn(async move {
|
|
state_ptr.close();
|
|
client.drop_window(&surface_id)
|
|
})
|
|
.detach();
|
|
drop(state);
|
|
}
|
|
}
|
|
|
|
impl WaylandWindow {
|
|
fn borrow(&self) -> Ref<'_, WaylandWindowState> {
|
|
self.0.state.borrow()
|
|
}
|
|
|
|
fn borrow_mut(&self) -> RefMut<'_, WaylandWindowState> {
|
|
self.0.state.borrow_mut()
|
|
}
|
|
|
|
pub fn new(
|
|
handle: AnyWindowHandle,
|
|
globals: Globals,
|
|
gpu_context: &BladeContext,
|
|
client: WaylandClientStatePtr,
|
|
params: WindowParams,
|
|
appearance: WindowAppearance,
|
|
) -> anyhow::Result<(Self, ObjectId)> {
|
|
let surface = globals.compositor.create_surface(&globals.qh, ());
|
|
let xdg_surface = globals
|
|
.wm_base
|
|
.get_xdg_surface(&surface, &globals.qh, surface.id());
|
|
let toplevel = xdg_surface.get_toplevel(&globals.qh, surface.id());
|
|
|
|
if let Some(size) = params.window_min_size {
|
|
toplevel.set_min_size(size.width.0 as i32, size.height.0 as i32);
|
|
}
|
|
|
|
if let Some(fractional_scale_manager) = globals.fractional_scale_manager.as_ref() {
|
|
fractional_scale_manager.get_fractional_scale(&surface, &globals.qh, surface.id());
|
|
}
|
|
|
|
// Attempt to set up window decorations based on the requested configuration
|
|
let decoration = globals
|
|
.decoration_manager
|
|
.as_ref()
|
|
.map(|decoration_manager| {
|
|
decoration_manager.get_toplevel_decoration(&toplevel, &globals.qh, surface.id())
|
|
});
|
|
|
|
let viewport = globals
|
|
.viewporter
|
|
.as_ref()
|
|
.map(|viewporter| viewporter.get_viewport(&surface, &globals.qh, ()));
|
|
|
|
let this = Self(WaylandWindowStatePtr {
|
|
state: Rc::new(RefCell::new(WaylandWindowState::new(
|
|
handle,
|
|
surface.clone(),
|
|
xdg_surface,
|
|
toplevel,
|
|
decoration,
|
|
appearance,
|
|
viewport,
|
|
client,
|
|
globals,
|
|
gpu_context,
|
|
params,
|
|
)?)),
|
|
callbacks: Rc::new(RefCell::new(Callbacks::default())),
|
|
});
|
|
|
|
// Kick things off
|
|
surface.commit();
|
|
|
|
Ok((this, surface.id()))
|
|
}
|
|
}
|
|
|
|
impl WaylandWindowStatePtr {
|
|
pub fn handle(&self) -> AnyWindowHandle {
|
|
self.state.borrow().handle
|
|
}
|
|
|
|
pub fn surface(&self) -> wl_surface::WlSurface {
|
|
self.state.borrow().surface.clone()
|
|
}
|
|
|
|
pub fn ptr_eq(&self, other: &Self) -> bool {
|
|
Rc::ptr_eq(&self.state, &other.state)
|
|
}
|
|
|
|
pub fn frame(&self) {
|
|
let mut state = self.state.borrow_mut();
|
|
state.surface.frame(&state.globals.qh, state.surface.id());
|
|
state.resize_throttle = false;
|
|
drop(state);
|
|
|
|
let mut cb = self.callbacks.borrow_mut();
|
|
if let Some(fun) = cb.request_frame.as_mut() {
|
|
fun(Default::default());
|
|
}
|
|
}
|
|
|
|
pub fn handle_xdg_surface_event(&self, event: xdg_surface::Event) {
|
|
match event {
|
|
xdg_surface::Event::Configure { serial } => {
|
|
{
|
|
let mut state = self.state.borrow_mut();
|
|
if let Some(window_controls) = state.in_progress_window_controls.take() {
|
|
state.window_controls = window_controls;
|
|
|
|
drop(state);
|
|
let mut callbacks = self.callbacks.borrow_mut();
|
|
if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
|
|
appearance_changed();
|
|
}
|
|
}
|
|
}
|
|
{
|
|
let mut state = self.state.borrow_mut();
|
|
|
|
if let Some(mut configure) = state.in_progress_configure.take() {
|
|
let got_unmaximized = state.maximized && !configure.maximized;
|
|
state.fullscreen = configure.fullscreen;
|
|
state.maximized = configure.maximized;
|
|
state.tiling = configure.tiling;
|
|
// Limit interactive resizes to once per vblank
|
|
if configure.resizing && state.resize_throttle {
|
|
return;
|
|
} else if configure.resizing {
|
|
state.resize_throttle = true;
|
|
}
|
|
if !configure.fullscreen && !configure.maximized {
|
|
configure.size = if got_unmaximized {
|
|
Some(state.window_bounds.size)
|
|
} else {
|
|
compute_outer_size(state.inset(), configure.size, state.tiling)
|
|
};
|
|
if let Some(size) = configure.size {
|
|
state.window_bounds = Bounds {
|
|
origin: Point::default(),
|
|
size,
|
|
};
|
|
}
|
|
}
|
|
drop(state);
|
|
if let Some(size) = configure.size {
|
|
self.resize(size);
|
|
}
|
|
}
|
|
}
|
|
let mut state = self.state.borrow_mut();
|
|
state.xdg_surface.ack_configure(serial);
|
|
|
|
let window_geometry = inset_by_tiling(
|
|
state.bounds.map_origin(|_| px(0.0)),
|
|
state.inset(),
|
|
state.tiling,
|
|
)
|
|
.map(|v| v.0 as i32)
|
|
.map_size(|v| if v <= 0 { 1 } else { v });
|
|
|
|
state.xdg_surface.set_window_geometry(
|
|
window_geometry.origin.x,
|
|
window_geometry.origin.y,
|
|
window_geometry.size.width,
|
|
window_geometry.size.height,
|
|
);
|
|
|
|
let request_frame_callback = !state.acknowledged_first_configure;
|
|
if request_frame_callback {
|
|
state.acknowledged_first_configure = true;
|
|
drop(state);
|
|
self.frame();
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
pub fn handle_toplevel_decoration_event(&self, event: zxdg_toplevel_decoration_v1::Event) {
|
|
match event {
|
|
zxdg_toplevel_decoration_v1::Event::Configure { mode } => match mode {
|
|
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => {
|
|
self.state.borrow_mut().decorations = WindowDecorations::Server;
|
|
if let Some(mut appearance_changed) =
|
|
self.callbacks.borrow_mut().appearance_changed.as_mut()
|
|
{
|
|
appearance_changed();
|
|
}
|
|
}
|
|
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => {
|
|
self.state.borrow_mut().decorations = WindowDecorations::Client;
|
|
// Update background to be transparent
|
|
if let Some(mut appearance_changed) =
|
|
self.callbacks.borrow_mut().appearance_changed.as_mut()
|
|
{
|
|
appearance_changed();
|
|
}
|
|
}
|
|
WEnum::Value(_) => {
|
|
log::warn!("Unknown decoration mode");
|
|
}
|
|
WEnum::Unknown(v) => {
|
|
log::warn!("Unknown decoration mode: {}", v);
|
|
}
|
|
},
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
pub fn handle_fractional_scale_event(&self, event: wp_fractional_scale_v1::Event) {
|
|
match event {
|
|
wp_fractional_scale_v1::Event::PreferredScale { scale } => {
|
|
self.rescale(scale as f32 / 120.0);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
pub fn handle_toplevel_event(&self, event: xdg_toplevel::Event) -> bool {
|
|
match event {
|
|
xdg_toplevel::Event::Configure {
|
|
width,
|
|
height,
|
|
states,
|
|
} => {
|
|
let mut size = if width == 0 || height == 0 {
|
|
None
|
|
} else {
|
|
Some(size(px(width as f32), px(height as f32)))
|
|
};
|
|
|
|
let states = extract_states::<xdg_toplevel::State>(&states);
|
|
|
|
let mut tiling = Tiling::default();
|
|
let mut fullscreen = false;
|
|
let mut maximized = false;
|
|
let mut resizing = false;
|
|
|
|
for state in states {
|
|
match state {
|
|
xdg_toplevel::State::Maximized => {
|
|
maximized = true;
|
|
}
|
|
xdg_toplevel::State::Fullscreen => {
|
|
fullscreen = true;
|
|
}
|
|
xdg_toplevel::State::Resizing => resizing = true,
|
|
xdg_toplevel::State::TiledTop => {
|
|
tiling.top = true;
|
|
}
|
|
xdg_toplevel::State::TiledLeft => {
|
|
tiling.left = true;
|
|
}
|
|
xdg_toplevel::State::TiledRight => {
|
|
tiling.right = true;
|
|
}
|
|
xdg_toplevel::State::TiledBottom => {
|
|
tiling.bottom = true;
|
|
}
|
|
_ => {
|
|
// noop
|
|
}
|
|
}
|
|
}
|
|
|
|
if fullscreen || maximized {
|
|
tiling = Tiling::tiled();
|
|
}
|
|
|
|
let mut state = self.state.borrow_mut();
|
|
state.in_progress_configure = Some(InProgressConfigure {
|
|
size,
|
|
fullscreen,
|
|
maximized,
|
|
resizing,
|
|
tiling,
|
|
});
|
|
|
|
false
|
|
}
|
|
xdg_toplevel::Event::Close => {
|
|
let mut cb = self.callbacks.borrow_mut();
|
|
if let Some(mut should_close) = cb.should_close.take() {
|
|
let result = (should_close)();
|
|
cb.should_close = Some(should_close);
|
|
if result {
|
|
drop(cb);
|
|
self.close();
|
|
}
|
|
result
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
xdg_toplevel::Event::WmCapabilities { capabilities } => {
|
|
let mut window_controls = WindowControls::default();
|
|
|
|
let states = extract_states::<xdg_toplevel::WmCapabilities>(&capabilities);
|
|
|
|
for state in states {
|
|
match state {
|
|
xdg_toplevel::WmCapabilities::Maximize => {
|
|
window_controls.maximize = true;
|
|
}
|
|
xdg_toplevel::WmCapabilities::Minimize => {
|
|
window_controls.minimize = true;
|
|
}
|
|
xdg_toplevel::WmCapabilities::Fullscreen => {
|
|
window_controls.fullscreen = true;
|
|
}
|
|
xdg_toplevel::WmCapabilities::WindowMenu => {
|
|
window_controls.window_menu = true;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
let mut state = self.state.borrow_mut();
|
|
state.in_progress_window_controls = Some(window_controls);
|
|
false
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::mutable_key_type)]
|
|
pub fn handle_surface_event(
|
|
&self,
|
|
event: wl_surface::Event,
|
|
outputs: HashMap<ObjectId, Output>,
|
|
) {
|
|
let mut state = self.state.borrow_mut();
|
|
|
|
match event {
|
|
wl_surface::Event::Enter { output } => {
|
|
let id = output.id();
|
|
|
|
let Some(output) = outputs.get(&id) else {
|
|
return;
|
|
};
|
|
|
|
state.outputs.insert(id, output.clone());
|
|
|
|
let scale = state.primary_output_scale();
|
|
|
|
// We use `PreferredBufferScale` instead to set the scale if it's available
|
|
if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
|
|
state.surface.set_buffer_scale(scale);
|
|
drop(state);
|
|
self.rescale(scale as f32);
|
|
}
|
|
}
|
|
wl_surface::Event::Leave { output } => {
|
|
state.outputs.remove(&output.id());
|
|
|
|
let scale = state.primary_output_scale();
|
|
|
|
// We use `PreferredBufferScale` instead to set the scale if it's available
|
|
if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
|
|
state.surface.set_buffer_scale(scale);
|
|
drop(state);
|
|
self.rescale(scale as f32);
|
|
}
|
|
}
|
|
wl_surface::Event::PreferredBufferScale { factor } => {
|
|
// We use `WpFractionalScale` instead to set the scale if it's available
|
|
if state.globals.fractional_scale_manager.is_none() {
|
|
state.surface.set_buffer_scale(factor);
|
|
drop(state);
|
|
self.rescale(factor as f32);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
pub fn handle_ime(&self, ime: ImeInput) {
|
|
let mut state = self.state.borrow_mut();
|
|
if let Some(mut input_handler) = state.input_handler.take() {
|
|
drop(state);
|
|
match ime {
|
|
ImeInput::InsertText(text) => {
|
|
input_handler.replace_text_in_range(None, &text);
|
|
}
|
|
ImeInput::SetMarkedText(text) => {
|
|
input_handler.replace_and_mark_text_in_range(None, &text, None);
|
|
}
|
|
ImeInput::UnmarkText => {
|
|
input_handler.unmark_text();
|
|
}
|
|
ImeInput::DeleteText => {
|
|
if let Some(marked) = input_handler.marked_text_range() {
|
|
input_handler.replace_text_in_range(Some(marked), "");
|
|
}
|
|
}
|
|
}
|
|
self.state.borrow_mut().input_handler = Some(input_handler);
|
|
}
|
|
}
|
|
|
|
pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
|
|
let mut state = self.state.borrow_mut();
|
|
let mut bounds: Option<Bounds<Pixels>> = None;
|
|
if let Some(mut input_handler) = state.input_handler.take() {
|
|
drop(state);
|
|
if let Some(selection) = input_handler.marked_text_range() {
|
|
bounds = input_handler.bounds_for_range(selection.start..selection.start);
|
|
}
|
|
self.state.borrow_mut().input_handler = Some(input_handler);
|
|
}
|
|
bounds
|
|
}
|
|
|
|
pub fn set_size_and_scale(&self, size: Option<Size<Pixels>>, scale: Option<f32>) {
|
|
let (size, scale) = {
|
|
let mut state = self.state.borrow_mut();
|
|
if size.map_or(true, |size| size == state.bounds.size)
|
|
&& scale.map_or(true, |scale| scale == state.scale)
|
|
{
|
|
return;
|
|
}
|
|
if let Some(size) = size {
|
|
state.bounds.size = size;
|
|
}
|
|
if let Some(scale) = scale {
|
|
state.scale = scale;
|
|
}
|
|
let device_bounds = state.bounds.to_device_pixels(state.scale);
|
|
state.renderer.update_drawable_size(device_bounds.size);
|
|
(state.bounds.size, state.scale)
|
|
};
|
|
|
|
if let Some(ref mut fun) = self.callbacks.borrow_mut().resize {
|
|
fun(size, scale);
|
|
}
|
|
|
|
{
|
|
let state = self.state.borrow();
|
|
if let Some(viewport) = &state.viewport {
|
|
viewport.set_destination(size.width.0 as i32, size.height.0 as i32);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn resize(&self, size: Size<Pixels>) {
|
|
self.set_size_and_scale(Some(size), None);
|
|
}
|
|
|
|
pub fn rescale(&self, scale: f32) {
|
|
self.set_size_and_scale(None, Some(scale));
|
|
}
|
|
|
|
pub fn close(&self) {
|
|
let mut callbacks = self.callbacks.borrow_mut();
|
|
if let Some(fun) = callbacks.close.take() {
|
|
fun()
|
|
}
|
|
}
|
|
|
|
pub fn handle_input(&self, input: PlatformInput) {
|
|
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
|
|
if !fun(input.clone()).propagate {
|
|
return;
|
|
}
|
|
}
|
|
if let PlatformInput::KeyDown(event) = input {
|
|
if event.keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
|
|
if let Some(key_char) = &event.keystroke.key_char {
|
|
let mut state = self.state.borrow_mut();
|
|
if let Some(mut input_handler) = state.input_handler.take() {
|
|
drop(state);
|
|
input_handler.replace_text_in_range(None, key_char);
|
|
self.state.borrow_mut().input_handler = Some(input_handler);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn set_focused(&self, focus: bool) {
|
|
self.state.borrow_mut().active = focus;
|
|
if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
|
|
fun(focus);
|
|
}
|
|
}
|
|
|
|
pub fn set_hovered(&self, focus: bool) {
|
|
if let Some(ref mut fun) = self.callbacks.borrow_mut().hover_status_change {
|
|
fun(focus);
|
|
}
|
|
}
|
|
|
|
pub fn set_appearance(&mut self, appearance: WindowAppearance) {
|
|
self.state.borrow_mut().appearance = appearance;
|
|
|
|
let mut callbacks = self.callbacks.borrow_mut();
|
|
if let Some(ref mut fun) = callbacks.appearance_changed {
|
|
(fun)()
|
|
}
|
|
}
|
|
|
|
pub fn primary_output_scale(&self) -> i32 {
|
|
self.state.borrow_mut().primary_output_scale()
|
|
}
|
|
}
|
|
|
|
fn extract_states<'a, S: TryFrom<u32> + 'a>(states: &'a [u8]) -> impl Iterator<Item = S> + 'a
|
|
where
|
|
<S as TryFrom<u32>>::Error: 'a,
|
|
{
|
|
states
|
|
.chunks_exact(4)
|
|
.flat_map(TryInto::<[u8; 4]>::try_into)
|
|
.map(u32::from_ne_bytes)
|
|
.flat_map(S::try_from)
|
|
}
|
|
|
|
impl rwh::HasWindowHandle for WaylandWindow {
|
|
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
|
let surface = self.0.surface().id().as_ptr() as *mut libc::c_void;
|
|
let c_ptr = NonNull::new(surface).ok_or(rwh::HandleError::Unavailable)?;
|
|
let handle = rwh::WaylandWindowHandle::new(c_ptr);
|
|
let raw_handle = rwh::RawWindowHandle::Wayland(handle);
|
|
Ok(unsafe { rwh::WindowHandle::borrow_raw(raw_handle) })
|
|
}
|
|
}
|
|
|
|
impl rwh::HasDisplayHandle for WaylandWindow {
|
|
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
|
|
let display = self
|
|
.0
|
|
.surface()
|
|
.backend()
|
|
.upgrade()
|
|
.ok_or(rwh::HandleError::Unavailable)?
|
|
.display_ptr() as *mut libc::c_void;
|
|
|
|
let c_ptr = NonNull::new(display).ok_or(rwh::HandleError::Unavailable)?;
|
|
let handle = rwh::WaylandDisplayHandle::new(c_ptr);
|
|
let raw_handle = rwh::RawDisplayHandle::Wayland(handle);
|
|
Ok(unsafe { rwh::DisplayHandle::borrow_raw(raw_handle) })
|
|
}
|
|
}
|
|
|
|
impl PlatformWindow for WaylandWindow {
|
|
fn bounds(&self) -> Bounds<Pixels> {
|
|
self.borrow().bounds
|
|
}
|
|
|
|
fn is_maximized(&self) -> bool {
|
|
self.borrow().maximized
|
|
}
|
|
|
|
fn window_bounds(&self) -> WindowBounds {
|
|
let state = self.borrow();
|
|
if state.fullscreen {
|
|
WindowBounds::Fullscreen(state.window_bounds)
|
|
} else if state.maximized {
|
|
WindowBounds::Maximized(state.window_bounds)
|
|
} else {
|
|
drop(state);
|
|
WindowBounds::Windowed(self.bounds())
|
|
}
|
|
}
|
|
|
|
fn inner_window_bounds(&self) -> WindowBounds {
|
|
let state = self.borrow();
|
|
if state.fullscreen {
|
|
WindowBounds::Fullscreen(state.window_bounds)
|
|
} else if state.maximized {
|
|
WindowBounds::Maximized(state.window_bounds)
|
|
} else {
|
|
let inset = state.inset();
|
|
drop(state);
|
|
WindowBounds::Windowed(self.bounds().inset(inset))
|
|
}
|
|
}
|
|
|
|
fn content_size(&self) -> Size<Pixels> {
|
|
self.borrow().bounds.size
|
|
}
|
|
|
|
fn resize(&mut self, size: Size<Pixels>) {
|
|
let state = self.borrow();
|
|
let state_ptr = self.0.clone();
|
|
let dp_size = size.to_device_pixels(self.scale_factor());
|
|
|
|
state.xdg_surface.set_window_geometry(
|
|
state.bounds.origin.x.0 as i32,
|
|
state.bounds.origin.y.0 as i32,
|
|
dp_size.width.0,
|
|
dp_size.height.0,
|
|
);
|
|
|
|
state
|
|
.globals
|
|
.executor
|
|
.spawn(async move { state_ptr.resize(size) })
|
|
.detach();
|
|
}
|
|
|
|
fn scale_factor(&self) -> f32 {
|
|
self.borrow().scale
|
|
}
|
|
|
|
fn appearance(&self) -> WindowAppearance {
|
|
self.borrow().appearance
|
|
}
|
|
|
|
fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
|
|
let state = self.borrow();
|
|
state.display.as_ref().map(|(id, display)| {
|
|
Rc::new(WaylandDisplay {
|
|
id: id.clone(),
|
|
name: display.name.clone(),
|
|
bounds: display.bounds.to_pixels(state.scale),
|
|
}) as Rc<dyn PlatformDisplay>
|
|
})
|
|
}
|
|
|
|
fn mouse_position(&self) -> Point<Pixels> {
|
|
self.borrow()
|
|
.client
|
|
.get_client()
|
|
.borrow()
|
|
.mouse_location
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn modifiers(&self) -> Modifiers {
|
|
self.borrow().client.get_client().borrow().modifiers
|
|
}
|
|
|
|
fn capslock(&self) -> Capslock {
|
|
self.borrow().client.get_client().borrow().capslock
|
|
}
|
|
|
|
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
|
|
self.borrow_mut().input_handler = Some(input_handler);
|
|
}
|
|
|
|
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
|
|
self.borrow_mut().input_handler.take()
|
|
}
|
|
|
|
fn prompt(
|
|
&self,
|
|
_level: PromptLevel,
|
|
_msg: &str,
|
|
_detail: Option<&str>,
|
|
_answers: &[PromptButton],
|
|
) -> Option<Receiver<usize>> {
|
|
None
|
|
}
|
|
|
|
fn activate(&self) {
|
|
// Try to request an activation token. Even though the activation is likely going to be rejected,
|
|
// KWin and Mutter can use the app_id to visually indicate we're requesting attention.
|
|
let state = self.borrow();
|
|
if let (Some(activation), Some(app_id)) = (&state.globals.activation, state.app_id.clone())
|
|
{
|
|
state.client.set_pending_activation(state.surface.id());
|
|
let token = activation.get_activation_token(&state.globals.qh, ());
|
|
// The serial isn't exactly important here, since the activation is probably going to be rejected anyway.
|
|
let serial = state.client.get_serial(SerialKind::MousePress);
|
|
token.set_app_id(app_id);
|
|
token.set_serial(serial, &state.globals.seat);
|
|
token.set_surface(&state.surface);
|
|
token.commit();
|
|
}
|
|
}
|
|
|
|
fn is_active(&self) -> bool {
|
|
self.borrow().active
|
|
}
|
|
|
|
fn is_hovered(&self) -> bool {
|
|
self.borrow().hovered
|
|
}
|
|
|
|
fn set_title(&mut self, title: &str) {
|
|
self.borrow().toplevel.set_title(title.to_string());
|
|
}
|
|
|
|
fn set_app_id(&mut self, app_id: &str) {
|
|
let mut state = self.borrow_mut();
|
|
state.toplevel.set_app_id(app_id.to_owned());
|
|
state.app_id = Some(app_id.to_owned());
|
|
}
|
|
|
|
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
|
let mut state = self.borrow_mut();
|
|
state.background_appearance = background_appearance;
|
|
update_window(state);
|
|
}
|
|
|
|
fn minimize(&self) {
|
|
self.borrow().toplevel.set_minimized();
|
|
}
|
|
|
|
fn zoom(&self) {
|
|
let state = self.borrow();
|
|
if !state.maximized {
|
|
state.toplevel.set_maximized();
|
|
} else {
|
|
state.toplevel.unset_maximized();
|
|
}
|
|
}
|
|
|
|
fn toggle_fullscreen(&self) {
|
|
let mut state = self.borrow_mut();
|
|
if !state.fullscreen {
|
|
state.toplevel.set_fullscreen(None);
|
|
} else {
|
|
state.toplevel.unset_fullscreen();
|
|
}
|
|
}
|
|
|
|
fn is_fullscreen(&self) -> bool {
|
|
self.borrow().fullscreen
|
|
}
|
|
|
|
fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
|
|
self.0.callbacks.borrow_mut().request_frame = Some(callback);
|
|
}
|
|
|
|
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
|
|
self.0.callbacks.borrow_mut().input = Some(callback);
|
|
}
|
|
|
|
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
|
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
|
|
}
|
|
|
|
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
|
self.0.callbacks.borrow_mut().hover_status_change = Some(callback);
|
|
}
|
|
|
|
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
|
self.0.callbacks.borrow_mut().resize = Some(callback);
|
|
}
|
|
|
|
fn on_moved(&self, callback: Box<dyn FnMut()>) {
|
|
self.0.callbacks.borrow_mut().moved = Some(callback);
|
|
}
|
|
|
|
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
|
|
self.0.callbacks.borrow_mut().should_close = Some(callback);
|
|
}
|
|
|
|
fn on_close(&self, callback: Box<dyn FnOnce()>) {
|
|
self.0.callbacks.borrow_mut().close = Some(callback);
|
|
}
|
|
|
|
fn on_hit_test_window_control(&self, _callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
|
|
}
|
|
|
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
|
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
|
}
|
|
|
|
fn draw(&self, scene: &Scene) {
|
|
let mut state = self.borrow_mut();
|
|
state.renderer.draw(scene);
|
|
}
|
|
|
|
fn completed_frame(&self) {
|
|
let state = self.borrow();
|
|
state.surface.commit();
|
|
}
|
|
|
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
|
let state = self.borrow();
|
|
state.renderer.sprite_atlas().clone()
|
|
}
|
|
|
|
fn show_window_menu(&self, position: Point<Pixels>) {
|
|
let state = self.borrow();
|
|
let serial = state.client.get_serial(SerialKind::MousePress);
|
|
state.toplevel.show_window_menu(
|
|
&state.globals.seat,
|
|
serial,
|
|
position.x.0 as i32,
|
|
position.y.0 as i32,
|
|
);
|
|
}
|
|
|
|
fn start_window_move(&self) {
|
|
let state = self.borrow();
|
|
let serial = state.client.get_serial(SerialKind::MousePress);
|
|
state.toplevel._move(&state.globals.seat, serial);
|
|
}
|
|
|
|
fn start_window_resize(&self, edge: crate::ResizeEdge) {
|
|
let state = self.borrow();
|
|
state.toplevel.resize(
|
|
&state.globals.seat,
|
|
state.client.get_serial(SerialKind::MousePress),
|
|
edge.to_xdg(),
|
|
)
|
|
}
|
|
|
|
fn window_decorations(&self) -> Decorations {
|
|
let state = self.borrow();
|
|
match state.decorations {
|
|
WindowDecorations::Server => Decorations::Server,
|
|
WindowDecorations::Client => Decorations::Client {
|
|
tiling: state.tiling,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn request_decorations(&self, decorations: WindowDecorations) {
|
|
let mut state = self.borrow_mut();
|
|
state.decorations = decorations;
|
|
if let Some(decoration) = state.decoration.as_ref() {
|
|
decoration.set_mode(decorations.to_xdg());
|
|
update_window(state);
|
|
}
|
|
}
|
|
|
|
fn window_controls(&self) -> WindowControls {
|
|
self.borrow().window_controls
|
|
}
|
|
|
|
fn set_client_inset(&self, inset: Pixels) {
|
|
let mut state = self.borrow_mut();
|
|
if Some(inset) != state.client_inset {
|
|
state.client_inset = Some(inset);
|
|
update_window(state);
|
|
}
|
|
}
|
|
|
|
fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) {
|
|
let state = self.borrow();
|
|
state.client.update_ime_position(bounds);
|
|
}
|
|
|
|
fn gpu_specs(&self) -> Option<GpuSpecs> {
|
|
self.borrow().renderer.gpu_specs().into()
|
|
}
|
|
}
|
|
|
|
fn update_window(mut state: RefMut<WaylandWindowState>) {
|
|
let opaque = !state.is_transparent();
|
|
|
|
state.renderer.update_transparency(!opaque);
|
|
let mut opaque_area = state.window_bounds.map(|v| v.0 as i32);
|
|
opaque_area.inset(state.inset().0 as i32);
|
|
|
|
let region = state
|
|
.globals
|
|
.compositor
|
|
.create_region(&state.globals.qh, ());
|
|
region.add(
|
|
opaque_area.origin.x,
|
|
opaque_area.origin.y,
|
|
opaque_area.size.width,
|
|
opaque_area.size.height,
|
|
);
|
|
|
|
// Note that rounded corners make this rectangle API hard to work with.
|
|
// As this is common when using CSD, let's just disable this API.
|
|
if state.background_appearance == WindowBackgroundAppearance::Opaque
|
|
&& state.decorations == WindowDecorations::Server
|
|
{
|
|
// Promise the compositor that this region of the window surface
|
|
// contains no transparent pixels. This allows the compositor to skip
|
|
// updating whatever is behind the surface for better performance.
|
|
state.surface.set_opaque_region(Some(®ion));
|
|
} else {
|
|
state.surface.set_opaque_region(None);
|
|
}
|
|
|
|
if let Some(ref blur_manager) = state.globals.blur_manager {
|
|
if state.background_appearance == WindowBackgroundAppearance::Blurred {
|
|
if state.blur.is_none() {
|
|
let blur = blur_manager.create(&state.surface, &state.globals.qh, ());
|
|
state.blur = Some(blur);
|
|
}
|
|
state.blur.as_ref().unwrap().commit();
|
|
} else {
|
|
// It probably doesn't hurt to clear the blur for opaque windows
|
|
blur_manager.unset(&state.surface);
|
|
if let Some(b) = state.blur.take() {
|
|
b.release()
|
|
}
|
|
}
|
|
}
|
|
|
|
region.destroy();
|
|
}
|
|
|
|
impl WindowDecorations {
|
|
fn to_xdg(&self) -> zxdg_toplevel_decoration_v1::Mode {
|
|
match self {
|
|
WindowDecorations::Client => zxdg_toplevel_decoration_v1::Mode::ClientSide,
|
|
WindowDecorations::Server => zxdg_toplevel_decoration_v1::Mode::ServerSide,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ResizeEdge {
|
|
fn to_xdg(&self) -> xdg_toplevel::ResizeEdge {
|
|
match self {
|
|
ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top,
|
|
ResizeEdge::TopRight => xdg_toplevel::ResizeEdge::TopRight,
|
|
ResizeEdge::Right => xdg_toplevel::ResizeEdge::Right,
|
|
ResizeEdge::BottomRight => xdg_toplevel::ResizeEdge::BottomRight,
|
|
ResizeEdge::Bottom => xdg_toplevel::ResizeEdge::Bottom,
|
|
ResizeEdge::BottomLeft => xdg_toplevel::ResizeEdge::BottomLeft,
|
|
ResizeEdge::Left => xdg_toplevel::ResizeEdge::Left,
|
|
ResizeEdge::TopLeft => xdg_toplevel::ResizeEdge::TopLeft,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The configuration event is in terms of the window geometry, which we are constantly
|
|
/// updating to account for the client decorations. But that's not the area we want to render
|
|
/// to, due to our intrusize CSD. So, here we calculate the 'actual' size, by adding back in the insets
|
|
fn compute_outer_size(
|
|
inset: Pixels,
|
|
new_size: Option<Size<Pixels>>,
|
|
tiling: Tiling,
|
|
) -> Option<Size<Pixels>> {
|
|
new_size.map(|mut new_size| {
|
|
if !tiling.top {
|
|
new_size.height += inset;
|
|
}
|
|
if !tiling.bottom {
|
|
new_size.height += inset;
|
|
}
|
|
if !tiling.left {
|
|
new_size.width += inset;
|
|
}
|
|
if !tiling.right {
|
|
new_size.width += inset;
|
|
}
|
|
|
|
new_size
|
|
})
|
|
}
|
|
|
|
fn inset_by_tiling(mut bounds: Bounds<Pixels>, inset: Pixels, tiling: Tiling) -> Bounds<Pixels> {
|
|
if !tiling.top {
|
|
bounds.origin.y += inset;
|
|
bounds.size.height -= inset;
|
|
}
|
|
if !tiling.bottom {
|
|
bounds.size.height -= inset;
|
|
}
|
|
if !tiling.left {
|
|
bounds.origin.x += inset;
|
|
bounds.size.width -= inset;
|
|
}
|
|
if !tiling.right {
|
|
bounds.size.width -= inset;
|
|
}
|
|
|
|
bounds
|
|
}
|