Merge pull request #204 from LogicalOverflow/wlcs

Add wlcs tests

Big thanks to @LogicalOverflow!
This commit is contained in:
Ottatop 2024-04-26 13:06:48 -05:00 committed by GitHub
commit a21eb9bcec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1176 additions and 12 deletions

4
.gitignore vendored
View file

@ -9,5 +9,9 @@ target/
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Don't push local doc builds
api/lua/doc/doc
# This is a library
api/rust/Cargo.lock
# Don't push compiled WLCS
wlcs

60
Cargo.lock generated
View file

@ -562,6 +562,15 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "container_of"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89f5bbeb86761f66a87f8e327265ea0111f82f1928a84037b9abedab9f79472b"
dependencies = [
"memoffset 0.6.5",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -1461,6 +1470,15 @@ dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.9.1"
@ -1532,6 +1550,17 @@ dependencies = [
"jni-sys",
]
[[package]]
name = "nix"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags 2.5.0",
"cfg-if",
"libc",
]
[[package]]
name = "nix"
version = "0.28.0"
@ -1765,7 +1794,7 @@ dependencies = [
"cliclack",
"dircpy",
"image",
"nix",
"nix 0.28.0",
"pinnacle",
"pinnacle-api",
"pinnacle-api-defs",
@ -3071,7 +3100,7 @@ dependencies = [
"dlib",
"libc",
"log",
"memoffset",
"memoffset 0.9.1",
"once_cell",
"pkg-config",
]
@ -3409,6 +3438,33 @@ dependencies = [
"memchr",
]
[[package]]
name = "wlcs"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d99c305ce368db32f0c675a3a17abbfd4123a59f4a1c56c0ee70b372cb7609e"
dependencies = [
"container_of",
"memoffset 0.9.1",
"nix 0.27.1",
"wayland-sys",
]
[[package]]
name = "wlcs_pinnacle"
version = "0.0.1"
dependencies = [
"pinnacle",
"pinnacle-api",
"smithay",
"tempfile",
"tokio",
"tracing",
"tracing-subscriber",
"wayland-sys",
"wlcs",
]
[[package]]
name = "x11-dl"
version = "2.21.0"

View file

@ -1,5 +1,10 @@
[workspace]
members = ["pinnacle-api-defs", "api/rust", "api/rust/pinnacle-api-macros"]
members = [
"pinnacle-api-defs",
"api/rust",
"api/rust/pinnacle-api-macros",
"wlcs_pinnacle",
]
[workspace.package]
authors = ["Ottatop <ottatop1227@gmail.com>"]
@ -15,6 +20,9 @@ prost = "0.12.4"
tonic = "0.11.0"
tonic-reflection = "0.11.0"
tonic-build = "0.11.0"
# Tracing
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "registry"] }
# API definitions
pinnacle-api-defs = { path = "./pinnacle-api-defs" }
# Misc.
@ -23,6 +31,7 @@ xdg = "2.5.2"
bitflags = "2.5.0"
clap = { version = "4.5.4", features = ["derive"] }
dircpy = "0.3.16"
tempfile = "3.10.1"
########################################################################yo😎###########
@ -42,8 +51,8 @@ keywords = ["wayland", "compositor", "smithay", "lua"]
# smithay is down there somewhere
smithay-drm-extras = { git = "https://github.com/Smithay/smithay", rev = "c293ec7" }
# Tracing
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "registry"] }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
tracing-appender = "0.2.3"
# Errors
anyhow = { version = "1.0.81", features = ["backtrace"] }
@ -76,8 +85,9 @@ dircpy = { workspace = true }
chrono = "0.4.37"
bytemuck = "1.15.0"
pinnacle-api = { path = "./api/rust" }
smithay = { workspace = true }
[dependencies.smithay]
[workspace.dependencies.smithay]
git = "https://github.com/Smithay/smithay"
rev = "c293ec7"
default-features = false
@ -110,12 +120,13 @@ xdg = { workspace = true }
[dev-dependencies]
temp-env = "0.3.6"
tempfile = "3.10.1"
tempfile = { workspace = true }
test-log = { version = "0.2.15", default-features = false, features = ["trace"] }
pinnacle = { path = ".", features = ["testing"] }
pinnacle = { path = ".", features = ["wlcs"] }
pinnacle-api = { path = "./api/rust" }
[features]
testing = [
"smithay/renderer_test",
]
wlcs = [ "testing" ]

15
compile_wlcs.sh Executable file
View file

@ -0,0 +1,15 @@
#!/bin/sh
WLCS_SHA=26c5a8cfef265b4ae021adebfec90d758c08792e
if [ -f "./wlcs/wlcs" ] && [ "$(cd wlcs; git rev-parse HEAD)" = "${WLCS_SHA}" ] ; then
echo "WLCS commit 26c5a8c is already compiled"
else
echo "Compiling WLCS"
git clone https://github.com/canonical/wlcs
cd wlcs || exit
# checkout a specific revision
git reset --hard "${WLCS_SHA}"
cmake -DWLCS_BUILD_ASAN=False -DWLCS_BUILD_TSAN=False -DWLCS_BUILD_UBSAN=False -DCMAKE_EXPORT_COMPILE_COMMANDS=1 .
make
fi

View file

@ -31,6 +31,12 @@
rust-analyzer
cargo-outdated
# wlcs
(writeScriptBin "wlcs" ''
#!/bin/sh
${wlcs}/libexec/wlcs/wlcs "$@"
'')
wayland
# build time stuff

View file

@ -45,6 +45,8 @@ use self::{udev::Udev, winit::Winit};
pub mod dummy;
pub mod udev;
pub mod winit;
#[cfg(feature = "wlcs")]
pub mod wlcs;
pub enum Backend {
/// The compositor is running in a Winit window

View file

@ -16,6 +16,8 @@ use smithay::{
use crate::state::State;
#[cfg(feature = "wlcs")]
use super::wlcs::Wlcs;
use super::Backend;
use super::BackendData;
@ -24,6 +26,8 @@ pub const DUMMY_OUTPUT_NAME: &str = "Dummy Window";
pub struct Dummy {
pub renderer: DummyRenderer,
// pub dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
#[cfg(feature = "wlcs")]
pub wlcs_state: Wlcs,
}
impl Backend {
@ -63,7 +67,7 @@ pub fn setup_dummy(
size: (0, 0).into(),
subpixel: Subpixel::Unknown,
make: "Pinnacle".to_string(),
model: "Winit Window".to_string(),
model: "Dummy Window".to_string(),
};
let output = Output::new(DUMMY_OUTPUT_NAME.to_string(), physical_properties);
@ -91,6 +95,8 @@ pub fn setup_dummy(
let backend = Dummy {
renderer,
// dmabuf_state,
#[cfg(feature = "wlcs")]
wlcs_state: Wlcs::default(),
};
let mut state = State::init(

135
src/backend/wlcs.rs Normal file
View file

@ -0,0 +1,135 @@
use std::{collections::HashMap, path::Path};
use smithay::{
backend::renderer::{test::DummyRenderer, ImportMemWl},
output::{Output, Subpixel},
reexports::{
calloop::{self, EventLoop},
wayland_server::{Client, Display},
},
utils::Transform,
};
use tracing::debug;
use crate::{
state::{State, WithState},
tag::TagId,
};
use super::{dummy::Dummy, Backend};
#[derive(Default)]
pub struct Wlcs {
pub clients: HashMap<i32, Client>,
}
impl Backend {
pub fn wlcs_mut(&mut self) -> &mut Wlcs {
let Backend::Dummy(dummy) = self else { unreachable!() };
&mut dummy.wlcs_state
}
}
pub fn setup_wlcs_dummy() -> anyhow::Result<(State, EventLoop<'static, State>)> {
let event_loop: EventLoop<State> = EventLoop::try_new()?;
let display: Display<State> = Display::new()?;
let display_handle = display.handle();
let loop_handle = event_loop.handle();
let mode = smithay::output::Mode {
size: (1920, 1080).into(),
refresh: 60_000,
};
let physical_properties = smithay::output::PhysicalProperties {
size: (0, 0).into(),
subpixel: Subpixel::Unknown,
make: "Pinnacle".to_string(),
model: "Dummy Window".to_string(),
};
let output = Output::new("Pinnacle Window".to_string(), physical_properties);
output.create_global::<State>(&display_handle);
output.change_current_state(
Some(mode),
Some(Transform::Flipped180),
None,
Some((0, 0).into()),
);
output.set_preferred(mode);
let renderer = DummyRenderer::new();
let shm_formats = renderer.shm_formats();
let backend = Dummy {
renderer,
wlcs_state: Wlcs::default(),
};
let mut state = State::init(
super::Backend::Dummy(backend),
display,
event_loop.get_signal(),
loop_handle,
false,
None,
)?;
state.output_focus_stack.set_focus(output.clone());
state.shm_state.update_formats(shm_formats);
state.space.map_output(&output, (0, 0));
Ok((state, event_loop))
}
impl State {
pub fn start_wlcs_config<F>(&mut self, socket_dir: &Path, run_config: F) -> anyhow::Result<()>
where
F: FnOnce() + Send + 'static,
{
// Clear state
debug!("Clearing tags");
for output in self.space.outputs() {
output.with_state_mut(|state| state.tags.clear());
}
TagId::reset();
debug!("Clearing input state");
self.input_state.clear();
self.config.clear(&self.loop_handle);
self.signal_state.clear();
self.input_state.reload_keybind = None;
self.input_state.kill_keybind = None;
if self.grpc_server_join_handle.is_none() {
self.start_grpc_server(socket_dir)?;
}
let (pinger, ping_source) = calloop::ping::make_ping()?;
let token = self
.loop_handle
.insert_source(ping_source, move |_, _, _state| {})?;
std::thread::spawn(move || {
run_config();
pinger.ping();
});
self.config.config_reload_on_crash_token = Some(token);
Ok(())
}
}

View file

@ -184,7 +184,7 @@ pub struct Config {
pub connector_saved_states: HashMap<OutputName, ConnectorSavedState>,
pub config_join_handle: Option<JoinHandle<()>>,
config_reload_on_crash_token: Option<RegistrationToken>,
pub(crate) config_reload_on_crash_token: Option<RegistrationToken>,
pub shutdown_sender:
Option<tokio::sync::mpsc::UnboundedSender<Result<ShutdownWatchResponse, tonic::Status>>>,
@ -208,7 +208,7 @@ impl Config {
.unwrap_or_else(|| get_config_dir(xdg_base_dirs))
}
fn clear(&mut self, loop_handle: &LoopHandle<State>) {
pub(crate) fn clear(&mut self, loop_handle: &LoopHandle<State>) {
self.window_rules.clear();
self.connector_saved_states.clear();
if let Some(join_handle) = self.config_join_handle.take() {

View file

@ -483,7 +483,7 @@ mod output {
local props = Pinnacle.output.get_focused():props()
assert(props.make == "Pinnacle")
assert(props.model == "Winit Window")
assert(props.model == "Dummy Window")
assert(props.x == 0)
assert(props.y == 0)
assert(props.logical_width == 1920)

24
wlcs_pinnacle/Cargo.toml Normal file
View file

@ -0,0 +1,24 @@
[package]
name = "wlcs_pinnacle"
version = "0.0.1"
authors.workspace = true
edition.workspace = true
repository.workspace = true
publish = false
[lib]
crate-type = ["cdylib"]
[dependencies]
smithay = { workspace = true }
pinnacle = { path = "..", features = [ "wlcs" ] }
pinnacle-api = { path = "../api/rust" }
wayland-sys = { version = "0.31.1", features = ["client", "server"] }
wlcs = "0.1"
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
tokio = { workspace = true, features = ["net", "rt"] }
tempfile = { workspace = true }

View file

@ -0,0 +1,36 @@
use pinnacle::state::State;
mod inner {
use pinnacle_api::layout::{CyclingLayoutManager, MasterStackLayout};
use pinnacle_api::window::rules::{WindowRule, WindowRuleCondition};
use pinnacle_api::ApiModules;
#[pinnacle_api::config(modules)]
async fn main() {
#[allow(unused_variables)]
let ApiModules { layout, window, .. } = modules;
window.add_window_rule(
WindowRuleCondition::default().all(vec![]),
WindowRule::new().floating(true),
);
let _layout_requester = layout.set_manager(CyclingLayoutManager::new([
Box::<MasterStackLayout>::default() as _,
]));
}
pub(crate) fn start_config() {
main()
}
}
pub fn run_config(state: &mut State) {
let temp_dir = tempfile::tempdir().expect("failed to setup temp dir for socket");
let socket_dir = temp_dir.path().to_owned();
state
.start_wlcs_config(&socket_dir, move || {
inner::start_config();
drop(temp_dir);
})
.expect("failed to start wlcs config");
}

View file

@ -0,0 +1,332 @@
use core::hash::Hash;
use smithay::{
backend::input::{
AbsolutePositionEvent, ButtonState, Device, DeviceCapability, Event, InputBackend,
InputEvent, PointerButtonEvent, PointerMotionAbsoluteEvent, PointerMotionEvent,
TouchDownEvent, TouchEvent, TouchMotionEvent, TouchSlot, TouchUpEvent, UnusedEvent,
},
utils::{Logical, Point},
};
pub struct WlcsInputBackend {}
impl InputBackend for WlcsInputBackend {
type Device = WlcsDevice;
type KeyboardKeyEvent = UnusedEvent;
type PointerAxisEvent = UnusedEvent;
type PointerButtonEvent = WlcsPointerButtonEvent;
type PointerMotionEvent = WlcsPointerMotionEvent;
type PointerMotionAbsoluteEvent = WlcsPointerMotionAbsoluteEvent;
type GestureSwipeBeginEvent = UnusedEvent;
type GestureSwipeUpdateEvent = UnusedEvent;
type GestureSwipeEndEvent = UnusedEvent;
type GesturePinchBeginEvent = UnusedEvent;
type GesturePinchUpdateEvent = UnusedEvent;
type GesturePinchEndEvent = UnusedEvent;
type GestureHoldBeginEvent = UnusedEvent;
type GestureHoldEndEvent = UnusedEvent;
type TouchDownEvent = WlcsTouchDownEvent;
type TouchUpEvent = WlcsTouchUpEvent;
type TouchMotionEvent = WlcsTouchMotionEvent;
type TouchCancelEvent = UnusedEvent;
type TouchFrameEvent = UnusedEvent;
type TabletToolAxisEvent = UnusedEvent;
type TabletToolProximityEvent = UnusedEvent;
type TabletToolTipEvent = UnusedEvent;
type TabletToolButtonEvent = UnusedEvent;
type SwitchToggleEvent = UnusedEvent;
type SpecialEvent = ();
}
#[derive(PartialEq, Eq)]
pub struct WlcsDevice {
pub device_id: u32,
pub capability: DeviceCapability,
}
impl Hash for WlcsDevice {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.device_id.hash(state);
}
}
impl Device for WlcsDevice {
fn id(&self) -> String {
format!("{}", self.device_id)
}
fn name(&self) -> String {
format!("wlcs-device-{}", self.device_id)
}
fn has_capability(&self, capability: DeviceCapability) -> bool {
self.capability == capability
}
fn usb_id(&self) -> Option<(u32, u32)> {
None
}
fn syspath(&self) -> Option<std::path::PathBuf> {
None
}
}
pub struct WlcsPointerButtonEvent {
pub device_id: u32,
pub time: u64,
pub button_code: u32,
pub state: ButtonState,
}
impl From<WlcsPointerButtonEvent> for InputEvent<WlcsInputBackend> {
fn from(event: WlcsPointerButtonEvent) -> Self {
InputEvent::<WlcsInputBackend>::PointerButton { event }
}
}
impl Event<WlcsInputBackend> for WlcsPointerButtonEvent {
fn time(&self) -> u64 {
self.time
}
fn device(&self) -> <WlcsInputBackend as InputBackend>::Device {
WlcsDevice {
device_id: self.device_id,
capability: DeviceCapability::Pointer,
}
}
}
impl PointerButtonEvent<WlcsInputBackend> for WlcsPointerButtonEvent {
fn button_code(&self) -> u32 {
self.button_code
}
fn state(&self) -> ButtonState {
self.state
}
}
pub struct WlcsPointerMotionEvent {
pub device_id: u32,
pub time: u64,
pub delta: Point<f64, Logical>,
}
impl From<WlcsPointerMotionEvent> for InputEvent<WlcsInputBackend> {
fn from(event: WlcsPointerMotionEvent) -> Self {
InputEvent::<WlcsInputBackend>::PointerMotion { event }
}
}
impl Event<WlcsInputBackend> for WlcsPointerMotionEvent {
fn time(&self) -> u64 {
self.time
}
fn device(&self) -> <WlcsInputBackend as InputBackend>::Device {
WlcsDevice {
device_id: self.device_id,
capability: DeviceCapability::Pointer,
}
}
}
impl PointerMotionEvent<WlcsInputBackend> for WlcsPointerMotionEvent {
fn delta_x(&self) -> f64 {
self.delta.x
}
fn delta_y(&self) -> f64 {
self.delta.y
}
fn delta_x_unaccel(&self) -> f64 {
self.delta_x()
}
fn delta_y_unaccel(&self) -> f64 {
self.delta_y()
}
}
pub struct WlcsPointerMotionAbsoluteEvent {
pub device_id: u32,
pub time: u64,
pub position: Point<f64, Logical>,
}
impl From<WlcsPointerMotionAbsoluteEvent> for InputEvent<WlcsInputBackend> {
fn from(event: WlcsPointerMotionAbsoluteEvent) -> Self {
InputEvent::<WlcsInputBackend>::PointerMotionAbsolute { event }
}
}
impl Event<WlcsInputBackend> for WlcsPointerMotionAbsoluteEvent {
fn time(&self) -> u64 {
self.time
}
fn device(&self) -> <WlcsInputBackend as InputBackend>::Device {
WlcsDevice {
device_id: self.device_id,
capability: DeviceCapability::Pointer,
}
}
}
impl AbsolutePositionEvent<WlcsInputBackend> for WlcsPointerMotionAbsoluteEvent {
fn x(&self) -> f64 {
self.position.x
}
fn y(&self) -> f64 {
self.position.y
}
fn x_transformed(&self, _width: i32) -> f64 {
self.x()
}
fn y_transformed(&self, _height: i32) -> f64 {
self.y()
}
}
impl PointerMotionAbsoluteEvent<WlcsInputBackend> for WlcsPointerMotionAbsoluteEvent {}
pub struct WlcsTouchDownEvent {
pub device_id: u32,
pub time: u64,
pub position: Point<f64, Logical>,
}
impl From<WlcsTouchDownEvent> for InputEvent<WlcsInputBackend> {
fn from(event: WlcsTouchDownEvent) -> Self {
InputEvent::<WlcsInputBackend>::TouchDown { event }
}
}
impl Event<WlcsInputBackend> for WlcsTouchDownEvent {
fn time(&self) -> u64 {
self.time
}
fn device(&self) -> <WlcsInputBackend as InputBackend>::Device {
WlcsDevice {
device_id: self.device_id,
capability: DeviceCapability::Touch,
}
}
}
impl TouchEvent<WlcsInputBackend> for WlcsTouchDownEvent {
fn slot(&self) -> TouchSlot {
None.into()
}
}
impl AbsolutePositionEvent<WlcsInputBackend> for WlcsTouchDownEvent {
fn x(&self) -> f64 {
self.position.x
}
fn y(&self) -> f64 {
self.position.y
}
fn x_transformed(&self, _width: i32) -> f64 {
self.x()
}
fn y_transformed(&self, _height: i32) -> f64 {
self.y()
}
}
impl TouchDownEvent<WlcsInputBackend> for WlcsTouchDownEvent {}
pub struct WlcsTouchUpEvent {
pub device_id: u32,
pub time: u64,
}
impl From<WlcsTouchUpEvent> for InputEvent<WlcsInputBackend> {
fn from(event: WlcsTouchUpEvent) -> Self {
InputEvent::<WlcsInputBackend>::TouchUp { event }
}
}
impl Event<WlcsInputBackend> for WlcsTouchUpEvent {
fn time(&self) -> u64 {
self.time
}
fn device(&self) -> <WlcsInputBackend as InputBackend>::Device {
WlcsDevice {
device_id: self.device_id,
capability: DeviceCapability::Touch,
}
}
}
impl TouchEvent<WlcsInputBackend> for WlcsTouchUpEvent {
fn slot(&self) -> TouchSlot {
None.into()
}
}
impl TouchUpEvent<WlcsInputBackend> for WlcsTouchUpEvent {}
pub struct WlcsTouchMotionEvent {
pub device_id: u32,
pub time: u64,
pub position: Point<f64, Logical>,
}
impl From<WlcsTouchMotionEvent> for InputEvent<WlcsInputBackend> {
fn from(event: WlcsTouchMotionEvent) -> Self {
InputEvent::<WlcsInputBackend>::TouchMotion { event }
}
}
impl Event<WlcsInputBackend> for WlcsTouchMotionEvent {
fn time(&self) -> u64 {
self.time
}
fn device(&self) -> <WlcsInputBackend as InputBackend>::Device {
WlcsDevice {
device_id: self.device_id,
capability: DeviceCapability::Touch,
}
}
}
impl TouchEvent<WlcsInputBackend> for WlcsTouchMotionEvent {
fn slot(&self) -> TouchSlot {
None.into()
}
}
impl AbsolutePositionEvent<WlcsInputBackend> for WlcsTouchMotionEvent {
fn x(&self) -> f64 {
self.position.x
}
fn y(&self) -> f64 {
self.position.y
}
fn x_transformed(&self, _width: i32) -> f64 {
self.x()
}
fn y_transformed(&self, _height: i32) -> f64 {
self.y()
}
}
impl TouchMotionEvent<WlcsInputBackend> for WlcsTouchMotionEvent {}

327
wlcs_pinnacle/src/lib.rs Normal file
View file

@ -0,0 +1,327 @@
pub(crate) mod config;
mod input_backend;
mod main_loop;
use std::{
io,
os::{
fd::{AsRawFd, OwnedFd},
unix::net::UnixStream,
},
sync::{
atomic::{AtomicU32, Ordering},
Once,
},
thread::{spawn, JoinHandle},
};
use smithay::{
reexports::calloop::channel::{channel, Sender},
utils::{Logical, Point},
};
use tracing::{info, warn};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer};
use wayland_sys::{
client::{wayland_client_handle, wl_display, wl_proxy},
common::{wl_fixed_t, wl_fixed_to_double},
ffi_dispatch,
};
use wlcs::{
extension_list,
ffi_display_server_api::{
WlcsExtensionDescriptor, WlcsIntegrationDescriptor, WlcsServerIntegration,
},
ffi_wrappers::wlcs_server,
wlcs_server_integration, Pointer, Touch, Wlcs,
};
wlcs_server_integration!(PinnacleHandle);
#[derive(Debug)]
pub enum WlcsEvent {
Stop,
NewClient {
stream: UnixStream,
client_id: i32,
},
PositionWindow {
client_id: i32,
surface_id: u32,
location: Point<i32, Logical>,
},
NewPointer {
device_id: u32,
},
PointerMoveRelative {
device_id: u32,
delta: Point<f64, Logical>,
},
PointerMoveAbsolute {
device_id: u32,
position: Point<f64, Logical>,
},
PointerButton {
device_id: u32,
button_id: i32,
pressed: bool,
},
NewTouch {
device_id: u32,
},
TouchDown {
device_id: u32,
position: Point<f64, Logical>,
},
TouchMove {
device_id: u32,
position: Point<f64, Logical>,
},
TouchUp {
device_id: u32,
},
}
struct PinnacleConnection {
sender: Sender<WlcsEvent>,
join: JoinHandle<()>,
}
impl PinnacleConnection {
fn start() -> Self {
let (sender, receiver) = channel();
let join = spawn(move || main_loop::run(receiver));
Self { sender, join }
}
}
struct PinnacleHandle {
server_conn: Option<PinnacleConnection>,
}
static SUPPORTED_EXTENSIONS: &[WlcsExtensionDescriptor] = extension_list!(
// Skip reasons:
// 5 Missing extension: gtk_primary_selection_device_manager>= 1
// 1 Missing extension: wlcs_non_existent_extension>= 1
// 89 Missing extension: wl_shell>= 2
// 1 Missing extension: xdg_not_really_an_extension>= 1
// 30 Missing extension: zwlr_foreign_toplevel_manager_v1>= 1
// 12 Missing extension: zwlr_virtual_pointer_manager_v1>= 1
// 15 Missing extension: zwp_pointer_constraints_v1>= 1
// 3 Missing extension: zwp_relative_pointer_manager_v1>= 1
// 12 Missing extension: zwp_text_input_manager_v2>= 1
// 11 Missing extension: zwp_text_input_manager_v3>= 1
// 180 Missing extension: zxdg_shell_v6>= 1
// mostly from https://github.com/Smithay/smithay/issues/781
("wl_compositor", 6),
("wl_subcompositor", 1),
("wl_shm", 1),
("wl_data_device_manager", 3),
("wl_seat", 9),
("wl_output", 4),
("wp_presentation", 1),
("wp_viewporter", 1),
("xdg_shell", 6),
("linux-dmabuf-v1", 5),
("xdg_shell", 6),
);
static DESCRIPTOR: WlcsIntegrationDescriptor = WlcsIntegrationDescriptor {
version: 1,
num_extensions: SUPPORTED_EXTENSIONS.len(),
supported_extensions: SUPPORTED_EXTENSIONS.as_ptr(),
};
static DEVICE_ID: AtomicU32 = AtomicU32::new(0);
fn new_device_id() -> u32 {
DEVICE_ID.fetch_add(1, Ordering::Relaxed)
}
fn init() {
let env_filter = EnvFilter::try_from_default_env();
let stdout_env_filter = env_filter.unwrap_or_else(|_| EnvFilter::new("info"));
let stdout_layer = tracing_subscriber::fmt::layer()
.compact()
.with_writer(std::io::stdout)
.with_filter(stdout_env_filter);
tracing_subscriber::registry().with(stdout_layer).init();
}
static INIT_ONCE: Once = Once::new();
impl Wlcs for PinnacleHandle {
type Pointer = PointerHandle;
type Touch = TouchHandle;
fn new() -> Self {
INIT_ONCE.call_once(init);
Self { server_conn: None }
}
fn start(&mut self) {
self.server_conn = Some(PinnacleConnection::start());
}
fn stop(&mut self) {
if let Some(conn) = self.server_conn.take() {
let _ = conn.sender.send(WlcsEvent::Stop);
let _ = conn.join.join();
}
}
fn create_client_socket(&self) -> io::Result<OwnedFd> {
info!("new client start");
let conn = self
.server_conn
.as_ref()
.ok_or(io::Error::from(io::ErrorKind::NotFound))?;
let (client, server) = UnixStream::pair()?;
conn.sender
.send(WlcsEvent::NewClient {
stream: server,
client_id: client.as_raw_fd(),
})
.map_err(|e| {
warn!("failed to send NewClient event");
io::Error::new(io::ErrorKind::ConnectionReset, e)
})?;
info!("new client end");
Ok(client.into())
}
fn position_window_absolute(
&self,
display: *mut wl_display,
surface: *mut wl_proxy,
x: i32,
y: i32,
) {
if let Some(conn) = &self.server_conn {
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) };
conn.sender
.send(WlcsEvent::PositionWindow {
client_id,
surface_id,
location: (x, y).into(),
})
.expect("failed to send position_window_absolute");
}
}
fn create_pointer(&mut self) -> Option<Self::Pointer> {
let device_id = new_device_id();
self.server_conn
.as_ref()
.map(|conn| conn.sender.clone())
.map(|sender| {
sender
.send(WlcsEvent::NewPointer { device_id })
.expect("failed to send new_pointer");
PointerHandle { device_id, sender }
})
}
fn create_touch(&mut self) -> Option<Self::Touch> {
let device_id = new_device_id();
self.server_conn
.as_ref()
.map(|conn| conn.sender.clone())
.map(|sender| {
sender
.send(WlcsEvent::NewTouch { device_id })
.expect("failed to send new_touch");
TouchHandle { device_id, sender }
})
}
fn get_descriptor(&self) -> &WlcsIntegrationDescriptor {
&DESCRIPTOR
}
}
struct PointerHandle {
device_id: u32,
sender: Sender<WlcsEvent>,
}
impl Pointer for PointerHandle {
fn move_absolute(&mut self, x: wl_fixed_t, y: wl_fixed_t) {
self.sender
.send(WlcsEvent::PointerMoveAbsolute {
device_id: self.device_id,
position: (wl_fixed_to_double(x), wl_fixed_to_double(y)).into(),
})
.expect("failed to send move_absolute");
}
fn move_relative(&mut self, dx: wl_fixed_t, dy: wl_fixed_t) {
self.sender
.send(WlcsEvent::PointerMoveRelative {
device_id: self.device_id,
delta: (wl_fixed_to_double(dx), wl_fixed_to_double(dy)).into(),
})
.expect("failed to send move_relative");
}
fn button_up(&mut self, button: i32) {
self.sender
.send(WlcsEvent::PointerButton {
device_id: self.device_id,
button_id: button,
pressed: false,
})
.expect("failed to send button_up");
}
fn button_down(&mut self, button: i32) {
self.sender
.send(WlcsEvent::PointerButton {
device_id: self.device_id,
button_id: button,
pressed: true,
})
.expect("failed to send button_down");
}
}
struct TouchHandle {
device_id: u32,
sender: Sender<WlcsEvent>,
}
impl Touch for TouchHandle {
fn touch_down(&mut self, x: wl_fixed_t, y: wl_fixed_t) {
self.sender
.send(WlcsEvent::TouchDown {
device_id: self.device_id,
position: (wl_fixed_to_double(x), wl_fixed_to_double(y)).into(),
})
.expect("failed to send touch_down");
}
fn touch_move(&mut self, x: wl_fixed_t, y: wl_fixed_t) {
self.sender
.send(WlcsEvent::TouchMove {
device_id: self.device_id,
position: (wl_fixed_to_double(x), wl_fixed_to_double(y)).into(),
})
.expect("failed to send touch_move");
}
fn touch_up(&mut self) {
self.sender
.send(WlcsEvent::TouchUp {
device_id: self.device_id,
})
.expect("failed to send touch_up");
}
}

View file

@ -0,0 +1,210 @@
use std::{sync::Arc, time::Duration};
use pinnacle::{
backend::wlcs::setup_wlcs_dummy,
state::{ClientState, State, WithState},
window::window_state::FloatingOrTiled,
};
use smithay::{
backend::input::{ButtonState, DeviceCapability, InputEvent},
reexports::{
calloop::channel::{Channel, Event},
wayland_server::{Client, Resource},
},
utils::Rectangle,
wayland::seat::WaylandFocus,
};
use crate::{
config::run_config,
input_backend::{
WlcsDevice, WlcsInputBackend, WlcsPointerButtonEvent, WlcsPointerMotionAbsoluteEvent,
WlcsPointerMotionEvent, WlcsTouchDownEvent, WlcsTouchUpEvent,
},
WlcsEvent,
};
pub(crate) fn run(channel: Channel<WlcsEvent>) {
let (mut state, mut event_loop) = setup_wlcs_dummy().expect("failed to setup dummy backend");
event_loop
.handle()
.insert_source(channel, move |event, &mut (), data| match event {
Event::Msg(msg) => handle_event(msg, data),
Event::Closed => handle_event(WlcsEvent::Stop, data),
})
.expect("failed to add wlcs event handler");
// FIXME: a better way to deal with tokio here?
let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime");
let _handle = rt.enter();
// FIXME: once starting pinnacle without xwayland is a thing, handle this
// | properly; in this case, we probably no longer need to start the
// | config manually anymore either, as this is only needed now,
// | because the config is started after xwayland reports its ready
// when xdiplay is None when starting the config, the grpc server is not
// started, until it is set; this bypasses this for now
state.xdisplay = Some(u32::MAX);
run_config(&mut state);
// wait for the config to connect to the layout service
while state.layout_state.layout_request_sender.is_none() {
event_loop
.dispatch(Some(Duration::from_millis(10)), &mut state)
.expect("event_loop error while waiting for config");
}
event_loop
.run(None, &mut state, |state| {
state.update_pointer_focus();
state.fixup_z_layering();
state.space.refresh();
state.popup_manager.cleanup();
state
.display_handle
.flush_clients()
.expect("failed to flush client buffers");
})
.expect("failed to run event_loop");
}
fn handle_event(event: WlcsEvent, state: &mut State) {
tracing::debug!("handle_event {:?}", event);
match event {
WlcsEvent::Stop => state.shutdown(),
WlcsEvent::NewClient { stream, client_id } => {
let client: Client = state
.display_handle
.insert_client(stream, Arc::new(ClientState::default()))
.expect("failed to insert new client");
state.backend.wlcs_mut().clients.insert(client_id, client);
}
WlcsEvent::PositionWindow {
client_id,
surface_id,
location,
} => {
let client = state.backend.wlcs_mut().clients.get(&client_id);
let window = 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
}
})
.cloned();
if let Some(window) = window {
state.space.map_element(window.clone(), location, false);
let size = state
.space
.element_geometry(&window)
.expect("window to be positioned was not mapped")
.size;
if window.with_state(|state| state.floating_or_tiled.is_tiled()) {
window.toggle_floating();
}
window.with_state_mut(|state| {
state.floating_or_tiled =
FloatingOrTiled::Floating(Rectangle::from_loc_and_size(location, size));
});
for output in state.space.outputs_for_element(&window) {
state.schedule_render(&output);
}
}
}
WlcsEvent::NewPointer { device_id } => {
state.process_input_event(InputEvent::<WlcsInputBackend>::DeviceAdded {
device: WlcsDevice {
device_id,
capability: DeviceCapability::Pointer,
},
})
}
WlcsEvent::PointerMoveAbsolute {
device_id,
position,
} => state.process_input_event(
WlcsPointerMotionAbsoluteEvent {
device_id,
time: Duration::from(state.clock.now()).as_millis() as u64,
position,
}
.into(),
),
WlcsEvent::PointerMoveRelative { device_id, delta } => state.process_input_event(
WlcsPointerMotionEvent {
device_id,
time: Duration::from(state.clock.now()).as_millis() as u64,
delta,
}
.into(),
),
WlcsEvent::PointerButton {
device_id,
button_id,
pressed,
} => state.process_input_event(
WlcsPointerButtonEvent {
device_id,
time: Duration::from(state.clock.now()).as_millis() as u64,
button_code: button_id as u32,
state: if pressed {
ButtonState::Pressed
} else {
ButtonState::Released
},
}
.into(),
),
WlcsEvent::NewTouch { device_id } => {
state.process_input_event(InputEvent::<WlcsInputBackend>::DeviceAdded {
device: WlcsDevice {
device_id,
capability: DeviceCapability::Pointer,
},
})
}
WlcsEvent::TouchDown {
device_id,
position,
} => state.process_input_event(
WlcsTouchDownEvent {
device_id,
time: Duration::from(state.clock.now()).as_millis() as u64,
position,
}
.into(),
),
WlcsEvent::TouchMove {
device_id,
position,
} => state.process_input_event(
WlcsTouchDownEvent {
device_id,
time: Duration::from(state.clock.now()).as_millis() as u64,
position,
}
.into(),
),
WlcsEvent::TouchUp { device_id } => state.process_input_event(
WlcsTouchUpEvent {
device_id,
time: Duration::from(state.clock.now()).as_millis() as u64,
}
.into(),
),
}
}