parent
552c7a6252
commit
fe1eab07ae
9 changed files with 227 additions and 32 deletions
|
|
@ -1041,18 +1041,20 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
|
|||
|
||||
/// Sets the window icon.
|
||||
///
|
||||
/// On Windows and X11, this is typically the small icon in the top-left
|
||||
/// On Windows, Wayland and X11, this is typically the small icon in the top-left
|
||||
/// corner of the titlebar.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Wayland / macOS / Orbital:** Unsupported.
|
||||
/// - **iOS / Android / Web / / macOS / Orbital:** Unsupported.
|
||||
///
|
||||
/// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's
|
||||
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
|
||||
///
|
||||
/// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM.
|
||||
/// That said, it's usually in the same ballpark as on Windows.
|
||||
///
|
||||
/// - **Wayland:** The compositor needs to implement `xdg_toplevel_icon`.
|
||||
fn set_window_icon(&self, window_icon: Option<Icon>);
|
||||
|
||||
/// Set the IME cursor editing area, where the `position` is the top left corner of that area
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ use std::ptr::NonNull;
|
|||
use dpi::{LogicalSize, PhysicalSize};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Proxy;
|
||||
use sctk::shm::slot::{Buffer, CreateBufferError, SlotPool};
|
||||
use wayland_client::protocol::wl_shm::Format;
|
||||
use winit_core::event_loop::ActiveEventLoop as CoreActiveEventLoop;
|
||||
use winit_core::window::{
|
||||
ActivationToken, PlatformWindowAttributes, Window as CoreWindow, WindowId,
|
||||
|
|
@ -138,3 +140,27 @@ fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> Phy
|
|||
let height = size.height as f64 * scale_factor;
|
||||
(width.round(), height.round()).into()
|
||||
}
|
||||
|
||||
/// Converts an image buffer to a Wayland buffer (`wl_buffer`)
|
||||
fn image_to_buffer(
|
||||
width: i32,
|
||||
height: i32,
|
||||
data: &[u8],
|
||||
format: Format,
|
||||
pool: &mut SlotPool,
|
||||
) -> Result<Buffer, CreateBufferError> {
|
||||
let (buffer, canvas) = pool.create_buffer(width, height, 4 * width, format)?;
|
||||
|
||||
for (canvas_chunk, rgba) in canvas.chunks_exact_mut(4).zip(data.chunks_exact(4)) {
|
||||
// Alpha in buffer is premultiplied.
|
||||
let alpha = rgba[3] as f32 / 255.;
|
||||
let r = (rgba[0] as f32 * alpha) as u32;
|
||||
let g = (rgba[1] as f32 * alpha) as u32;
|
||||
let b = (rgba[2] as f32 * alpha) as u32;
|
||||
let color = ((rgba[3] as u32) << 24) + (r << 16) + (g << 8) + b;
|
||||
let array: &mut [u8; 4] = canvas_chunk.try_into().unwrap();
|
||||
*array = color.to_le_bytes();
|
||||
}
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use crate::types::kwin_blur::KWinBlurManager;
|
|||
use crate::types::wp_fractional_scaling::FractionalScalingManager;
|
||||
use crate::types::wp_viewporter::ViewporterState;
|
||||
use crate::types::xdg_activation::XdgActivationState;
|
||||
use crate::types::xdg_toplevel_icon_manager::XdgToplevelIconManagerState;
|
||||
use crate::window::{WindowRequests, WindowState};
|
||||
use crate::WindowId;
|
||||
|
||||
|
|
@ -56,9 +57,6 @@ pub struct WinitState {
|
|||
/// The shm for software buffers, such as cursors.
|
||||
pub shm: Shm,
|
||||
|
||||
/// The pool where custom cursors are allocated.
|
||||
pub custom_cursor_pool: Arc<Mutex<SlotPool>>,
|
||||
|
||||
/// The XDG shell that is used for windows.
|
||||
pub xdg_shell: XdgShell,
|
||||
|
||||
|
|
@ -93,6 +91,12 @@ pub struct WinitState {
|
|||
/// Xdg activation.
|
||||
pub xdg_activation: Option<XdgActivationState>,
|
||||
|
||||
/// Xdg toplevel icon manager
|
||||
pub xdg_toplevel_icon_manager: Option<XdgToplevelIconManagerState>,
|
||||
|
||||
/// The pool where images are allocated (used for window icons and custom cursors)
|
||||
pub image_pool: Arc<Mutex<SlotPool>>,
|
||||
|
||||
/// Relative pointer.
|
||||
pub relative_pointer: Option<RelativePointerState>,
|
||||
|
||||
|
|
@ -158,7 +162,7 @@ impl WinitState {
|
|||
};
|
||||
|
||||
let shm = Shm::bind(globals, queue_handle).map_err(|err| os_error!(err))?;
|
||||
let custom_cursor_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap()));
|
||||
let image_pool = Arc::new(Mutex::new(SlotPool::new(2, &shm).unwrap()));
|
||||
|
||||
Ok(Self {
|
||||
registry_state,
|
||||
|
|
@ -167,10 +171,13 @@ impl WinitState {
|
|||
output_state,
|
||||
seat_state,
|
||||
shm,
|
||||
custom_cursor_pool,
|
||||
|
||||
xdg_shell: XdgShell::bind(globals, queue_handle).map_err(|err| os_error!(err))?,
|
||||
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
|
||||
xdg_toplevel_icon_manager: XdgToplevelIconManagerState::bind(globals, queue_handle)
|
||||
.ok(),
|
||||
|
||||
image_pool,
|
||||
|
||||
windows: Default::default(),
|
||||
window_requests: Default::default(),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ use sctk::reexports::client::protocol::wl_shm::Format;
|
|||
use sctk::shm::slot::{Buffer, SlotPool};
|
||||
use winit_core::cursor::{CursorImage, CustomCursorProvider};
|
||||
|
||||
use crate::image_to_buffer;
|
||||
|
||||
// Wrap in our own type to not impl trait on global type.
|
||||
#[derive(Debug)]
|
||||
pub struct WaylandCustomCursor(pub(crate) CursorImage);
|
||||
|
|
@ -36,25 +38,14 @@ pub struct CustomCursor {
|
|||
impl CustomCursor {
|
||||
pub(crate) fn new(pool: &mut SlotPool, image: &WaylandCustomCursor) -> Self {
|
||||
let image = &image.0;
|
||||
let (buffer, canvas) = pool
|
||||
.create_buffer(
|
||||
image.width() as i32,
|
||||
image.height() as i32,
|
||||
4 * (image.width() as i32),
|
||||
Format::Argb8888,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for (canvas_chunk, rgba) in canvas.chunks_exact_mut(4).zip(image.buffer().chunks_exact(4)) {
|
||||
// Alpha in buffer is premultiplied.
|
||||
let alpha = rgba[3] as f32 / 255.;
|
||||
let r = (rgba[0] as f32 * alpha) as u32;
|
||||
let g = (rgba[1] as f32 * alpha) as u32;
|
||||
let b = (rgba[2] as f32 * alpha) as u32;
|
||||
let color = ((rgba[3] as u32) << 24) + (r << 16) + (g << 8) + b;
|
||||
let array: &mut [u8; 4] = canvas_chunk.try_into().unwrap();
|
||||
*array = color.to_le_bytes();
|
||||
}
|
||||
let buffer = image_to_buffer(
|
||||
image.width() as i32,
|
||||
image.height() as i32,
|
||||
image.buffer(),
|
||||
Format::Argb8888,
|
||||
pool,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
CustomCursor {
|
||||
buffer,
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ pub mod kwin_blur;
|
|||
pub mod wp_fractional_scaling;
|
||||
pub mod wp_viewporter;
|
||||
pub mod xdg_activation;
|
||||
pub mod xdg_toplevel_icon_manager;
|
||||
|
|
|
|||
108
winit-wayland/src/types/xdg_toplevel_icon_manager.rs
Normal file
108
winit-wayland/src/types/xdg_toplevel_icon_manager.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
//! Handling of xdg toplevel icon manager, which is used for icon setting requests.
|
||||
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::shm::slot::{Buffer, SlotPool};
|
||||
use wayland_client::globals::{BindError, GlobalList};
|
||||
use wayland_client::protocol::wl_shm::Format;
|
||||
use wayland_client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
|
||||
use wayland_protocols::xdg::toplevel_icon::v1::client::xdg_toplevel_icon_manager_v1::XdgToplevelIconManagerV1;
|
||||
use wayland_protocols::xdg::toplevel_icon::v1::client::xdg_toplevel_icon_v1::XdgToplevelIconV1;
|
||||
use winit_core::icon::{Icon, RgbaIcon};
|
||||
|
||||
use crate::image_to_buffer;
|
||||
use crate::state::WinitState;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XdgToplevelIconManagerState {
|
||||
xdg_toplevel_icon_manager: XdgToplevelIconManagerV1,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum ToplevelIconError {
|
||||
/// The icon's unsupported
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToplevelIcon {
|
||||
buffer: Buffer,
|
||||
}
|
||||
|
||||
impl ToplevelIcon {
|
||||
pub fn new(icon: Icon, pool: &mut SlotPool) -> Result<Self, ToplevelIconError> {
|
||||
let icon = match icon.cast_ref::<RgbaIcon>() {
|
||||
Some(icon) => icon,
|
||||
None => return Err(ToplevelIconError::Unsupported),
|
||||
};
|
||||
|
||||
let buffer = image_to_buffer(
|
||||
icon.width() as i32,
|
||||
icon.height() as i32,
|
||||
icon.buffer(),
|
||||
Format::Argb8888,
|
||||
pool,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(Self { buffer })
|
||||
}
|
||||
|
||||
pub fn add_buffer(&self, xdg_toplevel_icon: &XdgToplevelIconV1) {
|
||||
xdg_toplevel_icon.add_buffer(self.buffer.wl_buffer(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ToplevelIconError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ToplevelIconError::Unsupported => write!(f, "this icon is unsupported on Wayland"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XdgToplevelIconManagerState {
|
||||
pub fn bind(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let xdg_toplevel_icon_manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { xdg_toplevel_icon_manager })
|
||||
}
|
||||
|
||||
pub fn global(&self) -> &XdgToplevelIconManagerV1 {
|
||||
&self.xdg_toplevel_icon_manager
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgToplevelIconManagerV1, GlobalData, WinitState> for XdgToplevelIconManagerState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &XdgToplevelIconManagerV1,
|
||||
_event: <XdgToplevelIconManagerV1 as Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
// No events.
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<XdgToplevelIconV1, GlobalData, WinitState> for XdgToplevelIconManagerState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &XdgToplevelIconV1,
|
||||
_event: <XdgToplevelIconV1 as Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
// No events.
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [XdgToplevelIconManagerV1: GlobalData] => XdgToplevelIconManagerState);
|
||||
delegate_dispatch!(WinitState: [XdgToplevelIconV1: GlobalData] => XdgToplevelIconManagerState);
|
||||
|
|
@ -114,6 +114,8 @@ impl Window {
|
|||
attributes.preferred_theme,
|
||||
);
|
||||
|
||||
window_state.set_window_icon(attributes.window_icon);
|
||||
|
||||
// Set transparency hint.
|
||||
window_state.set_transparent(attributes.transparent);
|
||||
|
||||
|
|
@ -501,7 +503,9 @@ impl CoreWindow for Window {
|
|||
|
||||
fn set_window_level(&self, _level: WindowLevel) {}
|
||||
|
||||
fn set_window_icon(&self, _window_icon: Option<winit_core::icon::Icon>) {}
|
||||
fn set_window_icon(&self, window_icon: Option<winit_core::icon::Icon>) {
|
||||
self.window_state.lock().unwrap().set_window_icon(window_icon)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_ime_cursor_area(&self, position: Position, size: Size) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use std::time::Duration;
|
|||
use ahash::HashSet;
|
||||
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Size};
|
||||
use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt};
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
|
|
@ -27,6 +28,7 @@ use sctk::shm::slot::SlotPool;
|
|||
use sctk::shm::Shm;
|
||||
use sctk::subcompositor::SubcompositorState;
|
||||
use tracing::{info, warn};
|
||||
use wayland_protocols::xdg::toplevel_icon::v1::client::xdg_toplevel_icon_manager_v1::XdgToplevelIconManagerV1;
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
|
||||
use winit_core::cursor::{CursorIcon, CustomCursor as CoreCustomCursor};
|
||||
use winit_core::error::{NotSupportedError, RequestError};
|
||||
|
|
@ -40,6 +42,7 @@ use crate::seat::{
|
|||
use crate::state::{WindowCompositorUpdate, WinitState};
|
||||
use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor};
|
||||
use crate::types::kwin_blur::KWinBlurManager;
|
||||
use crate::types::xdg_toplevel_icon_manager::ToplevelIcon;
|
||||
|
||||
#[cfg(feature = "sctk-adwaita")]
|
||||
pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
|
||||
|
|
@ -58,9 +61,6 @@ pub struct WindowState {
|
|||
/// The `Shm` to set cursor.
|
||||
pub shm: WlShm,
|
||||
|
||||
// A shared pool where to allocate custom cursors.
|
||||
custom_cursor_pool: Arc<Mutex<SlotPool>>,
|
||||
|
||||
/// The last received configure.
|
||||
pub last_configure: Option<WindowConfigure>,
|
||||
|
||||
|
|
@ -84,6 +84,15 @@ pub struct WindowState {
|
|||
/// The current window title.
|
||||
title: String,
|
||||
|
||||
/// Xdg toplevel icon manager to request icon setting.
|
||||
xdg_toplevel_icon_manager: Option<XdgToplevelIconManagerV1>,
|
||||
|
||||
/// The current window toplevel icon
|
||||
toplevel_icon: Option<ToplevelIcon>,
|
||||
|
||||
/// A shared pool where to allocate images (used for window icons and custom cursors)
|
||||
image_pool: Arc<Mutex<SlotPool>>,
|
||||
|
||||
/// Whether the frame is resizable.
|
||||
resizable: bool,
|
||||
|
||||
|
|
@ -180,7 +189,14 @@ impl WindowState {
|
|||
.as_ref()
|
||||
.map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
|
||||
|
||||
let xdg_toplevel_icon_manager = winit_state
|
||||
.xdg_toplevel_icon_manager
|
||||
.as_ref()
|
||||
.map(|toplevel_icon_manager_state| toplevel_icon_manager_state.global().clone());
|
||||
|
||||
Self {
|
||||
toplevel_icon: None,
|
||||
xdg_toplevel_icon_manager,
|
||||
blur: None,
|
||||
blur_manager: winit_state.kwin_blur_manager.clone(),
|
||||
compositor,
|
||||
|
|
@ -206,7 +222,7 @@ impl WindowState {
|
|||
resizable: true,
|
||||
scale_factor: 1.,
|
||||
shm: winit_state.shm.wl_shm().clone(),
|
||||
custom_cursor_pool: winit_state.custom_cursor_pool.clone(),
|
||||
image_pool: winit_state.image_pool.clone(),
|
||||
size: initial_size.to_logical(1.),
|
||||
stateless_size: initial_size.to_logical(1.),
|
||||
initial_size: Some(initial_size),
|
||||
|
|
@ -711,7 +727,7 @@ impl WindowState {
|
|||
};
|
||||
|
||||
let cursor = {
|
||||
let mut pool = self.custom_cursor_pool.lock().unwrap();
|
||||
let mut pool = self.image_pool.lock().unwrap();
|
||||
CustomCursor::new(&mut pool, cursor)
|
||||
};
|
||||
|
||||
|
|
@ -1069,6 +1085,45 @@ impl WindowState {
|
|||
self.title = title;
|
||||
}
|
||||
|
||||
/// Set the window's icon
|
||||
pub fn set_window_icon(&mut self, window_icon: Option<winit_core::icon::Icon>) {
|
||||
let xdg_toplevel_icon_manager = match self.xdg_toplevel_icon_manager.as_ref() {
|
||||
Some(xdg_toplevel_icon_manager) => xdg_toplevel_icon_manager,
|
||||
None => {
|
||||
warn!("`xdg_toplevel_icon_manager_v1` is not supported");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let (toplevel_icon, xdg_toplevel_icon) = match window_icon {
|
||||
Some(icon) => {
|
||||
let mut image_pool = self.image_pool.lock().unwrap();
|
||||
let toplevel_icon = match ToplevelIcon::new(icon, &mut image_pool) {
|
||||
Ok(toplevel_icon) => toplevel_icon,
|
||||
Err(error) => {
|
||||
warn!("Error setting window icon: {error}");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let xdg_toplevel_icon =
|
||||
xdg_toplevel_icon_manager.create_icon(&self.queue_handle, GlobalData);
|
||||
|
||||
toplevel_icon.add_buffer(&xdg_toplevel_icon);
|
||||
|
||||
(Some(toplevel_icon), Some(xdg_toplevel_icon))
|
||||
},
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
xdg_toplevel_icon_manager.set_icon(self.window.xdg_toplevel(), xdg_toplevel_icon.as_ref());
|
||||
self.toplevel_icon = toplevel_icon;
|
||||
|
||||
if let Some(xdg_toplevel_icon) = xdg_toplevel_icon {
|
||||
xdg_toplevel_icon.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark the window as transparent.
|
||||
#[inline]
|
||||
pub fn set_transparent(&mut self, transparent: bool) {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ changelog entry.
|
|||
- `keyboard::ModifiersKey` to track which modifier is exactly pressed.
|
||||
- `ActivationToken::as_raw` to get a ref to raw token.
|
||||
- Each platform now has corresponding `WindowAttributes` struct instead of trait extension.
|
||||
- On Wayland, added implementation for `Window::set_window_icon`
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue