Merge branch 'event' into 'master'

Decouple event handling from concrete logic

See merge request World/Phosh/squeekboard!581
This commit is contained in:
dcz
2022-12-06 12:14:17 +00:00
16 changed files with 220 additions and 144 deletions

View File

@ -53,7 +53,7 @@ typedef struct _EekGtkKeyboardPrivate
struct squeek_state_manager *state_manager; // shared reference struct squeek_state_manager *state_manager; // shared reference
struct submission *submission; // unowned reference struct submission *submission; // unowned reference
LevelKeyboard *keyboard; // unowned reference; it's kept in server-context Layout *keyboard; // unowned reference; it's kept in server-context
GdkEventSequence *sequence; // unowned reference GdkEventSequence *sequence; // unowned reference
LfbEvent *event; LfbEvent *event;

View File

@ -86,15 +86,15 @@ struct keymap squeek_key_map_from_str(const char *keymap_str) {
return km; return km;
} }
void level_keyboard_free(LevelKeyboard *self) { void layout_free(Layout *self) {
squeek_layout_free(self->layout); squeek_layout_free(self->layout);
g_free(self); g_free(self);
} }
LevelKeyboard* Layout*
level_keyboard_new (char *style_name, struct squeek_layout *layout) layout_new (char *style_name, struct squeek_layout *layout)
{ {
LevelKeyboard *keyboard = g_new0(LevelKeyboard, 1); Layout *keyboard = g_new0(Layout, 1);
if (!keyboard) { if (!keyboard) {
g_error("Failed to create a keyboard"); g_error("Failed to create a keyboard");
} }

View File

@ -39,17 +39,14 @@ struct keymap {
}; };
/// Keyboard info holder /// Keyboard info holder
struct _LevelKeyboard { struct _Layout {
char style_name[20]; // The name of the css class on layout char style_name[20]; // The name of the css class on layout
struct squeek_layout *layout; // owned struct squeek_layout *layout; // owned
}; };
typedef struct _LevelKeyboard LevelKeyboard;
gchar *eek_keyboard_get_keymap(LevelKeyboard *keyboard); Layout*
layout_new (char *style_name, struct squeek_layout *layout);
LevelKeyboard* void layout_free(Layout *self);
level_keyboard_new (char *style_name, struct squeek_layout *layout);
void level_keyboard_free(LevelKeyboard *self);
G_END_DECLS G_END_DECLS
#endif /* EEK_KEYBOARD_H */ #endif /* EEK_KEYBOARD_H */

View File

@ -206,7 +206,7 @@ eek_renderer_render_keyboard (EekRenderer *self,
struct render_geometry geometry, struct render_geometry geometry,
struct submission *submission, struct submission *submission,
cairo_t *cr, cairo_t *cr,
LevelKeyboard *keyboard) Layout *keyboard)
{ {
g_return_if_fail (geometry.allocation_width > 0.0); g_return_if_fail (geometry.allocation_width > 0.0);
g_return_if_fail (geometry.allocation_height > 0.0); g_return_if_fail (geometry.allocation_height > 0.0);
@ -316,7 +316,7 @@ renderer_init (EekRenderer *self)
} }
EekRenderer * EekRenderer *
eek_renderer_new (LevelKeyboard *keyboard, eek_renderer_new (Layout *keyboard,
PangoContext *pcontext) PangoContext *pcontext)
{ {
EekRenderer *renderer = calloc(1, sizeof(EekRenderer)); EekRenderer *renderer = calloc(1, sizeof(EekRenderer));

View File

@ -58,7 +58,7 @@ struct render_geometry {
}; };
GType eek_renderer_get_type (void) G_GNUC_CONST; GType eek_renderer_get_type (void) G_GNUC_CONST;
EekRenderer *eek_renderer_new (LevelKeyboard *keyboard, EekRenderer *eek_renderer_new (Layout *keyboard,
PangoContext *pcontext); PangoContext *pcontext);
void eek_renderer_set_scale_factor (EekRenderer *renderer, void eek_renderer_set_scale_factor (EekRenderer *renderer,
gint scale); gint scale);
@ -68,7 +68,7 @@ cairo_surface_t *eek_renderer_get_icon_surface(const gchar *icon_name,
gint scale); gint scale);
void eek_renderer_render_keyboard (EekRenderer *renderer, struct render_geometry geometry, struct submission *submission, void eek_renderer_render_keyboard (EekRenderer *renderer, struct render_geometry geometry, struct submission *submission,
cairo_t *cr, LevelKeyboard *keyboard); cairo_t *cr, Layout *keyboard);
void void
eek_renderer_free (EekRenderer *self); eek_renderer_free (EekRenderer *self);

View File

@ -39,7 +39,7 @@ typedef struct _EekBounds EekBounds;
typedef struct _EekboardContextService EekboardContextService; typedef struct _EekboardContextService EekboardContextService;
typedef struct _ServerContextService ServerContextService; typedef struct _ServerContextService ServerContextService;
typedef struct _LevelKeyboard LevelKeyboard; typedef struct _Layout Layout;
/** /**
* EekPoint: * EekPoint:

View File

@ -57,7 +57,7 @@ struct _EekboardContextService {
GObject parent; GObject parent;
struct squeek_state_manager *state_manager; // shared reference struct squeek_state_manager *state_manager; // shared reference
LevelKeyboard *keyboard; // currently used keyboard Layout *keyboard; // currently used keyboard
GSettings *settings; // Owned reference GSettings *settings; // Owned reference
/// Needed for keymap changes after keyboard updates. /// Needed for keymap changes after keyboard updates.
@ -127,9 +127,9 @@ settings_get_layout(GSettings *settings, char **type, char **layout)
} }
void eekboard_context_service_set_layout(EekboardContextService *context, char *style_name, struct squeek_layout *layout, uint32_t timestamp) { void eekboard_context_service_set_layout(EekboardContextService *context, char *style_name, struct squeek_layout *layout, uint32_t timestamp) {
LevelKeyboard *keyboard = level_keyboard_new(style_name, layout); Layout *keyboard = layout_new(style_name, layout);
// set as current // set as current
LevelKeyboard *previous_keyboard = context->keyboard; Layout *previous_keyboard = context->keyboard;
context->keyboard = keyboard; context->keyboard = keyboard;
// Update the keymap if necessary. // Update the keymap if necessary.
// TODO: Update submission on change event // TODO: Update submission on change event
@ -142,7 +142,7 @@ void eekboard_context_service_set_layout(EekboardContextService *context, char *
// replacing the keyboard above will cause the previous keyboard to get destroyed from the UI side (eek_gtk_keyboard_dispose) // replacing the keyboard above will cause the previous keyboard to get destroyed from the UI side (eek_gtk_keyboard_dispose)
if (previous_keyboard) { if (previous_keyboard) {
level_keyboard_free(previous_keyboard); layout_free(previous_keyboard);
} }
} }
@ -264,7 +264,7 @@ eekboard_context_service_destroy (EekboardContextService *context)
* Get keyboard currently active in @context. * Get keyboard currently active in @context.
* Returns: (transfer none): an #EekKeyboard * Returns: (transfer none): an #EekKeyboard
*/ */
LevelKeyboard * Layout *
eekboard_context_service_get_keyboard (EekboardContextService *context) eekboard_context_service_get_keyboard (EekboardContextService *context)
{ {
return context->keyboard; return context->keyboard;

View File

@ -41,10 +41,10 @@ G_DECLARE_FINAL_TYPE(EekboardContextService, eekboard_context_service, EEKBOARD,
EekboardContextService *eekboard_context_service_new(struct squeek_state_manager *state_manager); EekboardContextService *eekboard_context_service_new(struct squeek_state_manager *state_manager);
void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission); void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission);
void eekboard_context_service_destroy (EekboardContextService *context); void eekboard_context_service_destroy (EekboardContextService *context);
LevelKeyboard *eekboard_context_service_get_keyboard(EekboardContextService *context); Layout *eekboard_context_service_get_keyboard(EekboardContextService *context);
void eekboard_context_service_set_keymap(EekboardContextService *context, void eekboard_context_service_set_keymap(EekboardContextService *context,
const LevelKeyboard *keyboard); const Layout *keyboard);
G_END_DECLS G_END_DECLS
#endif /* EEKBOARD_CONTEXT_SERVICE_H */ #endif /* EEKBOARD_CONTEXT_SERVICE_H */

View File

@ -6,7 +6,7 @@
use std::thread; use std::thread;
use zbus::{Connection, ObjectServer, dbus_interface, fdo}; use zbus::{Connection, ObjectServer, dbus_interface, fdo};
use crate::event_loop; use crate::main;
use crate::state; use crate::state;
@ -15,7 +15,7 @@ use std::convert::TryInto;
/// Accepts commands controlling the debug mode /// Accepts commands controlling the debug mode
struct Manager { struct Manager {
sender: event_loop::driver::Threaded, sender: main::EventLoop,
enabled: bool, enabled: bool,
} }
@ -54,7 +54,7 @@ fn start(mgr: Manager) -> Result<(), Box<dyn std::error::Error>> {
} }
} }
pub fn init(sender: event_loop::driver::Threaded) { pub fn init(sender: main::EventLoop) {
let mgr = Manager { let mgr = Manager {
sender, sender,
enabled: false, enabled: false,

View File

@ -18,21 +18,22 @@
use crate::event_loop; use crate::event_loop;
use crate::logging; use crate::logging;
use crate::main::Commands;
use crate::state::{ Application, Event };
use glib; use glib;
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use std::time::Instant; use std::time::Instant;
use super::{ActorState, Outcome};
// Traits // Traits
use crate::logging::Warn; use crate::logging::Warn;
use super::Event;
/// Type of the sender that waits for external events type UISender<S> = glib::Sender<
type Sender = mpsc::Sender<Event>; <
/// Type of the sender that waits for internal state changes <S as ActorState>::Outcome as Outcome
type UISender = glib::Sender<Commands>; >::Commands
>;
/// This loop driver spawns a new thread which updates the state in a loop, /// This loop driver spawns a new thread which updates the state in a loop,
/// in response to incoming events. /// in response to incoming events.
@ -43,12 +44,27 @@ type UISender = glib::Sender<Commands>;
// This can/should be abstracted over Event and Commands, // This can/should be abstracted over Event and Commands,
// so that the C call-ins can be thrown away from here and defined near events. // so that the C call-ins can be thrown away from here and defined near events.
#[derive(Clone)] #[derive(Clone)]
pub struct Threaded { pub struct Threaded<S>
thread: Sender, where
S: ActorState + Send,
S::Event: Send,
<S::Outcome as Outcome>::Commands: Send,
{
/// Waits for external events
thread: mpsc::Sender<S::Event>,
} }
impl Threaded { impl<S> Threaded<S>
pub fn new(ui: UISender, initial_state: Application) -> Self { where
// Not sure why this needs 'static. It's already owned.
S: ActorState + Send + 'static,
S::Event: Send,
<S::Outcome as Outcome>::Commands: Send,
{
pub fn new(
ui: UISender<S>,
initial_state: S,
) -> Self {
let (sender, receiver) = mpsc::channel(); let (sender, receiver) = mpsc::channel();
let saved_sender = sender.clone(); let saved_sender = sender.clone();
thread::spawn(move || { thread::spawn(move || {
@ -71,13 +87,16 @@ impl Threaded {
} }
} }
pub fn send(&self, event: Event) -> Result<(), mpsc::SendError<Event>> { pub fn send(&self, event: S::Event) -> Result<(), mpsc::SendError<S::Event>> {
self.thread.send(event) self.thread.send(event)
} }
fn handle_loop_event(loop_sender: &Sender, state: event_loop::State, event: Event, ui: &UISender) fn handle_loop_event(
-> event_loop::State loop_sender: &mpsc::Sender<S::Event>,
{ state: event_loop::State<S>,
event: S::Event,
ui: &UISender<S>,
) -> event_loop::State<S> {
let now = Instant::now(); let now = Instant::now();
let (new_state, commands) = event_loop::handle_event(state.clone(), event, now); let (new_state, commands) = event_loop::handle_event(state.clone(), event, now);
@ -94,79 +113,16 @@ impl Threaded {
new_state new_state
} }
fn schedule_timeout_wake(loop_sender: &Sender, when: Instant) { fn schedule_timeout_wake(
loop_sender: &mpsc::Sender<S::Event>,
when: Instant,
) {
let sender = loop_sender.clone(); let sender = loop_sender.clone();
thread::spawn(move || { thread::spawn(move || {
let now = Instant::now(); let now = Instant::now();
thread::sleep(when - now); thread::sleep(when - now);
sender.send(Event::TimeoutReached(when)) sender.send(S::Event::new_timeout_reached(when))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't wake visibility manager"); .or_warn(&mut logging::Print, logging::Problem::Warning, "Can't wake manager");
}); });
} }
} }
/// For calling in only
mod c {
use super::*;
use crate::state::Presence;
use crate::state::LayoutChoice;
use crate::state::visibility;
use crate::util;
use crate::util::c::Wrapped;
use std::os::raw::c_char;
#[no_mangle]
pub extern "C"
fn squeek_state_send_force_visible(mgr: Wrapped<Threaded>) {
let sender = mgr.clone_ref();
let sender = sender.borrow();
sender.send(Event::Visibility(visibility::Event::ForceVisible))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
#[no_mangle]
pub extern "C"
fn squeek_state_send_force_hidden(sender: Wrapped<Threaded>) {
let sender = sender.clone_ref();
let sender = sender.borrow();
sender.send(Event::Visibility(visibility::Event::ForceHidden))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
#[no_mangle]
pub extern "C"
fn squeek_state_send_keyboard_present(sender: Wrapped<Threaded>, present: u32) {
let sender = sender.clone_ref();
let sender = sender.borrow();
let state =
if present == 0 { Presence::Missing }
else { Presence::Present };
sender.send(Event::PhysicalKeyboard(state))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
#[no_mangle]
pub extern "C"
fn squeek_state_send_layout_set(
sender: Wrapped<Threaded>,
name: *const c_char,
source: *const c_char,
// TODO: use when synthetic events are needed
_timestamp: u32,
) {
let sender = sender.clone_ref();
let sender = sender.borrow();
let string_or_empty = |v| String::from(
util::c::as_str(v)
.unwrap_or(Some(""))
.unwrap_or("")
);
sender
.send(Event::LayoutChoice(LayoutChoice {
name: string_or_empty(&name),
source: string_or_empty(&source).into(),
}))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
}

View File

@ -38,27 +38,49 @@
pub mod driver; pub mod driver;
// This module is tightly coupled to the shape of data passed around in this project.
// That's not a problem as long as there's only one loop.
// They can still be abstracted into Traits,
// and the loop parametrized over them.
use crate::main::Commands;
use crate::state;
use crate::state::Event;
use std::cmp; use std::cmp;
use std::time::{ Duration, Instant }; use std::time::{ Duration, Instant };
/// Carries the incoming data to affect the actor state,
/// plus an event to help schedule timed events.
pub trait Event: Clone {
fn new_timeout_reached(when: Instant) -> Self;
/// Returns the value of the reached timeout, if this event carries the timeout.
fn get_timeout_reached(&self) -> Option<Instant>;
}
/// The externally observable state of the actor.
pub trait Outcome {
type Commands;
/// Returns the instructions to emit in order to change the current visible state to the desired one.
fn get_commands_to_reach(&self, desired: &Self) -> Self::Commands;
}
/// Contains and calculates the intenal state of the actor.
pub trait ActorState: Clone {
type Event: Event;
type Outcome: Outcome;
/// Returns the new internal state after the event gets processed.
fn apply_event(self, e: Self::Event, time: Instant) -> Self;
/// Returns the observable state of the actor given this internal state.
fn get_outcome(&self, time: Instant) -> Self::Outcome;
/// Returns the next wake up to schedule if one is needed.
/// This may be called at any time, so should always return the correct value.
fn get_next_wake(&self, now: Instant) -> Option<Instant>;
}
/// This keeps the state of the tracker loop between iterations /// This keeps the state of the tracker loop between iterations
#[derive(Clone)] #[derive(Clone)]
struct State { struct State<S> {
state: state::Application, state: S,
scheduled_wakeup: Option<Instant>, scheduled_wakeup: Option<Instant>,
last_update: Instant, last_update: Instant,
} }
impl State { impl<S> State<S> {
fn new(initial_state: state::Application, now: Instant) -> Self { fn new(initial_state: S, now: Instant) -> Self {
Self { Self {
state: initial_state, state: initial_state,
scheduled_wakeup: None, scheduled_wakeup: None,
@ -73,11 +95,11 @@ impl State {
/// - determines next scheduled animation wakeup, /// - determines next scheduled animation wakeup,
/// and because this is a pure function, it's easily testable. /// and because this is a pure function, it's easily testable.
/// It returns the new state, and the message to send onwards. /// It returns the new state, and the message to send onwards.
fn handle_event( fn handle_event<S: ActorState>(
mut loop_state: State, mut loop_state: State<S>,
event: Event, event: S::Event,
now: Instant, now: Instant,
) -> (State, Commands) { ) -> (State<S>, <S::Outcome as Outcome>::Commands) {
// Calculate changes to send to the consumer, // Calculate changes to send to the consumer,
// based on publicly visible state. // based on publicly visible state.
// The internal state may change more often than the publicly visible one, // The internal state may change more often than the publicly visible one,
@ -93,8 +115,8 @@ fn handle_event(
.get_commands_to_reach(&new_outcome); .get_commands_to_reach(&new_outcome);
// Timeout events are special: they affect the scheduled timeout. // Timeout events are special: they affect the scheduled timeout.
loop_state.scheduled_wakeup = match event { loop_state.scheduled_wakeup = match event.get_timeout_reached() {
Event::TimeoutReached(when) => { Some(when) => {
if when > now { if when > now {
// Special handling for scheduled events coming in early. // Special handling for scheduled events coming in early.
// Wait at least 10 ms to avoid Zeno's paradox. // Wait at least 10 ms to avoid Zeno's paradox.
@ -112,7 +134,7 @@ fn handle_event(
None None
} }
}, },
_ => loop_state.scheduled_wakeup.clone(), None => loop_state.scheduled_wakeup.clone(),
}; };
// Reschedule timeout if the new state calls for it. // Reschedule timeout if the new state calls for it.
@ -152,6 +174,7 @@ mod test {
use crate::animation; use crate::animation;
use crate::imservice::{ ContentHint, ContentPurpose }; use crate::imservice::{ ContentHint, ContentPurpose };
use crate::panel; use crate::panel;
use crate::state;
use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility }; use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility };
use crate::state::test::application_with_fake_output; use crate::state::test::application_with_fake_output;
@ -162,6 +185,9 @@ mod test {
} }
} }
// TODO: This should only test the scheduling in handle_event.
// This means it should be separated from actual application logic,
// and use a mock state instead.
#[test] #[test]
fn schedule_hide() { fn schedule_hide() {
let start = Instant::now(); // doesn't matter when. It would be better to have a reproducible value though let start = Instant::now(); // doesn't matter when. It would be better to have a reproducible value though
@ -181,7 +207,7 @@ mod test {
now += animation::HIDING_TIMEOUT; now += animation::HIDING_TIMEOUT;
let (l, commands) = handle_event(l, Event::TimeoutReached(now), now); let (l, commands) = handle_event(l, state::Event::TimeoutReached(now), now);
assert_eq!(commands.panel_visibility, Some(panel::Command::Hide)); assert_eq!(commands.panel_visibility, Some(panel::Command::Hide));
assert_eq!(l.scheduled_wakeup, None); assert_eq!(l.scheduled_wakeup, None);
} }

View File

@ -10,7 +10,7 @@ use std::num::Wrapping;
use std::string::String; use std::string::String;
use std::time::Instant; use std::time::Instant;
use crate::event_loop::driver; use crate::main;
use crate::state; use crate::state;
use crate::state::Event; use crate::state::Event;
use ::logging; use ::logging;
@ -322,7 +322,7 @@ impl Default for IMProtocolState {
pub struct IMService { pub struct IMService {
/// Owned reference (still created and destroyed in C) /// Owned reference (still created and destroyed in C)
pub im: c::InputMethod, pub im: c::InputMethod,
sender: driver::Threaded, sender: main::EventLoop,
pending: IMProtocolState, pending: IMProtocolState,
current: IMProtocolState, // turn current into an idiomatic representation? current: IMProtocolState, // turn current into an idiomatic representation?
@ -338,7 +338,7 @@ pub enum SubmitError {
impl IMService { impl IMService {
pub fn new( pub fn new(
im: c::InputMethod, im: c::InputMethod,
sender: driver::Threaded, sender: main::EventLoop,
) -> Box<IMService> { ) -> Box<IMService> {
// IMService will be referenced to by C, // IMService will be referenced to by C,
// so it needs to stay in the same place in memory via Box // so it needs to stay in the same place in memory via Box

View File

@ -7,7 +7,9 @@ use crate::actors;
use crate::animation; use crate::animation;
use crate::debug; use crate::debug;
use crate::data::loading; use crate::data::loading;
use crate::event_loop;
use crate::panel; use crate::panel;
use crate::state;
use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver}; use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
@ -46,7 +48,7 @@ mod c {
/// The handle to which Commands should be sent /// The handle to which Commands should be sent
/// for processing in the main loop. /// for processing in the main loop.
receiver: Wrapped<Receiver<Commands>>, receiver: Wrapped<Receiver<Commands>>,
state_manager: Wrapped<driver::Threaded>, state_manager: Wrapped<EventLoop>,
submission: Wrapped<Submission>, submission: Wrapped<Submission>,
/// Not wrapped, because C needs to access this. /// Not wrapped, because C needs to access this.
wayland: *mut Wayland, wayland: *mut Wayland,
@ -90,6 +92,8 @@ mod c {
fn dbus_handler_set_visible(dbus: *const DBusHandler, visible: u8); fn dbus_handler_set_visible(dbus: *const DBusHandler, visible: u8);
} }
// INITIALIZATION
/// Creates what's possible in Rust to eliminate as many FFI calls as possible, /// Creates what's possible in Rust to eliminate as many FFI calls as possible,
/// because types aren't getting checked across their boundaries, /// because types aren't getting checked across their boundaries,
/// and that leads to suffering. /// and that leads to suffering.
@ -202,8 +206,76 @@ mod c {
} }
} }
} }
// EVENT PASSING
use crate::logging;
use crate::state::{Event, Presence};
use crate::state::LayoutChoice;
use crate::state::visibility;
use crate::util;
use logging::Warn;
#[no_mangle]
pub extern "C"
fn squeek_state_send_force_visible(mgr: Wrapped<EventLoop>) {
let sender = mgr.clone_ref();
let sender = sender.borrow();
sender.send(Event::Visibility(visibility::Event::ForceVisible))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
#[no_mangle]
pub extern "C"
fn squeek_state_send_force_hidden(sender: Wrapped<EventLoop>) {
let sender = sender.clone_ref();
let sender = sender.borrow();
sender.send(Event::Visibility(visibility::Event::ForceHidden))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
#[no_mangle]
pub extern "C"
fn squeek_state_send_keyboard_present(sender: Wrapped<EventLoop>, present: u32) {
let sender = sender.clone_ref();
let sender = sender.borrow();
let state =
if present == 0 { Presence::Missing }
else { Presence::Present };
sender.send(Event::PhysicalKeyboard(state))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
#[no_mangle]
pub extern "C"
fn squeek_state_send_layout_set(
sender: Wrapped<EventLoop>,
name: *const c_char,
source: *const c_char,
// TODO: use when synthetic events are needed
_timestamp: u32,
) {
let sender = sender.clone_ref();
let sender = sender.borrow();
let string_or_empty = |v| String::from(
util::c::as_str(v)
.unwrap_or(Some(""))
.unwrap_or("")
);
sender
.send(Event::LayoutChoice(LayoutChoice {
name: string_or_empty(&name),
source: string_or_empty(&source).into(),
}))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
} }
pub type EventLoop = event_loop::driver::Threaded<state::Application>;
pub mod commands { pub mod commands {
use crate::animation; use crate::animation;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@ -6,8 +6,8 @@
use std::ops; use std::ops;
use std::vec::Vec; use std::vec::Vec;
use crate::event_loop; use crate::logging;
use ::logging; use crate::main;
use crate::util::DivCeil; use crate::util::DivCeil;
// traits // traits
@ -438,11 +438,11 @@ type GlobalId = u32;
/// The outputs manager /// The outputs manager
pub struct Outputs { pub struct Outputs {
outputs: Vec<(Output, GlobalId)>, outputs: Vec<(Output, GlobalId)>,
sender: event_loop::driver::Threaded, sender: main::EventLoop,
} }
impl Outputs { impl Outputs {
pub fn new(sender: event_loop::driver::Threaded) -> Outputs { pub fn new(sender: main::EventLoop) -> Outputs {
Outputs { Outputs {
outputs: Vec::new(), outputs: Vec::new(),
sender, sender,

View File

@ -1,15 +1,15 @@
/*! Defines the application-wide message bus for updating state.*/ /*! Defines the application-wide message bus for updating state.*/
use crate::event_loop::driver::Threaded; use crate::main;
pub mod c { pub mod c {
use super::*; use super::*;
use crate::util::c::Wrapped; use crate::util::c::Wrapped;
pub type State = Wrapped<Threaded>; pub type State = Wrapped<main::EventLoop>;
} }
// The state receiver is an endpoint of a channel, so it's safely cloneable. // The state receiver is an endpoint of a channel, so it's safely cloneable.
// There's no need to keep it in a Rc. // There's no need to keep it in a Rc.
// The C version uses Wrapped with an underlying Rc, // The C version uses Wrapped with an underlying Rc,
// because Wrapped is well-tested already. // because Wrapped is well-tested already.
pub type State = Threaded; pub type State = main::EventLoop;

View File

@ -7,6 +7,8 @@
use crate::animation; use crate::animation;
use crate::debug; use crate::debug;
use crate::event_loop;
use crate::event_loop::ActorState;
use crate::imservice::{ ContentHint, ContentPurpose }; use crate::imservice::{ ContentHint, ContentPurpose };
use crate::layout::ArrangementKind; use crate::layout::ArrangementKind;
use crate::main; use crate::main;
@ -80,6 +82,19 @@ pub enum Event {
TimeoutReached(Instant), TimeoutReached(Instant),
} }
impl event_loop::Event for Event {
fn new_timeout_reached(when: Instant) -> Self {
Self::TimeoutReached(when)
}
fn get_timeout_reached(&self) -> Option<Instant> {
match self {
Self::TimeoutReached(when) => Some(*when),
_ => None,
}
}
}
impl From<InputMethod> for Event { impl From<InputMethod> for Event {
fn from(im: InputMethod) -> Self { fn from(im: InputMethod) -> Self {
Self::InputMethod(im) Self::InputMethod(im)
@ -119,13 +134,14 @@ pub struct Outcome {
pub im: InputMethod, pub im: InputMethod,
} }
impl Outcome { impl event_loop::Outcome for Outcome {
type Commands = Commands;
/// Returns the commands needed to apply changes as required by the new state. /// Returns the commands needed to apply changes as required by the new state.
/// This implementation doesn't actually take the old state into account, /// This implementation doesn't actually take the old state into account,
/// instead issuing all the commands as needed to reach the new state. /// instead issuing all the commands as needed to reach the new state.
/// The receivers of the commands bear the burden /// The receivers of the commands bear the burden
/// of checking if the commands end up being no-ops. /// of checking if the commands end up being no-ops.
pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands { fn get_commands_to_reach(&self, new_state: &Self) -> Commands {
// FIXME: handle switching outputs // FIXME: handle switching outputs
let (dbus_visible_set, panel_visibility) = match new_state.panel { let (dbus_visible_set, panel_visibility) = match new_state.panel {
animation::Outcome::Visible{output, height, ..} animation::Outcome::Visible{output, height, ..}
@ -425,8 +441,17 @@ Outcome:
}, },
) )
} }
}
pub fn get_outcome(&self, now: Instant) -> Outcome { impl ActorState for Application {
type Event = Event;
type Outcome = Outcome;
fn apply_event(self, e: Self::Event, time: Instant) -> Self {
Self::apply_event(self, e, time)
}
fn get_outcome(&self, now: Instant) -> Outcome {
// FIXME: include physical keyboard presence // FIXME: include physical keyboard presence
Outcome { Outcome {
panel: match self.preferred_output { panel: match self.preferred_output {
@ -474,7 +499,7 @@ Outcome:
} }
/// Returns the next time to update the outcome. /// Returns the next time to update the outcome.
pub fn get_next_wake(&self, now: Instant) -> Option<Instant> { fn get_next_wake(&self, now: Instant) -> Option<Instant> {
match self { match self {
Self { Self {
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,