mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Merge pull request #258 from pinnacle-comp/xcursor
Some checks failed
CI (Pinnacle) / Build (push) Waiting to run
CI (Pinnacle) / Run tests (push) Waiting to run
CI (Pinnacle) / Check formatting (push) Waiting to run
CI (Pinnacle) / Clippy check (push) Waiting to run
Build Lua Docs / Build Lua docs (push) Has been cancelled
Build Rust Docs / Build docs (push) Has been cancelled
Some checks failed
CI (Pinnacle) / Build (push) Waiting to run
CI (Pinnacle) / Run tests (push) Waiting to run
CI (Pinnacle) / Check formatting (push) Waiting to run
CI (Pinnacle) / Clippy check (push) Waiting to run
Build Lua Docs / Build Lua docs (push) Has been cancelled
Build Rust Docs / Build docs (push) Has been cancelled
Cursor enhancements
This commit is contained in:
commit
b655a17547
19 changed files with 655 additions and 365 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2117,7 +2117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-targets 0.52.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -66,6 +66,9 @@ features = [
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
too_many_arguments = "allow"
|
too_many_arguments = "allow"
|
||||||
|
new_without_default = "allow"
|
||||||
|
type_complexity = "allow"
|
||||||
|
let_and_return = "allow"
|
||||||
|
|
||||||
########################################################################yo😎###########
|
########################################################################yo😎###########
|
||||||
|
|
||||||
|
|
|
@ -350,6 +350,10 @@ local pinnacle_input_v0alpha1_SetLibinputSettingRequest_TapButtonMap = {
|
||||||
---@field tap_drag_lock boolean?
|
---@field tap_drag_lock boolean?
|
||||||
---@field tap boolean?
|
---@field tap boolean?
|
||||||
|
|
||||||
|
---@class SetXcursorRequest
|
||||||
|
---@field theme string?
|
||||||
|
---@field size integer?
|
||||||
|
|
||||||
-- Process
|
-- Process
|
||||||
|
|
||||||
---@class pinnacle.process.v0alpha1.SpawnRequest
|
---@class pinnacle.process.v0alpha1.SpawnRequest
|
||||||
|
@ -770,6 +774,13 @@ defs.pinnacle = {
|
||||||
request = "pinnacle.input.v0alpha1.SetLibinputSettingRequest",
|
request = "pinnacle.input.v0alpha1.SetLibinputSettingRequest",
|
||||||
response = "google.protobuf.Empty",
|
response = "google.protobuf.Empty",
|
||||||
},
|
},
|
||||||
|
---@type GrpcRequestArgs
|
||||||
|
SetXcursor = {
|
||||||
|
service = "pinnacle.input.v0alpha1.InputService",
|
||||||
|
method = "SetXcursor",
|
||||||
|
request = "pinnacle.input.v0alpha1.SetXcursorRequest",
|
||||||
|
response = "google.protobuf.Empty",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -352,4 +352,28 @@ function input.set_libinput_settings(settings)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Sets the current xcursor theme.
|
||||||
|
---
|
||||||
|
---Pinnacle reads `$XCURSOR_THEME` on startup to set the theme.
|
||||||
|
---This allows you to set it at runtime.
|
||||||
|
---
|
||||||
|
---@param theme string
|
||||||
|
function input.set_xcursor_theme(theme)
|
||||||
|
client.unary_request(input_service.SetXcursor, {
|
||||||
|
theme = theme,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
---Sets the current xcursor size.
|
||||||
|
---
|
||||||
|
---Pinnacle reads `$XCURSOR_SIZE` on startup to set the cursor size.
|
||||||
|
---This allows you to set it at runtime.
|
||||||
|
---
|
||||||
|
---@param size integer
|
||||||
|
function input.set_xcursor_size(size)
|
||||||
|
client.unary_request(input_service.SetXcursor, {
|
||||||
|
size = size,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
return input
|
return input
|
||||||
|
|
|
@ -143,6 +143,11 @@ message SetLibinputSettingRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SetXcursorRequest {
|
||||||
|
optional string theme = 1;
|
||||||
|
optional uint32 size = 2;
|
||||||
|
}
|
||||||
|
|
||||||
service InputService {
|
service InputService {
|
||||||
rpc SetKeybind(SetKeybindRequest) returns (stream SetKeybindResponse);
|
rpc SetKeybind(SetKeybindRequest) returns (stream SetKeybindResponse);
|
||||||
rpc SetMousebind(SetMousebindRequest) returns (stream SetMousebindResponse);
|
rpc SetMousebind(SetMousebindRequest) returns (stream SetMousebindResponse);
|
||||||
|
@ -153,4 +158,6 @@ service InputService {
|
||||||
rpc SetRepeatRate(SetRepeatRateRequest) returns (google.protobuf.Empty);
|
rpc SetRepeatRate(SetRepeatRateRequest) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
rpc SetLibinputSetting(SetLibinputSettingRequest) returns (google.protobuf.Empty);
|
rpc SetLibinputSetting(SetLibinputSettingRequest) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
|
rpc SetXcursor(SetXcursorRequest) returns (google.protobuf.Empty);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use pinnacle_api_defs::pinnacle::input::{
|
||||||
input_service_client::InputServiceClient,
|
input_service_client::InputServiceClient,
|
||||||
set_libinput_setting_request::{CalibrationMatrix, Setting},
|
set_libinput_setting_request::{CalibrationMatrix, Setting},
|
||||||
KeybindDescriptionsRequest, SetKeybindRequest, SetLibinputSettingRequest,
|
KeybindDescriptionsRequest, SetKeybindRequest, SetLibinputSettingRequest,
|
||||||
SetMousebindRequest, SetRepeatRateRequest, SetXkbConfigRequest,
|
SetMousebindRequest, SetRepeatRateRequest, SetXcursorRequest, SetXkbConfigRequest,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
@ -402,6 +402,46 @@ impl Input {
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the xcursor theme.
|
||||||
|
///
|
||||||
|
/// Pinnacle reads `$XCURSOR_THEME` on startup to determine the theme.
|
||||||
|
/// This allows you to set it at runtime.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// input.set_xcursor_theme("Adwaita");
|
||||||
|
/// ```
|
||||||
|
pub fn set_xcursor_theme(&self, theme: impl ToString) {
|
||||||
|
let mut client = self.create_input_client();
|
||||||
|
|
||||||
|
block_on_tokio(client.set_xcursor(SetXcursorRequest {
|
||||||
|
theme: Some(theme.to_string()),
|
||||||
|
size: None,
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the xcursor size.
|
||||||
|
///
|
||||||
|
/// Pinnacle reads `$XCURSOR_SIZE` on startup to determine the cursor size.
|
||||||
|
/// This allows you to set it at runtime.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// input.set_xcursor_size(64);
|
||||||
|
/// ```
|
||||||
|
pub fn set_xcursor_size(&self, size: u32) {
|
||||||
|
let mut client = self.create_input_client();
|
||||||
|
|
||||||
|
block_on_tokio(client.set_xcursor(SetXcursorRequest {
|
||||||
|
theme: None,
|
||||||
|
size: Some(size),
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait that designates anything that can be converted into a [`Keysym`].
|
/// A trait that designates anything that can be converted into a [`Keysym`].
|
||||||
|
|
27
src/api.rs
27
src/api.rs
|
@ -11,7 +11,7 @@ use pinnacle_api_defs::pinnacle::{
|
||||||
set_mousebind_request::MouseEdge,
|
set_mousebind_request::MouseEdge,
|
||||||
KeybindDescription, KeybindDescriptionsRequest, KeybindDescriptionsResponse, Modifier,
|
KeybindDescription, KeybindDescriptionsRequest, KeybindDescriptionsResponse, Modifier,
|
||||||
SetKeybindRequest, SetKeybindResponse, SetLibinputSettingRequest, SetMousebindRequest,
|
SetKeybindRequest, SetKeybindResponse, SetLibinputSettingRequest, SetMousebindRequest,
|
||||||
SetMousebindResponse, SetRepeatRateRequest, SetXkbConfigRequest,
|
SetMousebindResponse, SetRepeatRateRequest, SetXcursorRequest, SetXkbConfigRequest,
|
||||||
},
|
},
|
||||||
output::{
|
output::{
|
||||||
self,
|
self,
|
||||||
|
@ -586,6 +586,31 @@ impl input_service_server::InputService for InputService {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_xcursor(
|
||||||
|
&self,
|
||||||
|
request: Request<SetXcursorRequest>,
|
||||||
|
) -> Result<Response<()>, Status> {
|
||||||
|
let request = request.into_inner();
|
||||||
|
|
||||||
|
let theme = request.theme;
|
||||||
|
let size = request.size;
|
||||||
|
|
||||||
|
run_unary_no_response(&self.sender, move |state| {
|
||||||
|
if let Some(theme) = theme {
|
||||||
|
state.pinnacle.cursor_state.set_theme(&theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(size) = size {
|
||||||
|
state.pinnacle.cursor_state.set_size(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(output) = state.pinnacle.focused_output().cloned() {
|
||||||
|
state.schedule_render(&output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProcessService {
|
pub struct ProcessService {
|
||||||
|
|
|
@ -489,7 +489,6 @@ impl window_service_server::WindowService for WindowService {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(window) = pointer_focus.window_for(state) else {
|
let Some(window) = pointer_focus.window_for(state) else {
|
||||||
tracing::info!("Move grabs are currently not implemented for non-windows");
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(wl_surf) = window.wl_surface() else {
|
let Some(wl_surf) = window.wl_surface() else {
|
||||||
|
@ -498,6 +497,10 @@ impl window_service_server::WindowService for WindowService {
|
||||||
let seat = state.pinnacle.seat.clone();
|
let seat = state.pinnacle.seat.clone();
|
||||||
|
|
||||||
state.move_request_server(&wl_surf, &seat, SERIAL_COUNTER.next_serial(), button);
|
state.move_request_server(&wl_surf, &seat, SERIAL_COUNTER.next_serial(), button);
|
||||||
|
|
||||||
|
if let Some(output) = state.pinnacle.focused_output().cloned() {
|
||||||
|
state.schedule_render(&output);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -579,6 +582,10 @@ impl window_service_server::WindowService for WindowService {
|
||||||
edges.into(),
|
edges.into(),
|
||||||
button,
|
button,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let Some(output) = state.pinnacle.focused_output().cloned() {
|
||||||
|
state.schedule_render(&output);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,11 +32,9 @@ use smithay::{
|
||||||
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||||
renderer::{
|
renderer::{
|
||||||
self, damage,
|
self, damage,
|
||||||
element::{
|
element::{self, surface::render_elements_from_surface_tree, Element, Id},
|
||||||
self, surface::render_elements_from_surface_tree, texture::TextureBuffer, Element,
|
|
||||||
},
|
|
||||||
gles::{GlesRenderbuffer, GlesRenderer},
|
gles::{GlesRenderbuffer, GlesRenderer},
|
||||||
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer, MultiTexture},
|
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer},
|
||||||
sync::SyncPoint,
|
sync::SyncPoint,
|
||||||
utils::{CommitCounter, DamageSet},
|
utils::{CommitCounter, DamageSet},
|
||||||
Bind, Blit, BufferType, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen,
|
Bind, Blit, BufferType, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen,
|
||||||
|
@ -57,8 +55,8 @@ use smithay::{
|
||||||
reexports::{
|
reexports::{
|
||||||
ash::vk::ExtPhysicalDeviceDrmFn,
|
ash::vk::ExtPhysicalDeviceDrmFn,
|
||||||
calloop::{
|
calloop::{
|
||||||
self, generic::Generic, Dispatcher, Idle, Interest, LoopHandle, PostAction,
|
self, generic::Generic, timer::Timer, Dispatcher, Idle, Interest, LoopHandle,
|
||||||
RegistrationToken,
|
PostAction, RegistrationToken,
|
||||||
},
|
},
|
||||||
drm::control::{connector, crtc, ModeTypeFlags},
|
drm::control::{connector, crtc, ModeTypeFlags},
|
||||||
input::Libinput,
|
input::Libinput,
|
||||||
|
@ -86,8 +84,8 @@ use crate::{
|
||||||
config::ConnectorSavedState,
|
config::ConnectorSavedState,
|
||||||
output::{BlankingState, OutputMode, OutputName},
|
output::{BlankingState, OutputMode, OutputName},
|
||||||
render::{
|
render::{
|
||||||
pointer::PointerElement, pointer_render_elements, take_presentation_feedback,
|
pointer::pointer_render_elements, take_presentation_feedback, OutputRenderElement,
|
||||||
OutputRenderElement, CLEAR_COLOR, CLEAR_COLOR_LOCKED,
|
CLEAR_COLOR, CLEAR_COLOR_LOCKED,
|
||||||
},
|
},
|
||||||
state::{Pinnacle, State, SurfaceDmabufFeedback, WithState},
|
state::{Pinnacle, State, SurfaceDmabufFeedback, WithState},
|
||||||
};
|
};
|
||||||
|
@ -134,9 +132,6 @@ pub struct Udev {
|
||||||
allocator: Option<Box<dyn Allocator<Buffer = Dmabuf, Error = AnyError>>>,
|
allocator: Option<Box<dyn Allocator<Buffer = Dmabuf, Error = AnyError>>>,
|
||||||
pub(super) gpu_manager: GpuManager<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>,
|
pub(super) gpu_manager: GpuManager<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>,
|
||||||
backends: HashMap<DrmNode, UdevBackendData>,
|
backends: HashMap<DrmNode, UdevBackendData>,
|
||||||
pointer_images: Vec<(xcursor::parser::Image, TextureBuffer<MultiTexture>)>,
|
|
||||||
pointer_element: PointerElement<MultiTexture>,
|
|
||||||
pointer_image: crate::cursor::Cursor,
|
|
||||||
|
|
||||||
pub(super) upscale_filter: TextureFilter,
|
pub(super) upscale_filter: TextureFilter,
|
||||||
pub(super) downscale_filter: TextureFilter,
|
pub(super) downscale_filter: TextureFilter,
|
||||||
|
@ -225,9 +220,6 @@ impl Udev {
|
||||||
gpu_manager,
|
gpu_manager,
|
||||||
allocator: None,
|
allocator: None,
|
||||||
backends: HashMap::new(),
|
backends: HashMap::new(),
|
||||||
pointer_image: crate::cursor::Cursor::load(),
|
|
||||||
pointer_images: Vec::new(),
|
|
||||||
pointer_element: PointerElement::default(),
|
|
||||||
|
|
||||||
upscale_filter: TextureFilter::Linear,
|
upscale_filter: TextureFilter::Linear,
|
||||||
downscale_filter: TextureFilter::Linear,
|
downscale_filter: TextureFilter::Linear,
|
||||||
|
@ -933,7 +925,7 @@ impl Udev {
|
||||||
state
|
state
|
||||||
.backend
|
.backend
|
||||||
.udev_mut()
|
.udev_mut()
|
||||||
.on_vblank(&state.pinnacle, node, crtc, metadata);
|
.on_vblank(&mut state.pinnacle, node, crtc, metadata);
|
||||||
}
|
}
|
||||||
DrmEvent::Error(error) => {
|
DrmEvent::Error(error) => {
|
||||||
error!("{:?}", error);
|
error!("{:?}", error);
|
||||||
|
@ -1271,7 +1263,7 @@ impl Udev {
|
||||||
/// Mark [`OutputPresentationFeedback`]s as presented and schedule a new render on idle.
|
/// Mark [`OutputPresentationFeedback`]s as presented and schedule a new render on idle.
|
||||||
fn on_vblank(
|
fn on_vblank(
|
||||||
&mut self,
|
&mut self,
|
||||||
pinnacle: &Pinnacle,
|
pinnacle: &mut Pinnacle,
|
||||||
dev_id: DrmNode,
|
dev_id: DrmNode,
|
||||||
crtc: crtc::Handle,
|
crtc: crtc::Handle,
|
||||||
metadata: &mut Option<DrmEventMetadata>,
|
metadata: &mut Option<DrmEventMetadata>,
|
||||||
|
@ -1369,6 +1361,36 @@ impl Udev {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Schedule a render when the next frame of an animated cursor should be drawn.
|
||||||
|
//
|
||||||
|
// TODO: Remove this and improve the render pipeline.
|
||||||
|
// Because of how the event loop works and the current implementation of rendering,
|
||||||
|
// immediately queuing a render here has the possibility of not submitting a new frame to
|
||||||
|
// DRM, meaning no vblank. The event loop will wait as it has no events, so things like
|
||||||
|
// animated cursors may hitch and only update when, for example, the cursor is actively
|
||||||
|
// moving as this generates events.
|
||||||
|
//
|
||||||
|
// What we should do is what Niri does: if `render_surface` doesn't cause any damage,
|
||||||
|
// instead of setting the `RenderState` to Idle, set it to some "waiting for estimated
|
||||||
|
// vblank" state and have `render_surface` always schedule a timer to fire at the estimated
|
||||||
|
// vblank time that will attempt another render schedule.
|
||||||
|
//
|
||||||
|
// This has the advantage of scheduling a render in a source and not in an idle callback,
|
||||||
|
// meaning we are guarenteed to have a render happen immediately and we won't have to wait
|
||||||
|
// for another event or call `loop_signal.wakeup()`.
|
||||||
|
if let Some(until) = pinnacle
|
||||||
|
.cursor_state
|
||||||
|
.time_until_next_animated_cursor_frame()
|
||||||
|
{
|
||||||
|
let _ = pinnacle.loop_handle.insert_source(
|
||||||
|
Timer::from_duration(until),
|
||||||
|
move |_, _, state| {
|
||||||
|
state.schedule_render(&output);
|
||||||
|
calloop::timer::TimeoutAction::Drop
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render to the [`RenderSurface`] associated with the given `output`.
|
/// Render to the [`RenderSurface`] associated with the given `output`.
|
||||||
|
@ -1392,13 +1414,6 @@ impl Udev {
|
||||||
|
|
||||||
assert!(matches!(surface.render_state, RenderState::Scheduled(_)));
|
assert!(matches!(surface.render_state, RenderState::Scheduled(_)));
|
||||||
|
|
||||||
// TODO get scale from the rendersurface when supporting HiDPI
|
|
||||||
let frame = self.pointer_image.get_image(
|
|
||||||
1,
|
|
||||||
// output.current_scale().integer_scale() as u32,
|
|
||||||
pinnacle.clock.now().into(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let render_node = surface.render_node;
|
let render_node = surface.render_node;
|
||||||
let primary_gpu = self.primary_gpu;
|
let primary_gpu = self.primary_gpu;
|
||||||
let mut renderer = if primary_gpu == render_node {
|
let mut renderer = if primary_gpu == render_node {
|
||||||
|
@ -1413,32 +1428,19 @@ impl Udev {
|
||||||
let _ = renderer.upscale_filter(self.upscale_filter);
|
let _ = renderer.upscale_filter(self.upscale_filter);
|
||||||
let _ = renderer.downscale_filter(self.downscale_filter);
|
let _ = renderer.downscale_filter(self.downscale_filter);
|
||||||
|
|
||||||
let pointer_images = &mut self.pointer_images;
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
let (pointer_image, hotspot) = pointer_images
|
|
||||||
.iter()
|
// draw the cursor as relevant and
|
||||||
.find_map(|(image, texture)| {
|
// reset the cursor if the surface is no longer alive
|
||||||
if image == &frame {
|
if let CursorImageStatus::Surface(surface) = &pinnacle.cursor_state.cursor_image() {
|
||||||
Some((texture.clone(), (frame.xhot as i32, frame.yhot as i32)))
|
if !surface.alive() {
|
||||||
} else {
|
pinnacle
|
||||||
None
|
.cursor_state
|
||||||
|
.set_cursor_image(CursorImageStatus::default_named());
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.unwrap_or_else(|| {
|
|
||||||
let texture = TextureBuffer::from_memory(
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
&mut renderer,
|
|
||||||
&frame.pixels_rgba,
|
|
||||||
Fourcc::Abgr8888,
|
|
||||||
(frame.width as i32, frame.height as i32),
|
|
||||||
false,
|
|
||||||
1,
|
|
||||||
Transform::Normal,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.expect("Failed to import cursor bitmap");
|
|
||||||
let hotspot = (frame.xhot as i32, frame.yhot as i32);
|
|
||||||
pointer_images.push((frame, texture.clone()));
|
|
||||||
(texture, hotspot)
|
|
||||||
});
|
|
||||||
|
|
||||||
let pointer_location = pinnacle
|
let pointer_location = pinnacle
|
||||||
.seat
|
.seat
|
||||||
|
@ -1446,71 +1448,40 @@ impl Udev {
|
||||||
.map(|ptr| ptr.current_location())
|
.map(|ptr| ptr.current_location())
|
||||||
.unwrap_or((0.0, 0.0).into());
|
.unwrap_or((0.0, 0.0).into());
|
||||||
|
|
||||||
// set cursor
|
|
||||||
self.pointer_element.set_texture(pointer_image.clone());
|
|
||||||
|
|
||||||
// draw the cursor as relevant and
|
|
||||||
// reset the cursor if the surface is no longer alive
|
|
||||||
if let CursorImageStatus::Surface(surface) = &pinnacle.cursor_status {
|
|
||||||
if !surface.alive() {
|
|
||||||
pinnacle.cursor_status = CursorImageStatus::default_named();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pointer_element
|
|
||||||
.set_status(pinnacle.cursor_status.clone());
|
|
||||||
|
|
||||||
let pending_screencopy_with_cursor =
|
|
||||||
output.with_state(|state| state.screencopy.as_ref().map(|sc| sc.overlay_cursor()));
|
|
||||||
|
|
||||||
let mut output_render_elements = Vec::new();
|
let mut output_render_elements = Vec::new();
|
||||||
|
|
||||||
let should_blank = pinnacle.lock_state.is_locking()
|
let should_blank = pinnacle.lock_state.is_locking()
|
||||||
|| (pinnacle.lock_state.is_locked()
|
|| (pinnacle.lock_state.is_locked()
|
||||||
&& output.with_state(|state| state.lock_surface.is_none()));
|
&& output.with_state(|state| state.lock_surface.is_none()));
|
||||||
|
|
||||||
// If there isn't a pending screencopy that doesn't want to overlay the cursor,
|
// HACK: Doing `blit_frame_result` with something on the cursor/overlay plane overwrites
|
||||||
// render it.
|
// transparency. This workaround makes the cursor not be on the cursor plane for blitting.
|
||||||
match pending_screencopy_with_cursor {
|
let kind = if output.with_state(|state| {
|
||||||
Some(include_cursor) if pinnacle.lock_state.is_unlocked() => {
|
state
|
||||||
if include_cursor {
|
.screencopy
|
||||||
// HACK: Doing `RenderFrameResult::blit_frame_result` with something on the
|
.as_ref()
|
||||||
// | cursor plane causes the cursor to overwrite the pixels underneath it,
|
.is_some_and(|sc| sc.overlay_cursor())
|
||||||
// | leading to a transparent hole under the cursor.
|
}) {
|
||||||
// | To circumvent that, we set the cursor to render on the primary plane instead.
|
element::Kind::Unspecified
|
||||||
// | Unfortunately that means I can't composite the cursor separately from
|
} else {
|
||||||
// | the screencopy, meaning if you have an active screencopy recording
|
element::Kind::Cursor
|
||||||
// | without cursor overlay then the cursor will dim/flicker out/disappear.
|
};
|
||||||
self.pointer_element
|
|
||||||
.set_element_kind(element::Kind::Unspecified);
|
let (pointer_render_elements, cursor_ids) = pointer_render_elements(
|
||||||
let pointer_render_elements = pointer_render_elements(
|
|
||||||
output,
|
output,
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
|
&mut pinnacle.cursor_state,
|
||||||
&pinnacle.space,
|
&pinnacle.space,
|
||||||
pointer_location,
|
pointer_location,
|
||||||
&mut pinnacle.cursor_status,
|
|
||||||
pinnacle.dnd_icon.as_ref(),
|
pinnacle.dnd_icon.as_ref(),
|
||||||
hotspot.into(),
|
&pinnacle.clock,
|
||||||
&self.pointer_element,
|
kind,
|
||||||
);
|
);
|
||||||
self.pointer_element.set_element_kind(element::Kind::Cursor);
|
output_render_elements.extend(
|
||||||
output_render_elements.extend(pointer_render_elements);
|
pointer_render_elements
|
||||||
}
|
.into_iter()
|
||||||
}
|
.map(OutputRenderElement::from),
|
||||||
_ => {
|
|
||||||
let pointer_render_elements = pointer_render_elements(
|
|
||||||
output,
|
|
||||||
&mut renderer,
|
|
||||||
&pinnacle.space,
|
|
||||||
pointer_location,
|
|
||||||
&mut pinnacle.cursor_status,
|
|
||||||
pinnacle.dnd_icon.as_ref(),
|
|
||||||
hotspot.into(),
|
|
||||||
&self.pointer_element,
|
|
||||||
);
|
);
|
||||||
output_render_elements.extend(pointer_render_elements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if should_blank {
|
if should_blank {
|
||||||
output.with_state_mut(|state| {
|
output.with_state_mut(|state| {
|
||||||
|
@ -1586,6 +1557,7 @@ impl Udev {
|
||||||
surface,
|
surface,
|
||||||
&render_frame_result,
|
&render_frame_result,
|
||||||
&pinnacle.loop_handle,
|
&pinnacle.loop_handle,
|
||||||
|
cursor_ids,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1601,7 +1573,7 @@ impl Udev {
|
||||||
scanout_feedback: &feedback.scanout_feedback,
|
scanout_feedback: &feedback.scanout_feedback,
|
||||||
}),
|
}),
|
||||||
Duration::from(pinnacle.clock.now()),
|
Duration::from(pinnacle.clock.now()),
|
||||||
&pinnacle.cursor_status,
|
pinnacle.cursor_state.cursor_image(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let rendered = !render_frame_result.is_empty;
|
let rendered = !render_frame_result.is_empty;
|
||||||
|
@ -1624,6 +1596,9 @@ impl Udev {
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(true) => surface.render_state = RenderState::WaitingForVblank { dirty: false },
|
Ok(true) => surface.render_state = RenderState::WaitingForVblank { dirty: false },
|
||||||
|
// TODO: Don't immediately set this to Idle; this allows hot loops of `render_surface`.
|
||||||
|
// Instead, pull a Niri and schedule a timer for the next estimated vblank to allow
|
||||||
|
// another scheduled render.
|
||||||
Ok(false) | Err(_) => surface.render_state = RenderState::Idle,
|
Ok(false) | Err(_) => surface.render_state = RenderState::Idle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1650,6 +1625,7 @@ fn handle_pending_screencopy<'a>(
|
||||||
surface: &mut RenderSurface,
|
surface: &mut RenderSurface,
|
||||||
render_frame_result: &UdevRenderFrameResult<'a>,
|
render_frame_result: &UdevRenderFrameResult<'a>,
|
||||||
loop_handle: &LoopHandle<'static, State>,
|
loop_handle: &LoopHandle<'static, State>,
|
||||||
|
cursor_ids: Vec<Id>,
|
||||||
) {
|
) {
|
||||||
let Some(mut screencopy) = output.with_state_mut(|state| state.screencopy.take()) else {
|
let Some(mut screencopy) = output.with_state_mut(|state| state.screencopy.take()) else {
|
||||||
return;
|
return;
|
||||||
|
@ -1773,7 +1749,7 @@ fn handle_pending_screencopy<'a>(
|
||||||
output.current_scale().fractional_scale(),
|
output.current_scale().fractional_scale(),
|
||||||
renderer,
|
renderer,
|
||||||
[screencopy.physical_region()],
|
[screencopy.physical_region()],
|
||||||
[],
|
cursor_ids,
|
||||||
)?))
|
)?))
|
||||||
} else {
|
} else {
|
||||||
// `RenderFrameResult::blit_frame_result` doesn't expose a way to
|
// `RenderFrameResult::blit_frame_result` doesn't expose a way to
|
||||||
|
@ -1800,7 +1776,11 @@ fn handle_pending_screencopy<'a>(
|
||||||
Point::from((0, 0)),
|
Point::from((0, 0)),
|
||||||
untransformed_output_size,
|
untransformed_output_size,
|
||||||
)],
|
)],
|
||||||
[],
|
if !screencopy.overlay_cursor() {
|
||||||
|
cursor_ids
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// ayo are we supposed to wait this here (granted it doesn't do anything
|
// ayo are we supposed to wait this here (granted it doesn't do anything
|
||||||
|
@ -1880,7 +1860,11 @@ fn handle_pending_screencopy<'a>(
|
||||||
Point::from((0, 0)),
|
Point::from((0, 0)),
|
||||||
untransformed_output_size,
|
untransformed_output_size,
|
||||||
)],
|
)],
|
||||||
[],
|
if !screencopy.overlay_cursor() {
|
||||||
|
cursor_ids
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Can someone explain to me why it feels like some things are
|
// Can someone explain to me why it feels like some things are
|
||||||
|
|
|
@ -10,7 +10,7 @@ use smithay::{
|
||||||
self, buffer_type,
|
self, buffer_type,
|
||||||
damage::{self, OutputDamageTracker, RenderOutputResult},
|
damage::{self, OutputDamageTracker, RenderOutputResult},
|
||||||
element::{self, surface::render_elements_from_surface_tree},
|
element::{self, surface::render_elements_from_surface_tree},
|
||||||
gles::{GlesRenderbuffer, GlesRenderer, GlesTexture},
|
gles::{GlesRenderbuffer, GlesRenderer},
|
||||||
Bind, Blit, BufferType, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen,
|
Bind, Blit, BufferType, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen,
|
||||||
TextureFilter,
|
TextureFilter,
|
||||||
},
|
},
|
||||||
|
@ -38,8 +38,8 @@ use tracing::{debug, error, trace, warn};
|
||||||
use crate::{
|
use crate::{
|
||||||
output::{BlankingState, OutputMode},
|
output::{BlankingState, OutputMode},
|
||||||
render::{
|
render::{
|
||||||
pointer::PointerElement, pointer_render_elements, take_presentation_feedback, CLEAR_COLOR,
|
pointer::pointer_render_elements, take_presentation_feedback, OutputRenderElement,
|
||||||
CLEAR_COLOR_LOCKED,
|
CLEAR_COLOR, CLEAR_COLOR_LOCKED,
|
||||||
},
|
},
|
||||||
state::{Pinnacle, State, WithState},
|
state::{Pinnacle, State, WithState},
|
||||||
};
|
};
|
||||||
|
@ -172,6 +172,8 @@ impl Winit {
|
||||||
tracing::info!("EGL hardware-acceleration enabled");
|
tracing::info!("EGL hardware-acceleration enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
winit_backend.window().set_cursor_visible(false);
|
||||||
|
|
||||||
let mut winit = Winit {
|
let mut winit = Winit {
|
||||||
backend: winit_backend,
|
backend: winit_backend,
|
||||||
damage_tracker: OutputDamageTracker::from_output(&output),
|
damage_tracker: OutputDamageTracker::from_output(&output),
|
||||||
|
@ -263,18 +265,14 @@ impl Winit {
|
||||||
let full_redraw = &mut self.full_redraw;
|
let full_redraw = &mut self.full_redraw;
|
||||||
*full_redraw = full_redraw.saturating_sub(1);
|
*full_redraw = full_redraw.saturating_sub(1);
|
||||||
|
|
||||||
if let CursorImageStatus::Surface(surface) = &pinnacle.cursor_status {
|
if let CursorImageStatus::Surface(surface) = pinnacle.cursor_state.cursor_image() {
|
||||||
if !surface.alive() {
|
if !surface.alive() {
|
||||||
pinnacle.cursor_status = CursorImageStatus::default_named();
|
pinnacle
|
||||||
|
.cursor_state
|
||||||
|
.set_cursor_image(CursorImageStatus::default_named());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cursor_visible = !matches!(pinnacle.cursor_status, CursorImageStatus::Surface(_));
|
|
||||||
|
|
||||||
let mut pointer_element = PointerElement::<GlesTexture>::new();
|
|
||||||
|
|
||||||
pointer_element.set_status(pinnacle.cursor_status.clone());
|
|
||||||
|
|
||||||
// The z-index of these is determined by `state.fixup_z_layering()`, which is called at the end
|
// The z-index of these is determined by `state.fixup_z_layering()`, which is called at the end
|
||||||
// of every event loop cycle
|
// of every event loop cycle
|
||||||
let windows = pinnacle.space.elements().cloned().collect::<Vec<_>>();
|
let windows = pinnacle.space.elements().cloned().collect::<Vec<_>>();
|
||||||
|
@ -297,17 +295,21 @@ impl Winit {
|
||||||
.map(|ptr| ptr.current_location())
|
.map(|ptr| ptr.current_location())
|
||||||
.unwrap_or((0.0, 0.0).into());
|
.unwrap_or((0.0, 0.0).into());
|
||||||
|
|
||||||
let pointer_render_elements = pointer_render_elements(
|
let (pointer_render_elements, _cursor_ids) = pointer_render_elements(
|
||||||
&self.output,
|
&self.output,
|
||||||
self.backend.renderer(),
|
self.backend.renderer(),
|
||||||
|
&mut pinnacle.cursor_state,
|
||||||
&pinnacle.space,
|
&pinnacle.space,
|
||||||
pointer_location,
|
pointer_location,
|
||||||
&mut pinnacle.cursor_status,
|
|
||||||
pinnacle.dnd_icon.as_ref(),
|
pinnacle.dnd_icon.as_ref(),
|
||||||
(0, 0).into(), // Nonsurface cursors are hidden
|
&pinnacle.clock,
|
||||||
&pointer_element,
|
element::Kind::Cursor,
|
||||||
|
);
|
||||||
|
output_render_elements.extend(
|
||||||
|
pointer_render_elements
|
||||||
|
.into_iter()
|
||||||
|
.map(OutputRenderElement::from),
|
||||||
);
|
);
|
||||||
output_render_elements.extend(pointer_render_elements);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let should_blank = pinnacle.lock_state.is_locking()
|
let should_blank = pinnacle.lock_state.is_locking()
|
||||||
|
@ -411,8 +413,6 @@ impl Winit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.backend.window().set_cursor_visible(cursor_visible);
|
|
||||||
|
|
||||||
let time = pinnacle.clock.now();
|
let time = pinnacle.clock.now();
|
||||||
|
|
||||||
super::post_repaint(
|
super::post_repaint(
|
||||||
|
@ -421,7 +421,7 @@ impl Winit {
|
||||||
&pinnacle.space,
|
&pinnacle.space,
|
||||||
None,
|
None,
|
||||||
time.into(),
|
time.into(),
|
||||||
&pinnacle.cursor_status,
|
pinnacle.cursor_state.cursor_image(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if has_rendered {
|
if has_rendered {
|
||||||
|
|
270
src/cursor.rs
270
src/cursor.rs
|
@ -1,68 +1,181 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use std::{io::Read, time::Duration};
|
use std::time::{Duration, Instant};
|
||||||
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use xcursor::{parser::Image, CursorTheme};
|
use anyhow::Context;
|
||||||
|
use smithay::backend::allocator::Fourcc;
|
||||||
|
use smithay::{
|
||||||
|
backend::renderer::element::memory::MemoryRenderBuffer,
|
||||||
|
input::pointer::{CursorIcon, CursorImageStatus},
|
||||||
|
utils::Transform,
|
||||||
|
};
|
||||||
|
use xcursor::{
|
||||||
|
parser::{parse_xcursor, Image},
|
||||||
|
CursorTheme,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::render::pointer::PointerElement;
|
||||||
|
|
||||||
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
|
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
|
||||||
|
|
||||||
pub struct Cursor {
|
pub struct CursorState {
|
||||||
icons: Vec<Image>,
|
start_time: Instant,
|
||||||
|
current_cursor_image: CursorImageStatus,
|
||||||
|
theme: CursorTheme,
|
||||||
size: u32,
|
size: u32,
|
||||||
|
// memory buffer cache
|
||||||
|
mem_buffer_cache: Vec<(Image, MemoryRenderBuffer)>,
|
||||||
|
// map of cursor icons to loaded images
|
||||||
|
loaded_images: HashMap<CursorIcon, Option<Rc<XCursor>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cursor {
|
impl CursorState {
|
||||||
pub fn load() -> Self {
|
pub fn new() -> Self {
|
||||||
let name = std::env::var("XCURSOR_THEME")
|
let (theme, size) = load_xcursor_theme_from_env();
|
||||||
.ok()
|
|
||||||
.unwrap_or_else(|| "default".into());
|
|
||||||
let size = std::env::var("XCURSOR_SIZE")
|
|
||||||
.ok()
|
|
||||||
.and_then(|s| s.parse().ok())
|
|
||||||
.unwrap_or(24);
|
|
||||||
|
|
||||||
let theme = CursorTheme::load(&name);
|
std::env::set_var("XCURSOR_THEME", &theme);
|
||||||
let icons = load_icon(&theme)
|
std::env::set_var("XCURSOR_SIZE", size.to_string());
|
||||||
.map_err(|err| tracing::warn!("Unable to load xcursor: {}, using fallback cursor", err))
|
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
vec![Image {
|
|
||||||
size: 32,
|
|
||||||
width: 64,
|
|
||||||
height: 64,
|
|
||||||
xhot: 1,
|
|
||||||
yhot: 1,
|
|
||||||
delay: 1,
|
|
||||||
pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA),
|
|
||||||
pixels_argb: vec![], //unused
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
Cursor { icons, size }
|
Self {
|
||||||
|
start_time: Instant::now(),
|
||||||
|
current_cursor_image: CursorImageStatus::default_named(),
|
||||||
|
theme: CursorTheme::load(&theme),
|
||||||
|
size,
|
||||||
|
mem_buffer_cache: Default::default(),
|
||||||
|
loaded_images: Default::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_image(&self, scale: u32, time: Duration) -> Image {
|
pub fn set_theme(&mut self, theme: &str) {
|
||||||
let size = self.size * scale;
|
std::env::set_var("XCURSOR_THEME", theme);
|
||||||
frame(time.as_millis() as u32, size, &self.icons)
|
|
||||||
|
self.theme = CursorTheme::load(theme);
|
||||||
|
self.mem_buffer_cache.clear();
|
||||||
|
self.loaded_images.clear();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
|
pub fn set_size(&mut self, size: u32) {
|
||||||
// Follow the nominal size of the cursor to choose the nearest
|
std::env::set_var("XCURSOR_SIZE", size.to_string());
|
||||||
let nearest_image = images
|
|
||||||
.iter()
|
|
||||||
.min_by_key(|image| (size as i32 - image.size as i32).abs())
|
|
||||||
.expect("no nearest image");
|
|
||||||
|
|
||||||
images.iter().filter(move |image| {
|
self.size = size;
|
||||||
image.width == nearest_image.width && image.height == nearest_image.height
|
self.mem_buffer_cache.clear();
|
||||||
|
self.loaded_images.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_size(&self, scale: i32) -> u32 {
|
||||||
|
self.size * scale as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cursor_image(&mut self, image: CursorImageStatus) {
|
||||||
|
self.current_cursor_image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_image(&self) -> &CursorImageStatus {
|
||||||
|
&self.current_cursor_image
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_xcursor_images(&mut self, icon: CursorIcon) -> Option<Rc<XCursor>> {
|
||||||
|
self.loaded_images
|
||||||
|
.entry(icon)
|
||||||
|
.or_insert_with_key(|icon| {
|
||||||
|
let mut images = load_xcursor_images(&self.theme, *icon);
|
||||||
|
if *icon == CursorIcon::Default && images.is_err() {
|
||||||
|
images = Ok(fallback_cursor());
|
||||||
|
}
|
||||||
|
images.ok().map(Rc::new)
|
||||||
})
|
})
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buffer_for_image(&mut self, image: Image, scale: i32) -> MemoryRenderBuffer {
|
||||||
|
self.mem_buffer_cache
|
||||||
|
.iter()
|
||||||
|
.find_map(|(img, buf)| (*img == image).then(|| buf.clone()))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
// TODO: scale
|
||||||
|
let buffer = MemoryRenderBuffer::from_slice(
|
||||||
|
&image.pixels_rgba,
|
||||||
|
// Don't make Abgr, then the format doesn't match the
|
||||||
|
// cursor bo and this doesn't get put on the cursor plane
|
||||||
|
Fourcc::Argb8888,
|
||||||
|
(image.width as i32, image.height as i32),
|
||||||
|
scale,
|
||||||
|
Transform::Normal,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.mem_buffer_cache.push((image, buffer.clone()));
|
||||||
|
|
||||||
|
buffer
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pointer_element(&mut self) -> PointerElement {
|
||||||
|
match &self.current_cursor_image {
|
||||||
|
CursorImageStatus::Hidden => PointerElement::Hidden,
|
||||||
|
CursorImageStatus::Named(icon) => {
|
||||||
|
let cursor = self
|
||||||
|
.get_xcursor_images(*icon)
|
||||||
|
.or_else(|| self.get_xcursor_images(CursorIcon::Default))
|
||||||
|
.unwrap();
|
||||||
|
PointerElement::Named {
|
||||||
|
cursor,
|
||||||
|
size: self.size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CursorImageStatus::Surface(surface) => PointerElement::Surface {
|
||||||
|
surface: surface.clone(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: update render to wait for est vblank, then you can remove this
|
||||||
|
/// If the current cursor is named and animated, get the time to the next frame, in milliseconds.
|
||||||
|
pub fn time_until_next_animated_cursor_frame(&mut self) -> Option<Duration> {
|
||||||
|
match &self.current_cursor_image {
|
||||||
|
CursorImageStatus::Hidden => None,
|
||||||
|
CursorImageStatus::Named(icon) => {
|
||||||
|
let cursor = self
|
||||||
|
.get_xcursor_images(*icon)
|
||||||
|
.or_else(|| self.get_xcursor_images(CursorIcon::Default))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if cursor.images.len() <= 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut millis = self.start_time.duration_since(Instant::now()).as_millis() as u32;
|
||||||
|
let animation_length_ms = nearest_size_images(self.size, &cursor.images)
|
||||||
|
.fold(0, |acc, image| acc + image.delay);
|
||||||
|
millis %= animation_length_ms;
|
||||||
|
|
||||||
|
for img in nearest_size_images(self.size, &cursor.images) {
|
||||||
|
if millis < img.delay {
|
||||||
|
return Some(Duration::from_millis((img.delay - millis).into()));
|
||||||
|
}
|
||||||
|
millis -= img.delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
CursorImageStatus::Surface(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
|
pub struct XCursor {
|
||||||
let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay);
|
images: Vec<Image>,
|
||||||
millis %= total;
|
}
|
||||||
|
|
||||||
for img in nearest_images(size, images) {
|
impl XCursor {
|
||||||
|
pub fn image(&self, time: Duration, size: u32) -> Image {
|
||||||
|
let mut millis = time.as_millis() as u32;
|
||||||
|
let animation_length_ms =
|
||||||
|
nearest_size_images(size, &self.images).fold(0, |acc, image| acc + image.delay);
|
||||||
|
millis %= animation_length_ms;
|
||||||
|
|
||||||
|
for img in nearest_size_images(size, &self.images) {
|
||||||
if millis < img.delay {
|
if millis < img.delay {
|
||||||
return img.clone();
|
return img.clone();
|
||||||
}
|
}
|
||||||
|
@ -70,22 +183,61 @@ fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable!()
|
unreachable!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
fn nearest_size_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
|
||||||
enum Error {
|
// Follow the nominal size of the cursor to choose the nearest
|
||||||
#[error("Theme has no default cursor")]
|
let nearest_image = images
|
||||||
NoDefaultCursor,
|
.iter()
|
||||||
#[error("Error opening xcursor file: {0}")]
|
.min_by_key(|image| (size as i32 - image.size as i32).abs())
|
||||||
File(#[from] std::io::Error),
|
.unwrap();
|
||||||
#[error("Failed to parse XCursor file")]
|
|
||||||
Parse,
|
images.iter().filter(move |image| {
|
||||||
|
image.width == nearest_image.width && image.height == nearest_image.height
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_icon(theme: &CursorTheme) -> Result<Vec<Image>, Error> {
|
/// Loads a theme and size from $XCURSOR_THEME and $XCURSOR_SIZE.
|
||||||
let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?;
|
///
|
||||||
let mut cursor_file = std::fs::File::open(icon_path)?;
|
/// Defaults to "default" and 24 respectively.
|
||||||
let mut cursor_data = Vec::new();
|
fn load_xcursor_theme_from_env() -> (String, u32) {
|
||||||
cursor_file.read_to_end(&mut cursor_data)?;
|
let theme = std::env::var("XCURSOR_THEME").unwrap_or_else(|_| "default".into());
|
||||||
xcursor::parser::parse_xcursor(&cursor_data).ok_or(Error::Parse)
|
let size = std::env::var("XCURSOR_SIZE")
|
||||||
|
.ok()
|
||||||
|
.and_then(|size| size.parse::<u32>().ok())
|
||||||
|
.unwrap_or(24);
|
||||||
|
|
||||||
|
(theme, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load xcursor images for the given theme and icon.
|
||||||
|
///
|
||||||
|
/// Looks through legacy names as fallback.
|
||||||
|
fn load_xcursor_images(theme: &CursorTheme, icon: CursorIcon) -> anyhow::Result<XCursor> {
|
||||||
|
let icon_path = std::iter::once(&icon.name())
|
||||||
|
.chain(icon.alt_names())
|
||||||
|
.find_map(|name| theme.load_icon(name))
|
||||||
|
.context("no images for icon")?;
|
||||||
|
|
||||||
|
let cursor_bytes = std::fs::read(icon_path).context("failed to read xcursor file")?;
|
||||||
|
|
||||||
|
parse_xcursor(&cursor_bytes)
|
||||||
|
.map(|images| XCursor { images })
|
||||||
|
.context("failed to parse xcursor bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fallback_cursor() -> XCursor {
|
||||||
|
XCursor {
|
||||||
|
images: vec![Image {
|
||||||
|
size: 32,
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
xhot: 1,
|
||||||
|
yhot: 1,
|
||||||
|
delay: 1,
|
||||||
|
pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA),
|
||||||
|
pixels_argb: vec![], // unused
|
||||||
|
}],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,11 @@ use smithay::{
|
||||||
// | input::keyboard
|
// | input::keyboard
|
||||||
input::{
|
input::{
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, Focus, GestureHoldBeginEvent,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData,
|
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||||
MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent,
|
GestureSwipeUpdateEvent, GrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
|
||||||
|
RelativeMotionEvent,
|
||||||
},
|
},
|
||||||
Seat, SeatHandler,
|
Seat, SeatHandler,
|
||||||
},
|
},
|
||||||
|
@ -46,6 +47,10 @@ impl PointerGrab<State> for MoveSurfaceGrab {
|
||||||
handle.motion(state, None, event);
|
handle.motion(state, None, event);
|
||||||
|
|
||||||
if !self.window.alive() {
|
if !self.window.alive() {
|
||||||
|
state
|
||||||
|
.pinnacle
|
||||||
|
.cursor_state
|
||||||
|
.set_cursor_image(CursorImageStatus::default_named());
|
||||||
handle.unset_grab(self, state, event.serial, event.time, true);
|
handle.unset_grab(self, state, event.serial, event.time, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -168,6 +173,9 @@ impl PointerGrab<State> for MoveSurfaceGrab {
|
||||||
handle.button(data, event);
|
handle.button(data, event);
|
||||||
|
|
||||||
if !handle.current_pressed().contains(&self.start_data.button) {
|
if !handle.current_pressed().contains(&self.start_data.button) {
|
||||||
|
data.pinnacle
|
||||||
|
.cursor_state
|
||||||
|
.set_cursor_image(CursorImageStatus::default_named());
|
||||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,9 +319,9 @@ impl State {
|
||||||
.to_f64(); // TODO: add space f64 support or move away from space
|
.to_f64(); // TODO: add space f64 support or move away from space
|
||||||
|
|
||||||
let start_data = smithay::input::pointer::GrabStartData {
|
let start_data = smithay::input::pointer::GrabStartData {
|
||||||
focus: pointer
|
// If Some and same as the dragged window then the window is allowed to
|
||||||
.current_focus()
|
// change the cursor, which we don't want, therefore this is None
|
||||||
.map(|focus| (focus, initial_window_loc)),
|
focus: None,
|
||||||
button: button_used,
|
button: button_used,
|
||||||
location: pointer.current_location(),
|
location: pointer.current_location(),
|
||||||
};
|
};
|
||||||
|
@ -325,5 +333,9 @@ impl State {
|
||||||
};
|
};
|
||||||
|
|
||||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||||
|
|
||||||
|
self.pinnacle
|
||||||
|
.cursor_state
|
||||||
|
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Grabbing));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ use smithay::{
|
||||||
desktop::{space::SpaceElement, WindowSurface},
|
desktop::{space::SpaceElement, WindowSurface},
|
||||||
input::{
|
input::{
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, Focus, GestureHoldBeginEvent,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData,
|
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||||
PointerGrab, PointerInnerHandle,
|
GestureSwipeUpdateEvent, GrabStartData, PointerGrab, PointerInnerHandle,
|
||||||
},
|
},
|
||||||
Seat, SeatHandler,
|
Seat, SeatHandler,
|
||||||
},
|
},
|
||||||
|
@ -49,6 +49,23 @@ impl From<xdg_toplevel::ResizeEdge> for ResizeEdge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ResizeEdge {
|
||||||
|
fn cursor_icon(&self) -> CursorIcon {
|
||||||
|
match self.0 {
|
||||||
|
xdg_toplevel::ResizeEdge::None => CursorIcon::Default, // TODO: possibly different icon here?
|
||||||
|
xdg_toplevel::ResizeEdge::Top => CursorIcon::NResize,
|
||||||
|
xdg_toplevel::ResizeEdge::Bottom => CursorIcon::SResize,
|
||||||
|
xdg_toplevel::ResizeEdge::Left => CursorIcon::WResize,
|
||||||
|
xdg_toplevel::ResizeEdge::TopLeft => CursorIcon::NwResize,
|
||||||
|
xdg_toplevel::ResizeEdge::BottomLeft => CursorIcon::SwResize,
|
||||||
|
xdg_toplevel::ResizeEdge::Right => CursorIcon::EResize,
|
||||||
|
xdg_toplevel::ResizeEdge::TopRight => CursorIcon::NeResize,
|
||||||
|
xdg_toplevel::ResizeEdge::BottomRight => CursorIcon::SeResize,
|
||||||
|
_ => CursorIcon::Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ResizeSurfaceGrab {
|
pub struct ResizeSurfaceGrab {
|
||||||
start_data: GrabStartData<State>,
|
start_data: GrabStartData<State>,
|
||||||
window: WindowElement,
|
window: WindowElement,
|
||||||
|
@ -146,6 +163,9 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
|
||||||
handle.motion(data, None, event);
|
handle.motion(data, None, event);
|
||||||
|
|
||||||
if !self.window.alive() {
|
if !self.window.alive() {
|
||||||
|
data.pinnacle
|
||||||
|
.cursor_state
|
||||||
|
.set_cursor_image(CursorImageStatus::default_named());
|
||||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -242,6 +262,9 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
|
||||||
handle.button(data, event);
|
handle.button(data, event);
|
||||||
|
|
||||||
if !handle.current_pressed().contains(&self.button_used) {
|
if !handle.current_pressed().contains(&self.button_used) {
|
||||||
|
data.pinnacle
|
||||||
|
.cursor_state
|
||||||
|
.set_cursor_image(CursorImageStatus::default_named());
|
||||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -558,9 +581,7 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_data = smithay::input::pointer::GrabStartData {
|
let start_data = smithay::input::pointer::GrabStartData {
|
||||||
focus: pointer
|
focus: None,
|
||||||
.current_focus()
|
|
||||||
.map(|focus| (focus, initial_window_loc)),
|
|
||||||
button: button_used,
|
button: button_used,
|
||||||
location: pointer.current_location(),
|
location: pointer.current_location(),
|
||||||
};
|
};
|
||||||
|
@ -576,6 +597,10 @@ impl State {
|
||||||
|
|
||||||
if let Some(grab) = grab {
|
if let Some(grab) = grab {
|
||||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||||
|
|
||||||
|
self.pinnacle
|
||||||
|
.cursor_state
|
||||||
|
.set_cursor_image(CursorImageStatus::Named(edges.cursor_icon()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,15 @@ mod xwayland;
|
||||||
use std::{collections::HashMap, mem, os::fd::OwnedFd, sync::Arc};
|
use std::{collections::HashMap, mem, os::fd::OwnedFd, sync::Arc};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::renderer::utils::{self, with_renderer_surface_state},
|
backend::{
|
||||||
delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale,
|
input::TabletToolDescriptor,
|
||||||
delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_presentation,
|
renderer::utils::{self, with_renderer_surface_state},
|
||||||
delegate_primary_selection, delegate_relative_pointer, delegate_seat,
|
},
|
||||||
delegate_security_context, delegate_shm, delegate_viewporter, delegate_xwayland_shell,
|
delegate_compositor, delegate_cursor_shape, delegate_data_control, delegate_data_device,
|
||||||
|
delegate_fractional_scale, delegate_layer_shell, delegate_output, delegate_pointer_constraints,
|
||||||
|
delegate_presentation, delegate_primary_selection, delegate_relative_pointer, delegate_seat,
|
||||||
|
delegate_security_context, delegate_shm, delegate_tablet_manager, delegate_viewporter,
|
||||||
|
delegate_xwayland_shell,
|
||||||
desktop::{
|
desktop::{
|
||||||
self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, PopupKind,
|
self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, PopupKind,
|
||||||
PopupManager, WindowSurfaceType,
|
PopupManager, WindowSurfaceType,
|
||||||
|
@ -62,13 +66,11 @@ use smithay::{
|
||||||
SelectionHandler, SelectionSource, SelectionTarget,
|
SelectionHandler, SelectionSource, SelectionTarget,
|
||||||
},
|
},
|
||||||
shell::{
|
shell::{
|
||||||
wlr_layer::{
|
wlr_layer::{self, Layer, LayerSurfaceData, WlrLayerShellHandler, WlrLayerShellState},
|
||||||
self, Layer, LayerSurfaceCachedState, LayerSurfaceData, WlrLayerShellHandler,
|
|
||||||
WlrLayerShellState,
|
|
||||||
},
|
|
||||||
xdg::{PopupSurface, XdgPopupSurfaceData, XdgToplevelSurfaceData},
|
xdg::{PopupSurface, XdgPopupSurfaceData, XdgToplevelSurfaceData},
|
||||||
},
|
},
|
||||||
shm::{ShmHandler, ShmState},
|
shm::{ShmHandler, ShmState},
|
||||||
|
tablet_manager::TabletSeatHandler,
|
||||||
xwayland_shell::{XWaylandShellHandler, XWaylandShellState},
|
xwayland_shell::{XWaylandShellHandler, XWaylandShellState},
|
||||||
},
|
},
|
||||||
xwayland::XWaylandClientData,
|
xwayland::XWaylandClientData,
|
||||||
|
@ -541,7 +543,7 @@ impl SeatHandler for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
|
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
|
||||||
self.pinnacle.cursor_status = image;
|
self.pinnacle.cursor_state.set_cursor_image(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&Self::KeyboardFocus>) {
|
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&Self::KeyboardFocus>) {
|
||||||
|
@ -898,6 +900,17 @@ impl OutputPowerManagementHandler for State {
|
||||||
}
|
}
|
||||||
delegate_output_power_management!(State);
|
delegate_output_power_management!(State);
|
||||||
|
|
||||||
|
impl TabletSeatHandler for State {
|
||||||
|
fn tablet_tool_image(&mut self, tool: &TabletToolDescriptor, image: CursorImageStatus) {
|
||||||
|
// TODO:
|
||||||
|
let _ = tool;
|
||||||
|
let _ = image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate_tablet_manager!(State);
|
||||||
|
|
||||||
|
delegate_cursor_shape!(State);
|
||||||
|
|
||||||
impl Pinnacle {
|
impl Pinnacle {
|
||||||
fn position_popup(&self, popup: &PopupSurface) {
|
fn position_popup(&self, popup: &PopupSurface) {
|
||||||
trace!("State::position_popup");
|
trace!("State::position_popup");
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::{process::Stdio, time::Duration};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::Window,
|
desktop::Window,
|
||||||
|
input::pointer::CursorIcon,
|
||||||
utils::{Logical, Point, Rectangle, Size, SERIAL_COUNTER},
|
utils::{Logical, Point, Rectangle, Size, SERIAL_COUNTER},
|
||||||
wayland::selection::{
|
wayland::selection::{
|
||||||
data_device::{
|
data_device::{
|
||||||
|
@ -24,7 +25,6 @@ use smithay::{
|
||||||
use tracing::{debug, error, trace, warn};
|
use tracing::{debug, error, trace, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cursor::Cursor,
|
|
||||||
focus::keyboard::KeyboardFocusTarget,
|
focus::keyboard::KeyboardFocusTarget,
|
||||||
state::{Pinnacle, State, WithState},
|
state::{Pinnacle, State, WithState},
|
||||||
window::{window_state::FloatingOrTiled, WindowElement},
|
window::{window_state::FloatingOrTiled, WindowElement},
|
||||||
|
@ -505,8 +505,13 @@ impl Pinnacle {
|
||||||
)
|
)
|
||||||
.expect("Failed to attach x11wm");
|
.expect("Failed to attach x11wm");
|
||||||
|
|
||||||
let cursor = Cursor::load();
|
let cursor = state
|
||||||
let image = cursor.get_image(1, Duration::ZERO);
|
.pinnacle
|
||||||
|
.cursor_state
|
||||||
|
.get_xcursor_images(CursorIcon::Default)
|
||||||
|
.unwrap();
|
||||||
|
let image =
|
||||||
|
cursor.image(Duration::ZERO, state.pinnacle.cursor_state.cursor_size(1)); // TODO: scale
|
||||||
wm.set_cursor(
|
wm.set_cursor(
|
||||||
&image.pixels_rgba,
|
&image.pixels_rgba,
|
||||||
Size::from((image.width as u16, image.height as u16)),
|
Size::from((image.width as u16, image.height as u16)),
|
||||||
|
|
|
@ -438,7 +438,6 @@ impl State {
|
||||||
|
|
||||||
if self.pinnacle.lock_state.is_unlocked() {
|
if self.pinnacle.lock_state.is_unlocked() {
|
||||||
// Focus the topmost exclusive layer, if any
|
// Focus the topmost exclusive layer, if any
|
||||||
let mut exclusive_layers_exist = false;
|
|
||||||
for layer in self.pinnacle.layer_shell_state.layer_surfaces().rev() {
|
for layer in self.pinnacle.layer_shell_state.layer_surfaces().rev() {
|
||||||
let data = compositor::with_states(layer.wl_surface(), |states| {
|
let data = compositor::with_states(layer.wl_surface(), |states| {
|
||||||
*states.cached_state.current::<LayerSurfaceCachedState>()
|
*states.cached_state.current::<LayerSurfaceCachedState>()
|
||||||
|
@ -456,7 +455,6 @@ impl State {
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(layer_surface) = layer_surface {
|
if let Some(layer_surface) = layer_surface {
|
||||||
exclusive_layers_exist = true;
|
|
||||||
keyboard.set_focus(
|
keyboard.set_focus(
|
||||||
self,
|
self,
|
||||||
Some(KeyboardFocusTarget::LayerSurface(layer_surface)),
|
Some(KeyboardFocusTarget::LayerSurface(layer_surface)),
|
||||||
|
|
|
@ -5,7 +5,7 @@ pub mod render_elements;
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
use std::{ops::Deref, sync::Mutex};
|
use std::ops::Deref;
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::renderer::{
|
backend::renderer::{
|
||||||
|
@ -22,11 +22,9 @@ use smithay::{
|
||||||
},
|
},
|
||||||
PopupManager, Space, WindowSurface,
|
PopupManager, Space, WindowSurface,
|
||||||
},
|
},
|
||||||
input::pointer::{CursorImageAttributes, CursorImageStatus},
|
|
||||||
output::Output,
|
output::Output,
|
||||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
|
||||||
utils::{Logical, Point, Scale},
|
utils::{Logical, Point, Scale},
|
||||||
wayland::{compositor, shell::wlr_layer},
|
wayland::shell::wlr_layer,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -38,8 +36,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
pointer::{PointerElement, PointerRenderElement},
|
pointer::PointerRenderElement, texture::CommonTextureRenderElement,
|
||||||
texture::CommonTextureRenderElement,
|
|
||||||
util::surface::texture_render_elements_from_surface_tree,
|
util::surface::texture_render_elements_from_surface_tree,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -269,62 +266,6 @@ fn window_render_elements<R: PRenderer>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pointer_render_elements<R: PRenderer>(
|
|
||||||
output: &Output,
|
|
||||||
renderer: &mut R,
|
|
||||||
space: &Space<WindowElement>,
|
|
||||||
pointer_location: Point<f64, Logical>,
|
|
||||||
cursor_status: &mut CursorImageStatus,
|
|
||||||
dnd_icon: Option<&WlSurface>,
|
|
||||||
fallback_hotspot: Point<i32, Logical>,
|
|
||||||
pointer_element: &PointerElement<<R as Renderer>::TextureId>,
|
|
||||||
) -> Vec<OutputRenderElement<R>> {
|
|
||||||
let mut output_render_elements = Vec::new();
|
|
||||||
|
|
||||||
let Some(output_geometry) = space.output_geometry(output) else {
|
|
||||||
return output_render_elements;
|
|
||||||
};
|
|
||||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
|
||||||
|
|
||||||
if output_geometry.to_f64().contains(pointer_location) {
|
|
||||||
let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = cursor_status {
|
|
||||||
compositor::with_states(surface, |states| {
|
|
||||||
states
|
|
||||||
.data_map
|
|
||||||
.get::<Mutex<CursorImageAttributes>>()
|
|
||||||
.expect("surface data map had no CursorImageAttributes")
|
|
||||||
.lock()
|
|
||||||
.expect("failed to lock mutex")
|
|
||||||
.hotspot
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
fallback_hotspot
|
|
||||||
};
|
|
||||||
|
|
||||||
let cursor_pos = pointer_location - output_geometry.loc.to_f64() - cursor_hotspot.to_f64();
|
|
||||||
let cursor_pos_scaled = cursor_pos.to_physical_precise_round(scale);
|
|
||||||
|
|
||||||
output_render_elements.extend(pointer_element.render_elements(
|
|
||||||
renderer,
|
|
||||||
cursor_pos_scaled,
|
|
||||||
scale,
|
|
||||||
1.0,
|
|
||||||
));
|
|
||||||
|
|
||||||
if let Some(dnd_icon) = dnd_icon {
|
|
||||||
output_render_elements.extend(AsRenderElements::render_elements(
|
|
||||||
&smithay::desktop::space::SurfaceTree::from_surface(dnd_icon),
|
|
||||||
renderer,
|
|
||||||
cursor_pos_scaled,
|
|
||||||
scale,
|
|
||||||
1.0,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output_render_elements
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render elements for any pending layout transaction.
|
/// Render elements for any pending layout transaction.
|
||||||
///
|
///
|
||||||
/// Returns fullscreen_and_up elements then under_fullscreen elements.
|
/// Returns fullscreen_and_up elements then under_fullscreen elements.
|
||||||
|
|
|
@ -1,99 +1,133 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::{rc::Rc, sync::Mutex};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::renderer::{
|
backend::renderer::{
|
||||||
element::{
|
element::{
|
||||||
self,
|
self,
|
||||||
surface::{self, WaylandSurfaceRenderElement},
|
memory::MemoryRenderBufferRenderElement,
|
||||||
texture::{TextureBuffer, TextureRenderElement},
|
surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement},
|
||||||
AsRenderElements,
|
AsRenderElements, Element, Id,
|
||||||
},
|
},
|
||||||
ImportAll, Renderer, Texture,
|
ImportAll, ImportMem,
|
||||||
},
|
},
|
||||||
input::pointer::CursorImageStatus,
|
desktop::Space,
|
||||||
|
input::pointer::CursorImageAttributes,
|
||||||
|
output::Output,
|
||||||
|
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||||
render_elements,
|
render_elements,
|
||||||
utils::{Physical, Point, Scale},
|
utils::{Clock, Logical, Monotonic, Point, Scale},
|
||||||
|
wayland::compositor,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cursor::{CursorState, XCursor},
|
||||||
|
window::WindowElement,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::PRenderer;
|
use super::PRenderer;
|
||||||
|
|
||||||
pub struct PointerElement<T: Texture> {
|
pub enum PointerElement {
|
||||||
texture: Option<TextureBuffer<T>>,
|
Hidden,
|
||||||
status: CursorImageStatus,
|
Named { cursor: Rc<XCursor>, size: u32 },
|
||||||
kind: element::Kind,
|
Surface { surface: WlSurface },
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Texture> Default for PointerElement<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
texture: Default::default(),
|
|
||||||
status: CursorImageStatus::default_named(),
|
|
||||||
kind: element::Kind::Cursor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Texture> PointerElement<T> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_status(&mut self, status: CursorImageStatus) {
|
|
||||||
self.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_texture(&mut self, texture: TextureBuffer<T>) {
|
|
||||||
self.texture = Some(texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_element_kind(&mut self, kind: element::Kind) {
|
|
||||||
self.kind = kind;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render_elements! {
|
render_elements! {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub PointerRenderElement<R> where R: ImportAll;
|
pub PointerRenderElement<R> where R: ImportAll + ImportMem;
|
||||||
Surface=WaylandSurfaceRenderElement<R>,
|
Surface = WaylandSurfaceRenderElement<R>,
|
||||||
Texture=TextureRenderElement<<R as Renderer>::TextureId>,
|
Memory = MemoryRenderBufferRenderElement<R>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: PRenderer> AsRenderElements<R> for PointerElement<R::TextureId> {
|
/// Render pointer elements.
|
||||||
type RenderElement = PointerRenderElement<R>;
|
///
|
||||||
|
/// Additionally returns the ids of cursor elements for use in screencopy.
|
||||||
fn render_elements<C: From<Self::RenderElement>>(
|
pub fn pointer_render_elements<R: PRenderer>(
|
||||||
&self,
|
output: &Output,
|
||||||
renderer: &mut R,
|
renderer: &mut R,
|
||||||
location: Point<i32, Physical>,
|
cursor_state: &mut CursorState,
|
||||||
scale: Scale<f64>,
|
space: &Space<WindowElement>,
|
||||||
alpha: f32,
|
pointer_location: Point<f64, Logical>,
|
||||||
) -> Vec<C> {
|
dnd_icon: Option<&WlSurface>,
|
||||||
match &self.status {
|
clock: &Clock<Monotonic>,
|
||||||
CursorImageStatus::Hidden => vec![],
|
kind: element::Kind,
|
||||||
CursorImageStatus::Named(_) => {
|
) -> (Vec<PointerRenderElement<R>>, Vec<Id>) {
|
||||||
if let Some(texture) = self.texture.as_ref() {
|
let mut pointer_render_elements = Vec::new();
|
||||||
vec![PointerRenderElement::<R>::from(
|
let mut cursor_ids = Vec::new();
|
||||||
TextureRenderElement::from_texture_buffer(
|
|
||||||
location.to_f64(),
|
let Some(output_geometry) = space.output_geometry(output) else {
|
||||||
texture,
|
return (pointer_render_elements, cursor_ids);
|
||||||
|
};
|
||||||
|
|
||||||
|
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||||
|
let integer_scale = output.current_scale().integer_scale();
|
||||||
|
|
||||||
|
let pointer_elem = cursor_state.pointer_element();
|
||||||
|
|
||||||
|
if output_geometry.to_f64().contains(pointer_location) {
|
||||||
|
let cursor_pos = pointer_location - output_geometry.loc.to_f64();
|
||||||
|
|
||||||
|
let mut elements = match &pointer_elem {
|
||||||
|
PointerElement::Hidden => vec![],
|
||||||
|
PointerElement::Named { cursor, size } => {
|
||||||
|
let image = cursor.image(clock.now().into(), *size * integer_scale as u32);
|
||||||
|
let hotspot = (image.xhot as i32, image.yhot as i32);
|
||||||
|
let buffer = cursor_state.buffer_for_image(image, integer_scale);
|
||||||
|
let elem = MemoryRenderBufferRenderElement::from_buffer(
|
||||||
|
renderer,
|
||||||
|
(cursor_pos - Point::from(hotspot).downscale(integer_scale).to_f64())
|
||||||
|
.to_physical_precise_round(scale),
|
||||||
|
&buffer,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
self.kind,
|
kind,
|
||||||
),
|
|
||||||
)
|
|
||||||
.into()]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CursorImageStatus::Surface(surface) => {
|
|
||||||
let elements: Vec<PointerRenderElement<R>> =
|
|
||||||
surface::render_elements_from_surface_tree(
|
|
||||||
renderer, surface, location, scale, alpha, self.kind,
|
|
||||||
);
|
);
|
||||||
elements.into_iter().map(C::from).collect()
|
|
||||||
|
elem.map(|elem| vec![PointerRenderElement::Memory(elem)])
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
PointerElement::Surface { surface } => {
|
||||||
|
let hotspot = compositor::with_states(surface, |states| {
|
||||||
|
states
|
||||||
|
.data_map
|
||||||
|
.get::<Mutex<CursorImageAttributes>>()
|
||||||
|
.expect("surface data map had no CursorImageAttributes")
|
||||||
|
.lock()
|
||||||
|
.expect("failed to lock mutex")
|
||||||
|
.hotspot
|
||||||
|
});
|
||||||
|
|
||||||
|
let elems = render_elements_from_surface_tree(
|
||||||
|
renderer,
|
||||||
|
surface,
|
||||||
|
(cursor_pos - hotspot.to_f64()).to_physical_precise_round(scale),
|
||||||
|
scale,
|
||||||
|
1.0,
|
||||||
|
element::Kind::Cursor,
|
||||||
|
);
|
||||||
|
|
||||||
|
elems
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// rust analyzer is so broken wtf why is `elem` {unknown}
|
||||||
|
cursor_ids = elements.iter().map(|elem| elem.id()).cloned().collect();
|
||||||
|
|
||||||
|
if let Some(dnd_icon) = dnd_icon {
|
||||||
|
elements.extend(AsRenderElements::render_elements(
|
||||||
|
&smithay::desktop::space::SurfaceTree::from_surface(dnd_icon),
|
||||||
|
renderer,
|
||||||
|
cursor_pos.to_physical_precise_round(scale),
|
||||||
|
scale,
|
||||||
|
1.0,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pointer_render_elements = elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
(pointer_render_elements, cursor_ids)
|
||||||
}
|
}
|
||||||
|
|
15
src/state.rs
15
src/state.rs
|
@ -5,6 +5,7 @@ use crate::{
|
||||||
backend::{self, udev::Udev, winit::Winit, Backend},
|
backend::{self, udev::Udev, winit::Winit, Backend},
|
||||||
cli::{self, Cli},
|
cli::{self, Cli},
|
||||||
config::Config,
|
config::Config,
|
||||||
|
cursor::CursorState,
|
||||||
focus::OutputFocusStack,
|
focus::OutputFocusStack,
|
||||||
grab::resize_grab::ResizeSurfaceState,
|
grab::resize_grab::ResizeSurfaceState,
|
||||||
handlers::session_lock::LockState,
|
handlers::session_lock::LockState,
|
||||||
|
@ -23,7 +24,7 @@ use indexmap::IndexMap;
|
||||||
use pinnacle_api_defs::pinnacle::v0alpha1::ShutdownWatchResponse;
|
use pinnacle_api_defs::pinnacle::v0alpha1::ShutdownWatchResponse;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::{PopupManager, Space},
|
desktop::{PopupManager, Space},
|
||||||
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
|
input::{keyboard::XkbConfig, Seat, SeatState},
|
||||||
output::Output,
|
output::Output,
|
||||||
reexports::{
|
reexports::{
|
||||||
calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction},
|
calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction},
|
||||||
|
@ -36,6 +37,7 @@ use smithay::{
|
||||||
utils::{Clock, Monotonic},
|
utils::{Clock, Monotonic},
|
||||||
wayland::{
|
wayland::{
|
||||||
compositor::{self, CompositorClientState, CompositorState},
|
compositor::{self, CompositorClientState, CompositorState},
|
||||||
|
cursor_shape::CursorShapeManagerState,
|
||||||
dmabuf::DmabufFeedback,
|
dmabuf::DmabufFeedback,
|
||||||
fractional_scale::FractionalScaleManagerState,
|
fractional_scale::FractionalScaleManagerState,
|
||||||
idle_notify::IdleNotifierState,
|
idle_notify::IdleNotifierState,
|
||||||
|
@ -51,6 +53,7 @@ use smithay::{
|
||||||
shell::{wlr_layer::WlrLayerShellState, xdg::XdgShellState},
|
shell::{wlr_layer::WlrLayerShellState, xdg::XdgShellState},
|
||||||
shm::ShmState,
|
shm::ShmState,
|
||||||
socket::ListeningSocketSource,
|
socket::ListeningSocketSource,
|
||||||
|
tablet_manager::TabletManagerState,
|
||||||
viewporter::ViewporterState,
|
viewporter::ViewporterState,
|
||||||
xwayland_shell::XWaylandShellState,
|
xwayland_shell::XWaylandShellState,
|
||||||
},
|
},
|
||||||
|
@ -112,6 +115,7 @@ pub struct Pinnacle {
|
||||||
pub idle_notifier_state: IdleNotifierState<State>,
|
pub idle_notifier_state: IdleNotifierState<State>,
|
||||||
pub output_management_manager_state: OutputManagementManagerState,
|
pub output_management_manager_state: OutputManagementManagerState,
|
||||||
pub output_power_management_state: OutputPowerManagementState,
|
pub output_power_management_state: OutputPowerManagementState,
|
||||||
|
pub tablet_manager_state: TabletManagerState,
|
||||||
|
|
||||||
pub lock_state: LockState,
|
pub lock_state: LockState,
|
||||||
|
|
||||||
|
@ -123,7 +127,6 @@ pub struct Pinnacle {
|
||||||
|
|
||||||
pub popup_manager: PopupManager,
|
pub popup_manager: PopupManager,
|
||||||
|
|
||||||
pub cursor_status: CursorImageStatus,
|
|
||||||
pub dnd_icon: Option<WlSurface>,
|
pub dnd_icon: Option<WlSurface>,
|
||||||
|
|
||||||
/// The main window vec
|
/// The main window vec
|
||||||
|
@ -160,6 +163,9 @@ pub struct Pinnacle {
|
||||||
pub snowcap_shutdown_ping: Option<smithay::reexports::calloop::ping::Ping>,
|
pub snowcap_shutdown_ping: Option<smithay::reexports::calloop::ping::Ping>,
|
||||||
#[cfg(feature = "snowcap")]
|
#[cfg(feature = "snowcap")]
|
||||||
pub snowcap_join_handle: Option<tokio::task::JoinHandle<()>>,
|
pub snowcap_join_handle: Option<tokio::task::JoinHandle<()>>,
|
||||||
|
|
||||||
|
pub cursor_shape_manager_state: CursorShapeManagerState,
|
||||||
|
pub cursor_state: CursorState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
@ -286,7 +292,6 @@ impl Pinnacle {
|
||||||
seat_state,
|
seat_state,
|
||||||
shm_state: ShmState::new::<State>(&display_handle, vec![]),
|
shm_state: ShmState::new::<State>(&display_handle, vec![]),
|
||||||
space: Space::<WindowElement>::default(),
|
space: Space::<WindowElement>::default(),
|
||||||
cursor_status: CursorImageStatus::default_named(),
|
|
||||||
output_manager_state: OutputManagerState::new_with_xdg_output::<State>(&display_handle),
|
output_manager_state: OutputManagerState::new_with_xdg_output::<State>(&display_handle),
|
||||||
xdg_shell_state: XdgShellState::new::<State>(&display_handle),
|
xdg_shell_state: XdgShellState::new::<State>(&display_handle),
|
||||||
viewporter_state: ViewporterState::new::<State>(&display_handle),
|
viewporter_state: ViewporterState::new::<State>(&display_handle),
|
||||||
|
@ -333,6 +338,7 @@ impl Pinnacle {
|
||||||
&display_handle,
|
&display_handle,
|
||||||
filter_restricted_client,
|
filter_restricted_client,
|
||||||
),
|
),
|
||||||
|
tablet_manager_state: TabletManagerState::new::<State>(&display_handle),
|
||||||
|
|
||||||
lock_state: LockState::default(),
|
lock_state: LockState::default(),
|
||||||
|
|
||||||
|
@ -378,6 +384,9 @@ impl Pinnacle {
|
||||||
snowcap_shutdown_ping: None,
|
snowcap_shutdown_ping: None,
|
||||||
#[cfg(feature = "snowcap")]
|
#[cfg(feature = "snowcap")]
|
||||||
snowcap_join_handle: None,
|
snowcap_join_handle: None,
|
||||||
|
|
||||||
|
cursor_shape_manager_state: CursorShapeManagerState::new::<State>(&display_handle),
|
||||||
|
cursor_state: CursorState::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(pinnacle)
|
Ok(pinnacle)
|
||||||
|
|
Loading…
Reference in a new issue