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 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;
|
||||||
|
|||||||
@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 */
|
||||||
|
|||||||
@ -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));
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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 */
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
74
src/main.rs
74
src/main.rs
@ -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)]
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
33
src/state.rs
33
src/state.rs
@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user