session and seat management daemon
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(seat): finish libseat

r0chd (Jul 12, 2026, 11:17 PM +0200) aa94de3c 2b5396d3

+359 -28
+353 -25
seat/src/lib.rs
··· 1 + mod seat_v1 { 2 + hyprwire::include_protocol!("seat_v1"); 3 + pub use client::*; 4 + } 5 + 6 + use seat_v1::{device, seat, seat_manager}; 7 + use std::collections::HashMap; 8 + use std::os::fd::{AsRawFd, OwnedFd}; 1 9 use std::sync::atomic; 2 10 use std::{ffi, ptr}; 11 + 12 + const SESSIOND_SOCKET: &str = "/run/sessiond.sock"; 3 13 4 14 #[repr(C)] 5 15 pub struct LibseatSeatListener { ··· 12 22 listener: LibseatSeatListener, 13 23 userdata: *mut libc::c_void, 14 24 seat_name: ffi::CString, 25 + client: hyprwire::client::Client, 26 + queue: hyprwire::EventQueue, 27 + seat: seat::Seat, 28 + initialized: bool, 29 + enabled: bool, 30 + devices: HashMap<libc::c_int, DeviceHandle>, 31 + pending_open: Option<PendingOpen>, 15 32 } 16 33 17 34 type LibseatLogFunc = ··· 19 36 20 37 static LOG_LEVEL: atomic::AtomicI32 = atomic::AtomicI32::new(1); 21 38 39 + struct DeviceHandle { 40 + object: device::Device, 41 + _fd: OwnedFd, 42 + } 43 + 44 + enum PendingOpen { 45 + Waiting { 46 + _object: device::Device, 47 + }, 48 + Opened { 49 + object: device::Device, 50 + device_id: libc::c_int, 51 + fd: OwnedFd, 52 + }, 53 + Failed { 54 + errno: libc::c_int, 55 + }, 56 + Revoked, 57 + } 58 + 22 59 fn set_errno(errno: libc::c_int) { 23 60 // SAFETY: libc exposes the thread-local errno location for the current 24 61 // platform. Writing an integer errno value matches the C ABI contract. ··· 27 64 } 28 65 } 29 66 67 + fn errno_from_u32(errno: u32) -> libc::c_int { 68 + match libc::c_int::try_from(errno) { 69 + Ok(errno) if errno > 0 => errno, 70 + _ => libc::EIO, 71 + } 72 + } 73 + 74 + fn errno_from_io(error: &hyprwire::Error) -> libc::c_int { 75 + match error { 76 + hyprwire::Error::Io(error) => error.raw_os_error().unwrap_or(libc::EIO), 77 + hyprwire::Error::ProtocolNotFound => libc::ENOSYS, 78 + hyprwire::Error::VersionOutOfRange { .. } 79 + | hyprwire::Error::VersionNegotiationFailed 80 + | hyprwire::Error::ProtocolViolation(_) 81 + | hyprwire::Error::HandshakeTimeout 82 + | hyprwire::Error::ConnectionClosed => libc::EIO, 83 + } 84 + } 85 + 86 + fn dispatch_once(seat: &mut Libseat, block: bool) -> libc::c_int { 87 + let queue = seat.queue.clone(); 88 + match queue.dispatch_events(seat, block) { 89 + Ok(()) => 1, 90 + Err(hyprwire::Error::ConnectionClosed) if !block => 0, 91 + Err(err) => { 92 + set_errno(errno_from_io(&err)); 93 + -1 94 + } 95 + } 96 + } 97 + 98 + fn dispatch_until<F>(seat: &mut Libseat, mut done: F) -> libc::c_int 99 + where 100 + F: FnMut(&Libseat) -> bool, 101 + { 102 + while !done(seat) { 103 + if dispatch_once(seat, true) < 0 { 104 + return -1; 105 + } 106 + } 107 + 0 108 + } 109 + 110 + fn poll_loop_fd(seat: &Libseat, timeout: libc::c_int) -> libc::c_int { 111 + let mut pollfd = libc::pollfd { 112 + fd: seat.client.extract_loop_fd().as_raw_fd(), 113 + events: libc::POLLIN, 114 + revents: 0, 115 + }; 116 + 117 + // SAFETY: pollfd points to one valid pollfd entry for the duration of the 118 + // call. The fd is borrowed from the live Hyprwire client. 119 + let ready = unsafe { libc::poll(&raw mut pollfd, 1, timeout) }; 120 + if ready < 0 { 121 + return -1; 122 + } 123 + if ready == 0 { 124 + return 0; 125 + } 126 + if pollfd.revents & (libc::POLLERR | libc::POLLHUP | libc::POLLNVAL) != 0 { 127 + set_errno(libc::EIO); 128 + return -1; 129 + } 130 + 1 131 + } 132 + 133 + impl hyprwire::Dispatch<seat_manager::SeatManager> for Libseat { 134 + fn event( 135 + &mut self, 136 + _object: &seat_manager::SeatManager, 137 + event: <seat_manager::SeatManager as hyprwire::Object>::Event<'_>, 138 + ) { 139 + match event {} 140 + } 141 + } 142 + 143 + impl hyprwire::Dispatch<seat::Seat> for Libseat { 144 + fn event(&mut self, _object: &seat::Seat, event: <seat::Seat as hyprwire::Object>::Event<'_>) { 145 + match event { 146 + seat::Event::Enabled => { 147 + self.initialized = true; 148 + self.enabled = true; 149 + if let Some(enable_seat) = self.listener.enable_seat { 150 + let seat = ptr::from_mut::<Self>(self); 151 + // SAFETY: listener function pointers are provided by the C 152 + // caller at open time and are invoked with the live opaque 153 + // seat pointer plus its original userdata. 154 + unsafe { 155 + enable_seat(seat, self.userdata); 156 + } 157 + } 158 + } 159 + seat::Event::Disabled => { 160 + self.initialized = true; 161 + self.enabled = false; 162 + if let Some(disable_seat) = self.listener.disable_seat { 163 + let seat = ptr::from_mut::<Self>(self); 164 + // SAFETY: see the matching enable callback above. 165 + unsafe { 166 + disable_seat(seat, self.userdata); 167 + } 168 + } 169 + } 170 + seat::Event::SessionSwitched { .. } => {} 171 + seat::Event::SeatError { .. } => {} 172 + } 173 + } 174 + } 175 + 176 + impl hyprwire::Dispatch<device::Device> for Libseat { 177 + fn event( 178 + &mut self, 179 + object: &device::Device, 180 + event: <device::Device as hyprwire::Object>::Event<'_>, 181 + ) { 182 + match event { 183 + device::Event::Opened { device_id, fd, .. } => { 184 + let Ok(device_id) = libc::c_int::try_from(device_id) else { 185 + self.pending_open = Some(PendingOpen::Failed { 186 + errno: libc::EOVERFLOW, 187 + }); 188 + return; 189 + }; 190 + self.pending_open = Some(PendingOpen::Opened { 191 + object: object.clone(), 192 + device_id, 193 + fd, 194 + }); 195 + } 196 + device::Event::Failed { errno, .. } => { 197 + self.pending_open = Some(PendingOpen::Failed { 198 + errno: errno_from_u32(errno), 199 + }); 200 + } 201 + device::Event::Revoked => { 202 + if matches!(self.pending_open, Some(PendingOpen::Waiting { .. })) { 203 + self.pending_open = Some(PendingOpen::Revoked); 204 + return; 205 + } 206 + 207 + let revoked = self.devices.iter().find_map(|(device_id, handle)| { 208 + (handle.object == *object).then_some(*device_id) 209 + }); 210 + if let Some(device_id) = revoked { 211 + self.devices.remove(&device_id); 212 + } 213 + } 214 + } 215 + } 216 + } 217 + 30 218 unsafe fn seat_from_ptr<'a>(seat: *mut Libseat) -> Option<&'a mut Libseat> { 31 219 if seat.is_null() { 32 220 set_errno(libc::EINVAL); ··· 59 247 return ptr::null_mut(); 60 248 } 61 249 62 - let _ = userdata; 63 - set_errno(libc::ENOSYS); 64 - ptr::null_mut() 250 + let mut client = match hyprwire::client::Client::connect(SESSIOND_SOCKET) { 251 + Ok(client) => client, 252 + Err(err) => { 253 + set_errno(errno_from_io(&err)); 254 + return ptr::null_mut(); 255 + } 256 + }; 257 + client.add_implementation::<seat_v1::SeatV1Impl>(); 258 + let queue = client.new_event_queue(); 259 + 260 + // SAFETY: static string has no nul byte 261 + let seat_name = unsafe { ffi::CString::new("seat0").unwrap_unchecked() }; 262 + let mut state = OpenState; 263 + if let Err(err) = queue.wait_for_handshake(&mut state) { 264 + set_errno(errno_from_io(&err)); 265 + return ptr::null_mut(); 266 + } 267 + let manager = match client.bind::<seat_manager::SeatManager, OpenState>(&queue, &mut state, 1) { 268 + Ok(manager) => manager, 269 + Err(err) => { 270 + set_errno(errno_from_io(&err)); 271 + return ptr::null_mut(); 272 + } 273 + }; 274 + let Some(seat_object) = manager.send_open_seat::<Libseat>() else { 275 + set_errno(libc::EIO); 276 + return ptr::null_mut(); 277 + }; 278 + 279 + let mut seat = Box::new(Libseat { 280 + listener, 281 + userdata, 282 + seat_name, 283 + client, 284 + queue, 285 + seat: seat_object, 286 + initialized: false, 287 + enabled: false, 288 + devices: HashMap::new(), 289 + pending_open: None, 290 + }); 291 + 292 + if dispatch_until(&mut seat, |seat| seat.initialized) < 0 { 293 + return ptr::null_mut(); 294 + } 295 + 296 + Box::into_raw(seat) 297 + } 298 + 299 + struct OpenState; 300 + 301 + impl hyprwire::Dispatch<seat_manager::SeatManager> for OpenState { 302 + fn event( 303 + &mut self, 304 + _object: &seat_manager::SeatManager, 305 + event: <seat_manager::SeatManager as hyprwire::Object>::Event<'_>, 306 + ) { 307 + match event {} 308 + } 65 309 } 66 310 67 311 #[unsafe(no_mangle)] ··· 70 314 /// `seat` must be null or a pointer returned by `libseat_open_seat` that has 71 315 /// not been closed. 72 316 pub unsafe extern "C" fn libseat_disable_seat(seat: *mut Libseat) -> libc::c_int { 73 - if unsafe { seat_from_ptr(seat) }.is_none() { 317 + let Some(seat) = (unsafe { seat_from_ptr(seat) }) else { 74 318 return -1; 75 - } 76 - 77 - set_errno(libc::ENOSYS); 78 - -1 319 + }; 320 + seat.seat.send_ack_disabled(); 321 + seat.enabled = false; 322 + 0 79 323 } 80 324 81 325 #[unsafe(no_mangle)] ··· 105 349 path: *const libc::c_char, 106 350 fd: *mut libc::c_int, 107 351 ) -> libc::c_int { 108 - if unsafe { seat_from_ptr(seat) }.is_none() || path.is_null() || fd.is_null() { 352 + let Some(seat) = (unsafe { seat_from_ptr(seat) }) else { 353 + return -1; 354 + }; 355 + if path.is_null() || fd.is_null() { 109 356 set_errno(libc::EINVAL); 110 357 return -1; 111 358 } 359 + if !seat.enabled { 360 + set_errno(libc::EACCES); 361 + return -1; 362 + } 112 363 113 - set_errno(libc::ENOSYS); 114 - -1 364 + // SAFETY: path is checked for null above and must be a valid C string per 365 + // the function contract. 366 + let Ok(path) = unsafe { ffi::CStr::from_ptr(path) }.to_str() else { 367 + set_errno(libc::EINVAL); 368 + return -1; 369 + }; 370 + 371 + let Some(object) = seat.seat.send_open_device::<Libseat>(path) else { 372 + set_errno(libc::EIO); 373 + return -1; 374 + }; 375 + seat.pending_open = Some(PendingOpen::Waiting { _object: object }); 376 + 377 + if dispatch_until(seat, |seat| { 378 + !matches!(seat.pending_open, Some(PendingOpen::Waiting { .. })) 379 + }) < 0 380 + { 381 + seat.pending_open = None; 382 + return -1; 383 + } 384 + 385 + match seat.pending_open.take() { 386 + Some(PendingOpen::Opened { 387 + object, 388 + device_id, 389 + fd: device_fd, 390 + }) => { 391 + // SAFETY: fd was checked for null above. 392 + unsafe { 393 + *fd = device_fd.as_raw_fd(); 394 + } 395 + seat.devices.insert( 396 + device_id, 397 + DeviceHandle { 398 + object, 399 + _fd: device_fd, 400 + }, 401 + ); 402 + device_id 403 + } 404 + Some(PendingOpen::Failed { errno }) => { 405 + set_errno(errno); 406 + -1 407 + } 408 + Some(PendingOpen::Revoked | PendingOpen::Waiting { .. }) | None => { 409 + set_errno(libc::EIO); 410 + -1 411 + } 412 + } 115 413 } 116 414 117 415 #[unsafe(no_mangle)] ··· 123 421 seat: *mut Libseat, 124 422 device_id: libc::c_int, 125 423 ) -> libc::c_int { 126 - if unsafe { seat_from_ptr(seat) }.is_none() || device_id < 0 { 424 + let Some(seat) = (unsafe { seat_from_ptr(seat) }) else { 425 + return -1; 426 + }; 427 + if device_id < 0 { 127 428 set_errno(libc::EINVAL); 128 429 return -1; 129 430 } 130 431 131 - set_errno(libc::ENOSYS); 132 - -1 432 + if seat.devices.remove(&device_id).is_some() { 433 + 0 434 + } else { 435 + set_errno(libc::ENOENT); 436 + -1 437 + } 133 438 } 134 439 135 440 #[unsafe(no_mangle)] ··· 153 458 seat: *mut Libseat, 154 459 session: libc::c_int, 155 460 ) -> libc::c_int { 156 - if unsafe { seat_from_ptr(seat) }.is_none() || session <= 0 { 461 + let Some(seat) = (unsafe { seat_from_ptr(seat) }) else { 462 + return -1; 463 + }; 464 + if session <= 0 { 157 465 set_errno(libc::EINVAL); 158 466 return -1; 159 467 } 160 468 161 - set_errno(libc::ENOSYS); 162 - -1 469 + let Ok(session) = u32::try_from(session) else { 470 + set_errno(libc::EINVAL); 471 + return -1; 472 + }; 473 + seat.seat.send_switch_session(session); 474 + 0 163 475 } 164 476 165 477 #[unsafe(no_mangle)] ··· 168 480 /// `seat` must be null or a pointer returned by `libseat_open_seat` that has 169 481 /// not been closed. 170 482 pub unsafe extern "C" fn libseat_get_fd(seat: *mut Libseat) -> libc::c_int { 171 - if unsafe { seat_from_ptr(seat) }.is_none() { 483 + let Some(seat) = (unsafe { seat_from_ptr(seat) }) else { 172 484 return -1; 173 - } 174 - 175 - set_errno(libc::ENOSYS); 176 - -1 485 + }; 486 + seat.client.extract_loop_fd().as_raw_fd() 177 487 } 178 488 179 489 #[unsafe(no_mangle)] ··· 182 492 /// `seat` must be null or a pointer returned by `libseat_open_seat` that has 183 493 /// not been closed. 184 494 pub unsafe extern "C" fn libseat_dispatch(seat: *mut Libseat, timeout: libc::c_int) -> libc::c_int { 185 - if unsafe { seat_from_ptr(seat) }.is_none() || timeout < -1 { 495 + let Some(seat) = (unsafe { seat_from_ptr(seat) }) else { 496 + return -1; 497 + }; 498 + if timeout < -1 { 186 499 set_errno(libc::EINVAL); 187 500 return -1; 188 501 } 189 502 190 - set_errno(libc::ENOSYS); 191 - -1 503 + if timeout == 0 { 504 + let ready = poll_loop_fd(seat, 0); 505 + if ready <= 0 { 506 + return ready; 507 + } 508 + return dispatch_once(seat, false); 509 + } 510 + 511 + if timeout > 0 { 512 + let ready = poll_loop_fd(seat, timeout); 513 + if ready <= 0 { 514 + return ready; 515 + } 516 + return dispatch_once(seat, false); 517 + } 518 + 519 + dispatch_once(seat, true) 192 520 } 193 521 194 522 #[unsafe(no_mangle)]
seat/target/debug/.fingerprint/seat-dcd247189da2035b/dep-lib-seat

This is a binary file and will not be displayed.

seat/target/debug/deps/libseat.so

This is a binary file and will not be displayed.

+5 -2
seat/target/debug/deps/seat.d
··· 1 - /home/r0chd/Projects/sessiond/seat/target/debug/deps/seat.d: src/lib.rs 1 + /home/r0chd/Projects/sessiond/seat/target/debug/deps/seat.d: src/lib.rs /home/r0chd/Projects/sessiond/seat/target/debug/build/seat-f70c7a8c6d8342ea/out/seat_v1.rs 2 2 3 - /home/r0chd/Projects/sessiond/seat/target/debug/deps/libseat.so: src/lib.rs 3 + /home/r0chd/Projects/sessiond/seat/target/debug/deps/libseat.so: src/lib.rs /home/r0chd/Projects/sessiond/seat/target/debug/build/seat-f70c7a8c6d8342ea/out/seat_v1.rs 4 4 5 5 src/lib.rs: 6 + /home/r0chd/Projects/sessiond/seat/target/debug/build/seat-f70c7a8c6d8342ea/out/seat_v1.rs: 7 + 8 + # env-dep:OUT_DIR=/home/r0chd/Projects/sessiond/seat/target/debug/build/seat-f70c7a8c6d8342ea/out
+1 -1
seat/target/debug/libseat.d
··· 1 - /home/r0chd/Projects/sessiond/seat/target/debug/libseat.so: /home/r0chd/Projects/sessiond/seat/../protocols/seat_v1.xml /home/r0chd/Projects/sessiond/seat/build.rs /home/r0chd/Projects/sessiond/seat/src/lib.rs 1 + /home/r0chd/Projects/sessiond/seat/target/debug/libseat.so: /home/r0chd/Projects/sessiond/seat/../protocols/seat_v1.xml /home/r0chd/Projects/sessiond/seat/build.rs /home/r0chd/Projects/sessiond/seat/src/lib.rs /home/r0chd/Projects/sessiond/seat/target/debug/build/seat-f70c7a8c6d8342ea/out/seat_v1.rs
seat/target/debug/libseat.so

This is a binary file and will not be displayed.