diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index d489e85..cc5a2d8 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -29,8 +29,8 @@ use smithay::{ graphics::{CursorBackend, SwapBuffersError}, libinput::{LibinputInputBackend, LibinputSessionInterface}, session::{ - auto::{auto_session_bind, AutoSession}, - notify_multiplexer, AsSessionObserver, Session, SessionNotifier, SessionObserver, + auto::AutoSession, notify_multiplexer, AsSessionObserver, Session, SessionNotifier, + SessionObserver, }, udev::{primary_gpu, UdevBackend, UdevEvent}, }, @@ -209,10 +209,10 @@ pub fn run_udev( anvil_state.process_input_event(event) }) .unwrap(); - let session_event_source = auto_session_bind(notifier, event_loop.handle()) - .map_err(|(e, _)| e) + let session_event_source = event_loop + .handle() + .insert_source(notifier, |(), &mut (), _anvil_state| {}) .unwrap(); - for (dev, path) in udev_backend.device_list() { udev_handler.device_added(dev, path.into()) } @@ -246,7 +246,7 @@ pub fn run_udev( // Cleanup stuff state.window_map.borrow_mut().clear(); - let mut notifier = session_event_source.unbind(); + let mut notifier = event_loop.handle().remove(session_event_source); notifier.unregister(libinput_session_id); notifier.unregister(udev_session_id); diff --git a/src/backend/session/auto.rs b/src/backend/session/auto.rs index 4f541df..9e332d3 100644 --- a/src/backend/session/auto.rs +++ b/src/backend/session/auto.rs @@ -27,17 +27,20 @@ //! //! It is crucial to avoid errors during that state. Examples for object that might be registered //! for notifications are the [`Libinput`](input::Libinput) context or the [`Device`](::backend::drm::Device). +//! +//! The [`AutoSessionNotifier`](::backend::session::auto::AutoSessionNotifier) is to be inserted into +//! a calloop event source to have its events processed. #[cfg(feature = "backend_session_logind")] -use super::logind::{self, logind_session_bind, BoundLogindSession, LogindSession, LogindSessionNotifier}; +use super::logind::{self, LogindSession, LogindSessionNotifier}; use super::{ - direct::{self, direct_session_bind, BoundDirectSession, DirectSession, DirectSessionNotifier}, + direct::{self, DirectSession, DirectSessionNotifier}, AsErrno, Session, SessionNotifier, SessionObserver, }; use nix::fcntl::OFlag; -use std::{cell::RefCell, io::Error as IoError, os::unix::io::RawFd, path::Path, rc::Rc}; +use std::{cell::RefCell, io, os::unix::io::RawFd, path::Path, rc::Rc}; -use calloop::LoopHandle; +use calloop::{EventSource, Poll, Readiness, Token}; /// [`Session`] using the best available interface #[derive(Clone)] @@ -58,19 +61,6 @@ pub enum AutoSessionNotifier { Direct(DirectSessionNotifier), } -/// Bound session that is driven by a [`EventLoop`](calloop::EventLoop). -/// -/// See [`auto_session_bind`] for details. -/// -/// Dropping this object will close the session just like the [`AutoSessionNotifier`]. -pub enum BoundAutoSession { - /// Bound logind session - #[cfg(feature = "backend_session_logind")] - Logind(BoundLogindSession), - /// Bound direct / tty session - Direct(BoundDirectSession), -} - /// Id's used by the [`AutoSessionNotifier`] internally. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct AutoId(AutoIdInternal); @@ -139,26 +129,6 @@ impl AutoSession { } } -/// Bind an [`AutoSessionNotifier`] to an [`EventLoop`](calloop::EventLoop). -/// -/// Allows the [`AutoSessionNotifier`] to listen for incoming signals signalling the session state. -/// If you don't use this function [`AutoSessionNotifier`] will not correctly tell you the -/// session state and call its [`SessionObserver`]s. -pub fn auto_session_bind( - notifier: AutoSessionNotifier, - handle: LoopHandle, -) -> ::std::result::Result { - Ok(match notifier { - #[cfg(feature = "backend_session_logind")] - AutoSessionNotifier::Logind(logind) => BoundAutoSession::Logind( - logind_session_bind(logind, handle).map_err(|(e, n)| (e, AutoSessionNotifier::Logind(n)))?, - ), - AutoSessionNotifier::Direct(direct) => BoundAutoSession::Direct( - direct_session_bind(direct, handle).map_err(|(e, n)| (e, AutoSessionNotifier::Direct(n)))?, - ), - }) -} - impl Session for AutoSession { type Error = Error; @@ -208,10 +178,10 @@ impl SessionNotifier for AutoSessionNotifier { match *self { #[cfg(feature = "backend_session_logind")] AutoSessionNotifier::Logind(ref mut logind) => { - AutoId(AutoIdInternal::Logind(logind.register(signal))) + AutoId(AutoIdInternal::Logind(SessionNotifier::register(logind, signal))) } AutoSessionNotifier::Direct(ref mut direct) => { - AutoId(AutoIdInternal::Direct(direct.register(signal))) + AutoId(AutoIdInternal::Direct(SessionNotifier::register(direct, signal))) } } } @@ -221,10 +191,10 @@ impl SessionNotifier for AutoSessionNotifier { match (self, signal) { #[cfg(feature = "backend_session_logind")] (&mut AutoSessionNotifier::Logind(ref mut logind), AutoId(AutoIdInternal::Logind(signal))) => { - logind.unregister(signal) + SessionNotifier::unregister(logind, signal) } (&mut AutoSessionNotifier::Direct(ref mut direct), AutoId(AutoIdInternal::Direct(signal))) => { - direct.unregister(signal) + SessionNotifier::unregister(direct, signal) } // this pattern is needed when the logind backend is activated _ => unreachable!(), @@ -232,13 +202,43 @@ impl SessionNotifier for AutoSessionNotifier { } } -impl BoundAutoSession { - /// Unbind the session from the [`EventLoop`](calloop::EventLoop) again - pub fn unbind(self) -> AutoSessionNotifier { +impl EventSource for AutoSessionNotifier { + type Event = (); + type Metadata = (); + type Ret = (); + + fn process_events(&mut self, readiness: Readiness, token: Token, callback: F) -> io::Result<()> + where + F: FnMut((), &mut ()), + { match self { #[cfg(feature = "backend_session_logind")] - BoundAutoSession::Logind(logind) => AutoSessionNotifier::Logind(logind.unbind()), - BoundAutoSession::Direct(direct) => AutoSessionNotifier::Direct(direct.unbind()), + AutoSessionNotifier::Logind(s) => s.process_events(readiness, token, callback), + AutoSessionNotifier::Direct(s) => s.process_events(readiness, token, callback), + } + } + + fn register(&mut self, poll: &mut Poll, token: Token) -> io::Result<()> { + match self { + #[cfg(feature = "backend_session_logind")] + AutoSessionNotifier::Logind(s) => EventSource::register(s, poll, token), + AutoSessionNotifier::Direct(s) => EventSource::register(s, poll, token), + } + } + + fn reregister(&mut self, poll: &mut Poll, token: Token) -> io::Result<()> { + match self { + #[cfg(feature = "backend_session_logind")] + AutoSessionNotifier::Logind(s) => EventSource::reregister(s, poll, token), + AutoSessionNotifier::Direct(s) => EventSource::reregister(s, poll, token), + } + } + + fn unregister(&mut self, poll: &mut Poll) -> io::Result<()> { + match self { + #[cfg(feature = "backend_session_logind")] + AutoSessionNotifier::Logind(s) => EventSource::unregister(s, poll), + AutoSessionNotifier::Direct(s) => EventSource::unregister(s, poll), } } } diff --git a/src/backend/session/dbus/logind.rs b/src/backend/session/dbus/logind.rs index 6473c25..7a87c3c 100644 --- a/src/backend/session/dbus/logind.rs +++ b/src/backend/session/dbus/logind.rs @@ -30,11 +30,13 @@ //! //! It is crucial to avoid errors during that state. Examples for object that might be registered //! for notifications are the [`Libinput`](input::Libinput) context or the [`Device`](::backend::drm::Device). +//! +//! The [`LogindSessionNotifier`](::backend::session::dbus::logind::LogindSessionNotifier) is to be inserted into +//! a calloop event source to have its events processed. use crate::backend::session::{AsErrno, Session, SessionNotifier, SessionObserver}; use dbus::{ arg::{messageitem::MessageItem, OwnedFd}, - ffidisp::{BusType, Connection, ConnectionItem, Watch, WatchEvent}, strings::{BusName, Interface, Member, Path as DbusPath}, Message, }; @@ -52,14 +54,13 @@ use std::{ }; use systemd::login; -use calloop::{ - generic::{Fd, Generic}, - InsertError, Interest, LoopHandle, Readiness, Source, -}; +use calloop::{EventSource, Poll, Readiness, Token}; + +use super::DBusConnection; struct LogindSessionImpl { session_id: String, - conn: RefCell, + conn: RefCell, session_path: DbusPath<'static>, active: AtomicBool, signals: RefCell>>>, @@ -95,7 +96,7 @@ impl LogindSession { let vt = login::get_vt(session_id.clone()).ok(); // Create dbus connection - let conn = Connection::get_private(BusType::System).map_err(Error::FailedDbusConnection)?; + let conn = DBusConnection::new_system().map_err(Error::FailedDbusConnection)?; // and get the session path let session_path = LogindSessionImpl::blocking_call( &conn, @@ -202,7 +203,7 @@ impl LogindSessionNotifier { impl LogindSessionImpl { fn blocking_call<'d, 'p, 'i, 'm, D, P, I, M>( - conn: &Connection, + conn: &DBusConnection, destination: D, path: P, interface: I, @@ -226,15 +227,16 @@ impl LogindSessionImpl { message.append_items(&arguments) }; - let mut message = - conn.send_with_reply_and_block(message, 1000) - .map_err(|source| Error::FailedToSendDbusCall { - bus: destination.clone(), - path: path.clone(), - interface: interface.clone(), - member: method.clone(), - source, - })?; + let mut message = conn + .channel() + .send_with_reply_and_block(message, std::time::Duration::from_millis(1000)) + .map_err(|source| Error::FailedToSendDbusCall { + bus: destination.clone(), + path: path.clone(), + interface: interface.clone(), + member: method.clone(), + source, + })?; match message.as_result() { Ok(_) => Ok(message), @@ -248,101 +250,100 @@ impl LogindSessionImpl { } } - fn handle_signals(&self, signals: I) -> Result<(), Error> - where - I: IntoIterator, - { - for item in signals { - let message = if let ConnectionItem::Signal(ref s) = item { - s - } else { - continue; - }; - if &*message.interface().unwrap() == "org.freedesktop.login1.Manager" - && &*message.member().unwrap() == "SessionRemoved" - && message.get1::().unwrap() == self.session_id - { - error!(self.logger, "Session got closed by logind"); - //Ok... now what? - //This session will never live again, but the user maybe has other sessions open - //So lets just put it to sleep.. forever - for signal in &mut *self.signals.borrow_mut() { - if let Some(ref mut signal) = signal { - signal.pause(None); - } + fn handle_message(&self, message: dbus::Message) -> Result<(), Error> { + if &*message.interface().unwrap() == "org.freedesktop.login1.Manager" + && &*message.member().unwrap() == "SessionRemoved" + && message.get1::().unwrap() == self.session_id + { + error!(self.logger, "Session got closed by logind"); + //Ok... now what? + //This session will never live again, but the user maybe has other sessions open + //So lets just put it to sleep.. forever + for signal in &mut *self.signals.borrow_mut() { + if let Some(ref mut signal) = signal { + signal.pause(None); } - self.active.store(false, Ordering::SeqCst); - warn!(self.logger, "Session is now considered inactive"); - } else if &*message.interface().unwrap() == "org.freedesktop.login1.Session" { - if &*message.member().unwrap() == "PauseDevice" { - let (major, minor, pause_type) = message.get3::(); - let major = major.ok_or(Error::UnexpectedMethodReturn)?; - let minor = minor.ok_or(Error::UnexpectedMethodReturn)?; - // From https://www.freedesktop.org/wiki/Software/systemd/logind/: - // `force` means the device got paused by logind already and this is only an - // asynchronous notification. - // `pause` means logind tries to pause the device and grants you limited amount - // of time to pause it. You must respond to this via PauseDeviceComplete(). - // This synchronous pausing-mechanism is used for backwards-compatibility to VTs - // and logind is **free to not make use of it**. - // It is also free to send a forced PauseDevice if you don't respond in a timely manner - // (or for any other reason). - let pause_type = pause_type.ok_or(Error::UnexpectedMethodReturn)?; - debug!( - self.logger, - "Request of type \"{}\" to close device ({},{})", pause_type, major, minor - ); + } + self.active.store(false, Ordering::SeqCst); + warn!(self.logger, "Session is now considered inactive"); + } else if &*message.interface().unwrap() == "org.freedesktop.login1.Session" { + if &*message.member().unwrap() == "PauseDevice" { + let (major, minor, pause_type) = message.get3::(); + let major = major.ok_or(Error::UnexpectedMethodReturn)?; + let minor = minor.ok_or(Error::UnexpectedMethodReturn)?; + // From https://www.freedesktop.org/wiki/Software/systemd/logind/: + // `force` means the device got paused by logind already and this is only an + // asynchronous notification. + // `pause` means logind tries to pause the device and grants you limited amount + // of time to pause it. You must respond to this via PauseDeviceComplete(). + // This synchronous pausing-mechanism is used for backwards-compatibility to VTs + // and logind is **free to not make use of it**. + // It is also free to send a forced PauseDevice if you don't respond in a timely manner + // (or for any other reason). + let pause_type = pause_type.ok_or(Error::UnexpectedMethodReturn)?; + debug!( + self.logger, + "Request of type \"{}\" to close device ({},{})", pause_type, major, minor + ); - // gone means the device was unplugged from the system and you will no longer get any - // notifications about it. - // This is handled via udev and is not part of our session api. - if pause_type != "gone" { - for signal in &mut *self.signals.borrow_mut() { - if let Some(ref mut signal) = signal { - signal.pause(Some((major, minor))); - } - } - } - // the other possible types are "force" or "gone" (unplugged), - // both expect no acknowledgement (note even this is not *really* necessary, - // logind would just timeout and send a "force" event. There is no way to - // keep the device.) - if pause_type == "pause" { - LogindSessionImpl::blocking_call( - &*self.conn.borrow(), - "org.freedesktop.login1", - self.session_path.clone(), - "org.freedesktop.login1.Session", - "PauseDeviceComplete", - Some(vec![major.into(), minor.into()]), - )?; - } - } else if &*message.member().unwrap() == "ResumeDevice" { - let (major, minor, fd) = message.get3::(); - let major = major.ok_or(Error::UnexpectedMethodReturn)?; - let minor = minor.ok_or(Error::UnexpectedMethodReturn)?; - let fd = fd.ok_or(Error::UnexpectedMethodReturn)?.into_fd(); - debug!(self.logger, "Reactivating device ({},{})", major, minor); + // gone means the device was unplugged from the system and you will no longer get any + // notifications about it. + // This is handled via udev and is not part of our session api. + if pause_type != "gone" { for signal in &mut *self.signals.borrow_mut() { if let Some(ref mut signal) = signal { - signal.activate(Some((major, minor, Some(fd)))); + signal.pause(Some((major, minor))); } } } - } else if &*message.interface().unwrap() == "org.freedesktop.DBus.Properties" - && &*message.member().unwrap() == "PropertiesChanged" - { - use dbus::arg::{Array, Dict, Get, Iter, Variant}; - - let (_, changed, _) = - message.get3::>, Iter<'_>>, Array<'_, String, Iter<'_>>>(); - let mut changed = changed.ok_or(Error::UnexpectedMethodReturn)?; - if let Some((_, mut value)) = changed.find(|&(ref key, _)| &*key == "Active") { - if let Some(active) = Get::get(&mut value.0) { - self.active.store(active, Ordering::SeqCst); + // the other possible types are "force" or "gone" (unplugged), + // both expect no acknowledgement (note even this is not *really* necessary, + // logind would just timeout and send a "force" event. There is no way to + // keep the device.) + if pause_type == "pause" { + LogindSessionImpl::blocking_call( + &*self.conn.borrow(), + "org.freedesktop.login1", + self.session_path.clone(), + "org.freedesktop.login1.Session", + "PauseDeviceComplete", + Some(vec![major.into(), minor.into()]), + )?; + } + } else if &*message.member().unwrap() == "ResumeDevice" { + let (major, minor, fd) = message.get3::(); + let major = major.ok_or(Error::UnexpectedMethodReturn)?; + let minor = minor.ok_or(Error::UnexpectedMethodReturn)?; + let fd = fd.ok_or(Error::UnexpectedMethodReturn)?.into_fd(); + debug!(self.logger, "Reactivating device ({},{})", major, minor); + for signal in &mut *self.signals.borrow_mut() { + if let Some(ref mut signal) = signal { + signal.activate(Some((major, minor, Some(fd)))); } } } + } else if &*message.interface().unwrap() == "org.freedesktop.DBus.Properties" + && &*message.member().unwrap() == "PropertiesChanged" + { + use dbus::arg::{Array, Dict, Get, Iter, Variant}; + + let (_, changed, _) = message + .get3::>, Iter<'_>>, Array<'_, String, Iter<'_>>>(); + let mut changed = changed.ok_or(Error::UnexpectedMethodReturn)?; + if let Some((_, mut value)) = changed.find(|&(ref key, _)| &*key == "Active") { + if let Some(active) = Get::get(&mut value.0) { + self.active.store(active, Ordering::SeqCst); + } + } + } else { + // Handle default replies if necessary + if let Some(reply) = dbus::channel::default_reply(&message) { + self.conn + .borrow() + .channel() + .send(reply) + .map_err(|()| Error::SessionLost)?; + } } Ok(()) } @@ -439,78 +440,6 @@ impl SessionNotifier for LogindSessionNotifier { } } -/// Bound logind session that is driven by the [`EventLoop`](calloop::EventLoop). -/// -/// See [`logind_session_bind`] for details. -/// -/// Dropping this object will close the logind session just like the [`LogindSessionNotifier`]. -pub struct BoundLogindSession { - notifier: LogindSessionNotifier, - _watches: Vec, - sources: Vec>>, - kill_source: Box>)>, -} - -/// Bind a [`LogindSessionNotifier`] to an [`EventLoop`](calloop::EventLoop). -/// -/// Allows the [`LogindSessionNotifier`] to listen for incoming signals signalling the session state. -/// If you don't use this function [`LogindSessionNotifier`] will not correctly tell you the logind -/// session state and call it's [`SessionObserver`]s. -pub fn logind_session_bind( - notifier: LogindSessionNotifier, - handle: LoopHandle, -) -> ::std::result::Result { - let watches = notifier.internal.conn.borrow().watch_fds(); - - let internal_for_error = notifier.internal.clone(); - let sources = watches - .clone() - .into_iter() - .filter_map(|watch| { - let interest = match (watch.writable(), watch.readable()) { - (true, true) => Interest::Both, - (true, false) => Interest::Writable, - (false, true) => Interest::Readable, - (false, false) => return None, - }; - let source = Generic::from_fd(watch.fd(), interest, calloop::Mode::Level); - let source = handle.insert_source(source, { - let mut notifier = notifier.clone(); - move |readiness, fd, _| { - notifier.event(readiness, fd.0); - Ok(()) - } - }); - Some(source) - }) - .collect::<::std::result::Result>>, InsertError>>>() - .map_err(|err| { - ( - err.into(), - LogindSessionNotifier { - internal: internal_for_error, - }, - ) - })?; - - Ok(BoundLogindSession { - notifier, - _watches: watches, - sources, - kill_source: Box::new(move |source| handle.kill(source)), - }) -} - -impl BoundLogindSession { - /// Unbind the logind session from the [`EventLoop`](calloop::EventLoop) - pub fn unbind(self) -> LogindSessionNotifier { - for source in self.sources { - (self.kill_source)(source); - } - self.notifier - } -} - impl Drop for LogindSessionNotifier { fn drop(&mut self) { info!(self.internal.logger, "Closing logind session"); @@ -526,24 +455,42 @@ impl Drop for LogindSessionNotifier { } } -impl LogindSessionNotifier { - fn event(&mut self, readiness: Readiness, fd: RawFd) { - let conn = self.internal.conn.borrow(); - let items = conn.watch_handle( - fd, - if readiness.readable && readiness.writable { - WatchEvent::Readable as u32 | WatchEvent::Writable as u32 - } else if readiness.readable { - WatchEvent::Readable as u32 - } else if readiness.writable { - WatchEvent::Writable as u32 - } else { - return; - }, - ); - if let Err(err) = self.internal.handle_signals(items) { - error!(self.internal.logger, "Error handling dbus signals: {}", err); +impl EventSource for LogindSessionNotifier { + type Event = (); + type Metadata = (); + type Ret = (); + + fn process_events(&mut self, readiness: Readiness, token: Token, _: F) -> std::io::Result<()> + where + F: FnMut((), &mut ()), + { + // Accumulate the messages, and then process them, as we can't keep the borrow on the `DBusConnection` + // while processing the messages + let mut messages = Vec::new(); + self.internal + .conn + .borrow_mut() + .process_events(readiness, token, |msg, _| messages.push(msg))?; + + for msg in messages { + if let Err(err) = self.internal.handle_message(msg) { + error!(self.internal.logger, "Error handling dbus messages: {}", err); + } } + + Ok(()) + } + + fn register(&mut self, poll: &mut Poll, token: Token) -> std::io::Result<()> { + self.internal.conn.borrow_mut().register(poll, token) + } + + fn reregister(&mut self, poll: &mut Poll, token: Token) -> std::io::Result<()> { + self.internal.conn.borrow_mut().reregister(poll, token) + } + + fn unregister(&mut self, poll: &mut Poll) -> std::io::Result<()> { + self.internal.conn.borrow_mut().unregister(poll) } } diff --git a/src/backend/session/dbus/mod.rs b/src/backend/session/dbus/mod.rs index cc0676f..6525bac 100644 --- a/src/backend/session/dbus/mod.rs +++ b/src/backend/session/dbus/mod.rs @@ -1,2 +1,116 @@ +use std::io; + +use calloop::{EventSource, Interest, Mode, Poll, Readiness, Token}; + +use dbus::{ + blocking::LocalConnection, + channel::{BusType, Channel, Watch}, + Message, +}; + #[cfg(feature = "backend_session_logind")] pub mod logind; + +/// An internal wrapper for handling a DBus connection +/// +/// It acts as a calloop event source to dispatch the DBus events +pub(crate) struct DBusConnection { + cx: LocalConnection, + current_watch: Watch, +} + +impl DBusConnection { + pub fn new_system() -> Result { + let mut chan = Channel::get_private(BusType::System)?; + chan.set_watch_enabled(true); + Ok(DBusConnection { + cx: chan.into(), + current_watch: Watch { + fd: -1, + read: false, + write: false, + }, + }) + } + + pub fn add_match(&self, match_str: &str) -> Result<(), dbus::Error> { + self.cx.add_match_no_cb(match_str) + } + + pub fn channel(&self) -> &Channel { + self.cx.channel() + } +} + +impl EventSource for DBusConnection { + type Event = Message; + type Metadata = DBusConnection; + type Ret = (); + + fn process_events(&mut self, _: Readiness, _: Token, mut callback: F) -> io::Result<()> + where + F: FnMut(Message, &mut DBusConnection) -> (), + { + self.cx + .channel() + .read_write(Some(std::time::Duration::from_millis(0))) + .map_err(|()| io::Error::new(io::ErrorKind::NotConnected, "DBus connection is closed"))?; + while let Some(message) = self.cx.channel().pop_message() { + callback(message, self); + } + self.cx.channel().flush(); + Ok(()) + } + + fn register(&mut self, poll: &mut Poll, token: Token) -> io::Result<()> { + if self.current_watch.read || self.current_watch.write { + return Err(io::Error::new( + io::ErrorKind::AlreadyExists, + "DBus session already registered to calloop", + )); + } + // reregister handles all the watch logic + self.reregister(poll, token) + } + + fn reregister(&mut self, poll: &mut Poll, token: Token) -> io::Result<()> { + let new_watch = self.cx.channel().watch(); + let new_interest = match (new_watch.read, new_watch.write) { + (true, true) => Some(Interest::Both), + (true, false) => Some(Interest::Readable), + (false, true) => Some(Interest::Writable), + (false, false) => None, + }; + if new_watch.fd != self.current_watch.fd { + // remove the previous fd + if self.current_watch.read || self.current_watch.write { + poll.unregister(self.current_watch.fd)?; + } + // insert the new one + if let Some(interest) = new_interest { + poll.register(new_watch.fd, interest, Mode::Level, token)?; + } + } else { + // update the registration + if let Some(interest) = new_interest { + poll.reregister(self.current_watch.fd, interest, Mode::Level, token)?; + } else { + poll.unregister(self.current_watch.fd)?; + } + } + self.current_watch = new_watch; + Ok(()) + } + + fn unregister(&mut self, poll: &mut Poll) -> io::Result<()> { + if self.current_watch.read || self.current_watch.write { + poll.unregister(self.current_watch.fd)?; + } + self.current_watch = Watch { + fd: -1, + read: false, + write: false, + }; + Ok(()) + } +} diff --git a/src/backend/session/direct.rs b/src/backend/session/direct.rs index 2bc94c4..9d270c9 100644 --- a/src/backend/session/direct.rs +++ b/src/backend/session/direct.rs @@ -41,12 +41,12 @@ //! //! It is crucial to avoid errors during that state. Examples for object that might be registered //! for notifications are the [`Libinput`](input::Libinput) context or the [`Device`](::backend::drm::Device). +//! +//! The [`DirectSessionNotifier`](::backend::session::direct::DirectSessionNotifier) is to be inserted into +//! a calloop event source to have its events processed. use super::{AsErrno, Session, SessionNotifier, SessionObserver}; -use calloop::{ - signals::{Signal, Signals}, - LoopHandle, Source, -}; +use calloop::signals::{Signal, Signals}; use nix::{ fcntl::{self, open, OFlag}, libc::c_int, @@ -55,11 +55,8 @@ use nix::{ Error as NixError, Result as NixResult, }; use std::{ - cell::RefCell, - io::Error as IoError, os::unix::io::RawFd, path::Path, - rc::Rc, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -158,6 +155,7 @@ pub struct DirectSessionNotifier { signals: Vec>>, signal: Signal, logger: ::slog::Logger, + source: Option, } impl DirectSession { @@ -201,6 +199,7 @@ impl DirectSession { signals: Vec::new(), signal, logger: logger.new(o!("vt" => format!("{}", vt), "component" => "session_notifier")), + source: None, }, )), Err(err) => { @@ -390,64 +389,62 @@ impl DirectSessionNotifier { } } -/// Bound logind session that is driven by the [`EventLoop`](calloop::EventLoop). -/// -/// See [`direct_session_bind`] for details. -pub struct BoundDirectSession { - source: Source, - notifier: Rc>, - kill_source: Box)>, -} +impl calloop::EventSource for DirectSessionNotifier { + type Event = (); + type Metadata = (); + type Ret = (); -impl BoundDirectSession { - /// Unbind the direct session from the [`EventLoop`](calloop::EventLoop) - pub fn unbind(self) -> DirectSessionNotifier { - let BoundDirectSession { - source, - notifier, - kill_source, - } = self; - kill_source(source); - Rc::try_unwrap(notifier) - .map(RefCell::into_inner) - .unwrap_or_else(|_| panic!("Notifier should have been freed from the event loop!")) + fn process_events( + &mut self, + readiness: calloop::Readiness, + token: calloop::Token, + _: F, + ) -> std::io::Result<()> + where + F: FnMut((), &mut ()), + { + let mut source = self.source.take(); + if let Some(ref mut source) = source { + source.process_events(readiness, token, |_, _| self.signal_received())?; + } + self.source = source; + Ok(()) } -} -/// Bind a [`DirectSessionNotifier`] to an [`EventLoop`](calloop::EventLoop). -/// -/// Allows the [`DirectSessionNotifier`] to listen for incoming signals signalling the session state. -/// If you don't use this function [`DirectSessionNotifier`] will not correctly tell you the current -/// session state and call it's [`SessionObserver`]s. -pub fn direct_session_bind( - notifier: DirectSessionNotifier, - handle: LoopHandle, -) -> ::std::result::Result { - let signal = notifier.signal; - let source = match Signals::new(&[signal]) { - Ok(s) => s, - Err(e) => return Err((e, notifier)), - }; - let notifier = Rc::new(RefCell::new(notifier)); - let fail_notifier = notifier.clone(); - let source = handle - .insert_source(source, { - let notifier = notifier.clone(); - move |_, _, _| notifier.borrow_mut().signal_received() - }) - .map_err(move |e| { - // the backend in the closure should already have been dropped - let notifier = Rc::try_unwrap(fail_notifier) - .unwrap_or_else(|_| unreachable!()) - .into_inner(); - (e.into(), notifier) - })?; - let kill_source = Box::new(move |source| handle.kill(source)); - Ok(BoundDirectSession { - source, - notifier, - kill_source, - }) + fn register(&mut self, poll: &mut calloop::Poll, token: calloop::Token) -> std::io::Result<()> { + if self.source.is_some() { + return Err(std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + "This DirectSessionNotifier is already registered.", + )); + } + let mut source = Signals::new(&[self.signal])?; + source.register(poll, token)?; + self.source = Some(source); + Ok(()) + } + + fn reregister(&mut self, poll: &mut calloop::Poll, token: calloop::Token) -> std::io::Result<()> { + if let Some(ref mut source) = self.source { + source.reregister(poll, token) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "This DirectSessionNotifier is not currently registered.", + )) + } + } + + fn unregister(&mut self, poll: &mut calloop::Poll) -> std::io::Result<()> { + if let Some(mut source) = self.source.take() { + source.unregister(poll) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "This DirectSessionNotifier is not currently registered.", + )) + } + } } /// Errors related to direct/tty sessions