Merge branch 'event' into 'master'
Decouple event handling from concrete logic See merge request World/Phosh/squeekboard!581
This commit is contained in:
		@ -53,7 +53,7 @@ typedef struct _EekGtkKeyboardPrivate
 | 
			
		||||
    struct squeek_state_manager *state_manager; // shared 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
 | 
			
		||||
    LfbEvent *event;
 | 
			
		||||
 | 
			
		||||
@ -86,15 +86,15 @@ struct keymap squeek_key_map_from_str(const char *keymap_str) {
 | 
			
		||||
    return km;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void level_keyboard_free(LevelKeyboard *self) {
 | 
			
		||||
void layout_free(Layout *self) {
 | 
			
		||||
    squeek_layout_free(self->layout);
 | 
			
		||||
    g_free(self);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LevelKeyboard*
 | 
			
		||||
level_keyboard_new (char *style_name, struct squeek_layout *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) {
 | 
			
		||||
        g_error("Failed to create a keyboard");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -39,17 +39,14 @@ struct keymap {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Keyboard info holder
 | 
			
		||||
struct _LevelKeyboard {
 | 
			
		||||
struct _Layout {
 | 
			
		||||
    char style_name[20]; // The name of the css class on layout
 | 
			
		||||
    struct squeek_layout *layout; // owned
 | 
			
		||||
};
 | 
			
		||||
typedef struct _LevelKeyboard LevelKeyboard;
 | 
			
		||||
 | 
			
		||||
gchar *eek_keyboard_get_keymap(LevelKeyboard *keyboard);
 | 
			
		||||
 | 
			
		||||
LevelKeyboard*
 | 
			
		||||
level_keyboard_new (char *style_name, struct squeek_layout *layout);
 | 
			
		||||
void level_keyboard_free(LevelKeyboard *self);
 | 
			
		||||
Layout*
 | 
			
		||||
layout_new (char *style_name, struct squeek_layout *layout);
 | 
			
		||||
void layout_free(Layout *self);
 | 
			
		||||
 | 
			
		||||
G_END_DECLS
 | 
			
		||||
#endif  /* EEK_KEYBOARD_H */
 | 
			
		||||
 | 
			
		||||
@ -206,7 +206,7 @@ eek_renderer_render_keyboard (EekRenderer *self,
 | 
			
		||||
                              struct render_geometry geometry,
 | 
			
		||||
                              struct submission *submission,
 | 
			
		||||
                                   cairo_t     *cr,
 | 
			
		||||
                              LevelKeyboard *keyboard)
 | 
			
		||||
                              Layout *keyboard)
 | 
			
		||||
{
 | 
			
		||||
    g_return_if_fail (geometry.allocation_width > 0.0);
 | 
			
		||||
    g_return_if_fail (geometry.allocation_height > 0.0);
 | 
			
		||||
@ -316,7 +316,7 @@ renderer_init (EekRenderer *self)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EekRenderer *
 | 
			
		||||
eek_renderer_new (LevelKeyboard  *keyboard,
 | 
			
		||||
eek_renderer_new (Layout  *keyboard,
 | 
			
		||||
                  PangoContext *pcontext)
 | 
			
		||||
{
 | 
			
		||||
    EekRenderer *renderer = calloc(1, sizeof(EekRenderer));
 | 
			
		||||
 | 
			
		||||
@ -58,7 +58,7 @@ struct render_geometry {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
GType            eek_renderer_get_type         (void) G_GNUC_CONST;
 | 
			
		||||
EekRenderer     *eek_renderer_new              (LevelKeyboard     *keyboard,
 | 
			
		||||
EekRenderer     *eek_renderer_new              (Layout     *keyboard,
 | 
			
		||||
                                                PangoContext    *pcontext);
 | 
			
		||||
void             eek_renderer_set_scale_factor (EekRenderer     *renderer,
 | 
			
		||||
                                                gint             scale);
 | 
			
		||||
@ -68,7 +68,7 @@ cairo_surface_t *eek_renderer_get_icon_surface(const gchar     *icon_name,
 | 
			
		||||
                                                gint             scale);
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
eek_renderer_free (EekRenderer        *self);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,7 @@ typedef struct _EekBounds EekBounds;
 | 
			
		||||
 | 
			
		||||
typedef struct _EekboardContextService EekboardContextService;
 | 
			
		||||
typedef struct _ServerContextService ServerContextService;
 | 
			
		||||
typedef struct _LevelKeyboard LevelKeyboard;
 | 
			
		||||
typedef struct _Layout Layout;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * EekPoint:
 | 
			
		||||
 | 
			
		||||
@ -57,7 +57,7 @@ struct _EekboardContextService {
 | 
			
		||||
    GObject parent;
 | 
			
		||||
    struct squeek_state_manager *state_manager; // shared reference
 | 
			
		||||
 | 
			
		||||
    LevelKeyboard *keyboard; // currently used keyboard
 | 
			
		||||
    Layout *keyboard; // currently used keyboard
 | 
			
		||||
    GSettings *settings; // Owned reference
 | 
			
		||||
 | 
			
		||||
    /// 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) {
 | 
			
		||||
    LevelKeyboard *keyboard = level_keyboard_new(style_name, layout);
 | 
			
		||||
    Layout *keyboard = layout_new(style_name, layout);
 | 
			
		||||
    // set as current
 | 
			
		||||
    LevelKeyboard *previous_keyboard = context->keyboard;
 | 
			
		||||
    Layout *previous_keyboard = context->keyboard;
 | 
			
		||||
    context->keyboard = keyboard;
 | 
			
		||||
    // Update the keymap if necessary.
 | 
			
		||||
    // 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)
 | 
			
		||||
    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.
 | 
			
		||||
 * Returns: (transfer none): an #EekKeyboard
 | 
			
		||||
 */
 | 
			
		||||
LevelKeyboard *
 | 
			
		||||
Layout *
 | 
			
		||||
eekboard_context_service_get_keyboard (EekboardContextService *context)
 | 
			
		||||
{
 | 
			
		||||
    return context->keyboard;
 | 
			
		||||
 | 
			
		||||
@ -41,10 +41,10 @@ G_DECLARE_FINAL_TYPE(EekboardContextService, eekboard_context_service, EEKBOARD,
 | 
			
		||||
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_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,
 | 
			
		||||
                                         const LevelKeyboard *keyboard);
 | 
			
		||||
                                         const Layout *keyboard);
 | 
			
		||||
 | 
			
		||||
G_END_DECLS
 | 
			
		||||
#endif  /* EEKBOARD_CONTEXT_SERVICE_H */
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@
 | 
			
		||||
use std::thread;
 | 
			
		||||
use zbus::{Connection, ObjectServer, dbus_interface, fdo};
 | 
			
		||||
 | 
			
		||||
use crate::event_loop;
 | 
			
		||||
use crate::main;
 | 
			
		||||
use crate::state;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ use std::convert::TryInto;
 | 
			
		||||
 | 
			
		||||
/// Accepts commands controlling the debug mode
 | 
			
		||||
struct Manager {
 | 
			
		||||
    sender: event_loop::driver::Threaded,
 | 
			
		||||
    sender: main::EventLoop,
 | 
			
		||||
    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 {
 | 
			
		||||
        sender,
 | 
			
		||||
        enabled: false,
 | 
			
		||||
 | 
			
		||||
@ -18,21 +18,22 @@
 | 
			
		||||
 | 
			
		||||
use crate::event_loop;
 | 
			
		||||
use crate::logging;
 | 
			
		||||
use crate::main::Commands;
 | 
			
		||||
use crate::state::{ Application, Event };
 | 
			
		||||
use glib;
 | 
			
		||||
use std::sync::mpsc;
 | 
			
		||||
use std::thread;
 | 
			
		||||
use std::time::Instant;
 | 
			
		||||
use super::{ActorState, Outcome};
 | 
			
		||||
 | 
			
		||||
// Traits
 | 
			
		||||
use crate::logging::Warn;
 | 
			
		||||
use super::Event;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Type of the sender that waits for external events
 | 
			
		||||
type Sender = mpsc::Sender<Event>;
 | 
			
		||||
/// Type of the sender that waits for internal state changes
 | 
			
		||||
type UISender = glib::Sender<Commands>;
 | 
			
		||||
type UISender<S> = glib::Sender<
 | 
			
		||||
    <
 | 
			
		||||
        <S as ActorState>::Outcome as Outcome
 | 
			
		||||
    >::Commands
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
/// This loop driver spawns a new thread which updates the state in a loop,
 | 
			
		||||
/// in response to incoming events.
 | 
			
		||||
@ -43,12 +44,27 @@ type UISender = glib::Sender<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.
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct Threaded {
 | 
			
		||||
    thread: Sender,
 | 
			
		||||
pub struct Threaded<S>
 | 
			
		||||
where
 | 
			
		||||
    S: ActorState + Send,
 | 
			
		||||
    S::Event: Send,
 | 
			
		||||
    <S::Outcome as Outcome>::Commands: Send,
 | 
			
		||||
{
 | 
			
		||||
    /// Waits for external events
 | 
			
		||||
    thread: mpsc::Sender<S::Event>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Threaded {
 | 
			
		||||
    pub fn new(ui: UISender, initial_state: Application) -> Self {
 | 
			
		||||
impl<S> Threaded<S>
 | 
			
		||||
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 saved_sender = sender.clone();
 | 
			
		||||
        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)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn handle_loop_event(loop_sender: &Sender, state: event_loop::State, event: Event, ui: &UISender)
 | 
			
		||||
        -> event_loop::State
 | 
			
		||||
    {
 | 
			
		||||
    fn handle_loop_event(
 | 
			
		||||
        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 (new_state, commands) = event_loop::handle_event(state.clone(), event, now);
 | 
			
		||||
@ -94,79 +113,16 @@ impl Threaded {
 | 
			
		||||
        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();
 | 
			
		||||
        thread::spawn(move || {
 | 
			
		||||
            let now = Instant::now();
 | 
			
		||||
            thread::sleep(when - now);
 | 
			
		||||
            sender.send(Event::TimeoutReached(when))
 | 
			
		||||
                .or_warn(&mut logging::Print, logging::Problem::Warning, "Can't wake visibility manager");
 | 
			
		||||
            sender.send(S::Event::new_timeout_reached(when))
 | 
			
		||||
                .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");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -38,27 +38,49 @@
 | 
			
		||||
 | 
			
		||||
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::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
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
struct State {
 | 
			
		||||
    state: state::Application,
 | 
			
		||||
struct State<S> {
 | 
			
		||||
    state: S,
 | 
			
		||||
    scheduled_wakeup: Option<Instant>,
 | 
			
		||||
    last_update: Instant,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl State {
 | 
			
		||||
    fn new(initial_state: state::Application, now: Instant) -> Self {
 | 
			
		||||
impl<S> State<S> {
 | 
			
		||||
    fn new(initial_state: S, now: Instant) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            state: initial_state,
 | 
			
		||||
            scheduled_wakeup: None,
 | 
			
		||||
@ -73,11 +95,11 @@ impl State {
 | 
			
		||||
/// - determines next scheduled animation wakeup,
 | 
			
		||||
/// and because this is a pure function, it's easily testable.
 | 
			
		||||
/// It returns the new state, and the message to send onwards.
 | 
			
		||||
fn handle_event(
 | 
			
		||||
    mut loop_state: State,
 | 
			
		||||
    event: Event,
 | 
			
		||||
fn handle_event<S: ActorState>(
 | 
			
		||||
    mut loop_state: State<S>,
 | 
			
		||||
    event: S::Event,
 | 
			
		||||
    now: Instant,
 | 
			
		||||
) -> (State, Commands) {
 | 
			
		||||
) -> (State<S>, <S::Outcome as Outcome>::Commands) {
 | 
			
		||||
    // Calculate changes to send to the consumer,
 | 
			
		||||
    // based on publicly visible state.
 | 
			
		||||
    // 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);
 | 
			
		||||
    
 | 
			
		||||
    // Timeout events are special: they affect the scheduled timeout.
 | 
			
		||||
    loop_state.scheduled_wakeup = match event {
 | 
			
		||||
        Event::TimeoutReached(when) => {
 | 
			
		||||
    loop_state.scheduled_wakeup = match event.get_timeout_reached() {
 | 
			
		||||
        Some(when) => {
 | 
			
		||||
            if when > now {
 | 
			
		||||
                // Special handling for scheduled events coming in early.
 | 
			
		||||
                // Wait at least 10 ms to avoid Zeno's paradox.
 | 
			
		||||
@ -112,7 +134,7 @@ fn handle_event(
 | 
			
		||||
                None
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        _ => loop_state.scheduled_wakeup.clone(),
 | 
			
		||||
        None => loop_state.scheduled_wakeup.clone(),
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    // Reschedule timeout if the new state calls for it.
 | 
			
		||||
@ -152,6 +174,7 @@ mod test {
 | 
			
		||||
    use crate::animation;
 | 
			
		||||
    use crate::imservice::{ ContentHint, ContentPurpose };
 | 
			
		||||
    use crate::panel;
 | 
			
		||||
    use crate::state;
 | 
			
		||||
    use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility };
 | 
			
		||||
    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]
 | 
			
		||||
    fn schedule_hide() {
 | 
			
		||||
        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;
 | 
			
		||||
        
 | 
			
		||||
        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!(l.scheduled_wakeup, None);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ use std::num::Wrapping;
 | 
			
		||||
use std::string::String;
 | 
			
		||||
use std::time::Instant;
 | 
			
		||||
 | 
			
		||||
use crate::event_loop::driver;
 | 
			
		||||
use crate::main;
 | 
			
		||||
use crate::state;
 | 
			
		||||
use crate::state::Event;
 | 
			
		||||
use ::logging;
 | 
			
		||||
@ -322,7 +322,7 @@ impl Default for IMProtocolState {
 | 
			
		||||
pub struct IMService {
 | 
			
		||||
    /// Owned reference (still created and destroyed in C)
 | 
			
		||||
    pub im: c::InputMethod,
 | 
			
		||||
    sender: driver::Threaded,
 | 
			
		||||
    sender: main::EventLoop,
 | 
			
		||||
 | 
			
		||||
    pending: IMProtocolState,
 | 
			
		||||
    current: IMProtocolState, // turn current into an idiomatic representation?
 | 
			
		||||
@ -338,7 +338,7 @@ pub enum SubmitError {
 | 
			
		||||
impl IMService {
 | 
			
		||||
    pub fn new(
 | 
			
		||||
        im: c::InputMethod,
 | 
			
		||||
        sender: driver::Threaded,
 | 
			
		||||
        sender: main::EventLoop,
 | 
			
		||||
    ) -> Box<IMService> {
 | 
			
		||||
        // IMService will be referenced to by C,
 | 
			
		||||
        // so it needs to stay in the same place in memory via Box
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										74
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								src/main.rs
									
									
									
									
									
								
							@ -7,7 +7,9 @@ use crate::actors;
 | 
			
		||||
use crate::animation;
 | 
			
		||||
use crate::debug;
 | 
			
		||||
use crate::data::loading;
 | 
			
		||||
use crate::event_loop;
 | 
			
		||||
use crate::panel;
 | 
			
		||||
use crate::state;
 | 
			
		||||
use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -46,7 +48,7 @@ mod c {
 | 
			
		||||
        /// The handle to which Commands should be sent
 | 
			
		||||
        /// for processing in the main loop.
 | 
			
		||||
        receiver: Wrapped<Receiver<Commands>>,
 | 
			
		||||
        state_manager: Wrapped<driver::Threaded>,
 | 
			
		||||
        state_manager: Wrapped<EventLoop>,
 | 
			
		||||
        submission: Wrapped<Submission>,
 | 
			
		||||
        /// Not wrapped, because C needs to access this.
 | 
			
		||||
        wayland: *mut Wayland,
 | 
			
		||||
@ -89,6 +91,8 @@ mod c {
 | 
			
		||||
        // given that dbus handler is using glib.
 | 
			
		||||
        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,
 | 
			
		||||
    /// because types aren't getting checked across their boundaries,
 | 
			
		||||
@ -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 {
 | 
			
		||||
    use crate::animation;
 | 
			
		||||
    #[derive(Clone, Debug)]
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,8 @@
 | 
			
		||||
 | 
			
		||||
use std::ops;
 | 
			
		||||
use std::vec::Vec;
 | 
			
		||||
use crate::event_loop;
 | 
			
		||||
use ::logging;
 | 
			
		||||
use crate::logging;
 | 
			
		||||
use crate::main;
 | 
			
		||||
use crate::util::DivCeil;
 | 
			
		||||
 | 
			
		||||
// traits
 | 
			
		||||
@ -438,11 +438,11 @@ type GlobalId = u32;
 | 
			
		||||
/// The outputs manager
 | 
			
		||||
pub struct Outputs {
 | 
			
		||||
    outputs: Vec<(Output, GlobalId)>,
 | 
			
		||||
    sender: event_loop::driver::Threaded,
 | 
			
		||||
    sender: main::EventLoop,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Outputs {
 | 
			
		||||
    pub fn new(sender: event_loop::driver::Threaded) -> Outputs {
 | 
			
		||||
    pub fn new(sender: main::EventLoop) -> Outputs {
 | 
			
		||||
        Outputs {
 | 
			
		||||
            outputs: Vec::new(),
 | 
			
		||||
            sender,
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,15 @@
 | 
			
		||||
/*! Defines the application-wide message bus for updating state.*/
 | 
			
		||||
 | 
			
		||||
use crate::event_loop::driver::Threaded;
 | 
			
		||||
use crate::main;
 | 
			
		||||
 | 
			
		||||
pub mod c {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    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.
 | 
			
		||||
// There's no need to keep it in a Rc.
 | 
			
		||||
// The C version uses Wrapped with an underlying Rc,
 | 
			
		||||
// because Wrapped is well-tested already.
 | 
			
		||||
pub type State = Threaded;
 | 
			
		||||
pub type State = main::EventLoop;
 | 
			
		||||
							
								
								
									
										33
									
								
								src/state.rs
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								src/state.rs
									
									
									
									
									
								
							@ -7,6 +7,8 @@
 | 
			
		||||
 | 
			
		||||
use crate::animation;
 | 
			
		||||
use crate::debug;
 | 
			
		||||
use crate::event_loop;
 | 
			
		||||
use crate::event_loop::ActorState;
 | 
			
		||||
use crate::imservice::{ ContentHint, ContentPurpose };
 | 
			
		||||
use crate::layout::ArrangementKind;
 | 
			
		||||
use crate::main;
 | 
			
		||||
@ -80,6 +82,19 @@ pub enum Event {
 | 
			
		||||
    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 {
 | 
			
		||||
    fn from(im: InputMethod) -> Self {
 | 
			
		||||
        Self::InputMethod(im)
 | 
			
		||||
@ -119,13 +134,14 @@ pub struct Outcome {
 | 
			
		||||
    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.
 | 
			
		||||
    /// This implementation doesn't actually take the old state into account,
 | 
			
		||||
    /// instead issuing all the commands as needed to reach the new state.
 | 
			
		||||
    /// The receivers of the commands bear the burden
 | 
			
		||||
    /// 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
 | 
			
		||||
        let (dbus_visible_set, panel_visibility) = match new_state.panel {
 | 
			
		||||
            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
 | 
			
		||||
        Outcome {
 | 
			
		||||
            panel: match self.preferred_output {
 | 
			
		||||
@ -474,7 +499,7 @@ 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 {
 | 
			
		||||
            Self {
 | 
			
		||||
                visibility_override: visibility::State::NotForced,
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user