diff --git a/src/animation.rs b/src/animation.rs index 7ea07dfa..ec7fc100 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -6,12 +6,14 @@ use std::time::Duration; +use crate::outputs::OutputId; + /// The keyboard should hide after this has elapsed to prevent flickering. pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200); /// The outwardly visible state of visibility #[derive(PartialEq, Debug, Clone)] pub enum Outcome { - Visible, + Visible(OutputId), Hidden, } diff --git a/src/event_loop/mod.rs b/src/event_loop/mod.rs index 0dad4ba8..99f7cdc9 100644 --- a/src/event_loop/mod.rs +++ b/src/event_loop/mod.rs @@ -153,6 +153,7 @@ mod test { use crate::imservice::{ ContentHint, ContentPurpose }; use crate::main::PanelCommand; use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility }; + use crate::state::test::application_with_fake_output; fn imdetails_new() -> InputMethodDetails { InputMethodDetails { @@ -170,12 +171,12 @@ mod test { im: InputMethod::Active(imdetails_new()), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, - ..Application::new(start) + ..application_with_fake_output(start) }; let l = State::new(state, now); let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), now); - assert_eq!(commands.panel_visibility, Some(PanelCommand::Show)); + assert_matches!(commands.panel_visibility, Some(PanelCommand::Show(_))); assert_eq!(l.scheduled_wakeup, Some(now + animation::HIDING_TIMEOUT)); now += animation::HIDING_TIMEOUT; diff --git a/src/lib.rs b/src/lib.rs index da991b4e..6051250d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,9 @@ extern crate maplit; extern crate serde; extern crate xkbcommon; +#[cfg(test)] +#[macro_use] +mod assert_matches; #[macro_use] mod logging; diff --git a/src/main.rs b/src/main.rs index afef8288..b1186fac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ */ /*! Glue for the main loop. */ - +use crate::outputs::OutputId; use crate::state; use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver}; @@ -19,6 +19,7 @@ mod c { use crate::imservice::IMService; use crate::imservice::c::InputMethod; use crate::outputs::Outputs; + use crate::outputs::c::WlOutput; use crate::state; use crate::submission::Submission; use crate::util::c::Wrapped; @@ -74,7 +75,7 @@ mod c { extern "C" { #[allow(improper_ctypes)] fn init_wayland(wayland: *mut Wayland); - fn server_context_service_real_show_keyboard(service: *const UIManager); + fn server_context_service_real_show_keyboard(service: *const UIManager, output: WlOutput); fn server_context_service_real_hide_keyboard(service: *const UIManager); fn server_context_service_set_hint_purpose(service: *const UIManager, hint: u32, purpose: u32); // This should probably only get called from the gtk main loop, @@ -148,8 +149,8 @@ mod c { dbus_handler: *const DBusHandler, ) { match msg.panel_visibility { - Some(PanelCommand::Show) => unsafe { - server_context_service_real_show_keyboard(ui_manager); + Some(PanelCommand::Show(output)) => unsafe { + server_context_service_real_show_keyboard(ui_manager, output.0); }, Some(PanelCommand::Hide) => unsafe { server_context_service_real_hide_keyboard(ui_manager); @@ -175,7 +176,7 @@ mod c { #[derive(Clone, PartialEq, Debug)] pub enum PanelCommand { - Show, + Show(OutputId), Hide, } diff --git a/src/outputs.rs b/src/outputs.rs index d04872b2..327923d3 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -326,15 +326,15 @@ pub struct Size { /// wl_output mode #[derive(Clone, Copy, Debug)] -struct Mode { +pub struct Mode { width: i32, height: i32, } #[derive(Clone, Copy, Debug)] pub struct OutputState { - current_mode: Option, - transform: Option, + pub current_mode: Option, + pub transform: Option, pub scale: i32, } @@ -383,7 +383,7 @@ impl OutputState { /// Not guaranteed to exist, /// but can be used to look up state. #[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)] -pub struct OutputId(c::WlOutput); +pub struct OutputId(pub c::WlOutput); // WlOutput is a pointer, // but in the public interface, diff --git a/src/server-context-service.c b/src/server-context-service.c index 92bc3a10..a540d0dd 100644 --- a/src/server-context-service.c +++ b/src/server-context-service.c @@ -27,6 +27,7 @@ #include "submission.h" #include "wayland.h" #include "server-context-service.h" +#include "wayland-client-protocol.h" enum { PROP_0, @@ -205,7 +206,7 @@ make_widget (ServerContextService *self) // Called from rust void -server_context_service_real_show_keyboard (ServerContextService *self) +server_context_service_real_show_keyboard (ServerContextService *self, struct wl_output *output) { if (!self->window) { make_window (self); diff --git a/src/state.rs b/src/state.rs index 9119f4db..e2085aa5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -93,12 +93,12 @@ impl Outcome { pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands { let layout_hint_set = match new_state { Outcome { - visibility: animation::Outcome::Visible, + visibility: animation::Outcome::Visible(_), im: InputMethod::Active(hints), } => Some(hints.clone()), Outcome { - visibility: animation::Outcome::Visible, + visibility: animation::Outcome::Visible(_), im: InputMethod::InactiveSince(_), } => Some(InputMethodDetails { hint: ContentHint::NONE, @@ -110,9 +110,9 @@ impl Outcome { .. } => None, }; - +// FIXME: handle switching outputs let (dbus_visible_set, panel_visibility) = match new_state.visibility { - animation::Outcome::Visible => (Some(true), Some(PanelCommand::Show)), + animation::Outcome::Visible(output) => (Some(true), Some(PanelCommand::Show(output))), animation::Outcome::Hidden => (Some(false), Some(PanelCommand::Hide)), }; @@ -239,15 +239,18 @@ impl Application { pub fn get_outcome(&self, now: Instant) -> Outcome { // FIXME: include physical keyboard presence Outcome { - visibility: match (self.physical_keyboard, self.visibility_override) { - (_, visibility::State::ForcedHidden) => animation::Outcome::Hidden, - (_, visibility::State::ForcedVisible) => animation::Outcome::Visible, - (Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden, - (Presence::Missing, visibility::State::NotForced) => match self.im { - InputMethod::Active(_) => animation::Outcome::Visible, - InputMethod::InactiveSince(since) => { - if now < since + animation::HIDING_TIMEOUT { animation::Outcome::Visible } - else { animation::Outcome::Hidden } + visibility: match self.preferred_output { + None => animation::Outcome::Hidden, + Some(output) => match (self.physical_keyboard, self.visibility_override) { + (_, visibility::State::ForcedHidden) => animation::Outcome::Hidden, + (_, visibility::State::ForcedVisible) => animation::Outcome::Visible(output), + (Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden, + (Presence::Missing, visibility::State::NotForced) => match self.im { + InputMethod::Active(_) => animation::Outcome::Visible(output), + InputMethod::InactiveSince(since) => { + if now < since + animation::HIDING_TIMEOUT { animation::Outcome::Visible(output) } + else { animation::Outcome::Hidden } + }, }, }, }, @@ -274,9 +277,9 @@ impl Application { #[cfg(test)] -mod test { +pub mod test { use super::*; - + use crate::outputs::c::WlOutput; use std::time::Duration; fn imdetails_new() -> InputMethodDetails { @@ -286,6 +289,30 @@ mod test { } } + fn fake_output_id(id: usize) -> OutputId { + OutputId(unsafe { + std::mem::transmute::<_, WlOutput>(id) + }) + } + + pub fn application_with_fake_output(start: Instant) -> Application { + let id = fake_output_id(1); + let mut outputs = HashMap::new(); + outputs.insert( + id, + OutputState { + current_mode: None, + transform: None, + scale: 1, + }, + ); + Application { + preferred_output: Some(id), + outputs, + ..Application::new(start) + } + } + /// Test the original delay scenario: no flicker on quick switches. #[test] fn avoid_hide() { @@ -295,16 +322,16 @@ mod test { im: InputMethod::Active(imdetails_new()), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, - ..Application::new(start) + ..application_with_fake_output(start) }; let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); // Check 100ms at 1ms intervals. It should remain visible. for _i in 0..100 { now += Duration::from_millis(1); - assert_eq!( + assert_matches!( state.get_outcome(now).visibility, - animation::Outcome::Visible, + animation::Outcome::Visible(_), "Hidden when it should remain visible: {:?}", now.saturating_duration_since(start), ) @@ -312,7 +339,7 @@ mod test { let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now); - assert_eq!(state.get_outcome(now).visibility, animation::Outcome::Visible); + assert_matches!(state.get_outcome(now).visibility, animation::Outcome::Visible(_)); } /// Make sure that hiding works when input method goes away @@ -324,12 +351,12 @@ mod test { im: InputMethod::Active(imdetails_new()), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, - ..Application::new(start) + ..application_with_fake_output(start) }; let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); - while let animation::Outcome::Visible = state.get_outcome(now).visibility { + while let animation::Outcome::Visible(_) = state.get_outcome(now).visibility { now += Duration::from_millis(1); assert!( now < start + Duration::from_millis(250), @@ -349,7 +376,7 @@ mod test { im: InputMethod::Active(imdetails_new()), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, - ..Application::new(start) + ..application_with_fake_output(start) }; // This reflects the sequence from Wayland: // disable, disable, enable, disable @@ -359,7 +386,7 @@ mod test { let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now); let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); - while let animation::Outcome::Visible = state.get_outcome(now).visibility { + while let animation::Outcome::Visible(_) = state.get_outcome(now).visibility { now += Duration::from_millis(1); assert!( now < start + Duration::from_millis(250), @@ -388,14 +415,14 @@ mod test { im: InputMethod::InactiveSince(now), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, - ..Application::new(start) + ..application_with_fake_output(start) }; now += Duration::from_secs(1); let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now); - assert_eq!( + assert_matches!( state.get_outcome(now).visibility, - animation::Outcome::Visible, + animation::Outcome::Visible(_), "Failed to show: {:?}", now.saturating_duration_since(start), ); @@ -422,7 +449,7 @@ mod test { im: InputMethod::Active(imdetails_new()), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, - ..Application::new(start) + ..application_with_fake_output(start) }; now += Duration::from_secs(1); @@ -449,9 +476,9 @@ mod test { now += Duration::from_secs(1); let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now); - assert_eq!( + assert_matches!( state.get_outcome(now).visibility, - animation::Outcome::Visible, + animation::Outcome::Visible(_), "Failed to appear: {:?}", now.saturating_duration_since(start), );