diff --git a/build/build.rs b/build/build.rs index 4ec611af..dae1d884 100644 --- a/build/build.rs +++ b/build/build.rs @@ -30,6 +30,7 @@ mod tokens; mod vulkan; mod wire; mod wire_dbus; +mod wire_ei; mod wire_xcon; fn open(s: &str) -> io::Result> { @@ -46,6 +47,7 @@ fn open(s: &str) -> io::Result> { fn main() -> anyhow::Result<()> { wire::main()?; + wire_ei::main()?; wire_dbus::main()?; wire_xcon::main()?; enums::main()?; diff --git a/build/wire_ei.rs b/build/wire_ei.rs new file mode 100644 index 00000000..9f77a95f --- /dev/null +++ b/build/wire_ei.rs @@ -0,0 +1,679 @@ +use { + crate::{ + open, + tokens::{tokenize, Symbol, Token, TokenKind, TreeDelim}, + }, + anyhow::{bail, Context, Result}, + std::{fs::DirEntry, io::Write, os::unix::ffi::OsStrExt}, +}; + +#[derive(Debug)] +struct Lined { + #[allow(dead_code)] + line: u32, + val: T, +} + +#[derive(Debug)] +enum Type { + Id(String), + U32, + I32, + U64, + I64, + F32, + Str, + OptStr, + Fd, +} + +#[derive(Debug)] +struct Field { + name: String, + ty: Lined, +} + +#[derive(Debug)] +struct Message { + name: String, + camel_name: String, + safe_name: String, + id: u32, + fields: Vec>, + attribs: MessageAttribs, + has_reference_type: bool, +} + +#[derive(Debug, Default)] +struct MessageAttribs { + since: Option, + context: Option<&'static str>, +} + +struct Parser<'a> { + pos: usize, + tokens: &'a [Token<'a>], +} + +struct ParseResult { + requests: Vec>, + events: Vec>, +} + +impl<'a> Parser<'a> { + fn parse(&mut self) -> Result { + let mut requests = vec![]; + let mut events = vec![]; + while !self.eof() { + let (line, ty) = self.expect_ident()?; + let res = match ty.as_bytes() { + b"request" => &mut requests, + b"event" => &mut events, + _ => bail!("In line {}: Unexpected entry {:?}", line, ty), + }; + res.push(self.parse_message(res.len() as _)?); + } + Ok(ParseResult { requests, events }) + } + + fn eof(&self) -> bool { + self.pos == self.tokens.len() + } + + fn not_eof(&self) -> Result<()> { + if self.eof() { + bail!("Unexpected eof"); + } + Ok(()) + } + + fn yes_eof(&self) -> Result<()> { + if !self.eof() { + bail!( + "Unexpected trailing tokens in line {}", + self.tokens[self.pos].line + ); + } + Ok(()) + } + + fn parse_message_attribs(&mut self, attribs: &mut MessageAttribs) -> Result<()> { + let (_, tokens) = self.expect_tree(TreeDelim::Paren)?; + let mut parser = Parser { pos: 0, tokens }; + while !parser.eof() { + let (line, name) = parser.expect_ident()?; + match name { + "since" => { + parser.expect_symbol(Symbol::Equals)?; + attribs.since = Some(parser.expect_number()?.1) + } + "receiver" => attribs.context = Some("Receiver"), + "sender" => attribs.context = Some("Sender"), + _ => bail!("In line {}: Unexpected attribute {}", line, name), + } + if !parser.eof() { + parser.expect_symbol(Symbol::Comma)?; + } + } + Ok(()) + } + + fn parse_message(&mut self, id: u32) -> Result> { + let (line, name) = self.expect_ident()?; + let res: Result<_> = (|| { + self.not_eof()?; + let mut attribs = MessageAttribs::default(); + if let TokenKind::Tree { + delim: TreeDelim::Paren, + .. + } = self.tokens[self.pos].kind + { + self.parse_message_attribs(&mut attribs)?; + } + let (_, body) = self.expect_tree(TreeDelim::Brace)?; + let mut parser = Parser { + pos: 0, + tokens: body, + }; + let mut fields = vec![]; + while !parser.eof() { + fields.push(parser.parse_field()?); + } + let has_reference_type = fields.iter().any(|f| match &f.val.ty.val { + Type::OptStr | Type::Str => true, + _ => false, + }); + let safe_name = match name { + "move" => "move_", + _ => name, + }; + Ok(Lined { + line, + val: Message { + name: name.to_owned(), + camel_name: to_camel(name), + safe_name: safe_name.to_string(), + id, + fields, + attribs, + has_reference_type, + }, + }) + })(); + res.with_context(|| format!("While parsing message starting at line {}", line)) + } + + fn parse_field(&mut self) -> Result> { + let (line, name) = self.expect_ident()?; + let res: Result<_> = (|| { + self.expect_symbol(Symbol::Colon)?; + let ty = self.parse_type()?; + if !self.eof() { + self.expect_symbol(Symbol::Comma)?; + } + Ok(Lined { + line, + val: Field { + name: name.to_owned(), + ty, + }, + }) + })(); + res.with_context(|| format!("While parsing field starting at line {}", line)) + } + + fn expect_ident(&mut self) -> Result<(u32, &'a str)> { + self.not_eof()?; + let token = &self.tokens[self.pos]; + self.pos += 1; + match &token.kind { + TokenKind::Ident(id) => Ok((token.line, *id)), + k => bail!( + "In line {}: Expected identifier, found {}", + token.line, + k.name() + ), + } + } + + fn expect_number(&mut self) -> Result<(u32, u32)> { + self.not_eof()?; + let token = &self.tokens[self.pos]; + self.pos += 1; + match &token.kind { + TokenKind::Num(n) => Ok((token.line, *n)), + k => bail!( + "In line {}: Expected number, found {}", + token.line, + k.name() + ), + } + } + + fn expect_symbol(&mut self, symbol: Symbol) -> Result<()> { + self.not_eof()?; + let token = &self.tokens[self.pos]; + self.pos += 1; + match &token.kind { + TokenKind::Symbol(s) if *s == symbol => Ok(()), + k => bail!( + "In line {}: Expected {}, found {}", + token.line, + symbol.name(), + k.name() + ), + } + } + + fn expect_tree_(&mut self) -> Result<(u32, TreeDelim, &'a [Token<'a>])> { + self.not_eof()?; + let token = &self.tokens[self.pos]; + self.pos += 1; + match &token.kind { + TokenKind::Tree { delim, body } => Ok((token.line, *delim, body)), + k => bail!("In line {}: Expected tree, found {}", token.line, k.name()), + } + } + + fn expect_tree(&mut self, exp_delim: TreeDelim) -> Result<(u32, &'a [Token<'a>])> { + let (line, delim, tokens) = self.expect_tree_()?; + if delim == exp_delim { + Ok((line, tokens)) + } else { + bail!( + "In line {}: Expected {:?}-delimited tree, found {:?}-delimited tree", + line, + exp_delim, + delim.opening() + ) + } + } + + fn parse_type(&mut self) -> Result> { + self.not_eof()?; + let (line, ty) = self.expect_ident()?; + let ty = match ty.as_bytes() { + b"u32" => Type::U32, + b"i32" => Type::I32, + b"u64" => Type::U64, + b"i64" => Type::I64, + b"f32" => Type::F32, + b"str" => Type::Str, + b"optstr" => Type::OptStr, + b"fd" => Type::Fd, + b"id" => { + let (_, body) = self.expect_tree(TreeDelim::Paren)?; + let ident: Result<_> = (|| { + let mut parser = Parser { + pos: 0, + tokens: body, + }; + let id = parser.expect_ident()?; + parser.yes_eof()?; + Ok(id) + })(); + let (_, ident) = ident.with_context(|| { + format!("While parsing identifier starting in line {}", line) + })?; + Type::Id(to_camel(ident)) + } + _ => bail!("Unknown type {}", ty), + }; + Ok(Lined { line, val: ty }) + } +} + +fn parse_messages(s: &[u8]) -> Result { + let tokens = tokenize(s)?; + let mut parser = Parser { + pos: 0, + tokens: &tokens, + }; + parser.parse() +} + +fn to_camel(s: &str) -> String { + let mut last_was_underscore = true; + let mut res = String::new(); + for mut b in s.as_bytes().iter().copied() { + if b == b'_' { + last_was_underscore = true; + } else { + if last_was_underscore { + b = b.to_ascii_uppercase() + } + res.push(b as char); + last_was_underscore = false; + } + } + res +} + +fn write_type(f: &mut W, ty: &Type) -> Result<()> { + let ty = match ty { + Type::Id(id) => { + write!(f, "{}Id", id)?; + return Ok(()); + } + Type::U32 => "u32", + Type::I32 => "i32", + Type::U64 => "u64", + Type::I64 => "i64", + Type::F32 => "f32", + Type::Str => "&'a str", + Type::OptStr => "Option<&'a str>", + Type::Fd => "Rc", + }; + write!(f, "{}", ty)?; + Ok(()) +} + +fn write_field(f: &mut W, field: &Field) -> Result<()> { + write!(f, " pub {}: ", field.name)?; + write_type(f, &field.ty.val)?; + writeln!(f, ",")?; + Ok(()) +} + +fn write_message_type( + f: &mut W, + obj: &str, + message: &Message, + needs_lifetime: bool, +) -> Result<()> { + let lifetime = if needs_lifetime { "<'a>" } else { "" }; + writeln!(f, " pub struct {}{} {{", message.camel_name, lifetime)?; + writeln!(f, " pub self_id: {}Id,", obj)?; + for field in &message.fields { + write_field(f, &field.val)?; + } + writeln!(f, " }}")?; + writeln!( + f, + " impl{} std::fmt::Debug for {}{} {{", + lifetime, message.camel_name, lifetime + )?; + writeln!( + f, + " fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{" + )?; + write!(f, r#" write!(fmt, "{}("#, message.name)?; + for (i, field) in message.fields.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + let formatter = match &field.val.ty.val { + Type::OptStr | Type::Str | Type::Fd => "{:?}", + Type::Id(_) => "{:x}", + _ => "{}", + }; + write!(f, "{}: {}", field.val.name, formatter)?; + } + write!(f, r#")""#)?; + for field in &message.fields { + write!(f, ", self.{}", field.val.name)?; + } + writeln!(f, r")")?; + writeln!(f, " }}")?; + writeln!(f, " }}")?; + Ok(()) +} + +fn write_message(f: &mut W, obj: &str, message: &Message) -> Result<()> { + let has_reference_type = message.has_reference_type; + let uppercase = message.name.to_ascii_uppercase(); + writeln!(f)?; + writeln!(f, " pub const {}: u32 = {};", uppercase, message.id)?; + write_message_type(f, obj, message, has_reference_type)?; + let lifetime = if has_reference_type { "<'a>" } else { "" }; + let lifetime_b = if has_reference_type { "<'b>" } else { "" }; + let parser = if message.fields.len() > 0 { + "parser" + } else { + "_parser" + }; + writeln!( + f, + " impl<'a> EiRequestParser<'a> for {}{} {{", + message.camel_name, lifetime + )?; + writeln!( + f, + " type Generic<'b> = {}{};", + message.camel_name, lifetime_b, + )?; + writeln!( + f, + " fn parse({}: &mut EiMsgParser<'_, 'a>) -> Result {{", + parser + )?; + writeln!(f, " Ok(Self {{")?; + writeln!(f, " self_id: {}Id::NONE,", obj)?; + for field in &message.fields { + let p = match &field.val.ty.val { + Type::Id(_) => "object", + Type::U32 => "uint", + Type::I32 => "int", + Type::U64 => "ulong", + Type::I64 => "long", + Type::F32 => "float", + Type::OptStr => "optstr", + Type::Str => "str", + Type::Fd => "fd", + }; + writeln!(f, " {}: parser.{}()?,", field.val.name, p)?; + } + writeln!(f, " }})")?; + writeln!(f, " }}")?; + writeln!(f, " }}")?; + writeln!( + f, + " impl{} EiEventFormatter for {}{} {{", + lifetime, message.camel_name, lifetime + )?; + writeln!( + f, + " fn format(self, fmt: &mut EiMsgFormatter<'_>) {{" + )?; + writeln!(f, " fmt.header(self.self_id, {});", uppercase)?; + fn write_fmt_expr(f: &mut W, prefix: &str, ty: &Type, access: &str) -> Result<()> { + let p = match ty { + Type::Id(_) => "object", + Type::U32 => "uint", + Type::I32 => "int", + Type::U64 => "ulong", + Type::I64 => "long", + Type::F32 => "float", + Type::OptStr => "optstr", + Type::Str => "string", + Type::Fd => "fd", + }; + writeln!(f, " {prefix}fmt.{p}({access});")?; + Ok(()) + } + for field in &message.fields { + write_fmt_expr( + f, + "", + &field.val.ty.val, + &format!("self.{}", field.val.name), + )?; + } + writeln!(f, " }}")?; + writeln!(f, " fn id(&self) -> EiObjectId {{")?; + writeln!(f, " self.self_id.into()")?; + writeln!(f, " }}")?; + writeln!(f, " fn interface(&self) -> EiInterface {{")?; + writeln!(f, " {}", obj)?; + writeln!(f, " }}")?; + writeln!(f, " }}")?; + Ok(()) +} + +fn write_request_handler( + f: &mut W, + camel_obj_name: &str, + messages: &ParseResult, +) -> Result<()> { + writeln!(f)?; + writeln!( + f, + " pub trait {camel_obj_name}RequestHandler: crate::ei::ei_object::EiObject + Sized {{" + )?; + writeln!(f, " type Error: std::error::Error;")?; + for message in &messages.requests { + let msg = &message.val; + let lt = match msg.has_reference_type { + true => "<'_>", + false => "", + }; + writeln!(f)?; + writeln!( + f, + " fn {}(&self, req: {}{lt}, _slf: &Rc) -> Result<(), Self::Error>;", + msg.safe_name, msg.camel_name + )?; + } + writeln!(f)?; + writeln!(f, " #[inline(always)]")?; + writeln!(f, " fn handle_request_impl(")?; + writeln!(f, " self: Rc,")?; + writeln!(f, " client: &crate::ei::ei_client::EiClient,")?; + writeln!(f, " req: u32,")?; + writeln!( + f, + " parser: crate::utils::buffd::EiMsgParser<'_, '_>," + )?; + writeln!( + f, + " ) -> Result<(), crate::ei::ei_client::EiClientError> {{" + )?; + if messages.requests.is_empty() { + writeln!(f, " #![allow(unused_variables)]")?; + writeln!( + f, + " Err(crate::ei::ei_client::EiClientError::InvalidMethod)" + )?; + } else { + writeln!(f, " let method;")?; + writeln!( + f, + " let error: Box = match req {{" + )?; + for message in &messages.requests { + let msg = &message.val; + write!(f, " {} ", msg.id)?; + let mut have_cond = false; + if let Some(since) = msg.attribs.since { + write!(f, "if self.version() >= {since} ")?; + have_cond = true; + } + if let Some(context) = msg.attribs.context { + if have_cond { + write!(f, "&&")?; + } else { + write!(f, "if")?; + } + write!(f, " self.context() == EiContext::{context} ")?; + } + writeln!(f, "=> {{")?; + writeln!(f, " method = \"{}\";", msg.name)?; + writeln!( + f, + " match client.parse(&*self, parser) {{" + )?; + writeln!( + f, + " Ok(req) => match self.{}(req, &self) {{", + msg.safe_name + )?; + writeln!(f, " Ok(()) => return Ok(()),")?; + writeln!(f, " Err(e) => Box::new(e),")?; + writeln!(f, " }},")?; + writeln!( + f, + " Err(e) => Box::new(crate::ei::ei_client::EiParserError(e))," + )?; + writeln!(f, " }}")?; + writeln!(f, " }},")?; + } + writeln!( + f, + " _ => return Err(crate::ei::ei_client::EiClientError::InvalidMethod)," + )?; + writeln!(f, " }};")?; + writeln!( + f, + " Err(crate::ei::ei_client::EiClientError::MethodError {{" + )?; + writeln!(f, " interface: {camel_obj_name},")?; + writeln!(f, " method,")?; + writeln!(f, " error,")?; + writeln!(f, " }})")?; + } + writeln!(f, " }}")?; + writeln!(f, " }}")?; + Ok(()) +} + +fn write_file(f: &mut W, file: &DirEntry, obj_names: &mut Vec) -> Result<()> { + let file_name = file.file_name(); + let file_name = std::str::from_utf8(file_name.as_bytes())?; + println!("cargo:rerun-if-changed=wire-ei/{}", file_name); + let obj_name = file_name.split(".").next().unwrap(); + obj_names.push(obj_name.to_string()); + let camel_obj_name = to_camel(obj_name); + writeln!(f)?; + writeln!(f, "ei_id!({}Id);", camel_obj_name)?; + writeln!(f)?; + writeln!( + f, + "pub const {}: EiInterface = EiInterface(\"{}\");", + camel_obj_name, obj_name + )?; + let contents = std::fs::read(file.path())?; + let messages = parse_messages(&contents)?; + writeln!(f)?; + writeln!(f, "pub mod {} {{", obj_name)?; + writeln!(f, " use super::*;")?; + for message in messages.requests.iter().chain(messages.events.iter()) { + write_message(f, &camel_obj_name, &message.val)?; + } + write_request_handler(f, &camel_obj_name, &messages)?; + writeln!(f, "}}")?; + Ok(()) +} + +fn write_interface_versions(f: &mut W, obj_names: &[String]) -> Result<()> { + writeln!(f)?; + writeln!(f, "pub struct EiInterfaceVersions {{")?; + for obj_name in obj_names { + writeln!(f, " pub {obj_name}: EiInterfaceVersion,")?; + } + writeln!(f, "}}")?; + writeln!(f)?; + writeln!(f, "impl EiInterfaceVersions {{")?; + writeln!( + f, + " pub fn for_each(&self, mut f: impl FnMut(EiInterface, &EiInterfaceVersion)) {{" + )?; + for obj_name in obj_names { + let camel = to_camel(obj_name); + writeln!(f, " f(crate::wire_ei::{camel}, &self.{obj_name});")?; + } + writeln!(f, " }}")?; + writeln!(f)?; + writeln!( + f, + " pub fn match_(&self, name: &str, f: impl FnOnce(&EiInterfaceVersion)) -> bool {{" + )?; + writeln!(f, " match name {{")?; + for obj_name in obj_names { + writeln!(f, " \"{obj_name}\" => f(&self.{obj_name}),")?; + } + writeln!(f, " _ => return false,")?; + writeln!(f, " }}")?; + writeln!(f, " true")?; + writeln!(f, " }}")?; + for obj_name in obj_names { + writeln!(f)?; + writeln!(f, " #[allow(dead_code)]")?; + writeln!(f, " pub fn {obj_name}(&self) -> EiVersion {{")?; + writeln!(f, " self.{obj_name}.version.get()")?; + writeln!(f, " }}")?; + } + writeln!(f, "}}")?; + Ok(()) +} + +pub fn main() -> Result<()> { + let mut f = open("wire_ei.rs")?; + writeln!(f, "use std::rc::Rc;")?; + writeln!(f, "use uapi::OwnedFd;")?; + writeln!(f, "use crate::ei::{{EiContext, EiInterfaceVersion}};")?; + writeln!( + f, + "use crate::ei::ei_client::{{EiEventFormatter, EiRequestParser}};" + )?; + writeln!( + f, + "use crate::ei::ei_object::{{EiObjectId, EiInterface, EiVersion}};" + )?; + writeln!( + f, + "use crate::utils::buffd::{{EiMsgFormatter, EiMsgParser, EiMsgParserError}};" + )?; + println!("cargo:rerun-if-changed=wire-ei"); + let mut files = vec![]; + for file in std::fs::read_dir("wire-ei")? { + files.push(file?); + } + files.sort_by_key(|f| f.file_name()); + let mut obj_names = vec![]; + for file in files { + write_file(&mut f, &file, &mut obj_names) + .with_context(|| format!("While processing {}", file.path().display()))?; + } + write_interface_versions(&mut f, &obj_names).context("Could not write interface versions")?; + Ok(()) +} diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index cc2bf7f3..fa70777d 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -992,6 +992,10 @@ impl Client { keymap } + pub fn set_ei_socket_enabled(&self, enabled: bool) { + self.send(&ClientMessage::SetEiSocketEnabled { enabled }) + } + pub fn latch(&self, seat: Seat, f: F) { if !self.feat_mod_mask.get() { log::error!("compositor does not support latching"); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index e53d2350..1a91eb66 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -506,6 +506,9 @@ pub enum ClientMessage<'a> { device: InputDevice, matrix: [[f32; 3]; 2], }, + SetEiSocketEnabled { + enabled: bool, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 349a868a..702ca31f 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -564,3 +564,12 @@ pub enum SwitchEvent { /// event of this kind is generated. ConvertedToTablet, } + +/// Enables or disables the unauthenticated libei socket. +/// +/// Even if the socket is disabled, application can still request access via the portal. +/// +/// The default is `false`. +pub fn set_libei_socket_enabled(enabled: bool) { + get!().set_ei_socket_enabled(enabled); +} diff --git a/release-notes.md b/release-notes.md index e7e1abd0..1778b9fc 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ - Add support for adaptive sync. - Add support for tearing. - Add support for touch input. +- Add support for libei. # 1.4.0 (2024-07-07) diff --git a/src/backend.rs b/src/backend.rs index 1ede68ed..9816bff3 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -418,7 +418,9 @@ pub enum InputEvent { time_usec: u64, id: i32, }, - TouchFrame, + TouchFrame { + time_usec: u64, + }, } pub enum DrmEvent { diff --git a/src/backends/metal/input.rs b/src/backends/metal/input.rs index 8f638a79..49a137b1 100644 --- a/src/backends/metal/input.rs +++ b/src/backends/metal/input.rs @@ -582,7 +582,9 @@ impl MetalBackend { } fn handle_touch_frame(self: &Rc, event: LibInputEvent) { - let (_, dev) = unpack!(self, event, touch_event); - dev.event(InputEvent::TouchFrame) + let (event, dev) = unpack!(self, event, touch_event); + dev.event(InputEvent::TouchFrame { + time_usec: event.time_usec(), + }) } } diff --git a/src/client.rs b/src/client.rs index 4b316aa1..19ba92cc 100644 --- a/src/client.rs +++ b/src/client.rs @@ -18,12 +18,11 @@ use { errorfmt::ErrorFmt, numcell::NumCell, pending_serial::PendingSerial, - trim::AsciiTrim, + pid_info::{get_pid_info, get_socket_creds, PidInfo}, }, wire::WlRegistryId, }, ahash::AHashMap, - bstr::ByteSlice, std::{ cell::{Cell, RefCell}, collections::VecDeque, @@ -123,22 +122,8 @@ impl Clients { effective_caps: ClientCaps, bounding_caps: ClientCaps, ) -> Result<(), ClientError> { - let (uid, pid) = { - let mut cred = c::ucred { - pid: 0, - uid: 0, - gid: 0, - }; - match uapi::getsockopt(socket.raw(), c::SOL_SOCKET, c::SO_PEERCRED, &mut cred) { - Ok(_) => (cred.uid, cred.pid), - Err(e) => { - log::error!( - "Cannot determine peer credentials of new connection: {:?}", - crate::utils::oserror::OsError::from(e) - ); - return Ok(()); - } - } + let Some((uid, pid)) = get_socket_creds(&socket) else { + return Ok(()); }; self.spawn2( id, @@ -274,12 +259,6 @@ pub trait RequestParser<'a>: Debug + Sized { fn parse(parser: &mut MsgParser<'_, 'a>) -> Result; } -pub struct PidInfo { - pub _uid: c::uid_t, - pub pid: c::pid_t, - pub comm: String, -} - pub struct Client { pub id: ClientId, pub state: Rc, @@ -516,18 +495,3 @@ pub trait WaylandObjectLookup: Copy + Into { fn lookup(client: &Client, id: Self) -> Option>; } - -fn get_pid_info(uid: c::uid_t, pid: c::pid_t) -> PidInfo { - let comm = match std::fs::read(format!("/proc/{}/comm", pid)) { - Ok(name) => name.trim().as_bstr().to_string(), - Err(e) => { - log::warn!("Could not read `comm` of pid {}: {}", pid, ErrorFmt(e)); - "Unknown".to_string() - } - }; - PidInfo { - _uid: uid, - pid, - comm, - } -} diff --git a/src/compositor.rs b/src/compositor.rs index ab335892..ee3d617a 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -15,6 +15,7 @@ use { config::ConfigProxy, damage::{visualize_damage, DamageVisualizer}, dbus::Dbus, + ei::ei_client::EiClients, forker, globals::Globals, ifs::{ @@ -109,6 +110,7 @@ pub enum CompositorError { } pub const WAYLAND_DISPLAY: &str = "WAYLAND_DISPLAY"; +pub const LIBEI_SOCKET: &str = "LIBEI_SOCKET"; pub const DISPLAY: &str = "DISPLAY"; const STATIC_VARS: &[(&str, &str)] = &[ @@ -251,6 +253,11 @@ fn start_compositor2( default_vrr_mode: Cell::new(VrrMode::NEVER), default_vrr_cursor_hz: Cell::new(None), default_tearing_mode: Cell::new(TearingMode::VARIANT_3), + ei_acceptor: Default::default(), + ei_acceptor_future: Default::default(), + enable_ei_acceptor: Default::default(), + ei_clients: EiClients::new(), + slow_ei_clients: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -305,6 +312,7 @@ async fn start_compositor3(state: Rc, test_future: Option) { if state.create_default_seat.get() && state.globals.seats.is_empty() { state.create_seat(DEFAULT_SEAT_NAME); } + state.update_ei_acceptor(); let _geh = start_global_event_handlers(&state, &backend); state.start_xwayland(); @@ -351,6 +359,7 @@ fn start_global_event_handlers( eng.spawn2(Phase::Present, perform_toplevel_screencasts(state.clone())), eng.spawn2(Phase::PostLayout, perform_screencast_realloc(state.clone())), eng.spawn2(Phase::PostLayout, visualize_damage(state.clone())), + eng.spawn(tasks::handle_slow_ei_clients(state.clone())), ] } diff --git a/src/config/handler.rs b/src/config/handler.rs index 5b9e5396..905af050 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -699,6 +699,11 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_ei_socket_enabled(&self, enabled: bool) { + self.state.enable_ei_acceptor.set(enabled); + self.state.update_ei_acceptor(); + } + fn handle_get_workspace(&self, name: &str) { let name = Rc::new(name.to_owned()); let ws = match self.workspaces_by_name.get(&name) { @@ -1910,6 +1915,9 @@ impl ConfigProxyHandler { ClientMessage::SetCalibrationMatrix { device, matrix } => self .handle_set_calibration_matrix(device, matrix) .wrn("set_calibration_matrix")?, + ClientMessage::SetEiSocketEnabled { enabled } => { + self.handle_set_ei_socket_enabled(enabled) + } } Ok(()) } diff --git a/src/ei.rs b/src/ei.rs new file mode 100644 index 00000000..16d2a638 --- /dev/null +++ b/src/ei.rs @@ -0,0 +1,33 @@ +use {crate::ei::ei_object::EiVersion, std::cell::Cell}; + +pub mod ei_acceptor; +pub mod ei_client; +pub mod ei_ifs; +pub mod ei_object; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum EiContext { + Sender, + Receiver, +} + +pub struct EiInterfaceVersion { + pub server_max_version: EiVersion, + pub client_max_version: Cell, + pub version: Cell, +} + +impl EiInterfaceVersion { + pub fn new(server_max_version: u32) -> Self { + Self { + server_max_version: EiVersion(server_max_version), + client_max_version: Cell::new(EiVersion(0)), + version: Cell::new(EiVersion(0)), + } + } + + pub fn set_client_version(&self, version: EiVersion) { + self.client_max_version.set(version); + self.version.set(self.server_max_version.min(version)); + } +} diff --git a/src/ei/ei_acceptor.rs b/src/ei/ei_acceptor.rs new file mode 100644 index 00000000..9f02f776 --- /dev/null +++ b/src/ei/ei_acceptor.rs @@ -0,0 +1,156 @@ +use { + crate::{ + async_engine::SpawnedFuture, + state::State, + utils::{errorfmt::ErrorFmt, oserror::OsError, xrd::xrd}, + }, + std::rc::Rc, + thiserror::Error, + uapi::{c, format_ustr, Errno, OwnedFd, Ustring}, +}; + +#[derive(Debug, Error)] +pub enum EiAcceptorError { + #[error("XDG_RUNTIME_DIR is not set")] + XrdNotSet, + #[error("XDG_RUNTIME_DIR ({0:?}) is too long to form a unix socket address")] + XrdTooLong(String), + #[error("Could not create a libei socket")] + SocketFailed(#[source] OsError), + #[error("Could not stat the existing socket")] + SocketStat(#[source] OsError), + #[error("Could not start listening for incoming connections")] + ListenFailed(#[source] OsError), + #[error("Could not open the lock file")] + OpenLockFile(#[source] OsError), + #[error("Could not lock the lock file")] + LockLockFile(#[source] OsError), + #[error("Could not bind the socket to an address")] + BindFailed(#[source] OsError), + #[error("All libei addresses in the range 0..1000 are already in use")] + AddressesInUse, +} + +pub struct EiAcceptor { + socket: EiAllocatedSocket, +} + +struct EiAllocatedSocket { + // eis-x + name: String, + // /run/user/1000/eis-x + path: Ustring, + insecure: Rc, + // /run/user/1000/eis-x.lock + lock_path: Ustring, + _lock_fd: OwnedFd, +} + +impl Drop for EiAllocatedSocket { + fn drop(&mut self) { + let _ = uapi::unlink(&self.path); + let _ = uapi::unlink(&self.lock_path); + } +} + +fn bind_socket( + insecure: &Rc, + xrd: &str, + id: u32, +) -> Result { + let mut addr: c::sockaddr_un = uapi::pod_zeroed(); + addr.sun_family = c::AF_UNIX as _; + let name = format!("eis-{}", id); + let path = format_ustr!("{}/{}", xrd, name); + let lock_path = format_ustr!("{}.lock", path.display()); + if path.len() + 1 > addr.sun_path.len() { + return Err(EiAcceptorError::XrdTooLong(xrd.to_string())); + } + let lock_fd = match uapi::open(&*lock_path, c::O_CREAT | c::O_CLOEXEC | c::O_RDWR, 0o644) { + Ok(l) => l, + Err(e) => return Err(EiAcceptorError::OpenLockFile(e.into())), + }; + if let Err(e) = uapi::flock(lock_fd.raw(), c::LOCK_EX | c::LOCK_NB) { + return Err(EiAcceptorError::LockLockFile(e.into())); + } + match uapi::lstat(&path) { + Ok(_) => { + log::info!("Unlinking {}", path.display()); + let _ = uapi::unlink(&path); + } + Err(Errno(c::ENOENT)) => {} + Err(e) => return Err(EiAcceptorError::SocketStat(e.into())), + } + let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]); + sun_path[..path.len()].copy_from_slice(path.as_bytes()); + sun_path[path.len()] = 0; + if let Err(e) = uapi::bind(insecure.raw(), &addr) { + return Err(EiAcceptorError::BindFailed(e.into())); + } + Ok(EiAllocatedSocket { + name, + path, + insecure: insecure.clone(), + lock_path, + _lock_fd: lock_fd, + }) +} + +fn allocate_socket() -> Result { + let xrd = match xrd() { + Some(d) => d, + _ => return Err(EiAcceptorError::XrdNotSet), + }; + let socket = match uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0) { + Ok(f) => Rc::new(f), + Err(e) => return Err(EiAcceptorError::SocketFailed(e.into())), + }; + for i in 1..1000 { + match bind_socket(&socket, &xrd, i) { + Ok(s) => return Ok(s), + Err(e) => { + log::warn!("Cannot use the eis-{} socket: {}", i, ErrorFmt(e)); + } + } + } + Err(EiAcceptorError::AddressesInUse) +} + +impl EiAcceptor { + pub fn spawn( + state: &Rc, + ) -> Result<(Rc, SpawnedFuture<()>), EiAcceptorError> { + let socket = allocate_socket()?; + log::info!("bound to socket {}", socket.path.display()); + if let Err(e) = uapi::listen(socket.insecure.raw(), 4096) { + return Err(EiAcceptorError::ListenFailed(e.into())); + } + let acc = Rc::new(EiAcceptor { socket }); + let future = state + .eng + .spawn(accept(acc.socket.insecure.clone(), state.clone())); + Ok((acc, future)) + } + + pub fn socket_name(&self) -> &str { + &self.socket.name + } +} + +async fn accept(fd: Rc, state: Rc) { + loop { + let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await { + Ok(fd) => fd, + Err(e) => { + log::error!("Could not accept a client: {}", ErrorFmt(e)); + break; + } + }; + let id = state.clients.id(); + if let Err(e) = state.ei_clients.spawn(id, &state, fd) { + log::error!("Could not spawn a client: {}", ErrorFmt(e)); + break; + } + } + state.ring.stop(); +} diff --git a/src/ei/ei_client.rs b/src/ei/ei_client.rs new file mode 100644 index 00000000..f5a22f14 --- /dev/null +++ b/src/ei/ei_client.rs @@ -0,0 +1,301 @@ +pub use crate::ei::ei_client::ei_error::{EiClientError, EiParserError}; +use { + crate::{ + async_engine::SpawnedFuture, + client::ClientId, + ei::{ + ei_client::ei_objects::EiObjects, + ei_ifs::{ei_connection::EiConnection, ei_handshake::EiHandshake}, + ei_object::{EiInterface, EiObject, EiObjectId}, + EiContext, EiInterfaceVersion, + }, + ifs::wl_seat::WlSeatGlobal, + leaks::Tracker, + state::State, + utils::{ + asyncevent::AsyncEvent, + buffd::{EiMsgFormatter, EiMsgParser, EiMsgParserError, OutBufferSwapchain}, + clonecell::CloneCell, + errorfmt::ErrorFmt, + numcell::NumCell, + pid_info::{get_pid_info, get_socket_creds, PidInfo}, + }, + wire_ei::EiInterfaceVersions, + }, + ahash::AHashMap, + std::{ + cell::{Cell, RefCell}, + error::Error, + fmt::Debug, + mem, + ops::DerefMut, + rc::Rc, + }, + uapi::{c, OwnedFd}, +}; + +mod ei_error; +mod ei_objects; +mod ei_tasks; + +pub struct EiClients { + pub clients: RefCell>, + shutdown_clients: RefCell>, +} + +impl EiClients { + pub fn new() -> Self { + Self { + clients: Default::default(), + shutdown_clients: Default::default(), + } + } + + pub fn announce_seat(&self, seat: &Rc) { + for ei_client in self.clients.borrow().values() { + if let Some(connection) = ei_client.data.connection.get() { + connection.announce_seat(&seat); + } + } + } + + pub fn clear(&self) { + mem::take(self.clients.borrow_mut().deref_mut()); + mem::take(self.shutdown_clients.borrow_mut().deref_mut()); + } + + pub fn spawn( + &self, + id: ClientId, + global: &Rc, + socket: Rc, + ) -> Result<(), EiClientError> { + let Some((uid, pid)) = get_socket_creds(&socket) else { + return Ok(()); + }; + self.spawn2(id, global, socket, uid, pid)?; + Ok(()) + } + + pub fn spawn2( + &self, + id: ClientId, + global: &Rc, + socket: Rc, + uid: c::uid_t, + pid: c::pid_t, + ) -> Result, EiClientError> { + let versions = EiInterfaceVersions { + ei_button: EiInterfaceVersion::new(1), + ei_callback: EiInterfaceVersion::new(1), + ei_connection: EiInterfaceVersion::new(1), + ei_device: EiInterfaceVersion::new(2), + ei_handshake: EiInterfaceVersion::new(1), + ei_keyboard: EiInterfaceVersion::new(1), + ei_pingpong: EiInterfaceVersion::new(1), + ei_pointer: EiInterfaceVersion::new(1), + ei_pointer_absolute: EiInterfaceVersion::new(1), + ei_scroll: EiInterfaceVersion::new(1), + ei_seat: EiInterfaceVersion::new(1), + ei_touchscreen: EiInterfaceVersion::new(1), + }; + let data = Rc::new(EiClient { + id, + state: global.clone(), + context: Cell::new(EiContext::Receiver), + connection: Default::default(), + checking_queue_size: Cell::new(false), + socket, + objects: EiObjects::new(), + swapchain: Default::default(), + flush_request: Default::default(), + shutdown: Default::default(), + tracker: Default::default(), + pid_info: get_pid_info(uid, pid), + disconnect_announced: Cell::new(false), + versions, + name: Default::default(), + last_serial: Default::default(), + }); + track!(data, data); + let handshake = Rc::new(EiHandshake::new(&data)); + track!(data, handshake); + handshake.send_handshake_version(); + data.objects.add_handshake(&handshake); + let client = EiClientHolder { + _handler: global.eng.spawn(ei_tasks::ei_client(data.clone())), + data: data.clone(), + }; + log::info!( + "Client {} connected, pid: {}, uid: {}, fd: {}, comm: {:?}", + id, + pid, + uid, + client.data.socket.raw(), + data.pid_info.comm, + ); + self.clients.borrow_mut().insert(client.data.id, client); + Ok(data) + } + + pub fn kill(&self, client: ClientId) { + log::info!("Removing client {}", client); + if self.clients.borrow_mut().remove(&client).is_none() { + self.shutdown_clients.borrow_mut().remove(&client); + } + } + + pub fn shutdown(&self, client_id: ClientId) { + if let Some(client) = self.clients.borrow_mut().remove(&client_id) { + log::info!("Shutting down client {}", client.data.id); + client.data.shutdown.trigger(); + client.data.flush_request.trigger(); + self.shutdown_clients.borrow_mut().insert(client_id, client); + } + } +} + +impl Drop for EiClients { + fn drop(&mut self) { + let _clients1 = mem::take(&mut *self.clients.borrow_mut()); + let _clients2 = mem::take(&mut *self.shutdown_clients.borrow_mut()); + } +} + +pub struct EiClientHolder { + pub data: Rc, + _handler: SpawnedFuture<()>, +} + +impl Drop for EiClientHolder { + fn drop(&mut self) { + self.data.objects.destroy(); + self.data.flush_request.clear(); + self.data.shutdown.clear(); + self.data.connection.take(); + } +} + +pub trait EiEventFormatter: Debug { + fn format(self, fmt: &mut EiMsgFormatter<'_>); + fn id(&self) -> EiObjectId; + fn interface(&self) -> EiInterface; +} + +pub trait EiRequestParser<'a>: Debug + Sized { + type Generic<'b>: EiRequestParser<'b>; + fn parse(parser: &mut EiMsgParser<'_, 'a>) -> Result; +} + +pub struct EiClient { + pub id: ClientId, + pub state: Rc, + pub context: Cell, + pub connection: CloneCell>>, + checking_queue_size: Cell, + socket: Rc, + pub objects: EiObjects, + swapchain: Rc>, + flush_request: AsyncEvent, + shutdown: AsyncEvent, + pub tracker: Tracker, + pub pid_info: PidInfo, + pub disconnect_announced: Cell, + pub versions: EiInterfaceVersions, + pub name: RefCell>, + pub last_serial: NumCell, +} + +impl EiClient { + pub fn new_id>(&self) -> T { + self.objects.id() + } + + pub fn serial(&self) -> u32 { + (self.last_serial.fetch_add(1) + 1) as u32 + } + + pub fn last_serial(&self) -> u32 { + self.last_serial.get() as u32 + } + + pub fn error(&self, message: impl Error) { + let msg = ErrorFmt(message).to_string(); + log::error!("Client {}: A fatal error occurred: {}", self.id, msg); + match self.connection.get() { + Some(d) => { + d.send_disconnected(Some(&msg)); + self.state.clients.shutdown(self.id); + } + _ => { + self.state.clients.kill(self.id); + } + } + } + + pub fn parse<'a, R: EiRequestParser<'a>>( + &self, + obj: &impl EiObject, + mut parser: EiMsgParser<'_, 'a>, + ) -> Result { + let res = R::parse(&mut parser)?; + parser.eof()?; + log::trace!( + "Client {} -> {}@{:x}.{:?}", + self.id, + obj.interface().name(), + obj.id(), + res + ); + Ok(res) + } + + pub fn event(self: &Rc, event: T) { + log::trace!( + "Client {} <= {}@{:x}.{:?}", + self.id, + event.interface().name(), + event.id(), + event, + ); + let mut fds = vec![]; + let mut swapchain = self.swapchain.borrow_mut(); + let mut fmt = EiMsgFormatter::new(&mut swapchain.cur, &mut fds); + event.format(&mut fmt); + fmt.write_len(); + if swapchain.cur.is_full() { + swapchain.commit(); + if swapchain.exceeds_limit() { + if !self.checking_queue_size.replace(true) { + self.state.slow_ei_clients.push(self.clone()); + } + } + } + self.flush_request.trigger(); + } + + pub async fn check_queue_size(&self) { + if self.swapchain.borrow_mut().exceeds_limit() { + self.state.eng.yield_now().await; + if self.swapchain.borrow_mut().exceeds_limit() { + log::error!("Client {} is too slow at fetching events", self.id); + self.state.ei_clients.kill(self.id); + return; + } + } + self.checking_queue_size.set(false); + } + + pub fn add_client_obj(&self, obj: &Rc) -> Result<(), EiClientError> { + self.objects.add_client_object(obj.clone())?; + Ok(()) + } + + pub fn add_server_obj(&self, obj: &Rc) { + self.objects.add_server_object(obj.clone()); + } + + pub fn remove_obj(self: &Rc, obj: &T) -> Result<(), EiClientError> { + self.objects.remove_obj(obj.id()) + } +} diff --git a/src/ei/ei_client/ei_error.rs b/src/ei/ei_client/ei_error.rs new file mode 100644 index 00000000..df23f458 --- /dev/null +++ b/src/ei/ei_client/ei_error.rs @@ -0,0 +1,45 @@ +use { + crate::{ + ei::ei_object::{EiInterface, EiObjectId}, + utils::buffd::{BufFdError, EiMsgParserError}, + }, + std::error::Error, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum EiClientError { + #[error("An error occurred reading from/writing to the client")] + Io(#[from] BufFdError), + #[error("An error occurred while processing a request")] + RequestError(#[source] Box), + #[error("Client tried to invoke a non-existent method")] + InvalidMethod, + #[error("The message size is < 16")] + MessageSizeTooSmall, + #[error("The message size is > 2^16")] + MessageSizeTooLarge, + #[error("The size of the message is not a multiple of 4")] + UnalignedMessage, + #[error("The object id is unknown")] + UnknownId, + #[error("Client tried to access non-existent object {0}")] + InvalidObject(EiObjectId), + #[error("The id is already in use")] + IdAlreadyInUse, + #[error("The client object id is out of bounds")] + ClientIdOutOfBounds, + #[error("Could not process a `{}.{}` request", .interface.name(), .method)] + MethodError { + interface: EiInterface, + method: &'static str, + #[source] + error: Box, + }, + #[error("Could not add object {0} to the client")] + AddObjectError(EiObjectId, #[source] Box), +} + +#[derive(Debug, Error)] +#[error("Parsing failed")] +pub struct EiParserError(#[source] pub EiMsgParserError); diff --git a/src/ei/ei_client/ei_objects.rs b/src/ei/ei_client/ei_objects.rs new file mode 100644 index 00000000..56499328 --- /dev/null +++ b/src/ei/ei_client/ei_objects.rs @@ -0,0 +1,83 @@ +use { + crate::{ + ei::{ + ei_client::ei_error::EiClientError, + ei_ifs::ei_handshake::EiHandshake, + ei_object::{EiObject, EiObjectId}, + }, + utils::{copyhashmap::CopyHashMap, numcell::NumCell}, + }, + std::rc::Rc, +}; + +pub struct EiObjects { + registry: CopyHashMap>, + next_sever_id: NumCell, +} + +pub const MIN_SERVER_ID: u64 = 0xff00_0000_0000_0000; + +impl EiObjects { + pub fn new() -> Self { + Self { + registry: Default::default(), + next_sever_id: NumCell::new(MIN_SERVER_ID), + } + } + + pub fn destroy(&self) { + for obj in self.registry.lock().values_mut() { + obj.break_loops(); + } + self.registry.clear(); + } + + pub fn id(&self) -> T + where + EiObjectId: Into, + { + EiObjectId::from_raw(self.next_sever_id.fetch_add(1)).into() + } + + pub fn get_obj(&self, id: EiObjectId) -> Option> { + self.registry.get(&id) + } + + pub fn add_server_object(&self, obj: Rc) { + let id = obj.id(); + assert!(id.raw() >= MIN_SERVER_ID); + assert!(!self.registry.contains(&id)); + self.registry.set(id, obj.clone()); + } + + pub fn add_handshake(&self, obj: &Rc) { + assert_eq!(obj.id.raw(), 0); + assert!(self.registry.is_empty()); + self.registry.set(obj.id.into(), obj.clone()); + } + + pub fn add_client_object(&self, obj: Rc) -> Result<(), EiClientError> { + let id = obj.id(); + let res = (|| { + if id.raw() == 0 || id.raw() >= MIN_SERVER_ID { + return Err(EiClientError::ClientIdOutOfBounds); + } + if self.registry.contains(&id) { + return Err(EiClientError::IdAlreadyInUse); + } + self.registry.set(id, obj.clone()); + Ok(()) + })(); + if let Err(e) = res { + return Err(EiClientError::AddObjectError(id, Box::new(e))); + } + Ok(()) + } + + pub fn remove_obj(&self, id: EiObjectId) -> Result<(), EiClientError> { + match self.registry.remove(&id) { + Some(_) => Ok(()), + _ => Err(EiClientError::UnknownId), + } + } +} diff --git a/src/ei/ei_client/ei_tasks.rs b/src/ei/ei_client/ei_tasks.rs new file mode 100644 index 00000000..bffa79f2 --- /dev/null +++ b/src/ei/ei_client/ei_tasks.rs @@ -0,0 +1,141 @@ +use { + crate::{ + async_engine::Phase, + ei::{ + ei_client::{ei_error::EiClientError, EiClient}, + ei_object::EiObjectId, + }, + utils::{ + buffd::{BufFdIn, BufFdOut, EiMsgParser}, + errorfmt::ErrorFmt, + vec_ext::VecExt, + }, + }, + futures_util::{select, FutureExt}, + std::{collections::VecDeque, mem, rc::Rc, time::Duration}, +}; + +pub async fn ei_client(data: Rc) { + let mut recv = data.state.eng.spawn(receive(data.clone())).fuse(); + let mut shutdown = data.shutdown.triggered().fuse(); + let _send = data.state.eng.spawn2(Phase::PostLayout, send(data.clone())); + select! { + _ = recv => { }, + _ = shutdown => { }, + } + drop(recv); + data.flush_request.trigger(); + match data.state.wheel.timeout(5000).await { + Ok(_) => { + log::error!("Could not shut down client {} within 5 seconds", data.id); + } + Err(e) => { + log::error!("Could not create a timeout: {}", ErrorFmt(e)); + } + } + data.state.ei_clients.kill(data.id); +} + +async fn receive(data: Rc) { + let recv = async { + let mut buf = BufFdIn::new(&data.socket, &data.state.ring); + let mut data_buf = Vec::::new(); + loop { + let mut hdr = [0u32; 4]; + buf.read_full(&mut hdr[..]).await?; + #[cfg(target_endian = "little")] + let obj_id = (hdr[0] as u64) | ((hdr[1] as u64) << 32); + #[cfg(target_endian = "big")] + let obj_id = (hdr[1] as u64) | ((hdr[0] as u64) << 32); + let obj_id = EiObjectId::from_raw(obj_id); + let len = hdr[2] as usize; + let request = hdr[3]; + if len < 16 { + return Err(EiClientError::MessageSizeTooSmall); + } + if len > (u16::MAX as usize) { + return Err(EiClientError::MessageSizeTooLarge); + } + if len % 4 != 0 { + return Err(EiClientError::UnalignedMessage); + } + let len = (len - 16) / 4; + data_buf.clear(); + data_buf.reserve(len); + let unused = data_buf.split_at_spare_mut_ext().1; + buf.read_full(&mut unused[..len]).await?; + unsafe { + data_buf.set_len(len); + } + let obj = match data.objects.get_obj(obj_id) { + Some(obj) => obj, + _ => match data.connection.get() { + None => { + return Err(EiClientError::InvalidObject(obj_id)); + } + Some(c) => { + c.send_invalid_object(obj_id); + continue; + } + }, + }; + let parser = EiMsgParser::new(&mut buf, &data_buf[..]); + if let Err(e) = obj.handle_request(&data, request, parser) { + return Err(EiClientError::RequestError(Box::new(e))); + } + } + }; + let res: Result<(), EiClientError> = recv.await; + if let Err(e) = res { + if data.disconnect_announced.get() { + log::info!("Client {} terminated the connection", data.id); + data.state.ei_clients.kill(data.id); + } else { + let e = ErrorFmt(e); + log::error!( + "An error occurred while trying to handle a message from client {}: {}", + data.id, + e + ); + if let Some(c) = data.connection.get() { + c.send_disconnected(Some(&e.to_string())); + data.state.ei_clients.shutdown(data.id); + } else { + data.state.ei_clients.kill(data.id); + } + } + } +} + +async fn send(data: Rc) { + let send = async { + let mut out = BufFdOut::new(&data.socket, &data.state.ring); + let mut buffers = VecDeque::new(); + loop { + data.flush_request.triggered().await; + { + let mut swapchain = data.swapchain.borrow_mut(); + swapchain.commit(); + mem::swap(&mut swapchain.pending, &mut buffers); + } + let timeout = data.state.now() + Duration::from_millis(5000); + while let Some(mut cur) = buffers.pop_front() { + out.flush(&mut cur, timeout).await?; + data.swapchain.borrow_mut().free.push(cur); + } + } + }; + let res: Result<(), EiClientError> = send.await; + if let Err(e) = res { + if data.disconnect_announced.get() { + log::info!("Client {} terminated the connection", data.id); + } else { + log::error!( + "An error occurred while sending data to client {}: {}", + data.id, + ErrorFmt(e) + ); + } + } + data.state.ei_clients.kill(data.id); +} diff --git a/src/ei/ei_ifs.rs b/src/ei/ei_ifs.rs new file mode 100644 index 00000000..029103ef --- /dev/null +++ b/src/ei/ei_ifs.rs @@ -0,0 +1,44 @@ +macro_rules! ei_device_interface { + ($camel:ident, $snake:ident, $field:ident) => { + impl EiDeviceInterface for $camel { + fn new(device: &Rc, version: EiVersion) -> Rc { + let v = Rc::new(Self { + id: device.client.new_id(), + client: device.client.clone(), + tracker: Default::default(), + version, + device: device.clone(), + }); + track!(v.client, v); + v + } + + fn destroy(&self) -> Result<(), EiClientError> { + self.send_destroyed(self.client.serial()); + self.client.remove_obj(self)?; + self.device.seat.$field.take(); + Ok(()) + } + + fn send_destroyed(&self, serial: u32) { + self.client.event(crate::wire_ei::$snake::Destroyed { + self_id: self.id, + serial, + }); + } + } + }; +} + +pub mod ei_button; +pub mod ei_callback; +pub mod ei_connection; +pub mod ei_device; +pub mod ei_handshake; +pub mod ei_keyboard; +pub mod ei_pingpong; +pub mod ei_pointer; +pub mod ei_pointer_absolute; +pub mod ei_scroll; +pub mod ei_seat; +pub mod ei_touchscreen; diff --git a/src/ei/ei_ifs/ei_button.rs b/src/ei/ei_ifs/ei_button.rs new file mode 100644 index 00000000..fc3aa200 --- /dev/null +++ b/src/ei/ei_ifs/ei_button.rs @@ -0,0 +1,72 @@ +use { + crate::{ + backend::KeyState, + ei::{ + ei_client::{EiClient, EiClientError}, + ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, + ei_object::{EiObject, EiVersion}, + }, + leaks::Tracker, + wire_ei::{ + ei_button::{ClientButton, EiButtonRequestHandler, Release, ServerButton}, + EiButtonId, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct EiButton { + pub id: EiButtonId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, + pub device: Rc, +} + +ei_device_interface!(EiButton, ei_button, button); + +impl EiButton { + pub fn send_button(&self, button: u32, state: KeyState) { + self.client.event(ServerButton { + self_id: self.id, + button, + state: state as _, + }); + } +} + +impl EiButtonRequestHandler for EiButton { + type Error = EiButtonError; + + fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { + self.destroy()?; + Ok(()) + } + + fn client_button(&self, req: ClientButton, _slf: &Rc) -> Result<(), Self::Error> { + let pressed = match req.state { + 0 => KeyState::Released, + 1 => KeyState::Pressed, + _ => return Err(EiButtonError::InvalidButtonState(req.state)), + }; + self.device.button_changes.push((req.button, pressed)); + Ok(()) + } +} + +ei_object_base! { + self = EiButton; + version = self.version; +} + +impl EiObject for EiButton {} + +#[derive(Debug, Error)] +pub enum EiButtonError { + #[error(transparent)] + EiClientError(Box), + #[error("Invalid button state {0}")] + InvalidButtonState(u32), +} +efrom!(EiButtonError, EiClientError); diff --git a/src/ei/ei_ifs/ei_callback.rs b/src/ei/ei_ifs/ei_callback.rs new file mode 100644 index 00000000..f65ed15e --- /dev/null +++ b/src/ei/ei_ifs/ei_callback.rs @@ -0,0 +1,49 @@ +use { + crate::{ + ei::{ + ei_client::{EiClient, EiClientError}, + ei_object::{EiObject, EiVersion}, + }, + leaks::Tracker, + wire_ei::{ + ei_callback::{Done, EiCallbackRequestHandler}, + EiCallbackId, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct EiCallback { + pub id: EiCallbackId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, +} + +impl EiCallback { + pub fn send_done(&self, callback_data: u64) { + self.client.event(Done { + self_id: self.id, + callback_data, + }); + } +} + +impl EiCallbackRequestHandler for EiCallback { + type Error = EiCallbackError; +} + +ei_object_base! { + self = EiCallback; + version = self.version; +} + +impl EiObject for EiCallback {} + +#[derive(Debug, Error)] +pub enum EiCallbackError { + #[error(transparent)] + EiClientError(Box), +} +efrom!(EiCallbackError, EiClientError); diff --git a/src/ei/ei_ifs/ei_connection.rs b/src/ei/ei_ifs/ei_connection.rs new file mode 100644 index 00000000..510995a3 --- /dev/null +++ b/src/ei/ei_ifs/ei_connection.rs @@ -0,0 +1,160 @@ +use { + crate::{ + ei::{ + ei_client::{EiClient, EiClientError}, + ei_ifs::{ + ei_callback::EiCallback, + ei_seat::{ + EiSeat, EI_CAP_BUTTON, EI_CAP_KEYBOARD, EI_CAP_POINTER, + EI_CAP_POINTER_ABSOLUTE, EI_CAP_SCROLL, EI_CAP_TOUCHSCREEN, + }, + }, + ei_object::{EiObject, EiObjectId, EiVersion}, + EiContext, + }, + ifs::wl_seat::WlSeatGlobal, + leaks::Tracker, + wire_ei::{ + ei_connection::{ + Disconnect, Disconnected, EiConnectionRequestHandler, InvalidObject, Seat, + }, + EiButton, EiConnectionId, EiKeyboard, EiPointer, EiPointerAbsolute, EiScroll, + EiTouchscreen, + }, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct EiConnection { + pub id: EiConnectionId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, +} + +impl EiConnection { + pub fn send_invalid_object(&self, id: EiObjectId) { + self.client.event(InvalidObject { + self_id: self.id, + last_serial: self.client.last_serial(), + invalid_id: id, + }); + } + + pub fn send_disconnected(&self, error: Option<&str>) { + self.client.event(Disconnected { + self_id: self.id, + last_serial: self.client.last_serial(), + reason: error.is_some() as _, + explanation: error, + }); + } + + pub fn send_seat(&self, seat: &EiSeat) { + self.client.event(Seat { + self_id: self.id, + seat: seat.id, + version: seat.version.0, + }); + } + + pub fn announce_seat(&self, seat: &Rc) { + let version = self.client.versions.ei_seat.version.get(); + if version == EiVersion(0) { + return; + } + let xkb_state_id = match self.context() { + EiContext::Sender => seat.seat_xkb_state().borrow().id, + EiContext::Receiver => seat.latest_xkb_state().borrow().id, + }; + let seat = Rc::new(EiSeat { + id: self.client.new_id(), + client: self.client.clone(), + tracker: Default::default(), + version, + seat: seat.clone(), + capabilities: Cell::new(0), + kb_state_id: Cell::new(xkb_state_id), + device: Default::default(), + pointer: Default::default(), + pointer_absolute: Default::default(), + keyboard: Default::default(), + button: Default::default(), + scroll: Default::default(), + touchscreen: Default::default(), + }); + track!(self.client, seat); + self.client.add_server_obj(&seat); + self.send_seat(&seat); + let v = &self.client.versions; + let caps = [ + (EI_CAP_POINTER, EiPointer, &v.ei_pointer), + ( + EI_CAP_POINTER_ABSOLUTE, + EiPointerAbsolute, + &v.ei_pointer_absolute, + ), + (EI_CAP_SCROLL, EiScroll, &v.ei_scroll), + (EI_CAP_BUTTON, EiButton, &v.ei_button), + (EI_CAP_KEYBOARD, EiKeyboard, &v.ei_keyboard), + (EI_CAP_TOUCHSCREEN, EiTouchscreen, &v.ei_touchscreen), + ]; + for (mask, interface, version) in caps { + if version.version.get() > EiVersion(0) { + seat.send_capability(interface, mask); + } + } + seat.send_name(&seat.seat.seat_name()); + seat.send_done(); + seat.seat.add_ei_seat(&seat); + } +} + +impl EiConnectionRequestHandler for EiConnection { + type Error = EiConnectionError; + + fn sync( + &self, + req: crate::wire_ei::ei_connection::Sync, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let version = EiVersion(req.version); + if version > self.client.versions.ei_callback.version.get() { + return Err(EiConnectionError::CallbackVersion(req.version)); + } + let cb = Rc::new(EiCallback { + id: req.callback, + client: self.client.clone(), + tracker: Default::default(), + version, + }); + track!(self.client, cb); + self.client.add_client_obj(&cb)?; + cb.send_done(0); + self.client.remove_obj(&*cb)?; + Ok(()) + } + + fn disconnect(&self, _req: Disconnect, _slf: &Rc) -> Result<(), Self::Error> { + self.client.disconnect_announced.set(true); + self.client.state.ei_clients.shutdown(self.client.id); + Ok(()) + } +} + +ei_object_base! { + self = EiConnection; + version = self.version; +} + +impl EiObject for EiConnection {} + +#[derive(Debug, Error)] +pub enum EiConnectionError { + #[error(transparent)] + EiClientError(Box), + #[error("The callback version is too large: {0}")] + CallbackVersion(u32), +} +efrom!(EiConnectionError, EiClientError); diff --git a/src/ei/ei_ifs/ei_device.rs b/src/ei/ei_ifs/ei_device.rs new file mode 100644 index 00000000..77553e01 --- /dev/null +++ b/src/ei/ei_ifs/ei_device.rs @@ -0,0 +1,249 @@ +use { + crate::{ + backend::{KeyState, ScrollAxis}, + ei::{ + ei_client::{EiClient, EiClientError}, + ei_ifs::{ei_seat::EiSeat, ei_touchscreen::TouchChange}, + ei_object::{EiObject, EiVersion}, + }, + fixed::Fixed, + ifs::wl_seat::PX_PER_SCROLL, + leaks::Tracker, + rect::Rect, + scale::Scale, + utils::syncqueue::SyncQueue, + wire_ei::{ + ei_device::{ + ClientFrame, ClientStartEmulating, ClientStopEmulating, Destroyed, DeviceType, + Done, EiDeviceRequestHandler, Interface, Paused, Region, Release, Resumed, + ServerFrame, ServerStartEmulating, + }, + EiDeviceId, + }, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub const EI_DEVICE_TYPE_VIRTUAL: u32 = 1; +#[allow(dead_code)] +pub const EI_DEVICE_TYPE_PHYSICAL: u32 = 2; + +pub struct EiDevice { + pub id: EiDeviceId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, + pub seat: Rc, + + pub button_changes: SyncQueue<(u32, KeyState)>, + pub touch_changes: SyncQueue<(u32, TouchChange)>, + pub scroll_px: [Cell>; 2], + pub scroll_v120: [Cell>; 2], + pub scroll_stop: [Cell>; 2], + pub absolute_motion: Cell>, + pub relative_motion: Cell>, + pub key_changes: SyncQueue<(u32, KeyState)>, +} + +pub trait EiDeviceInterface: EiObject { + fn new(device: &Rc, version: EiVersion) -> Rc; + + fn destroy(&self) -> Result<(), EiClientError>; + + fn send_destroyed(&self, serial: u32); +} + +impl EiDevice { + pub fn send_interface(&self, interface: &T) + where + T: EiDeviceInterface, + { + self.client.event(Interface { + self_id: self.id, + object: interface.id(), + interface_name: interface.interface().name(), + version: interface.version().0, + }); + } + + pub fn send_device_type(&self, ty: u32) { + self.client.event(DeviceType { + self_id: self.id, + device_type: ty, + }); + } + + pub fn send_resumed(&self, serial: u32) { + self.client.event(Resumed { + self_id: self.id, + serial, + }); + } + + pub fn send_start_emulating(&self, serial: u32, sequence: u32) { + self.client.event(ServerStartEmulating { + self_id: self.id, + serial, + sequence, + }); + } + + pub fn send_region(&self, rect: Rect, scale: Scale) { + self.client.event(Region { + self_id: self.id, + offset_x: rect.x1() as u32, + offset_y: rect.y1() as u32, + width: rect.width() as u32, + hight: rect.height() as u32, + scale: scale.to_f64() as f32, + }); + } + + #[allow(dead_code)] + pub fn send_paused(&self, serial: u32) { + self.client.event(Paused { + self_id: self.id, + serial, + }); + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + pub fn send_frame(&self, serial: u32, timestamp: u64) { + self.client.event(ServerFrame { + self_id: self.id, + serial, + timestamp, + }); + } + + pub fn send_destroyed(&self, serial: u32) { + self.client.event(Destroyed { + self_id: self.id, + serial, + }); + } + + pub fn destroy(&self) -> Result<(), EiClientError> { + macro_rules! destroy_interface { + ($name:ident) => { + if let Some(interface) = self.seat.$name.take() { + interface.destroy()?; + } + }; + } + destroy_interface!(pointer); + destroy_interface!(pointer_absolute); + destroy_interface!(scroll); + destroy_interface!(button); + destroy_interface!(keyboard); + destroy_interface!(touchscreen); + self.send_destroyed(self.client.serial()); + self.client.remove_obj(self)?; + Ok(()) + } +} + +impl EiDeviceRequestHandler for EiDevice { + type Error = EiDeviceError; + + fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { + self.destroy()?; + Ok(()) + } + + fn client_start_emulating( + &self, + _req: ClientStartEmulating, + _slf: &Rc, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn client_stop_emulating( + &self, + _req: ClientStopEmulating, + _slf: &Rc, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn client_frame(&self, req: ClientFrame, _slf: &Rc) -> Result<(), Self::Error> { + let seat = &self.seat.seat; + let time = req.timestamp; + while let Some((button, pressed)) = self.button_changes.pop() { + seat.button_event(time, button, pressed); + } + while let Some((button, pressed)) = self.key_changes.pop() { + seat.key_event_with_seat_state(time, button, pressed); + } + if let Some((x, y)) = self.relative_motion.take() { + let x = Fixed::from_f32(x); + let y = Fixed::from_f32(y); + seat.motion_event(time, x, y, x, y); + } + if let Some((x, y)) = self.absolute_motion.take() { + let x = Fixed::from_f32(x); + let y = Fixed::from_f32(y); + seat.motion_event_abs(time, x, y); + } + { + let mut need_frame = false; + for axis in [ScrollAxis::Horizontal, ScrollAxis::Vertical] { + let idx = axis as usize; + if let Some(v120) = self.scroll_v120[idx].take() { + need_frame = true; + seat.axis_120(v120, axis, false); + } + if let Some(px) = self.scroll_px[idx].take() { + need_frame = true; + seat.axis_px(Fixed::from_f32(px), axis, false); + } + if self.scroll_stop[idx].take() == Some(true) { + need_frame = true; + seat.axis_stop(axis); + } + } + if need_frame { + seat.axis_frame(PX_PER_SCROLL, time); + } + } + if self.touch_changes.is_not_empty() { + while let Some((touch_id, change)) = self.touch_changes.pop() { + let id = touch_id as i32; + match change { + TouchChange::Down(x, y) => { + let x = Fixed::from_f32(x); + let y = Fixed::from_f32(y); + seat.touch_down_at(time, id, x, y); + } + TouchChange::Motion(x, y) => { + let x = Fixed::from_f32(x); + let y = Fixed::from_f32(y); + seat.touch_motion_at(time, id, x, y); + } + TouchChange::Up => seat.touch_up(time, id), + } + } + seat.touch_frame(time); + } + Ok(()) + } +} + +ei_object_base! { + self = EiDevice; + version = self.version; +} + +impl EiObject for EiDevice {} + +#[derive(Debug, Error)] +pub enum EiDeviceError { + #[error(transparent)] + EiClientError(Box), +} +efrom!(EiDeviceError, EiClientError); diff --git a/src/ei/ei_ifs/ei_handshake.rs b/src/ei/ei_ifs/ei_handshake.rs new file mode 100644 index 00000000..759db643 --- /dev/null +++ b/src/ei/ei_ifs/ei_handshake.rs @@ -0,0 +1,189 @@ +use { + crate::{ + ei::{ + ei_client::{EiClient, EiClientError}, + ei_ifs::ei_connection::EiConnection, + ei_object::{EiInterface, EiObject, EiVersion, EI_HANDSHAKE_ID}, + EiContext, + }, + leaks::Tracker, + wire_ei::{ + ei_handshake::{ + ClientHandshakeVersion, ClientInterfaceVersion, Connection, ContextType, + EiHandshakeRequestHandler, Finish, Name, ServerHandshakeVersion, + ServerInterfaceVersion, + }, + EiHandshake, EiHandshakeId, + }, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct EiHandshake { + pub id: EiHandshakeId, + client: Rc, + version: Cell, + pub tracker: Tracker, + have_context_type: Cell, +} + +impl EiHandshake { + pub fn new(client: &Rc) -> Self { + Self { + id: EI_HANDSHAKE_ID, + client: client.clone(), + version: Cell::new(EiVersion(1)), + tracker: Default::default(), + have_context_type: Cell::new(false), + } + } + + pub fn send_handshake_version(&self) { + self.client.event(ServerHandshakeVersion { + self_id: self.id, + version: 1, + }); + } + + fn send_interface_version(&self, interface: EiInterface, version: EiVersion) { + self.client.event(ServerInterfaceVersion { + self_id: self.id, + name: interface.0, + version: version.0, + }); + } + + fn send_connection(&self, serial: u32, connection: &EiConnection) { + self.client.event(Connection { + self_id: self.id, + serial, + connection: connection.id, + version: connection.version.0, + }); + } +} + +impl EiHandshakeRequestHandler for EiHandshake { + type Error = EiHandshakeError; + + fn client_handshake_version( + &self, + req: ClientHandshakeVersion, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let version = EiVersion(req.version); + if version > self.client.versions.ei_handshake.server_max_version { + return Err(EiHandshakeError::UnknownHandshakeVersion); + } + self.client + .versions + .ei_handshake + .set_client_version(version); + Ok(()) + } + + fn finish(&self, _req: Finish, _slf: &Rc) -> Result<(), Self::Error> { + if !self.have_context_type.get() { + return Err(EiHandshakeError::NoContextType); + } + if self.client.name.borrow().is_none() { + return Err(EiHandshakeError::NoName); + } + if self.client.versions.ei_connection.version.get() == EiVersion(0) { + return Err(EiHandshakeError::NoConnectionVersion); + } + if self.client.versions.ei_callback.version.get() == EiVersion(0) { + return Err(EiHandshakeError::NoCallbackVersion); + } + self.client.versions.for_each(|interface, version| { + let version = version.version.get(); + if version > EiVersion(0) && interface != EiHandshake { + self.send_interface_version(interface, version); + } + }); + let connection = Rc::new(EiConnection { + id: self.client.new_id(), + client: self.client.clone(), + tracker: Default::default(), + version: self.client.versions.ei_connection.version.get(), + }); + self.client.add_server_obj(&connection); + track!(self.client, connection); + self.client.connection.set(Some(connection.clone())); + self.send_connection(self.client.serial(), &connection); + self.client.remove_obj(self)?; + for seat in self.client.state.globals.seats.lock().values() { + connection.announce_seat(seat); + } + Ok(()) + } + + fn context_type(&self, req: ContextType, _slf: &Rc) -> Result<(), Self::Error> { + if self.have_context_type.replace(true) { + return Err(EiHandshakeError::ContextTypeSet); + } + let ty = match req.context_type { + 1 => EiContext::Receiver, + 2 => EiContext::Sender, + _ => return Err(EiHandshakeError::UnknownContextType(req.context_type)), + }; + self.client.context.set(ty); + self.have_context_type.set(true); + Ok(()) + } + + fn name(&self, req: Name<'_>, _slf: &Rc) -> Result<(), Self::Error> { + let name = &mut *self.client.name.borrow_mut(); + if name.is_some() { + return Err(EiHandshakeError::NameSet); + } + *name = Some(req.name.to_string()); + Ok(()) + } + + fn client_interface_version( + &self, + req: ClientInterfaceVersion<'_>, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.client.versions.match_(req.name, |v| { + v.set_client_version(EiVersion(req.version)); + }); + Ok(()) + } +} + +ei_object_base! { + self = EiHandshake; + version = self.version.get(); +} + +impl EiObject for EiHandshake { + fn context(&self) -> EiContext { + panic!("context requested for EiHandshake") + } +} + +#[derive(Debug, Error)] +pub enum EiHandshakeError { + #[error(transparent)] + EiClientError(Box), + #[error("ei_handshake version is too large")] + UnknownHandshakeVersion, + #[error("Name is already set")] + NameSet, + #[error("Unknown context type {0}")] + UnknownContextType(u32), + #[error("Context type is already set")] + ContextTypeSet, + #[error("Client did not set connection version")] + NoConnectionVersion, + #[error("Client did not set callback version")] + NoCallbackVersion, + #[error("Client did not set context type")] + NoContextType, + #[error("Client did not set name")] + NoName, +} +efrom!(EiHandshakeError, EiClientError); diff --git a/src/ei/ei_ifs/ei_keyboard.rs b/src/ei/ei_ifs/ei_keyboard.rs new file mode 100644 index 00000000..93831261 --- /dev/null +++ b/src/ei/ei_ifs/ei_keyboard.rs @@ -0,0 +1,97 @@ +use { + crate::{ + backend::KeyState, + ei::{ + ei_client::{EiClient, EiClientError}, + ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, + ei_object::{EiObject, EiVersion}, + }, + leaks::Tracker, + wire_ei::{ + ei_keyboard::{ + ClientKey, EiKeyboardRequestHandler, Keymap, Modifiers, Release, ServerKey, + }, + EiKeyboardId, + }, + xkbcommon::KeyboardState, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct EiKeyboard { + pub id: EiKeyboardId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, + pub device: Rc, +} + +ei_device_interface!(EiKeyboard, ei_keyboard, keyboard); + +const KEYMAP_TYPE_XKB: u32 = 1; + +impl EiKeyboard { + pub fn send_keymap(&self, state: &KeyboardState) { + self.client.event(Keymap { + self_id: self.id, + keymap_type: KEYMAP_TYPE_XKB, + size: state.map_len as _, + keymap: state.map.clone(), + }); + } + + pub fn send_modifiers(&self, state: &KeyboardState) { + self.client.event(Modifiers { + self_id: self.id, + serial: self.client.serial(), + depressed: state.mods.mods_depressed, + locked: state.mods.mods_locked, + latched: state.mods.mods_latched, + group: state.mods.group, + }); + } + + pub fn send_key(&self, key: u32, state: u32) { + self.client.event(ServerKey { + self_id: self.id, + key, + state, + }); + } +} + +impl EiKeyboardRequestHandler for EiKeyboard { + type Error = EiKeyboardError; + + fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { + self.destroy()?; + Ok(()) + } + + fn client_key(&self, req: ClientKey, _slf: &Rc) -> Result<(), Self::Error> { + let pressed = match req.state { + 0 => KeyState::Released, + 1 => KeyState::Pressed, + _ => return Err(EiKeyboardError::InvalidKeyState(req.state)), + }; + self.device.key_changes.push((req.key, pressed)); + Ok(()) + } +} + +ei_object_base! { + self = EiKeyboard; + version = self.version; +} + +impl EiObject for EiKeyboard {} + +#[derive(Debug, Error)] +pub enum EiKeyboardError { + #[error(transparent)] + EiClientError(Box), + #[error("Invalid key state {0}")] + InvalidKeyState(u32), +} +efrom!(EiKeyboardError, EiClientError); diff --git a/src/ei/ei_ifs/ei_pingpong.rs b/src/ei/ei_ifs/ei_pingpong.rs new file mode 100644 index 00000000..53220676 --- /dev/null +++ b/src/ei/ei_ifs/ei_pingpong.rs @@ -0,0 +1,45 @@ +use { + crate::{ + ei::{ + ei_client::{EiClient, EiClientError}, + ei_object::{EiObject, EiVersion}, + }, + leaks::Tracker, + wire_ei::{ + ei_pingpong::{Done, EiPingpongRequestHandler}, + EiPingpongId, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +#[allow(dead_code)] +pub struct EiPingpong { + pub id: EiPingpongId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, +} + +impl EiPingpongRequestHandler for EiPingpong { + type Error = EiPingpongError; + + fn done(&self, _req: Done, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } +} + +ei_object_base! { + self = EiPingpong; + version = self.version; +} + +impl EiObject for EiPingpong {} + +#[derive(Debug, Error)] +pub enum EiPingpongError { + #[error(transparent)] + EiClientError(Box), +} +efrom!(EiPingpongError, EiClientError); diff --git a/src/ei/ei_ifs/ei_pointer.rs b/src/ei/ei_ifs/ei_pointer.rs new file mode 100644 index 00000000..85038da7 --- /dev/null +++ b/src/ei/ei_ifs/ei_pointer.rs @@ -0,0 +1,71 @@ +use { + crate::{ + ei::{ + ei_client::{EiClient, EiClientError}, + ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, + ei_object::{EiObject, EiVersion}, + }, + fixed::Fixed, + leaks::Tracker, + wire_ei::{ + ei_pointer::{ + ClientMotionRelative, EiPointerRequestHandler, Release, ServerMotionRelative, + }, + EiPointerId, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct EiPointer { + pub id: EiPointerId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, + pub device: Rc, +} + +ei_device_interface!(EiPointer, ei_pointer, pointer); + +impl EiPointer { + pub fn send_motion(&self, dx: Fixed, dy: Fixed) { + self.client.event(ServerMotionRelative { + self_id: self.id, + x: dx.to_f32(), + y: dy.to_f32(), + }); + } +} + +impl EiPointerRequestHandler for EiPointer { + type Error = EiPointerError; + + fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { + self.destroy()?; + Ok(()) + } + + fn client_motion_relative( + &self, + req: ClientMotionRelative, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.device.relative_motion.set(Some((req.x, req.y))); + Ok(()) + } +} + +ei_object_base! { + self = EiPointer; + version = self.version; +} + +impl EiObject for EiPointer {} + +#[derive(Debug, Error)] +pub enum EiPointerError { + #[error(transparent)] + EiClientError(Box), +} +efrom!(EiPointerError, EiClientError); diff --git a/src/ei/ei_ifs/ei_pointer_absolute.rs b/src/ei/ei_ifs/ei_pointer_absolute.rs new file mode 100644 index 00000000..cd1cfe79 --- /dev/null +++ b/src/ei/ei_ifs/ei_pointer_absolute.rs @@ -0,0 +1,72 @@ +use { + crate::{ + ei::{ + ei_client::{EiClient, EiClientError}, + ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, + ei_object::{EiObject, EiVersion}, + }, + fixed::Fixed, + leaks::Tracker, + wire_ei::{ + ei_pointer_absolute::{ + ClientMotionAbsolute, EiPointerAbsoluteRequestHandler, Release, + ServerMotionAbsolute, + }, + EiPointerAbsoluteId, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct EiPointerAbsolute { + pub id: EiPointerAbsoluteId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, + pub device: Rc, +} + +ei_device_interface!(EiPointerAbsolute, ei_pointer_absolute, pointer_absolute); + +impl EiPointerAbsolute { + pub fn send_motion_absolute(&self, x: Fixed, y: Fixed) { + self.client.event(ServerMotionAbsolute { + self_id: self.id, + x: x.to_f32(), + y: y.to_f32(), + }); + } +} + +impl EiPointerAbsoluteRequestHandler for EiPointerAbsolute { + type Error = EiCallbackError; + + fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { + self.destroy()?; + Ok(()) + } + + fn client_motion_absolute( + &self, + req: ClientMotionAbsolute, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.device.absolute_motion.set(Some((req.x, req.y))); + Ok(()) + } +} + +ei_object_base! { + self = EiPointerAbsolute; + version = self.version; +} + +impl EiObject for EiPointerAbsolute {} + +#[derive(Debug, Error)] +pub enum EiCallbackError { + #[error(transparent)] + EiClientError(Box), +} +efrom!(EiCallbackError, EiClientError); diff --git a/src/ei/ei_ifs/ei_scroll.rs b/src/ei/ei_ifs/ei_scroll.rs new file mode 100644 index 00000000..93ae2e61 --- /dev/null +++ b/src/ei/ei_ifs/ei_scroll.rs @@ -0,0 +1,106 @@ +use { + crate::{ + ei::{ + ei_client::{EiClient, EiClientError}, + ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, + ei_object::{EiObject, EiVersion}, + }, + fixed::Fixed, + leaks::Tracker, + wire_ei::{ + ei_scroll::{ + ClientScroll, ClientScrollDiscrete, ClientScrollStop, EiScrollRequestHandler, + Release, ServerScroll, ServerScrollDiscrete, ServerScrollStop, + }, + EiScrollId, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct EiScroll { + pub id: EiScrollId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, + pub device: Rc, +} + +ei_device_interface!(EiScroll, ei_scroll, scroll); + +impl EiScroll { + pub fn send_scroll(&self, x: Fixed, y: Fixed) { + self.client.event(ServerScroll { + self_id: self.id, + x: x.to_f32(), + y: y.to_f32(), + }); + } + + pub fn send_scroll_discrete(&self, x: i32, y: i32) { + self.client.event(ServerScrollDiscrete { + self_id: self.id, + x, + y, + }); + } + + pub fn send_scroll_stop(&self, x: bool, y: bool) { + self.client.event(ServerScrollStop { + self_id: self.id, + x: x as _, + y: y as _, + is_cancel: 0, + }); + } +} + +impl EiScrollRequestHandler for EiScroll { + type Error = EiScrollError; + + fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { + self.destroy()?; + Ok(()) + } + + fn client_scroll(&self, req: ClientScroll, _slf: &Rc) -> Result<(), Self::Error> { + self.device.scroll_px[0].set(Some(req.x)); + self.device.scroll_px[1].set(Some(req.y)); + Ok(()) + } + + fn client_scroll_discrete( + &self, + req: ClientScrollDiscrete, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.device.scroll_v120[0].set(Some(req.x)); + self.device.scroll_v120[1].set(Some(req.y)); + Ok(()) + } + + fn client_scroll_stop( + &self, + req: ClientScrollStop, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.device.scroll_stop[0].set(Some(req.x != 0)); + self.device.scroll_stop[1].set(Some(req.y != 0)); + Ok(()) + } +} + +ei_object_base! { + self = EiScroll; + version = self.version; +} + +impl EiObject for EiScroll {} + +#[derive(Debug, Error)] +pub enum EiScrollError { + #[error(transparent)] + EiClientError(Box), +} +efrom!(EiScrollError, EiClientError); diff --git a/src/ei/ei_ifs/ei_seat.rs b/src/ei/ei_ifs/ei_seat.rs new file mode 100644 index 00000000..40430299 --- /dev/null +++ b/src/ei/ei_ifs/ei_seat.rs @@ -0,0 +1,425 @@ +use { + crate::{ + backend::KeyState, + ei::{ + ei_client::{EiClient, EiClientError}, + ei_ifs::{ + ei_button::EiButton, + ei_device::{EiDevice, EiDeviceInterface, EI_DEVICE_TYPE_VIRTUAL}, + ei_keyboard::EiKeyboard, + ei_pointer::EiPointer, + ei_pointer_absolute::EiPointerAbsolute, + ei_scroll::EiScroll, + ei_touchscreen::EiTouchscreen, + }, + ei_object::{EiInterface, EiObject, EiVersion}, + EiContext, + }, + fixed::Fixed, + ifs::wl_seat::{wl_pointer::PendingScroll, WlSeatGlobal}, + leaks::Tracker, + tree::Node, + utils::{array, bitflags::BitflagsExt, clonecell::CloneCell}, + wire_ei::{ + ei_seat::{ + Bind, Capability, Destroyed, Device, Done, EiSeatRequestHandler, Name, Release, + }, + EiSeatId, + }, + xkbcommon::{DynKeyboardState, KeyboardState, KeyboardStateId}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub const EI_CAP_POINTER: u64 = 1 << 0; +pub const EI_CAP_POINTER_ABSOLUTE: u64 = 1 << 1; +pub const EI_CAP_SCROLL: u64 = 1 << 2; +pub const EI_CAP_BUTTON: u64 = 1 << 3; +pub const EI_CAP_KEYBOARD: u64 = 1 << 4; +pub const EI_CAP_TOUCHSCREEN: u64 = 1 << 5; + +pub const EI_CAP_ALL: u64 = (1 << 6) - 1; + +pub struct EiSeat { + pub id: EiSeatId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, + pub seat: Rc, + pub capabilities: Cell, + pub kb_state_id: Cell, + + pub device: CloneCell>>, + pub pointer: CloneCell>>, + pub pointer_absolute: CloneCell>>, + pub keyboard: CloneCell>>, + pub button: CloneCell>>, + pub scroll: CloneCell>>, + pub touchscreen: CloneCell>>, +} + +impl EiSeat { + fn is_sender(&self) -> bool { + self.context() == EiContext::Sender + } + + pub fn regions_changed(self: &Rc) { + if self.touchscreen.is_none() && self.pointer_absolute.is_none() { + return; + } + let kb_state = self.get_kb_state(); + let kb_state = kb_state.borrow(); + if let Err(e) = self.recreate_all(false, &kb_state) { + self.client.error(e); + } + } + + pub fn handle_xkb_state_change(self: &Rc, old_id: KeyboardStateId, new: &KeyboardState) { + if self.keyboard.is_none() { + return; + } + if self.kb_state_id.get() != old_id { + return; + } + self.kb_state_id.set(new.id); + if let Err(e) = self.recreate_all(false, new) { + self.client.error(e); + } + } + + pub fn handle_modifiers_changed(self: &Rc, state: &KeyboardState) { + let old_id = self.kb_state_id.get(); + if old_id != state.id { + if self.is_sender() { + return; + } + self.handle_xkb_state_change(old_id, state); + return; + } + if let Some(kb) = self.keyboard.get() { + kb.send_modifiers(state); + } + } + + pub fn handle_key( + self: &Rc, + time_usec: u64, + key: u32, + state: u32, + kb_state: &KeyboardState, + ) { + if self.is_sender() { + return; + } + let old_id = self.kb_state_id.get(); + if old_id != kb_state.id { + self.handle_xkb_state_change(old_id, kb_state); + } + if let Some(kb) = self.keyboard.get() { + kb.send_key(key, state); + kb.device.send_frame(self.client.serial(), time_usec); + } + } + + pub fn handle_motion_abs(&self, time_usec: u64, x: Fixed, y: Fixed) { + if self.is_sender() { + return; + } + if let Some(v) = self.pointer_absolute.get() { + v.send_motion_absolute(x, y); + v.device.send_frame(self.client.serial(), time_usec); + } + } + + pub fn handle_motion(&self, time_usec: u64, dx: Fixed, dy: Fixed) { + if self.is_sender() { + return; + } + if let Some(v) = self.pointer.get() { + v.send_motion(dx, dy); + v.device.send_frame(self.client.serial(), time_usec); + } + } + + pub fn handle_button(&self, time_usec: u64, button: u32, state: KeyState) { + if self.is_sender() { + return; + } + if let Some(b) = self.button.get() { + b.send_button(button, state); + b.device.send_frame(self.client.serial(), time_usec); + } + } + + pub fn handle_pending_scroll(&self, time_usec: u64, ps: &PendingScroll) { + if self.is_sender() { + return; + } + if let Some(b) = self.scroll.get() { + b.send_scroll( + ps.px[0].get().unwrap_or_default(), + ps.px[1].get().unwrap_or_default(), + ); + b.send_scroll_discrete( + ps.v120[0].get().unwrap_or_default(), + ps.v120[1].get().unwrap_or_default(), + ); + b.send_scroll_stop(ps.stop[0].get(), ps.stop[1].get()); + b.device.send_frame(self.client.serial(), time_usec); + } + } + + pub fn handle_touch_down(&self, id: u32, x: Fixed, y: Fixed) { + if self.is_sender() { + return; + } + if let Some(b) = self.touchscreen.get() { + b.send_down(id, x, y); + } + } + + pub fn handle_touch_motion(&self, id: u32, x: Fixed, y: Fixed) { + if self.is_sender() { + return; + } + if let Some(b) = self.touchscreen.get() { + b.send_motion(id, x, y); + } + } + + pub fn handle_touch_up(&self, id: u32) { + if self.is_sender() { + return; + } + if let Some(b) = self.touchscreen.get() { + b.send_up(id); + } + } + + pub fn handle_touch_frame(&self, time_usec: u64) { + if self.is_sender() { + return; + } + if let Some(b) = self.touchscreen.get() { + b.device.send_frame(self.client.serial(), time_usec); + } + } + + pub fn send_capability(&self, interface: EiInterface, mask: u64) { + self.client.event(Capability { + self_id: self.id, + mask, + interface: interface.0, + }); + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + pub fn send_name(&self, name: &str) { + self.client.event(Name { + self_id: self.id, + name, + }); + } + + pub fn send_device(&self, device: &EiDevice) { + self.client.event(Device { + self_id: self.id, + device: device.id, + version: device.version.0, + }); + } + + pub fn send_destroyed(&self) { + self.client.event(Destroyed { + self_id: self.id, + serial: self.client.serial(), + }); + } + + fn create_interface(self: &Rc, field: &CloneCell>>, version: EiVersion) + where + T: EiDeviceInterface, + { + if version == EiVersion(0) { + return; + } + let Some(device) = self.device.get() else { + return; + }; + let interface = T::new(&device, version); + self.client.add_server_obj(&interface); + device.send_interface(&*interface); + field.set(Some(interface.clone())); + } + + fn create_pointer(self: &Rc) { + self.create_interface(&self.pointer, self.client.versions.ei_pointer()); + } + + fn create_button(self: &Rc) { + self.create_interface(&self.button, self.client.versions.ei_button()); + } + + fn create_keyboard(self: &Rc) { + self.create_interface(&self.keyboard, self.client.versions.ei_keyboard()); + } + + fn create_scroll(self: &Rc) { + self.create_interface(&self.scroll, self.client.versions.ei_scroll()); + } + + fn create_pointer_absolute(self: &Rc) { + self.create_interface( + &self.pointer_absolute, + self.client.versions.ei_pointer_absolute(), + ); + } + + fn create_touchscreen(self: &Rc) { + self.create_interface(&self.touchscreen, self.client.versions.ei_touchscreen()); + } + + fn get_kb_state(&self) -> Rc { + match self.context() { + EiContext::Sender => self.seat.seat_xkb_state(), + EiContext::Receiver => self.seat.latest_xkb_state(), + } + } + + fn recreate_all( + self: &Rc, + create_all: bool, + kb_state: &KeyboardState, + ) -> Result<(), EiClientError> { + self.kb_state_id.set(kb_state.id); + let have_outputs = self.client.state.root.outputs.is_not_empty(); + let create_pointer = create_all || self.pointer.is_some(); + let create_pointer_absolute = + have_outputs && (create_all || self.pointer_absolute.is_some()); + let create_scroll = create_all || self.scroll.is_some(); + let create_button = create_all || self.button.is_some(); + let create_keyboard = create_all || self.keyboard.is_some(); + let create_touchscreen = have_outputs && (create_all || self.touchscreen.is_some()); + if let Some(device) = self.device.take() { + device.destroy()?; + } + let version = self.client.versions.ei_device(); + if version == EiVersion(0) { + return Ok(()); + } + let device = Rc::new(EiDevice { + id: self.client.new_id(), + client: self.client.clone(), + tracker: Default::default(), + version, + seat: self.clone(), + button_changes: Default::default(), + touch_changes: Default::default(), + scroll_px: array::from_fn(|_| Default::default()), + scroll_v120: array::from_fn(|_| Default::default()), + scroll_stop: array::from_fn(|_| Default::default()), + absolute_motion: Default::default(), + relative_motion: Default::default(), + key_changes: Default::default(), + }); + track!(self.client, device); + self.device.set(Some(device.clone())); + self.client.add_server_obj(&device); + self.send_device(&device); + device.send_device_type(EI_DEVICE_TYPE_VIRTUAL); + let caps = self.capabilities.get(); + macro_rules! apply { + ($cap:expr, $create:ident) => { + if $create && caps.contains($cap) { + self.$create(); + } + }; + } + apply!(EI_CAP_POINTER, create_pointer); + apply!(EI_CAP_POINTER_ABSOLUTE, create_pointer_absolute); + apply!(EI_CAP_SCROLL, create_scroll); + apply!(EI_CAP_BUTTON, create_button); + apply!(EI_CAP_KEYBOARD, create_keyboard); + apply!(EI_CAP_TOUCHSCREEN, create_touchscreen); + for output in self.client.state.root.outputs.lock().values() { + device.send_region( + output.node_absolute_position(), + output.global.persistent.scale.get(), + ); + } + if let Some(kb) = self.keyboard.get() { + kb.send_keymap(kb_state); + } + device.send_done(); + device.send_resumed(self.client.serial()); + if self.context() == EiContext::Receiver { + device.send_start_emulating(self.client.serial(), 1); + } + if let Some(kb) = self.keyboard.get() { + kb.send_modifiers(kb_state); + } + Ok(()) + } + + pub fn is_touch_input(&self) -> bool { + self.capabilities.get().contains(EI_CAP_TOUCHSCREEN) && self.context() == EiContext::Sender + } +} + +impl EiSeatRequestHandler for EiSeat { + type Error = EiSeatError; + + fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { + self.seat.remove_ei_seat(self); + self.send_destroyed(); + if let Some(device) = self.device.take() { + device.destroy()?; + } + self.client.remove_obj(self)?; + Ok(()) + } + + fn bind(&self, req: Bind, slf: &Rc) -> Result<(), Self::Error> { + let caps = req.capabilities; + let unknown = caps & !EI_CAP_ALL; + if unknown != 0 { + return Err(EiSeatError::UnknownCapabilities(unknown)); + } + self.capabilities.set(caps); + let kb_state = self.get_kb_state(); + slf.recreate_all(true, &kb_state.borrow())?; + self.seat.update_capabilities(); + Ok(()) + } +} + +ei_object_base! { + self = EiSeat; + version = self.version; +} + +impl EiObject for EiSeat { + fn break_loops(&self) { + self.seat.remove_ei_seat(self); + self.device.take(); + self.pointer.take(); + self.pointer_absolute.take(); + self.keyboard.take(); + self.button.take(); + self.scroll.take(); + self.touchscreen.take(); + } +} + +#[derive(Debug, Error)] +pub enum EiSeatError { + #[error(transparent)] + EiClientError(Box), + #[error("Capabilities {0} are unknown")] + UnknownCapabilities(u64), +} +efrom!(EiSeatError, EiClientError); diff --git a/src/ei/ei_ifs/ei_touchscreen.rs b/src/ei/ei_ifs/ei_touchscreen.rs new file mode 100644 index 00000000..52c0d60d --- /dev/null +++ b/src/ei/ei_ifs/ei_touchscreen.rs @@ -0,0 +1,108 @@ +use { + crate::{ + ei::{ + ei_client::{EiClient, EiClientError}, + ei_ifs::ei_device::{EiDevice, EiDeviceInterface}, + ei_object::{EiObject, EiVersion}, + }, + fixed::Fixed, + leaks::Tracker, + wire_ei::{ + ei_touchscreen::{ + ClientDown, ClientMotion, ClientUp, EiTouchscreenRequestHandler, Release, + ServerDown, ServerMotion, ServerUp, + }, + EiTouchscreenId, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct EiTouchscreen { + pub id: EiTouchscreenId, + pub client: Rc, + pub tracker: Tracker, + pub version: EiVersion, + pub device: Rc, +} + +#[derive(Copy, Clone, Debug)] +pub enum TouchChange { + Down(f32, f32), + Motion(f32, f32), + Up, +} + +ei_device_interface!(EiTouchscreen, ei_touchscreen, touchscreen); + +impl EiTouchscreen { + pub fn send_down(&self, touchid: u32, x: Fixed, y: Fixed) { + self.client.event(ServerDown { + self_id: self.id, + touchid, + x: x.to_f32(), + y: y.to_f32(), + }); + } + + pub fn send_motion(&self, touchid: u32, x: Fixed, y: Fixed) { + self.client.event(ServerMotion { + self_id: self.id, + touchid, + x: x.to_f32(), + y: y.to_f32(), + }); + } + + pub fn send_up(&self, touchid: u32) { + self.client.event(ServerUp { + self_id: self.id, + touchid, + }); + } +} + +impl EiTouchscreenRequestHandler for EiTouchscreen { + type Error = EiTouchscreenError; + + fn release(&self, _req: Release, _slf: &Rc) -> Result<(), Self::Error> { + self.destroy()?; + Ok(()) + } + + fn client_down(&self, req: ClientDown, _slf: &Rc) -> Result<(), Self::Error> { + self.device + .touch_changes + .push((req.touchid, TouchChange::Down(req.x, req.y))); + Ok(()) + } + + fn client_motion(&self, req: ClientMotion, _slf: &Rc) -> Result<(), Self::Error> { + self.device + .touch_changes + .push((req.touchid, TouchChange::Motion(req.x, req.y))); + Ok(()) + } + + fn client_up(&self, req: ClientUp, _slf: &Rc) -> Result<(), Self::Error> { + self.device + .touch_changes + .push((req.touchid, TouchChange::Up)); + Ok(()) + } +} + +ei_object_base! { + self = EiTouchscreen; + version = self.version; +} + +impl EiObject for EiTouchscreen {} + +#[derive(Debug, Error)] +pub enum EiTouchscreenError { + #[error(transparent)] + EiClientError(Box), +} +efrom!(EiTouchscreenError, EiClientError); diff --git a/src/ei/ei_object.rs b/src/ei/ei_object.rs new file mode 100644 index 00000000..9aac90bd --- /dev/null +++ b/src/ei/ei_object.rs @@ -0,0 +1,94 @@ +use { + crate::{ + ei::{ + ei_client::{EiClient, EiClientError}, + EiContext, + }, + utils::buffd::EiMsgParser, + wire_ei::EiHandshakeId, + }, + std::{ + cmp::Ordering, + fmt::{Display, Formatter, LowerHex}, + rc::Rc, + }, +}; + +pub const EI_HANDSHAKE_ID: EiHandshakeId = EiHandshakeId::from_raw(0); + +#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] +pub struct EiObjectId(u64); + +impl EiObjectId { + #[allow(dead_code)] + pub const NONE: Self = EiObjectId(0); + + pub fn from_raw(raw: u64) -> Self { + Self(raw) + } + + pub fn raw(self) -> u64 { + self.0 + } +} + +impl Display for EiObjectId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl LowerHex for EiObjectId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + LowerHex::fmt(&self.0, f) + } +} + +pub trait EiObjectBase { + fn id(&self) -> EiObjectId; + fn version(&self) -> EiVersion; + fn client(&self) -> &EiClient; + fn handle_request( + self: Rc, + client: &EiClient, + request: u32, + parser: EiMsgParser<'_, '_>, + ) -> Result<(), EiClientError>; + fn interface(&self) -> EiInterface; +} + +pub trait EiObject: EiObjectBase + 'static { + fn break_loops(&self) {} + + fn context(&self) -> EiContext { + self.client().context.get() + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct EiInterface(pub &'static str); + +impl EiInterface { + pub fn name(self) -> &'static str { + self.0 + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct EiVersion(pub u32); + +impl EiVersion { + // pub const ALL: EiVersion = EiVersion(0); +} + +impl PartialEq for EiVersion { + fn eq(&self, other: &u32) -> bool { + self.0 == *other + } +} + +impl PartialOrd for EiVersion { + fn partial_cmp(&self, other: &u32) -> Option { + self.0.partial_cmp(other) + } +} diff --git a/src/fixed.rs b/src/fixed.rs index 3bd02e9a..c758f637 100644 --- a/src/fixed.rs +++ b/src/fixed.rs @@ -19,10 +19,18 @@ impl Fixed { Self((f * 256.0) as i32) } + pub fn from_f32(f: f32) -> Self { + Self::from_f64(f as f64) + } + pub fn to_f64(self) -> f64 { self.0 as f64 / 256.0 } + pub fn to_f32(self) -> f32 { + self.0 as f32 / 256.0 + } + pub fn from_1616(i: i32) -> Self { Self(i >> 8) } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 339a7a63..48d2190d 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -25,6 +25,7 @@ use { async_engine::SpawnedFuture, client::{Client, ClientError, ClientId}, cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner}, + ei::ei_ifs::ei_seat::EiSeat, fixed::Fixed, globals::{Global, GlobalName}, ifs::{ @@ -84,6 +85,7 @@ use { WlSeatId, WlTouchId, ZwlrDataControlDeviceV1Id, ZwpPrimarySelectionDeviceV1Id, ZwpRelativePointerV1Id, ZwpTextInputV3Id, }, + wire_ei::EiSeatId, xkbcommon::{DynKeyboardState, KeyboardState, KeymapId, XkbKeymap, XkbState}, }, ahash::AHashMap, @@ -195,6 +197,7 @@ pub struct WlSeatGlobal { pinch_bindings: PerClientBindings, hold_bindings: PerClientBindings, tablet: TabletSeatData, + ei_seats: CopyHashMap<(ClientId, EiSeatId), Rc>, } const CHANGE_CURSOR_MOVED: u32 = 1 << 0; @@ -263,6 +266,7 @@ impl WlSeatGlobal { pinch_bindings: Default::default(), hold_bindings: Default::default(), tablet: Default::default(), + ei_seats: Default::default(), }); slf.pointer_cursor.set_owner(slf.clone()); let seat = slf.clone(); @@ -280,10 +284,14 @@ impl WlSeatGlobal { slf } - fn update_capabilities(&self) { + pub fn update_capabilities(&self) { let mut caps = POINTER | KEYBOARD; if self.num_touch_devices.get() > 0 { caps |= TOUCH; + } else { + if self.ei_seats.lock().values().any(|s| s.is_touch_input()) { + caps |= TOUCH; + } } if self.capabilities.replace(caps) != caps { for client in self.bindings.borrow().values() { @@ -481,6 +489,9 @@ impl WlSeatGlobal { } fn handle_xkb_state_change(&self, old: &XkbState, new: &XkbState) { + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_xkb_state_change(old.kb_state.id, &new.kb_state); + }); let Some(surface) = self.keyboard_node.get().node_into_surface() else { return; }; @@ -888,6 +899,7 @@ impl WlSeatGlobal { self.hold_bindings.clear(); self.cursor_user_group.detach(); self.tablet_clear(); + self.ei_seats.clear(); } pub fn id(&self) -> SeatId { @@ -982,6 +994,30 @@ impl WlSeatGlobal { self.pointer_owner .set_window_management_enabled(self, enabled); } + + pub fn add_ei_seat(&self, ei: &Rc) { + self.ei_seats.set((ei.client.id, ei.id), ei.clone()); + self.update_capabilities(); + } + + pub fn remove_ei_seat(&self, ei: &EiSeat) { + self.ei_seats.remove(&(ei.client.id, ei.id)); + self.update_capabilities(); + } + + pub fn seat_xkb_state(&self) -> Rc { + self.seat_xkb_state.get() + } + + pub fn latest_xkb_state(&self) -> Rc { + self.latest_kb_state.get() + } + + pub fn output_extents_changed(&self) { + self.for_each_ei_seat(|ei_seat| { + ei_seat.regions_changed(); + }); + } } impl CursorUserOwner for WlSeatGlobal { diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index ee6c55c7..e47cb4f1 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -1,8 +1,11 @@ use { crate::{ - backend::{ConnectorId, InputDeviceId, InputEvent, KeyState, AXIS_120}, + backend::{ + AxisSource, ConnectorId, InputDeviceId, InputEvent, KeyState, ScrollAxis, AXIS_120, + }, client::ClientId, config::InvokedShortcut, + ei::ei_ifs::ei_seat::EiSeat, fixed::Fixed, ifs::{ ipc::{ @@ -245,10 +248,7 @@ impl WlSeatGlobal { | InputEvent::TabletPadModeSwitch { time_usec, .. } | InputEvent::TabletPadRing { time_usec, .. } | InputEvent::TabletPadStrip { time_usec, .. } - | InputEvent::TouchDown { time_usec, .. } - | InputEvent::TouchUp { time_usec, .. } - | InputEvent::TouchMotion { time_usec, .. } - | InputEvent::TouchCancel { time_usec, .. } => { + | InputEvent::TouchFrame { time_usec, .. } => { self.last_input_usec.set(time_usec); if self.idle_notifications.is_not_empty() { for notification in self.idle_notifications.lock().drain_values() { @@ -262,7 +262,10 @@ impl WlSeatGlobal { | InputEvent::Axis120 { .. } | InputEvent::TabletToolAdded { .. } | InputEvent::TabletToolRemoved { .. } - | InputEvent::TouchFrame => {} + | InputEvent::TouchDown { .. } + | InputEvent::TouchUp { .. } + | InputEvent::TouchMotion { .. } + | InputEvent::TouchCancel { .. } => {} } match event { InputEvent::ConnectorPosition { .. } @@ -297,7 +300,7 @@ impl WlSeatGlobal { InputEvent::TouchUp { .. } => {} InputEvent::TouchMotion { .. } => {} InputEvent::TouchCancel { .. } => {} - InputEvent::TouchFrame => {} + InputEvent::TouchFrame { .. } => {} } match event { InputEvent::Key { @@ -324,19 +327,21 @@ impl WlSeatGlobal { state, } => self.button_event(time_usec, button, state), - InputEvent::AxisSource { source } => self.pointer_owner.axis_source(source), + InputEvent::AxisSource { source } => self.axis_source(source), InputEvent::Axis120 { dist, axis, inverted, - } => self.pointer_owner.axis_120(dist, axis, inverted), + } => self.axis_120(dist, axis, inverted), InputEvent::AxisPx { dist, axis, inverted, - } => self.pointer_owner.axis_px(dist, axis, inverted), - InputEvent::AxisStop { axis } => self.pointer_owner.axis_stop(axis), - InputEvent::AxisFrame { time_usec } => self.pointer_owner.frame(dev, self, time_usec), + } => self.axis_px(dist, axis, inverted), + InputEvent::AxisStop { axis } => self.axis_stop(axis), + InputEvent::AxisFrame { time_usec } => { + self.axis_frame(dev.px_per_scroll_wheel.get(), time_usec) + } InputEvent::SwipeBegin { time_usec, finger_count, @@ -445,7 +450,7 @@ impl WlSeatGlobal { y_normed, } => self.touch_motion(time_usec, id, dev.get_rect(&self.state), x_normed, y_normed), InputEvent::TouchCancel { time_usec, id } => self.touch_cancel(time_usec, id), - InputEvent::TouchFrame => self.touch_frame(), + InputEvent::TouchFrame { time_usec } => self.touch_frame(time_usec), } } @@ -481,7 +486,14 @@ impl WlSeatGlobal { let pos = output.global.pos.get(); x += Fixed::from_int(pos.x1()); y += Fixed::from_int(pos.y1()); - (x, y) = self.set_pointer_cursor_position(x, y); + self.motion_event_abs(time_usec, x, y); + } + + pub fn motion_event_abs(self: &Rc, time_usec: u64, x: Fixed, y: Fixed) { + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_motion_abs(time_usec, x, y); + }); + let (x, y) = self.set_pointer_cursor_position(x, y); if let Some(c) = self.constraint.get() { if c.ty == ConstraintType::Lock || !c.contains(x.round_down(), y.round_down()) { c.deactivate(); @@ -493,7 +505,7 @@ impl WlSeatGlobal { self.cursor_moved(time_usec); } - fn motion_event( + pub fn motion_event( self: &Rc, time_usec: u64, dx: Fixed, @@ -501,6 +513,9 @@ impl WlSeatGlobal { dx_unaccelerated: Fixed, dy_unaccelerated: Fixed, ) { + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_motion(time_usec, dx, dy); + }); self.pointer_owner.relative_motion( self, time_usec, @@ -545,13 +560,37 @@ impl WlSeatGlobal { self.cursor_moved(time_usec); } - fn button_event(self: &Rc, time_usec: u64, button: u32, state: KeyState) { + pub fn button_event(self: &Rc, time_usec: u64, button: u32, state: KeyState) { + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_button(time_usec, button, state); + }); self.state.for_each_seat_tester(|t| { t.send_button(self.id, time_usec, button, state); }); self.pointer_owner.button(self, time_usec, button, state); } + pub fn axis_source(&self, axis_source: AxisSource) { + self.pointer_owner.axis_source(axis_source); + } + + pub fn axis_120(&self, delta: i32, axis: ScrollAxis, inverted: bool) { + self.pointer_owner.axis_120(delta, axis, inverted); + } + + pub fn axis_px(&self, delta: Fixed, axis: ScrollAxis, inverted: bool) { + self.pointer_owner.axis_px(delta, axis, inverted); + } + + pub fn axis_stop(&self, axis: ScrollAxis) { + self.pointer_owner.axis_stop(axis); + } + + pub fn axis_frame(self: &Rc, px_per_scroll_wheel: f64, time_usec: u64) { + self.pointer_owner + .frame(px_per_scroll_wheel, self, time_usec); + } + fn swipe_begin(self: &Rc, time_usec: u64, finger_count: u32) { self.state.for_each_seat_tester(|t| { t.send_swipe_begin(self.id, time_usec, finger_count); @@ -660,16 +699,26 @@ impl WlSeatGlobal { x_normed: Fixed, y_normed: Fixed, ) { - self.cursor_group().deactivate(); let x = Fixed::from_f64(rect.x1() as f64 + rect.width() as f64 * x_normed.to_f64()); let y = Fixed::from_f64(rect.y1() as f64 + rect.height() as f64 * y_normed.to_f64()); + self.touch_down_at(time_usec, id, x, y); + } + + pub fn touch_down_at(self: &Rc, time_usec: u64, id: i32, x: Fixed, y: Fixed) { + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_touch_down(id as _, x, y); + }); + self.cursor_group().deactivate(); self.state.for_each_seat_tester(|t| { t.send_touch_down(self.id, time_usec, id, x, y); }); self.touch_owner.down(self, time_usec, id, x, y); } - fn touch_up(self: &Rc, time_usec: u64, id: i32) { + pub fn touch_up(self: &Rc, time_usec: u64, id: i32) { + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_touch_up(id as _); + }); self.state.for_each_seat_tester(|t| { t.send_touch_up(self.id, time_usec, id); }); @@ -684,9 +733,16 @@ impl WlSeatGlobal { x_normed: Fixed, y_normed: Fixed, ) { - self.cursor_group().deactivate(); let x = Fixed::from_f64(rect.x1() as f64 + rect.width() as f64 * x_normed.to_f64()); let y = Fixed::from_f64(rect.y1() as f64 + rect.height() as f64 * y_normed.to_f64()); + self.touch_motion_at(time_usec, id, x, y); + } + + pub fn touch_motion_at(self: &Rc, time_usec: u64, id: i32, x: Fixed, y: Fixed) { + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_touch_motion(id as _, x, y); + }); + self.cursor_group().deactivate(); self.state.for_each_seat_tester(|t| { t.send_touch_motion(self.id, time_usec, id, x, y); }); @@ -694,16 +750,31 @@ impl WlSeatGlobal { } fn touch_cancel(self: &Rc, time_usec: u64, id: i32) { + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_touch_up(id as _); + }); self.state.for_each_seat_tester(|t| { t.send_touch_cancel(self.id, time_usec, id); }); self.touch_owner.cancel(self); } - fn touch_frame(self: &Rc) { + pub fn touch_frame(self: &Rc, time_usec: u64) { + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_touch_frame(time_usec); + }); self.touch_owner.frame(self); } + pub fn key_event_with_seat_state( + self: &Rc, + time_usec: u64, + key: u32, + key_state: KeyState, + ) { + self.key_event(time_usec, key, key_state, || self.seat_xkb_state.get()); + } + pub(super) fn key_event( self: &Rc, time_usec: u64, @@ -787,8 +858,14 @@ impl WlSeatGlobal { Some(g) => g.on_key(time_usec, key, state, &xkb_state.kb_state), _ => node.node_on_key(self, time_usec, key, state, &xkb_state.kb_state), } + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_key(time_usec, key, state, &xkb_state.kb_state); + }); } if new_mods { + self.for_each_ei_seat(|ei_seat| { + ei_seat.handle_modifiers_changed(&xkb_state.kb_state); + }); self.state.for_each_seat_tester(|t| { t.send_modifiers(self.id, &xkb_state.kb_state.mods); }); @@ -808,6 +885,14 @@ impl WlSeatGlobal { drop(xkb_state); self.latest_kb_state.set(xkb_state_rc); } + + pub(super) fn for_each_ei_seat(&self, mut f: impl FnMut(&Rc)) { + if self.ei_seats.is_not_empty() { + for ei_seat in self.ei_seats.lock().values() { + f(ei_seat); + } + } + } } impl WlSeatGlobal { diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index b5e760c7..527ae609 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -13,7 +13,6 @@ use { wl_surface::{dnd_icon::DndIcon, WlSurface}, xdg_toplevel_drag_v1::XdgToplevelDragV1, }, - state::DeviceHandlerData, tree::{ContainingNode, FindTreeUsecase, FoundNode, Node, ToplevelNode, WorkspaceNode}, utils::{clonecell::CloneCell, smallmap::SmallMap}, }, @@ -73,15 +72,18 @@ impl PointerOwnerHolder { self.pending_scroll.stop[axis as usize].set(true); } - pub fn frame(&self, dev: &DeviceHandlerData, seat: &Rc, time_usec: u64) { + pub fn frame(&self, px_per_scroll_wheel: f64, seat: &Rc, time_usec: u64) { self.pending_scroll.time_usec.set(time_usec); let pending = self.pending_scroll.take(); for axis in 0..2 { if let Some(dist) = pending.v120[axis].get() { - let px = (dist as f64 / AXIS_120 as f64) * dev.px_per_scroll_wheel.get(); + let px = (dist as f64 / AXIS_120 as f64) * px_per_scroll_wheel; pending.px[axis].set(Some(Fixed::from_f64(px))); } } + seat.for_each_ei_seat(|ei_seat| { + ei_seat.handle_pending_scroll(time_usec, &pending); + }); seat.state.for_each_seat_tester(|t| { t.send_axis(seat.id, time_usec, &pending); }); diff --git a/src/macros.rs b/src/macros.rs index 015dcbbe..41cd330c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -655,3 +655,86 @@ macro_rules! pw_object_base { } } } + +macro_rules! ei_id { + ($name:ident) => { + #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] + pub struct $name(u64); + + #[allow(dead_code)] + impl $name { + pub const NONE: Self = $name(0); + + pub const fn from_raw(raw: u64) -> Self { + Self(raw) + } + + pub fn raw(self) -> u64 { + self.0 + } + + pub fn is_some(self) -> bool { + self.0 != 0 + } + + pub fn is_none(self) -> bool { + self.0 == 0 + } + } + + impl From for $name { + fn from(f: crate::ei::ei_object::EiObjectId) -> Self { + Self(f.raw()) + } + } + + impl From<$name> for crate::ei::ei_object::EiObjectId { + fn from(f: $name) -> Self { + crate::ei::ei_object::EiObjectId::from_raw(f.0) + } + } + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + + impl std::fmt::LowerHex for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::LowerHex::fmt(&self.0, f) + } + } + }; +} + +macro_rules! ei_object_base { + ($self:ident = $oname:ident; version = $version:expr;) => { + impl crate::ei::ei_object::EiObjectBase for $oname { + fn id(&$self) -> crate::ei::ei_object::EiObjectId { + $self.id.into() + } + + fn version(&$self) -> crate::ei::ei_object::EiVersion { + $version + } + + fn client(&$self) -> &crate::ei::ei_client::EiClient { + &$self.client + } + + fn handle_request( + $self: std::rc::Rc, + client: &crate::ei::ei_client::EiClient, + request: u32, + parser: crate::utils::buffd::EiMsgParser<'_, '_>, + ) -> Result<(), crate::ei::ei_client::EiClientError> { + $self.handle_request_impl(client, request, parser) + } + + fn interface(&$self) -> crate::ei::ei_object::EiInterface { + crate::wire_ei::$oname + } + } + }; +} diff --git a/src/main.rs b/src/main.rs index 9e8d4cbb..e4e72a12 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,6 +58,7 @@ mod damage; mod dbus; mod drm_feedback; mod edid; +mod ei; mod fixed; mod forker; mod format; @@ -97,6 +98,7 @@ mod video; mod wheel; mod wire; mod wire_dbus; +mod wire_ei; mod wire_xcon; mod wl_usr; mod xcon; diff --git a/src/state.rs b/src/state.rs index 5318ab8d..3564d9d1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,12 +11,17 @@ use { cli::RunArgs, client::{Client, ClientId, Clients, SerialRange, NUM_CACHED_SERIAL_RANGES}, clientmem::ClientMemOffset, + compositor::LIBEI_SOCKET, config::ConfigProxy, cursor::{Cursor, ServerCursors}, cursor_user::{CursorUserGroup, CursorUserGroupId, CursorUserGroupIds, CursorUserIds}, damage::DamageVisualizer, dbus::Dbus, drm_feedback::{DrmFeedback, DrmFeedbackIds}, + ei::{ + ei_acceptor::EiAcceptor, + ei_client::{EiClient, EiClients}, + }, fixed::Fixed, forker::ForkerProxy, format::Format, @@ -204,6 +209,11 @@ pub struct State { pub default_vrr_mode: Cell<&'static VrrMode>, pub default_vrr_cursor_hz: Cell>, pub default_tearing_mode: Cell<&'static TearingMode>, + pub ei_acceptor: CloneCell>>, + pub ei_acceptor_future: CloneCell>>, + pub enable_ei_acceptor: Cell, + pub ei_clients: EiClients, + pub slow_ei_clients: AsyncQueue>, } // impl Drop for State { @@ -827,6 +837,10 @@ impl State { } self.wheel.clear(); self.eng.clear(); + self.ei_acceptor.take(); + self.ei_acceptor_future.take(); + self.ei_clients.clear(); + self.slow_ei_clients.clear(); } pub fn disable_hardware_cursors(&self) { @@ -1019,6 +1033,7 @@ impl State { let global_name = self.globals.name(); let seat = WlSeatGlobal::new(global_name, name, self); self.globals.add_global(self, &seat); + self.ei_clients.announce_seat(&seat); seat } @@ -1095,6 +1110,48 @@ impl State { pub fn now_msec(&self) -> u64 { self.eng.now().msec() } + + pub fn output_extents_changed(&self) { + self.root.update_extents(); + for seat in self.globals.seats.lock().values() { + seat.output_extents_changed(); + } + } + + pub fn update_ei_acceptor(self: &Rc) { + self.update_ei_acceptor2(); + if let Some(forker) = self.forker.get() { + match self.ei_acceptor.get() { + None => { + forker.unsetenv(LIBEI_SOCKET.as_bytes()); + } + Some(s) => { + forker.setenv(LIBEI_SOCKET.as_bytes(), s.socket_name().as_bytes()); + } + } + } + } + + fn update_ei_acceptor2(self: &Rc) { + if self.ei_acceptor.is_some() == self.enable_ei_acceptor.get() { + return; + } + if self.enable_ei_acceptor.get() { + let (acceptor, future) = match EiAcceptor::spawn(self) { + Ok(v) => v, + Err(e) => { + log::error!("Could not create libei socket: {}", ErrorFmt(e)); + return; + } + }; + self.ei_acceptor.set(Some(acceptor)); + self.ei_acceptor_future.set(Some(future)); + } else { + log::info!("Disabling libei socket"); + self.ei_acceptor.take(); + self.ei_acceptor_future.take(); + } + } } #[derive(Debug, Error)] diff --git a/src/tasks.rs b/src/tasks.rs index ccaf6812..7ebb569b 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -10,7 +10,10 @@ mod udev_utils; use { crate::{ state::State, - tasks::{backend::BackendEventHandler, slow_clients::SlowClientHandler}, + tasks::{ + backend::BackendEventHandler, + slow_clients::{SlowClientHandler, SlowEiClientHandler}, + }, }, std::rc::Rc, }; @@ -25,3 +28,8 @@ pub async fn handle_slow_clients(state: Rc) { let mut sch = SlowClientHandler { state }; sch.handle_events().await; } + +pub async fn handle_slow_ei_clients(state: Rc) { + let mut sch = SlowEiClientHandler { state }; + sch.handle_events().await; +} diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index ebdaca2f..db1cc6f6 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -199,7 +199,7 @@ impl ConnectorHandler { self.state.outputs.set(self.id, output_data); on.schedule_update_render_data(); self.state.root.outputs.set(self.id, on.clone()); - self.state.root.update_extents(); + self.state.output_extents_changed(); global.opt.node.set(Some(on.clone())); global.opt.global.set(Some(global.clone())); let mut ws_to_move = VecDeque::new(); @@ -280,7 +280,7 @@ impl ConnectorHandler { } global.destroyed.set(true); self.state.root.outputs.remove(&self.id); - self.state.root.update_extents(); + self.state.output_extents_changed(); self.state.outputs.remove(&self.id); on.lock_surface.take(); { diff --git a/src/tasks/slow_clients.rs b/src/tasks/slow_clients.rs index 84e75758..a14b3f12 100644 --- a/src/tasks/slow_clients.rs +++ b/src/tasks/slow_clients.rs @@ -12,3 +12,16 @@ impl SlowClientHandler { } } } + +pub struct SlowEiClientHandler { + pub state: Rc, +} + +impl SlowEiClientHandler { + pub async fn handle_events(&mut self) { + loop { + let client = self.state.slow_ei_clients.pop().await; + client.check_queue_size().await; + } + } +} diff --git a/src/tree/output.rs b/src/tree/output.rs index e6100f84..1bedcd31 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -610,7 +610,7 @@ impl OutputNode { } self.global.persistent.pos.set((rect.x1(), rect.y1())); self.global.pos.set(*rect); - self.state.root.update_extents(); + self.state.output_extents_changed(); self.update_rects(); if let Some(ls) = self.lock_surface.get() { ls.change_extents(*rect); diff --git a/src/utils.rs b/src/utils.rs index 850931c7..91463c6a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -33,6 +33,7 @@ pub mod option_ext; pub mod oserror; pub mod page_size; pub mod pending_serial; +pub mod pid_info; pub mod process_name; pub mod ptr_ext; pub mod queue; diff --git a/src/utils/buffd.rs b/src/utils/buffd.rs index 16448956..cf28640b 100644 --- a/src/utils/buffd.rs +++ b/src/utils/buffd.rs @@ -2,12 +2,16 @@ use {crate::io_uring::IoUringError, thiserror::Error}; pub use { buf_in::BufFdIn, buf_out::{BufFdOut, OutBuffer, OutBufferSwapchain}, + ei_formatter::EiMsgFormatter, + ei_parser::{EiMsgParser, EiMsgParserError}, formatter::MsgFormatter, parser::{MsgParser, MsgParserError}, }; mod buf_in; mod buf_out; +mod ei_formatter; +mod ei_parser; mod formatter; mod parser; diff --git a/src/utils/buffd/ei_formatter.rs b/src/utils/buffd/ei_formatter.rs new file mode 100644 index 00000000..d04b1507 --- /dev/null +++ b/src/utils/buffd/ei_formatter.rs @@ -0,0 +1,106 @@ +use { + crate::{ + ei::ei_object::EiObjectId, + utils::buffd::buf_out::{MsgFds, OutBuffer, OutBufferMeta, OUT_BUF_SIZE}, + }, + std::{mem, rc::Rc}, + uapi::OwnedFd, +}; + +pub struct EiMsgFormatter<'a> { + buf: &'a mut [u8], + meta: &'a mut OutBufferMeta, + pos: usize, + fds: &'a mut Vec>, +} + +impl<'a> EiMsgFormatter<'a> { + pub fn new(buf: &'a mut OutBuffer, fds: &'a mut Vec>) -> Self { + Self { + pos: buf.meta.write_pos, + buf: &mut buf.buf[..], + fds, + meta: &mut buf.meta, + } + } + + fn write(&mut self, bytes: &[u8]) { + if bytes.len() > OUT_BUF_SIZE - self.meta.write_pos { + panic!("Out buffer overflow"); + } + self.buf[self.meta.write_pos..self.meta.write_pos + bytes.len()].copy_from_slice(bytes); + self.meta.write_pos += bytes.len(); + } + + pub fn int(&mut self, int: i32) -> &mut Self { + self.write(uapi::as_bytes(&int)); + self + } + + pub fn uint(&mut self, int: u32) -> &mut Self { + self.write(uapi::as_bytes(&int)); + self + } + + #[allow(dead_code)] + pub fn long(&mut self, int: i64) -> &mut Self { + self.write(uapi::as_bytes(&int)); + self + } + + pub fn ulong(&mut self, int: u64) -> &mut Self { + self.write(uapi::as_bytes(&int)); + self + } + + pub fn float(&mut self, f: f32) -> &mut Self { + self.write(uapi::as_bytes(&f)); + self + } + + pub fn optstr + ?Sized>(&mut self, s: Option<&S>) -> &mut Self { + match s { + Some(s) => self.string(s), + _ => self.uint(0), + } + } + + pub fn string + ?Sized>(&mut self, s: &S) -> &mut Self { + let s = s.as_ref(); + let len = s.len() + 1; + let cap = (len + 3) & !3; + self.uint(len as u32); + self.write(uapi::as_bytes(s)); + let none = [0; 4]; + self.write(&none[..cap - len + 1]); + self + } + + pub fn fd(&mut self, fd: Rc) -> &mut Self { + self.fds.push(fd); + self + } + + pub fn object>(&mut self, obj: T) -> &mut Self { + self.ulong(obj.into().raw()) + } + + pub fn header>(&mut self, obj: T, event: u32) -> &mut Self { + self.object(obj).uint(0).uint(event) + } + + pub fn write_len(self) { + assert!(self.meta.write_pos - self.pos >= 16); + assert_eq!(self.pos % 4, 0); + unsafe { + let second_ptr = self.buf.as_ptr().add(self.pos + 8) as *mut u32; + *second_ptr = (self.meta.write_pos - self.pos) as u32; + } + if self.fds.len() > 0 { + self.meta.fds.push_back(MsgFds { + pos: self.pos, + fds: mem::take(self.fds), + }) + } + } +} diff --git a/src/utils/buffd/ei_parser.rs b/src/utils/buffd/ei_parser.rs new file mode 100644 index 00000000..361e6523 --- /dev/null +++ b/src/utils/buffd/ei_parser.rs @@ -0,0 +1,112 @@ +use { + crate::{ei::ei_object::EiObjectId, utils::buffd::BufFdIn}, + std::{ptr, rc::Rc}, + thiserror::Error, + uapi::OwnedFd, +}; + +#[derive(Debug, Error)] +pub enum EiMsgParserError { + #[error("The message ended unexpectedly")] + UnexpectedEof, + #[error("The message contained a string of size 0")] + EmptyString, + #[error("Message is missing a required file descriptor")] + MissingFd, + #[error("There is trailing data after the message")] + TrailingData, + #[error("String is not UTF-8")] + NonUtf8, +} + +pub struct EiMsgParser<'a, 'b> { + buf: &'a mut BufFdIn, + pos: usize, + data: &'b [u8], +} + +impl<'a, 'b> EiMsgParser<'a, 'b> { + pub fn new(buf: &'a mut BufFdIn, data: &'b [u32]) -> Self { + Self { + buf, + pos: 0, + data: uapi::as_bytes(data), + } + } + + pub fn int(&mut self) -> Result { + if self.data.len() - self.pos < 4 { + return Err(EiMsgParserError::UnexpectedEof); + } + let res = unsafe { *(self.data.as_ptr().add(self.pos) as *const i32) }; + self.pos += 4; + Ok(res) + } + + pub fn uint(&mut self) -> Result { + self.int().map(|i| i as u32) + } + + pub fn long(&mut self) -> Result { + if self.data.len() - self.pos < 8 { + return Err(EiMsgParserError::UnexpectedEof); + } + let res = unsafe { ptr::read_unaligned(self.data.as_ptr().add(self.pos) as *const i64) }; + self.pos += 8; + Ok(res) + } + + pub fn ulong(&mut self) -> Result { + self.long().map(|i| i as u64) + } + + pub fn object(&mut self) -> Result + where + EiObjectId: Into, + { + self.ulong().map(|i| EiObjectId::from_raw(i).into()) + } + + pub fn float(&mut self) -> Result { + Ok(f32::from_bits(self.uint()?)) + } + + pub fn optstr(&mut self) -> Result, EiMsgParserError> { + let len = self.uint()? as usize; + if len == 0 { + return Ok(None); + } + let cap = (len + 3) & !3; + if cap > self.data.len() - self.pos { + return Err(EiMsgParserError::UnexpectedEof); + } + let pos = self.pos; + self.pos += cap; + match std::str::from_utf8(&self.data[pos..pos + len - 1]) { + Ok(s) => Ok(Some(s)), + Err(_) => Err(EiMsgParserError::NonUtf8), + } + } + + pub fn str(&mut self) -> Result<&'b str, EiMsgParserError> { + match self.optstr()? { + Some(s) => Ok(s), + _ => Err(EiMsgParserError::EmptyString), + } + } + + pub fn fd(&mut self) -> Result, EiMsgParserError> { + match self.buf.get_fd() { + Ok(fd) => Ok(fd), + _ => Err(EiMsgParserError::MissingFd), + } + } + + pub fn eof(&self) -> Result<(), EiMsgParserError> { + if self.pos == self.data.len() { + Ok(()) + } else { + Err(EiMsgParserError::TrailingData) + } + } +} diff --git a/src/utils/pid_info.rs b/src/utils/pid_info.rs new file mode 100644 index 00000000..e9266aeb --- /dev/null +++ b/src/utils/pid_info.rs @@ -0,0 +1,44 @@ +use { + crate::utils::{errorfmt::ErrorFmt, oserror::OsError, trim::AsciiTrim}, + bstr::ByteSlice, + uapi::{c, OwnedFd}, +}; + +pub struct PidInfo { + pub _uid: c::uid_t, + pub pid: c::pid_t, + pub comm: String, +} + +pub fn get_pid_info(uid: c::uid_t, pid: c::pid_t) -> PidInfo { + let comm = match std::fs::read(format!("/proc/{}/comm", pid)) { + Ok(name) => name.trim().as_bstr().to_string(), + Err(e) => { + log::warn!("Could not read `comm` of pid {}: {}", pid, ErrorFmt(e)); + "Unknown".to_string() + } + }; + PidInfo { + _uid: uid, + pid, + comm, + } +} + +pub fn get_socket_creds(socket: &OwnedFd) -> Option<(c::uid_t, c::pid_t)> { + let mut cred = c::ucred { + pid: 0, + uid: 0, + gid: 0, + }; + match uapi::getsockopt(socket.raw(), c::SOL_SOCKET, c::SO_PEERCRED, &mut cred) { + Ok(_) => Some((cred.uid, cred.pid)), + Err(e) => { + log::error!( + "Cannot determine peer credentials of new connection: {:?}", + OsError::from(e) + ); + None + } + } +} diff --git a/src/utils/syncqueue.rs b/src/utils/syncqueue.rs index 2cb2c936..55d8a1ba 100644 --- a/src/utils/syncqueue.rs +++ b/src/utils/syncqueue.rs @@ -37,6 +37,10 @@ impl SyncQueue { unsafe { self.el.get().deref_mut().is_empty() } } + pub fn is_not_empty(&self) -> bool { + !self.is_empty() + } + pub fn swap(&self, queue: &mut VecDeque) { unsafe { mem::swap(self.el.get().deref_mut(), queue); diff --git a/src/wire_ei.rs b/src/wire_ei.rs new file mode 100644 index 00000000..c2a8369b --- /dev/null +++ b/src/wire_ei.rs @@ -0,0 +1,3 @@ +#![allow(non_upper_case_globals)] + +include!(concat!(env!("OUT_DIR"), "/wire_ei.rs")); diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index e2fc7532..5cee1953 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -299,6 +299,11 @@ pub struct Tearing { pub mode: Option, } +#[derive(Debug, Clone, Default)] +pub struct Libei { + pub enable_socket: Option, +} + #[derive(Debug, Clone)] pub struct Shortcut { pub mask: Modifiers, @@ -334,6 +339,7 @@ pub struct Config { pub window_management_key: Option, pub vrr: Option, pub tearing: Option, + pub libei: Libei, } #[derive(Debug, Error)] diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs index b431f296..f457fd55 100644 --- a/toml-config/src/config/parsers.rs +++ b/toml-config/src/config/parsers.rs @@ -20,6 +20,7 @@ mod idle; mod input; mod input_match; pub mod keymap; +mod libei; mod log_level; mod mode; pub mod modified_keysym; diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs index 9b2bfe86..be4f0b82 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/toml-config/src/config/parsers/config.rs @@ -14,6 +14,7 @@ use { idle::IdleParser, input::InputsParser, keymap::KeymapParser, + libei::LibeiParser, log_level::LogLevelParser, output::OutputsParser, repeat_rate::RepeatRateParser, @@ -27,7 +28,7 @@ use { vrr::VrrParser, }, spanned::SpannedErrorExt, - Action, Config, Theme, + Action, Config, Libei, Theme, }, toml::{ toml_span::{DespanExt, Span, Spanned}, @@ -110,6 +111,7 @@ impl Parser for ConfigParser<'_> { window_management_key_val, vrr_val, tearing_val, + libei_val, ), ) = ext.extract(( ( @@ -144,6 +146,7 @@ impl Parser for ConfigParser<'_> { recover(opt(str("window-management-key"))), opt(val("vrr")), opt(val("tearing")), + opt(val("libei")), ), ))?; let mut keymap = None; @@ -326,6 +329,15 @@ impl Parser for ConfigParser<'_> { } } } + let mut libei = Libei::default(); + if let Some(value) = libei_val { + match value.parse(&mut LibeiParser(self.0)) { + Ok(v) => libei = v, + Err(e) => { + log::warn!("Could not parse libei setting: {}", self.0.error(e)); + } + } + } Ok(Config { keymap, repeat_rate, @@ -352,6 +364,7 @@ impl Parser for ConfigParser<'_> { window_management_key, vrr, tearing, + libei, }) } } diff --git a/toml-config/src/config/parsers/libei.rs b/toml-config/src/config/parsers/libei.rs new file mode 100644 index 00000000..83e552fb --- /dev/null +++ b/toml-config/src/config/parsers/libei.rs @@ -0,0 +1,44 @@ +use { + crate::{ + config::{ + context::Context, + extractor::{bol, opt, recover, Extractor, ExtractorError}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + Libei, + }, + toml::{ + toml_span::{DespanExt, Span, Spanned}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum LibeiParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extract(#[from] ExtractorError), +} + +pub struct LibeiParser<'a>(pub &'a Context<'a>); + +impl Parser for LibeiParser<'_> { + type Value = Libei; + type Error = LibeiParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let enable_socket = ext.extract(recover(opt(bol("enable-socket"))))?; + Ok(Libei { + enable_socket: enable_socket.despan(), + }) + } +} diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index da6b25a3..fa7a8b11 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -17,7 +17,8 @@ use { get_workspace, input::{ capability::CAP_SWITCH, get_seat, input_devices, on_input_device_removed, - on_new_input_device, FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, + on_new_input_device, set_libei_socket_enabled, FocusFollowsMouseMode, InputDevice, + Seat, SwitchEvent, }, is_reload, keyboard::{Keymap, ModifiedKeySym}, @@ -1047,6 +1048,7 @@ fn load_config(initial_load: bool, persistent: &Rc) { set_tearing_mode(mode); } } + set_libei_socket_enabled(config.libei.enable_socket.unwrap_or(false)); } fn create_command(exec: &Exec) -> Command { diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 04cf12e7..47228da7 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -585,6 +585,10 @@ "tearing": { "description": "Configures the default tearing settings.\n\nThis can be overwritten for individual outputs.\n\nBy default, the tearing mode is `variant3`.\n\n- Example:\n\n ```toml\n tearing.mode = \"never\"\n ```\n", "$ref": "#/$defs/Tearing" + }, + "libei": { + "description": "Configures the libei settings.\n\n- Example:\n\n ```toml\n libei.enable-socket = true\n ```\n", + "$ref": "#/$defs/Libei" } }, "required": [] @@ -967,6 +971,17 @@ } ] }, + "Libei": { + "description": "Describes libei settings.\n\n- Example:\n\n ```toml\n libei.enable-socket = \"true\"\n ```\n", + "type": "object", + "properties": { + "enable-socket": { + "type": "boolean", + "description": "Enables or disables the unauthenticated libei socket.\n\nEven if the socket is disabled, application can still request access via the portal.\n\nThe default is `false`.\n" + } + }, + "required": [] + }, "LogLevel": { "type": "string", "description": "A log level.", diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 95d5adf4..6ea5b050 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -1142,6 +1142,18 @@ The table has the following fields: The value of this field should be a [Tearing](#types-Tearing). +- `libei` (optional): + + Configures the libei settings. + + - Example: + + ```toml + libei.enable-socket = true + ``` + + The value of this field should be a [Libei](#types-Libei). + ### `Connector` @@ -2045,6 +2057,32 @@ The table has the following fields: The value of this field should be a string. + +### `Libei` + +Describes libei settings. + +- Example: + + ```toml + libei.enable-socket = "true" + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `enable-socket` (optional): + + Enables or disables the unauthenticated libei socket. + + Even if the socket is disabled, application can still request access via the portal. + + The default is `false`. + + The value of this field should be a boolean. + + ### `LogLevel` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 190de9c5..746247ad 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -2228,6 +2228,17 @@ Config: ```toml tearing.mode = "never" ``` + libei: + ref: Libei + required: false + description: | + Configures the libei settings. + + - Example: + + ```toml + libei.enable-socket = true + ``` Idle: @@ -2457,3 +2468,25 @@ TearingMode: description: | Tearing is enabled when a single application is displayed and the application has requested tearing. + + +Libei: + kind: table + description: | + Describes libei settings. + + - Example: + + ```toml + libei.enable-socket = "true" + ``` + fields: + enable-socket: + kind: boolean + required: false + description: | + Enables or disables the unauthenticated libei socket. + + Even if the socket is disabled, application can still request access via the portal. + + The default is `false`. diff --git a/wire-ei/ei_button.txt b/wire-ei/ei_button.txt new file mode 100644 index 00000000..3744d68b --- /dev/null +++ b/wire-ei/ei_button.txt @@ -0,0 +1,16 @@ +request release { +} + +request client_button (sender) { + button: u32, + state: u32, +} + +event destroyed { + serial: u32, +} + +event server_button (receiver) { + button: u32, + state: u32, +} diff --git a/wire-ei/ei_callback.txt b/wire-ei/ei_callback.txt new file mode 100644 index 00000000..27c4ded8 --- /dev/null +++ b/wire-ei/ei_callback.txt @@ -0,0 +1,3 @@ +event done { + callback_data: u64, +} diff --git a/wire-ei/ei_connection.txt b/wire-ei/ei_connection.txt new file mode 100644 index 00000000..f89d9b7f --- /dev/null +++ b/wire-ei/ei_connection.txt @@ -0,0 +1,28 @@ +request sync { + callback: id(ei_callback), + version: u32, +} + +request disconnect { +} + +event disconnected { + last_serial: u32, + reason: u32, + explanation: optstr, +} + +event seat { + seat: id(ei_seat), + version: u32, +} + +event invalid_object { + last_serial: u32, + invalid_id: id(ei_object), +} + +event ping { + ping: id(ei_pingpong), + version: u32, +} diff --git a/wire-ei/ei_device.txt b/wire-ei/ei_device.txt new file mode 100644 index 00000000..bee6824d --- /dev/null +++ b/wire-ei/ei_device.txt @@ -0,0 +1,76 @@ +request release { +} + +request client_start_emulating (sender) { + last_serial: u32, + sequence: u32, +} + +request client_stop_emulating (sender) { + last_serial: u32, +} + +request client_frame (sender) { + last_serial: u32, + timestamp: u64, +} + +event destroyed { + serial: u32, +} + +event name { + name: str, +} + +event device_type { + device_type: u32, +} + +event dimensions { + width: u32, + height: u32, +} + +event region { + offset_x: u32, + offset_y: u32, + width: u32, + hight: u32, + scale: f32, +} + +event interface { + object: id(ei_object), + interface_name: str, + version: u32, +} + +event done { +} + +event resumed { + serial: u32, +} + +event paused { + serial: u32, +} + +event server_start_emulating (receiver) { + serial: u32, + sequence: u32, +} + +event server_stop_emulating (receiver) { + serial: u32, +} + +event server_frame (receiver) { + serial: u32, + timestamp: u64, +} + +event region_mapping_id (since = 2) { + mapping_id: str, +} diff --git a/wire-ei/ei_handshake.txt b/wire-ei/ei_handshake.txt new file mode 100644 index 00000000..403919d5 --- /dev/null +++ b/wire-ei/ei_handshake.txt @@ -0,0 +1,34 @@ +request client_handshake_version { + version: u32, +} + +request finish { +} + +request context_type { + context_type: u32, +} + +request name { + name: str, +} + +request client_interface_version { + name: str, + version: u32, +} + +event server_handshake_version { + version: u32, +} + +event server_interface_version { + name: str, + version: u32, +} + +event connection { + serial: u32, + connection: id(ei_connection), + version: u32, +} diff --git a/wire-ei/ei_keyboard.txt b/wire-ei/ei_keyboard.txt new file mode 100644 index 00000000..e71b0fd1 --- /dev/null +++ b/wire-ei/ei_keyboard.txt @@ -0,0 +1,30 @@ +request release { +} + +request client_key (sender) { + key: u32, + state: u32, +} + +event destroyed { + serial: u32, +} + +event keymap { + keymap_type: u32, + size: u32, + keymap: fd, +} + +event server_key (receiver) { + key: u32, + state: u32, +} + +event modifiers { + serial: u32, + depressed: u32, + locked: u32, + latched: u32, + group: u32, +} diff --git a/wire-ei/ei_pingpong.txt b/wire-ei/ei_pingpong.txt new file mode 100644 index 00000000..aacb4cda --- /dev/null +++ b/wire-ei/ei_pingpong.txt @@ -0,0 +1,3 @@ +request done { + callback_data: u64, +} diff --git a/wire-ei/ei_pointer.txt b/wire-ei/ei_pointer.txt new file mode 100644 index 00000000..91fbbf5e --- /dev/null +++ b/wire-ei/ei_pointer.txt @@ -0,0 +1,16 @@ +request release { +} + +request client_motion_relative (sender) { + x: f32, + y: f32, +} + +event destroyed { + serial: u32, +} + +event server_motion_relative (receiver) { + x: f32, + y: f32, +} diff --git a/wire-ei/ei_pointer_absolute.txt b/wire-ei/ei_pointer_absolute.txt new file mode 100644 index 00000000..ade8a139 --- /dev/null +++ b/wire-ei/ei_pointer_absolute.txt @@ -0,0 +1,16 @@ +request release { +} + +request client_motion_absolute (sender) { + x: f32, + y: f32, +} + +event destroyed { + serial: u32, +} + +event server_motion_absolute (receiver) { + x: f32, + y: f32, +} diff --git a/wire-ei/ei_scroll.txt b/wire-ei/ei_scroll.txt new file mode 100644 index 00000000..91b95fd2 --- /dev/null +++ b/wire-ei/ei_scroll.txt @@ -0,0 +1,38 @@ +request release { +} + +request client_scroll (sender) { + x: f32, + y: f32, +} + +request client_scroll_discrete (sender) { + x: i32, + y: i32, +} + +request client_scroll_stop (sender) { + x: u32, + y: u32, + is_cancel: u32, +} + +event destroyed { + serial: u32, +} + +event server_scroll (receiver) { + x: f32, + y: f32, +} + +event server_scroll_discrete (receiver) { + x: i32, + y: i32, +} + +event server_scroll_stop (receiver) { + x: u32, + y: u32, + is_cancel: u32, +} diff --git a/wire-ei/ei_seat.txt b/wire-ei/ei_seat.txt new file mode 100644 index 00000000..c8a11f0e --- /dev/null +++ b/wire-ei/ei_seat.txt @@ -0,0 +1,27 @@ +request release { +} + +request bind { + capabilities: u64, +} + +event destroyed { + serial: u32, +} + +event name { + name: str, +} + +event capability { + mask: u64, + interface: str, +} + +event done { +} + +event device { + device: id(ei_device), + version: u32, +} diff --git a/wire-ei/ei_touchscreen.txt b/wire-ei/ei_touchscreen.txt new file mode 100644 index 00000000..2bca8286 --- /dev/null +++ b/wire-ei/ei_touchscreen.txt @@ -0,0 +1,38 @@ +request release { +} + +request client_down (sender) { + touchid: u32, + x: f32, + y: f32, +} + +request client_motion (sender) { + touchid: u32, + x: f32, + y: f32, +} + +request client_up (sender) { + touchid: u32, +} + +event destroyed { + serial: u32, +} + +event server_down (receiver) { + touchid: u32, + x: f32, + y: f32, +} + +event server_motion (receiver) { + touchid: u32, + x: f32, + y: f32, +} + +event server_up (receiver) { + touchid: u32, +}