Carry output information on visible command all the way to C

This commit is contained in:
Dorota Czaplejewicz
2022-01-30 13:30:12 +00:00
parent e6c19a1e6a
commit f040e708a4
7 changed files with 77 additions and 42 deletions

View File

@ -6,12 +6,14 @@
use std::time::Duration; use std::time::Duration;
use crate::outputs::OutputId;
/// The keyboard should hide after this has elapsed to prevent flickering. /// The keyboard should hide after this has elapsed to prevent flickering.
pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200); pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200);
/// The outwardly visible state of visibility /// The outwardly visible state of visibility
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub enum Outcome { pub enum Outcome {
Visible, Visible(OutputId),
Hidden, Hidden,
} }

View File

@ -153,6 +153,7 @@ mod test {
use crate::imservice::{ ContentHint, ContentPurpose }; use crate::imservice::{ ContentHint, ContentPurpose };
use crate::main::PanelCommand; use crate::main::PanelCommand;
use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility }; use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility };
use crate::state::test::application_with_fake_output;
fn imdetails_new() -> InputMethodDetails { fn imdetails_new() -> InputMethodDetails {
InputMethodDetails { InputMethodDetails {
@ -170,12 +171,12 @@ mod test {
im: InputMethod::Active(imdetails_new()), im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..Application::new(start) ..application_with_fake_output(start)
}; };
let l = State::new(state, now); let l = State::new(state, now);
let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), 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)); assert_eq!(l.scheduled_wakeup, Some(now + animation::HIDING_TIMEOUT));
now += animation::HIDING_TIMEOUT; now += animation::HIDING_TIMEOUT;

View File

@ -14,6 +14,9 @@ extern crate maplit;
extern crate serde; extern crate serde;
extern crate xkbcommon; extern crate xkbcommon;
#[cfg(test)]
#[macro_use]
mod assert_matches;
#[macro_use] #[macro_use]
mod logging; mod logging;

View File

@ -3,7 +3,7 @@
*/ */
/*! Glue for the main loop. */ /*! Glue for the main loop. */
use crate::outputs::OutputId;
use crate::state; use crate::state;
use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver}; use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
@ -19,6 +19,7 @@ mod c {
use crate::imservice::IMService; use crate::imservice::IMService;
use crate::imservice::c::InputMethod; use crate::imservice::c::InputMethod;
use crate::outputs::Outputs; use crate::outputs::Outputs;
use crate::outputs::c::WlOutput;
use crate::state; use crate::state;
use crate::submission::Submission; use crate::submission::Submission;
use crate::util::c::Wrapped; use crate::util::c::Wrapped;
@ -74,7 +75,7 @@ mod c {
extern "C" { extern "C" {
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
fn init_wayland(wayland: *mut Wayland); 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_real_hide_keyboard(service: *const UIManager);
fn server_context_service_set_hint_purpose(service: *const UIManager, hint: u32, purpose: u32); 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, // This should probably only get called from the gtk main loop,
@ -148,8 +149,8 @@ mod c {
dbus_handler: *const DBusHandler, dbus_handler: *const DBusHandler,
) { ) {
match msg.panel_visibility { match msg.panel_visibility {
Some(PanelCommand::Show) => unsafe { Some(PanelCommand::Show(output)) => unsafe {
server_context_service_real_show_keyboard(ui_manager); server_context_service_real_show_keyboard(ui_manager, output.0);
}, },
Some(PanelCommand::Hide) => unsafe { Some(PanelCommand::Hide) => unsafe {
server_context_service_real_hide_keyboard(ui_manager); server_context_service_real_hide_keyboard(ui_manager);
@ -175,7 +176,7 @@ mod c {
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum PanelCommand { pub enum PanelCommand {
Show, Show(OutputId),
Hide, Hide,
} }

View File

@ -326,15 +326,15 @@ pub struct Size {
/// wl_output mode /// wl_output mode
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
struct Mode { pub struct Mode {
width: i32, width: i32,
height: i32, height: i32,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct OutputState { pub struct OutputState {
current_mode: Option<Mode>, pub current_mode: Option<Mode>,
transform: Option<c::Transform>, pub transform: Option<c::Transform>,
pub scale: i32, pub scale: i32,
} }
@ -383,7 +383,7 @@ impl OutputState {
/// Not guaranteed to exist, /// Not guaranteed to exist,
/// but can be used to look up state. /// but can be used to look up state.
#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)] #[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)]
pub struct OutputId(c::WlOutput); pub struct OutputId(pub c::WlOutput);
// WlOutput is a pointer, // WlOutput is a pointer,
// but in the public interface, // but in the public interface,

View File

@ -27,6 +27,7 @@
#include "submission.h" #include "submission.h"
#include "wayland.h" #include "wayland.h"
#include "server-context-service.h" #include "server-context-service.h"
#include "wayland-client-protocol.h"
enum { enum {
PROP_0, PROP_0,
@ -205,7 +206,7 @@ make_widget (ServerContextService *self)
// Called from rust // Called from rust
void void
server_context_service_real_show_keyboard (ServerContextService *self) server_context_service_real_show_keyboard (ServerContextService *self, struct wl_output *output)
{ {
if (!self->window) { if (!self->window) {
make_window (self); make_window (self);

View File

@ -93,12 +93,12 @@ impl Outcome {
pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands { pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands {
let layout_hint_set = match new_state { let layout_hint_set = match new_state {
Outcome { Outcome {
visibility: animation::Outcome::Visible, visibility: animation::Outcome::Visible(_),
im: InputMethod::Active(hints), im: InputMethod::Active(hints),
} => Some(hints.clone()), } => Some(hints.clone()),
Outcome { Outcome {
visibility: animation::Outcome::Visible, visibility: animation::Outcome::Visible(_),
im: InputMethod::InactiveSince(_), im: InputMethod::InactiveSince(_),
} => Some(InputMethodDetails { } => Some(InputMethodDetails {
hint: ContentHint::NONE, hint: ContentHint::NONE,
@ -110,9 +110,9 @@ impl Outcome {
.. ..
} => None, } => None,
}; };
// FIXME: handle switching outputs
let (dbus_visible_set, panel_visibility) = match new_state.visibility { 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)), animation::Outcome::Hidden => (Some(false), Some(PanelCommand::Hide)),
}; };
@ -239,18 +239,21 @@ impl Application {
pub fn get_outcome(&self, now: Instant) -> Outcome { pub fn get_outcome(&self, now: Instant) -> Outcome {
// FIXME: include physical keyboard presence // FIXME: include physical keyboard presence
Outcome { Outcome {
visibility: match (self.physical_keyboard, self.visibility_override) { 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::ForcedHidden) => animation::Outcome::Hidden,
(_, visibility::State::ForcedVisible) => animation::Outcome::Visible, (_, visibility::State::ForcedVisible) => animation::Outcome::Visible(output),
(Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden, (Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden,
(Presence::Missing, visibility::State::NotForced) => match self.im { (Presence::Missing, visibility::State::NotForced) => match self.im {
InputMethod::Active(_) => animation::Outcome::Visible, InputMethod::Active(_) => animation::Outcome::Visible(output),
InputMethod::InactiveSince(since) => { InputMethod::InactiveSince(since) => {
if now < since + animation::HIDING_TIMEOUT { animation::Outcome::Visible } if now < since + animation::HIDING_TIMEOUT { animation::Outcome::Visible(output) }
else { animation::Outcome::Hidden } else { animation::Outcome::Hidden }
}, },
}, },
}, },
},
im: self.im.clone(), im: self.im.clone(),
} }
} }
@ -274,9 +277,9 @@ impl Application {
#[cfg(test)] #[cfg(test)]
mod test { pub mod test {
use super::*; use super::*;
use crate::outputs::c::WlOutput;
use std::time::Duration; use std::time::Duration;
fn imdetails_new() -> InputMethodDetails { 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 the original delay scenario: no flicker on quick switches.
#[test] #[test]
fn avoid_hide() { fn avoid_hide() {
@ -295,16 +322,16 @@ mod test {
im: InputMethod::Active(imdetails_new()), im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..Application::new(start) ..application_with_fake_output(start)
}; };
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
// Check 100ms at 1ms intervals. It should remain visible. // Check 100ms at 1ms intervals. It should remain visible.
for _i in 0..100 { for _i in 0..100 {
now += Duration::from_millis(1); now += Duration::from_millis(1);
assert_eq!( assert_matches!(
state.get_outcome(now).visibility, state.get_outcome(now).visibility,
animation::Outcome::Visible, animation::Outcome::Visible(_),
"Hidden when it should remain visible: {:?}", "Hidden when it should remain visible: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
) )
@ -312,7 +339,7 @@ mod test {
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now); 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 /// Make sure that hiding works when input method goes away
@ -324,12 +351,12 @@ mod test {
im: InputMethod::Active(imdetails_new()), im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..Application::new(start) ..application_with_fake_output(start)
}; };
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), 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); now += Duration::from_millis(1);
assert!( assert!(
now < start + Duration::from_millis(250), now < start + Duration::from_millis(250),
@ -349,7 +376,7 @@ mod test {
im: InputMethod::Active(imdetails_new()), im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..Application::new(start) ..application_with_fake_output(start)
}; };
// This reflects the sequence from Wayland: // This reflects the sequence from Wayland:
// disable, disable, enable, disable // 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::Active(imdetails_new())), now);
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), 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); now += Duration::from_millis(1);
assert!( assert!(
now < start + Duration::from_millis(250), now < start + Duration::from_millis(250),
@ -388,14 +415,14 @@ mod test {
im: InputMethod::InactiveSince(now), im: InputMethod::InactiveSince(now),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..Application::new(start) ..application_with_fake_output(start)
}; };
now += Duration::from_secs(1); now += Duration::from_secs(1);
let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now); let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now);
assert_eq!( assert_matches!(
state.get_outcome(now).visibility, state.get_outcome(now).visibility,
animation::Outcome::Visible, animation::Outcome::Visible(_),
"Failed to show: {:?}", "Failed to show: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
); );
@ -422,7 +449,7 @@ mod test {
im: InputMethod::Active(imdetails_new()), im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..Application::new(start) ..application_with_fake_output(start)
}; };
now += Duration::from_secs(1); now += Duration::from_secs(1);
@ -449,9 +476,9 @@ mod test {
now += Duration::from_secs(1); now += Duration::from_secs(1);
let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now); let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now);
assert_eq!( assert_matches!(
state.get_outcome(now).visibility, state.get_outcome(now).visibility,
animation::Outcome::Visible, animation::Outcome::Visible(_),
"Failed to appear: {:?}", "Failed to appear: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
); );