Integrate native Rime and stabilize input, output, and rendering
- Add optional modifier-only Super binding whose tap is cancelled by other input, focus changes, grabs, locks, and session teardown. - Ship the native ferese-rime user service, Rime defaults, installer inventory, package dependencies, session environment, and docs. - Vendor Smithay 8a27023 with the SHM texture-cache format fix and add GPU regressions for format changes and resize snapshots. - Release reused CRTC owners before staging output modes, then restore changed outputs before former owners when a transaction fails. - Wait for resize-capture GPU completion, expand filtered surface damage, and preserve IME popup origin under fractional scaling. - Mark weekends like holidays in both calendar layouts.
This commit is contained in:
1 parent
a2be421974
commit
993bc0f88f
391 files changed
+130908
-76
No files matched your search
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "wlcs_anvil"
|
||||
version = "0.0.1"
|
||||
authors = ["Victor Berger <[email protected]>", "Drakulix (Victoria Brekenfeld)"]
|
||||
license = "MIT"
|
||||
publish = false
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
smithay = { path = "..", default-features=false, features=["wayland_frontend", "backend_egl", "use_system_lib", "renderer_test"] }
|
||||
anvil = { path = "../anvil", default-features=false }
|
||||
wayland-sys = { version = "0.31.1", features = ["client", "server"] }
|
||||
wlcs = "0.1"
|
||||
Vendored
+266
@@ -0,0 +1,266 @@
|
||||
mod main_loop;
|
||||
|
||||
use std::{
|
||||
io::{Error, ErrorKind},
|
||||
os::{
|
||||
fd::{AsRawFd, OwnedFd},
|
||||
unix::net::UnixStream,
|
||||
},
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
thread::JoinHandle,
|
||||
};
|
||||
|
||||
use smithay::{
|
||||
reexports::calloop::{
|
||||
self,
|
||||
channel::{Sender, channel},
|
||||
},
|
||||
utils::{Logical, Point},
|
||||
};
|
||||
|
||||
use wayland_sys::{
|
||||
client::*,
|
||||
common::{wl_fixed_t, wl_fixed_to_double},
|
||||
ffi_dispatch,
|
||||
};
|
||||
use wlcs::{
|
||||
Wlcs, extension_list,
|
||||
ffi_display_server_api::{WlcsExtensionDescriptor, WlcsIntegrationDescriptor, WlcsServerIntegration},
|
||||
ffi_wrappers::wlcs_server,
|
||||
wlcs_server_integration,
|
||||
};
|
||||
|
||||
wlcs_server_integration!(AnvilDisplayServerHandle);
|
||||
|
||||
static SUPPORTED_EXTENSIONS: &[WlcsExtensionDescriptor] = extension_list!(
|
||||
("wl_compositor", 4),
|
||||
("wl_subcompositor", 1),
|
||||
("wl_data_device_manager", 3),
|
||||
("wl_seat", 7),
|
||||
("wl_output", 4),
|
||||
("xdg_wm_base", 3),
|
||||
);
|
||||
|
||||
static DESCRIPTOR: WlcsIntegrationDescriptor = WlcsIntegrationDescriptor {
|
||||
version: 1,
|
||||
num_extensions: SUPPORTED_EXTENSIONS.len(),
|
||||
supported_extensions: SUPPORTED_EXTENSIONS.as_ptr(),
|
||||
};
|
||||
|
||||
static DEVICE_ID: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
/// Event sent by WLCS to control the compositor
|
||||
#[derive(Debug)]
|
||||
pub enum WlcsEvent {
|
||||
/// Stop the running server
|
||||
Exit,
|
||||
/// Create a new client from given RawFd
|
||||
NewClient {
|
||||
stream: UnixStream,
|
||||
client_id: i32,
|
||||
},
|
||||
/// Position this window from the client associated with this Fd on the global space
|
||||
PositionWindow {
|
||||
client_id: i32,
|
||||
surface_id: u32,
|
||||
location: Point<i32, Logical>,
|
||||
},
|
||||
/* Pointer related events */
|
||||
/// A new pointer device is available
|
||||
NewPointer {
|
||||
device_id: u32,
|
||||
},
|
||||
/// Move the pointer in absolute coordinate space
|
||||
PointerMoveAbsolute {
|
||||
device_id: u32,
|
||||
location: Point<f64, Logical>,
|
||||
},
|
||||
/// Move the pointer in relative coordinate space
|
||||
PointerMoveRelative {
|
||||
device_id: u32,
|
||||
delta: Point<f64, Logical>,
|
||||
},
|
||||
/// Press a pointer button
|
||||
PointerButtonDown {
|
||||
device_id: u32,
|
||||
button_id: i32,
|
||||
},
|
||||
/// Release a pointer button
|
||||
PointerButtonUp {
|
||||
device_id: u32,
|
||||
button_id: i32,
|
||||
},
|
||||
/// A pointer device is removed
|
||||
PointerRemoved {
|
||||
device_id: u32,
|
||||
},
|
||||
/* Touch related events */
|
||||
/// A new touch device is available
|
||||
NewTouch {
|
||||
device_id: u32,
|
||||
},
|
||||
/// A touch point is down
|
||||
TouchDown {
|
||||
device_id: u32,
|
||||
location: Point<f64, Logical>,
|
||||
},
|
||||
/// A touch point moved
|
||||
TouchMove {
|
||||
device_id: u32,
|
||||
location: Point<f64, Logical>,
|
||||
},
|
||||
/// A touch point is up
|
||||
TouchUp {
|
||||
device_id: u32,
|
||||
},
|
||||
TouchRemoved {
|
||||
device_id: u32,
|
||||
},
|
||||
}
|
||||
|
||||
struct AnvilDisplayServerHandle {
|
||||
server: Option<(Sender<WlcsEvent>, JoinHandle<()>)>,
|
||||
}
|
||||
|
||||
impl Wlcs for AnvilDisplayServerHandle {
|
||||
type Pointer = PointerHandle;
|
||||
type Touch = TouchHandle;
|
||||
|
||||
fn new() -> Self {
|
||||
AnvilDisplayServerHandle { server: None }
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
let (tx, rx) = channel();
|
||||
let join = crate::start_anvil(rx);
|
||||
self.server = Some((tx, join));
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
if let Some((sender, join)) = self.server.take() {
|
||||
let _ = sender.send(WlcsEvent::Exit);
|
||||
let _ = join.join();
|
||||
}
|
||||
}
|
||||
|
||||
fn create_client_socket(&self) -> std::io::Result<OwnedFd> {
|
||||
if let Some((ref sender, _)) = self.server {
|
||||
if let Ok((client_side, server_side)) = UnixStream::pair() {
|
||||
if let Err(e) = sender.send(WlcsEvent::NewClient {
|
||||
stream: server_side,
|
||||
client_id: client_side.as_raw_fd(),
|
||||
}) {
|
||||
return Err(Error::new(ErrorKind::ConnectionReset, e));
|
||||
}
|
||||
return Ok(client_side.into());
|
||||
}
|
||||
}
|
||||
Err(Error::from(ErrorKind::NotFound))
|
||||
}
|
||||
|
||||
fn position_window_absolute(&self, display: *mut wl_display, surface: *mut wl_proxy, x: i32, y: i32) {
|
||||
let client_id = unsafe { ffi_dispatch!(wayland_client_handle(), wl_display_get_fd, display) };
|
||||
let surface_id = unsafe { ffi_dispatch!(wayland_client_handle(), wl_proxy_get_id, surface) };
|
||||
if let Some((ref sender, _)) = self.server {
|
||||
let _ = sender.send(WlcsEvent::PositionWindow {
|
||||
client_id,
|
||||
surface_id,
|
||||
location: (x, y).into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn create_pointer(&mut self) -> Option<Self::Pointer> {
|
||||
let Some(ref server) = self.server else {
|
||||
return None;
|
||||
};
|
||||
Some(PointerHandle {
|
||||
device_id: DEVICE_ID.fetch_add(1, Ordering::Relaxed),
|
||||
sender: server.0.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_touch(&mut self) -> Option<Self::Touch> {
|
||||
let Some(ref server) = self.server else {
|
||||
return None;
|
||||
};
|
||||
Some(TouchHandle {
|
||||
device_id: DEVICE_ID.fetch_add(1, Ordering::Relaxed),
|
||||
sender: server.0.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_descriptor(&self) -> &WlcsIntegrationDescriptor {
|
||||
&crate::DESCRIPTOR
|
||||
}
|
||||
}
|
||||
|
||||
struct PointerHandle {
|
||||
device_id: u32,
|
||||
sender: Sender<WlcsEvent>,
|
||||
}
|
||||
|
||||
impl wlcs::Pointer for PointerHandle {
|
||||
fn move_absolute(&mut self, x: wl_fixed_t, y: wl_fixed_t) {
|
||||
let _ = self.sender.send(WlcsEvent::PointerMoveAbsolute {
|
||||
device_id: self.device_id,
|
||||
location: (wl_fixed_to_double(x), wl_fixed_to_double(y)).into(),
|
||||
});
|
||||
}
|
||||
|
||||
fn move_relative(&mut self, dx: wl_fixed_t, dy: wl_fixed_t) {
|
||||
let _ = self.sender.send(WlcsEvent::PointerMoveRelative {
|
||||
device_id: self.device_id,
|
||||
delta: (wl_fixed_to_double(dx), wl_fixed_to_double(dy)).into(),
|
||||
});
|
||||
}
|
||||
|
||||
fn button_up(&mut self, button: i32) {
|
||||
let _ = self.sender.send(WlcsEvent::PointerButtonUp {
|
||||
device_id: self.device_id,
|
||||
button_id: button,
|
||||
});
|
||||
}
|
||||
|
||||
fn button_down(&mut self, button: i32) {
|
||||
let _ = self.sender.send(WlcsEvent::PointerButtonDown {
|
||||
device_id: self.device_id,
|
||||
button_id: button,
|
||||
});
|
||||
}
|
||||
|
||||
fn destroy(&mut self) {}
|
||||
}
|
||||
|
||||
struct TouchHandle {
|
||||
device_id: u32,
|
||||
sender: Sender<WlcsEvent>,
|
||||
}
|
||||
|
||||
impl wlcs::Touch for TouchHandle {
|
||||
fn touch_down(&mut self, x: wl_fixed_t, y: wl_fixed_t) {
|
||||
let _ = self.sender.send(WlcsEvent::TouchDown {
|
||||
device_id: self.device_id,
|
||||
location: (wl_fixed_to_double(x), wl_fixed_to_double(y)).into(),
|
||||
});
|
||||
}
|
||||
|
||||
fn touch_move(&mut self, x: wl_fixed_t, y: wl_fixed_t) {
|
||||
let _ = self.sender.send(WlcsEvent::TouchMove {
|
||||
device_id: self.device_id,
|
||||
location: (wl_fixed_to_double(x), wl_fixed_to_double(y)).into(),
|
||||
});
|
||||
}
|
||||
|
||||
fn touch_up(&mut self) {
|
||||
let _ = self.sender.send(WlcsEvent::TouchUp {
|
||||
device_id: self.device_id,
|
||||
});
|
||||
}
|
||||
|
||||
fn destroy(&mut self) {}
|
||||
}
|
||||
|
||||
fn start_anvil(channel: calloop::channel::Channel<WlcsEvent>) -> JoinHandle<()> {
|
||||
std::thread::spawn(move || main_loop::run(channel))
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex, atomic::Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
input::{ButtonState, InputTime},
|
||||
renderer::{
|
||||
damage::OutputDamageTracker,
|
||||
element::AsRenderElements,
|
||||
test::{DummyFramebuffer, DummyRenderer},
|
||||
},
|
||||
},
|
||||
input::pointer::{
|
||||
ButtonEvent, CursorImageAttributes, CursorImageStatus, MotionEvent, RelativeMotionEvent,
|
||||
},
|
||||
output::{Mode, Output, PhysicalProperties, Subpixel},
|
||||
reexports::{
|
||||
calloop::{
|
||||
EventLoop,
|
||||
channel::{Channel, Event as ChannelEvent},
|
||||
},
|
||||
wayland_server::{Client, Display, Resource, protocol::wl_surface},
|
||||
},
|
||||
utils::{IsAlive, SERIAL_COUNTER as SCOUNTER, Scale},
|
||||
wayland::compositor,
|
||||
};
|
||||
|
||||
use anvil::{AnvilState, ClientState, drawing::PointerElement, render::*, state::Backend};
|
||||
|
||||
use crate::WlcsEvent;
|
||||
|
||||
const OUTPUT_NAME: &str = "anvil";
|
||||
|
||||
struct TestState {
|
||||
clients: HashMap<i32, Client>,
|
||||
}
|
||||
|
||||
impl Backend for TestState {
|
||||
fn seat_name(&self) -> String {
|
||||
"anvil_wlcs".into()
|
||||
}
|
||||
|
||||
fn reset_buffers(&mut self, _output: &Output) {}
|
||||
fn early_import(&mut self, _surface: &wl_surface::WlSurface) {}
|
||||
fn update_led_state(&mut self, _led_state: smithay::input::keyboard::LedState) {}
|
||||
}
|
||||
|
||||
pub fn run(channel: Channel<WlcsEvent>) {
|
||||
let mut event_loop =
|
||||
EventLoop::<AnvilState<TestState>>::try_new().expect("Failed to init the event loop.");
|
||||
|
||||
let display = Display::new().expect("Failed to init display");
|
||||
let test_state = TestState {
|
||||
clients: HashMap::new(),
|
||||
};
|
||||
let mut state = AnvilState::init(display, event_loop.handle(), test_state, false);
|
||||
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(channel, move |event, &mut (), data| match event {
|
||||
ChannelEvent::Msg(evt) => handle_event(evt, data),
|
||||
ChannelEvent::Closed => handle_event(WlcsEvent::Exit, data),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut renderer = DummyRenderer::default();
|
||||
let mut framebuffer = DummyFramebuffer;
|
||||
|
||||
let mode = Mode {
|
||||
size: (800, 600).into(),
|
||||
refresh: 60_000,
|
||||
};
|
||||
|
||||
let output = Output::new(
|
||||
OUTPUT_NAME.to_string(),
|
||||
PhysicalProperties {
|
||||
size: (0, 0).into(),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: "Smithay".into(),
|
||||
model: "WLCS".into(),
|
||||
serial_number: "Unknown".into(),
|
||||
},
|
||||
);
|
||||
let _global = output.create_global::<AnvilState<TestState>>(&state.display_handle);
|
||||
output.change_current_state(Some(mode), None, None, Some((0, 0).into()));
|
||||
output.set_preferred(mode);
|
||||
state.space.map_output(&output, (0, 0));
|
||||
|
||||
let mut damage_tracker = OutputDamageTracker::from_output(&output);
|
||||
let mut pointer_element = PointerElement::default();
|
||||
|
||||
while state.running.load(Ordering::SeqCst) {
|
||||
// pretend to draw something
|
||||
{
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let mut elements: Vec<CustomRenderElements<_>> = Vec::new();
|
||||
|
||||
// draw the cursor as relevant
|
||||
// reset the cursor if the surface is no longer alive
|
||||
let mut reset = false;
|
||||
if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
|
||||
reset = !surface.alive();
|
||||
}
|
||||
if reset {
|
||||
state.cursor_status = CursorImageStatus::default_named();
|
||||
}
|
||||
|
||||
let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
|
||||
compositor::with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<CursorImageAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.hotspot
|
||||
})
|
||||
} else {
|
||||
(0, 0).into()
|
||||
};
|
||||
let cursor_pos = state.pointer.current_location();
|
||||
|
||||
pointer_element.set_status(state.cursor_status.clone());
|
||||
elements.extend(
|
||||
pointer_element.render_elements(
|
||||
&mut renderer,
|
||||
(cursor_pos - cursor_hotspot.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round(),
|
||||
scale,
|
||||
1.0,
|
||||
),
|
||||
);
|
||||
|
||||
// draw the dnd icon if any
|
||||
if let Some(icon) = state.dnd_icon.as_ref() {
|
||||
if icon.surface.alive() {
|
||||
let dnd_icon_pos = (cursor_pos + icon.offset.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round();
|
||||
elements.extend(AsRenderElements::<DummyRenderer>::render_elements(
|
||||
&smithay::desktop::space::SurfaceTree::from_surface(&icon.surface),
|
||||
&mut renderer,
|
||||
dnd_icon_pos,
|
||||
scale,
|
||||
1.0,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let _ = render_output(
|
||||
&output,
|
||||
&state.space,
|
||||
elements,
|
||||
&mut renderer,
|
||||
&mut framebuffer,
|
||||
&mut damage_tracker,
|
||||
0,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
// Send frame events so that client start drawing their next frame
|
||||
state.space.elements().for_each(|window| {
|
||||
window.send_frame(&output, state.clock.now(), Some(Duration::ZERO), |_, _| {
|
||||
Some(output.clone())
|
||||
})
|
||||
});
|
||||
|
||||
if event_loop
|
||||
.dispatch(Some(Duration::from_millis(16)), &mut state)
|
||||
.is_err()
|
||||
{
|
||||
state.running.store(false, Ordering::SeqCst);
|
||||
} else {
|
||||
state.space.refresh();
|
||||
state.popups.cleanup();
|
||||
state.display_handle.flush_clients().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(event: WlcsEvent, state: &mut AnvilState<TestState>) {
|
||||
match event {
|
||||
WlcsEvent::Exit => state.running.store(false, Ordering::SeqCst),
|
||||
WlcsEvent::NewClient { stream, client_id } => {
|
||||
let client = state
|
||||
.display_handle
|
||||
.insert_client(stream, Arc::new(ClientState::default()))
|
||||
.expect("Failed to insert client");
|
||||
state.backend_data.clients.insert(client_id, client);
|
||||
}
|
||||
WlcsEvent::PositionWindow {
|
||||
client_id,
|
||||
surface_id,
|
||||
location,
|
||||
} => {
|
||||
// find the surface
|
||||
let client = state.backend_data.clients.get(&client_id);
|
||||
let toplevel = state.space.elements().find(|w| {
|
||||
if let Some(surface) = w.wl_surface() {
|
||||
state.display_handle.get_client(surface.id()).ok().as_ref() == client
|
||||
&& surface.id().protocol_id() == surface_id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
if let Some(toplevel) = toplevel.cloned() {
|
||||
// set its location
|
||||
state.space.map_element(toplevel, location, false);
|
||||
}
|
||||
}
|
||||
// pointer inputs
|
||||
WlcsEvent::NewPointer { .. } => {}
|
||||
WlcsEvent::PointerMoveAbsolute { location, .. } => {
|
||||
let serial = SCOUNTER.next_serial();
|
||||
let under = state.surface_under(location);
|
||||
let time = InputTime::now();
|
||||
let ptr = state.pointer.clone();
|
||||
ptr.motion(
|
||||
state,
|
||||
under,
|
||||
&MotionEvent {
|
||||
location,
|
||||
serial,
|
||||
time,
|
||||
},
|
||||
);
|
||||
ptr.frame(state);
|
||||
}
|
||||
WlcsEvent::PointerMoveRelative { delta, .. } => {
|
||||
let pointer_location = state.pointer.current_location() + delta;
|
||||
let serial = SCOUNTER.next_serial();
|
||||
let under = state.surface_under(pointer_location);
|
||||
let time = InputTime::now();
|
||||
let ptr = state.pointer.clone();
|
||||
ptr.motion(
|
||||
state,
|
||||
under.clone(),
|
||||
&MotionEvent {
|
||||
location: pointer_location,
|
||||
serial,
|
||||
time,
|
||||
},
|
||||
);
|
||||
ptr.relative_motion(
|
||||
state,
|
||||
under,
|
||||
&RelativeMotionEvent {
|
||||
delta,
|
||||
delta_unaccel: delta,
|
||||
time,
|
||||
},
|
||||
);
|
||||
ptr.frame(state);
|
||||
}
|
||||
WlcsEvent::PointerButtonDown { button_id, .. } => {
|
||||
let serial = SCOUNTER.next_serial();
|
||||
let ptr = state.seat.get_pointer().unwrap();
|
||||
if !ptr.is_grabbed() {
|
||||
let under = state
|
||||
.space
|
||||
.element_under(ptr.current_location())
|
||||
.map(|(w, _)| w.clone());
|
||||
if let Some(window) = under.as_ref() {
|
||||
state.space.raise_element(window, true);
|
||||
}
|
||||
state
|
||||
.seat
|
||||
.get_keyboard()
|
||||
.unwrap()
|
||||
.set_focus(state, under.map(Into::into), serial);
|
||||
}
|
||||
let time = InputTime::now();
|
||||
ptr.button(
|
||||
state,
|
||||
&ButtonEvent {
|
||||
button: button_id as u32,
|
||||
state: ButtonState::Pressed,
|
||||
serial,
|
||||
time,
|
||||
},
|
||||
);
|
||||
ptr.frame(state);
|
||||
}
|
||||
WlcsEvent::PointerButtonUp { button_id, .. } => {
|
||||
let serial = SCOUNTER.next_serial();
|
||||
let time = InputTime::now();
|
||||
let ptr = state.seat.get_pointer().unwrap();
|
||||
ptr.button(
|
||||
state,
|
||||
&ButtonEvent {
|
||||
button: button_id as u32,
|
||||
state: ButtonState::Released,
|
||||
serial,
|
||||
time,
|
||||
},
|
||||
);
|
||||
ptr.frame(state);
|
||||
}
|
||||
WlcsEvent::PointerRemoved { .. } => {}
|
||||
// touch inputs
|
||||
WlcsEvent::NewTouch { .. } => {}
|
||||
WlcsEvent::TouchDown { .. } => {}
|
||||
WlcsEvent::TouchMove { .. } => {}
|
||||
WlcsEvent::TouchUp { .. } => {}
|
||||
WlcsEvent::TouchRemoved { .. } => {}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user