From 3b0b8bea0d9d9252dbfba2f4eecf23ef7641b577 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Fri, 28 Jan 2022 15:26:22 +0000 Subject: [PATCH 1/9] Save outputs state --- src/event_loop/mod.rs | 1 + src/outputs.rs | 10 ++++++---- src/state.rs | 24 ++++++++++++++++++++---- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/event_loop/mod.rs b/src/event_loop/mod.rs index fa749aab..0dad4ba8 100644 --- a/src/event_loop/mod.rs +++ b/src/event_loop/mod.rs @@ -170,6 +170,7 @@ mod test { im: InputMethod::Active(imdetails_new()), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, + ..Application::new(start) }; let l = State::new(state, now); diff --git a/src/outputs.rs b/src/outputs.rs index e82995bc..d04872b2 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -23,7 +23,7 @@ pub mod c { // Defined in C #[repr(transparent)] - #[derive(Clone, PartialEq, Copy, Debug)] + #[derive(Clone, PartialEq, Copy, Debug, Eq, Hash)] pub struct WlOutput(*const c_void); impl WlOutput { @@ -382,7 +382,7 @@ impl OutputState { /// Not guaranteed to exist, /// but can be used to look up state. -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)] pub struct OutputId(c::WlOutput); // WlOutput is a pointer, @@ -399,8 +399,10 @@ struct Output { #[derive(Debug)] struct NotFound; +/// Wayland global ID type type GlobalId = u32; +/// The outputs manager pub struct Outputs { outputs: Vec<(Output, GlobalId)>, sender: event_loop::driver::Threaded, @@ -463,6 +465,6 @@ pub enum ChangeType { #[derive(Clone, Copy, Debug)] pub struct Event { - output: OutputId, - change: ChangeType, + pub output: OutputId, + pub change: ChangeType, } diff --git a/src/state.rs b/src/state.rs index 9009f1f4..f9bdcd8c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -9,9 +9,10 @@ use crate::animation; use crate::imservice::{ ContentHint, ContentPurpose }; use crate::main::{ Commands, PanelCommand }; use crate::outputs; +use crate::outputs::{OutputId, OutputState}; +use std::collections::HashMap; use std::time::Instant; - #[derive(Clone, Copy)] pub enum Presence { Present, @@ -139,6 +140,7 @@ pub struct Application { pub im: InputMethod, pub visibility_override: visibility::State, pub physical_keyboard: Presence, + pub outputs: HashMap, } impl Application { @@ -153,6 +155,7 @@ impl Application { im: InputMethod::InactiveSince(now), visibility_override: visibility::State::NotForced, physical_keyboard: Presence::Missing, + outputs: Default::default(), } } @@ -173,9 +176,17 @@ impl Application { ..self }, - Event::Output(output) => { - println!("Stub: output event {:?}", output); - self + Event::Output(outputs::Event { output, change }) => { + let mut app = self; + match change { + outputs::ChangeType::Altered(state) => { + app.outputs.insert(output, state); + }, + outputs::ChangeType::Removed => { + app.outputs.remove(&output); + }, + }; + app }, Event::InputMethod(new_im) => match (self.im.clone(), new_im) { @@ -271,6 +282,7 @@ mod test { im: InputMethod::Active(imdetails_new()), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, + ..Application::new(start) }; let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); @@ -299,6 +311,7 @@ mod test { im: InputMethod::Active(imdetails_new()), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, + ..Application::new(start) }; let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); @@ -323,6 +336,7 @@ mod test { im: InputMethod::Active(imdetails_new()), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, + ..Application::new(start) }; // This reflects the sequence from Wayland: // disable, disable, enable, disable @@ -361,6 +375,7 @@ mod test { im: InputMethod::InactiveSince(now), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, + ..Application::new(start) }; now += Duration::from_secs(1); @@ -394,6 +409,7 @@ mod test { im: InputMethod::Active(imdetails_new()), physical_keyboard: Presence::Missing, visibility_override: visibility::State::NotForced, + ..Application::new(start) }; now += Duration::from_secs(1); From 3f598086b74df57ba33dd9886b6e44a9b7f4cb53 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 30 Jan 2022 12:43:17 +0000 Subject: [PATCH 2/9] Store preferred output --- src/state.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/state.rs b/src/state.rs index f9bdcd8c..9119f4db 100644 --- a/src/state.rs +++ b/src/state.rs @@ -140,6 +140,12 @@ pub struct Application { pub im: InputMethod, pub visibility_override: visibility::State, pub physical_keyboard: Presence, + /// The output on which the panel should appear. + /// This is stored as part of the state + /// because it's not clear how to derive the output from the rest of the state. + /// It should probably follow the focused input, + /// but not sure about being allowed on non-touch displays. + pub preferred_output: Option, pub outputs: HashMap, } @@ -155,6 +161,7 @@ impl Application { im: InputMethod::InactiveSince(now), visibility_override: visibility::State::NotForced, physical_keyboard: Presence::Missing, + preferred_output: None, outputs: Default::default(), } } @@ -181,9 +188,15 @@ impl Application { match change { outputs::ChangeType::Altered(state) => { app.outputs.insert(output, state); + app.preferred_output = app.preferred_output.or(Some(output)); }, outputs::ChangeType::Removed => { app.outputs.remove(&output); + if app.preferred_output == Some(output) { + // There's currently no policy to choose one output over another, + // so just take whichever comes first. + app.preferred_output = app.outputs.keys().next().map(|output| *output); + } }, }; app From e6c19a1e6a87a2caab3ec6a8f3d236ed2c0b968e Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Fri, 4 Feb 2022 09:38:00 +0000 Subject: [PATCH 3/9] deps: Vendor assert_matches The library is small and simple enough to be considered "finished". In addition, it doesn't seem to be shipped by Debian. In relation to its usefulness, it's little effrt to copy it. --- src/assert_matches.rs | 358 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 src/assert_matches.rs diff --git a/src/assert_matches.rs b/src/assert_matches.rs new file mode 100644 index 00000000..a7ef3c2d --- /dev/null +++ b/src/assert_matches.rs @@ -0,0 +1,358 @@ +/* Taken from https://github.com/murarth/assert_matches + * + * git commit: 26b8b40a12823c068a829ba475d0eccc13dfc221 + * + * assert_matches is distributed under the terms of both the MIT license and the Apache License (Version 2.0). + * +Copyright (c) 2016 Murarth + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + */ + +//! Provides a macro, `assert_matches!`, which tests whether a value +//! matches a given pattern, causing a panic if the match fails. +//! +//! See the macro [`assert_matches!`] documentation for more information. +//! +//! Also provides a debug-only counterpart, [`debug_assert_matches!`]. +//! +//! See the macro [`debug_assert_matches!`] documentation for more information +//! about this macro. +//! +//! [`assert_matches!`]: macro.assert_matches.html +//! [`debug_assert_matches!`]: macro.debug_assert_matches.html + +#![deny(missing_docs)] +#![cfg_attr(not(test), no_std)] + +/// Asserts that an expression matches a given pattern. +/// +/// A guard expression may be supplied to add further restrictions to the +/// expected value of the expression. +/// +/// A `match` arm may be supplied to perform additional assertions or to yield +/// a value from the macro invocation. +/// +/// # Examples +/// +/// ``` +/// #[macro_use] extern crate assert_matches; +/// +/// #[derive(Debug)] +/// enum Foo { +/// A(i32), +/// B(&'static str), +/// } +/// +/// # fn main() { +/// let a = Foo::A(1); +/// +/// // Assert that `a` matches the pattern `Foo::A(_)`. +/// assert_matches!(a, Foo::A(_)); +/// +/// // Assert that `a` matches the pattern and +/// // that the contained value meets the condition `i > 0`. +/// assert_matches!(a, Foo::A(i) if i > 0); +/// +/// let b = Foo::B("foobar"); +/// +/// // Assert that `b` matches the pattern `Foo::B(_)`. +/// assert_matches!(b, Foo::B(s) => { +/// // Perform additional assertions on the variable binding `s`. +/// assert!(s.starts_with("foo")); +/// assert!(s.ends_with("bar")); +/// }); +/// +/// // Assert that `b` matches the pattern and yield the string `s`. +/// let s = assert_matches!(b, Foo::B(s) => s); +/// +/// // Perform an assertion on the value `s`. +/// assert_eq!(s, "foobar"); +/// # } +/// ``` +#[macro_export] +macro_rules! assert_matches { + ( $e:expr , $($pat:pat)|+ ) => { + match $e { + $($pat)|+ => (), + ref e => panic!("assertion failed: `{:?}` does not match `{}`", + e, stringify!($($pat)|+)) + } + }; + ( $e:expr , $($pat:pat)|+ if $cond:expr ) => { + match $e { + $($pat)|+ if $cond => (), + ref e => panic!("assertion failed: `{:?}` does not match `{}`", + e, stringify!($($pat)|+ if $cond)) + } + }; + ( $e:expr , $($pat:pat)|+ => $arm:expr ) => { + match $e { + $($pat)|+ => $arm, + ref e => panic!("assertion failed: `{:?}` does not match `{}`", + e, stringify!($($pat)|+)) + } + }; + ( $e:expr , $($pat:pat)|+ if $cond:expr => $arm:expr ) => { + match $e { + $($pat)|+ if $cond => $arm, + ref e => panic!("assertion failed: `{:?}` does not match `{}`", + e, stringify!($($pat)|+ if $cond)) + } + }; + ( $e:expr , $($pat:pat)|+ , $($arg:tt)* ) => { + match $e { + $($pat)|+ => (), + ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}", + e, stringify!($($pat)|+), format_args!($($arg)*)) + } + }; + ( $e:expr , $($pat:pat)|+ if $cond:expr , $($arg:tt)* ) => { + match $e { + $($pat)|+ if $cond => (), + ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}", + e, stringify!($($pat)|+ if $cond), format_args!($($arg)*)) + } + }; + ( $e:expr , $($pat:pat)|+ => $arm:expr , $($arg:tt)* ) => { + match $e { + $($pat)|+ => $arm, + ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}", + e, stringify!($($pat)|+), format_args!($($arg)*)) + } + }; + ( $e:expr , $($pat:pat)|+ if $cond:expr => $arm:expr , $($arg:tt)* ) => { + match $e { + $($pat)|+ if $cond => $arm, + ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}", + e, stringify!($($pat)|+ if $cond), format_args!($($arg)*)) + } + }; +} + +/// Asserts that an expression matches a given pattern. +/// +/// Unlike [`assert_matches!`], `debug_assert_matches!` statements are only enabled +/// in non-optimized builds by default. An optimized build will omit all +/// `debug_assert_matches!` statements unless `-C debug-assertions` is passed +/// to the compiler. +/// +/// See the macro [`assert_matches!`] documentation for more information. +/// +/// [`assert_matches!`]: macro.assert_matches.html +#[macro_export(local_inner_macros)] +macro_rules! debug_assert_matches { + ( $($tt:tt)* ) => { { + if _assert_matches_cfg!(debug_assertions) { + assert_matches!($($tt)*); + } + } } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! _assert_matches_cfg { + ( $($tt:tt)* ) => { cfg!($($tt)*) } +} + +#[cfg(test)] +mod test { + use std::panic::{catch_unwind, UnwindSafe}; + + #[derive(Debug)] + enum Foo { + A(i32), + B(&'static str), + C(&'static str), + } + + #[test] + fn test_assert_succeed() { + let a = Foo::A(123); + + assert_matches!(a, Foo::A(_)); + assert_matches!(a, Foo::A(123)); + assert_matches!(a, Foo::A(i) if i == 123); + assert_matches!(a, Foo::A(42) | Foo::A(123)); + + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(_)); + assert_matches!(b, Foo::B("foo")); + assert_matches!(b, Foo::B(s) if s == "foo"); + assert_matches!(b, Foo::B(s) => assert_eq!(s, "foo")); + assert_matches!(b, Foo::B(s) => { assert_eq!(s, "foo"); assert!(true) }); + assert_matches!(b, Foo::B(s) if s == "foo" => assert_eq!(s, "foo")); + assert_matches!(b, Foo::B(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(true) }); + + let c = Foo::C("foo"); + + assert_matches!(c, Foo::B(_) | Foo::C(_)); + assert_matches!(c, Foo::B("foo") | Foo::C("foo")); + assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo"); + assert_matches!(c, Foo::B(s) | Foo::C(s) => assert_eq!(s, "foo")); + assert_matches!(c, Foo::B(s) | Foo::C(s) => { assert_eq!(s, "foo"); assert!(true) }); + assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo" => assert_eq!(s, "foo")); + assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(true) }); + } + + #[test] + #[should_panic] + fn test_assert_panic_0() { + let a = Foo::A(123); + + assert_matches!(a, Foo::B(_)); + } + + #[test] + #[should_panic] + fn test_assert_panic_1() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B("bar")); + } + + #[test] + #[should_panic] + fn test_assert_panic_2() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(s) if s == "bar"); + } + + #[test] + #[should_panic] + fn test_assert_panic_3() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(s) => assert_eq!(s, "bar")); + } + + #[test] + #[should_panic] + fn test_assert_panic_4() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(s) if s == "bar" => assert_eq!(s, "foo")); + } + + #[test] + #[should_panic] + fn test_assert_panic_5() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(s) if s == "foo" => assert_eq!(s, "bar")); + } + + #[test] + #[should_panic] + fn test_assert_panic_6() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(false) }); + } + + #[test] + fn test_assert_no_move() { + let b = &mut Foo::A(0); + assert_matches!(*b, Foo::A(0)); + } + + #[test] + fn assert_with_message() { + let a = Foo::A(0); + + assert_matches!(a, Foo::A(_), "o noes"); + assert_matches!(a, Foo::A(n) if n == 0, "o noes"); + assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes"); + assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes"); + assert_matches!(a, Foo::A(n) if n == 0 => assert_eq!(n, 0), "o noes"); + assert_matches!(a, Foo::A(n) if n == 0 => { assert_eq!(n, 0); assert!(n < 1) }, "o noes"); + assert_matches!(a, Foo::A(_), "o noes {:?}", a); + assert_matches!(a, Foo::A(n) if n == 0, "o noes {:?}", a); + assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes {:?}", a); + assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes {:?}", a); + assert_matches!(a, Foo::A(_), "o noes {value:?}", value=a); + assert_matches!(a, Foo::A(n) if n == 0, "o noes {value:?}", value=a); + assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes {value:?}", value=a); + assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes {value:?}", value=a); + assert_matches!(a, Foo::A(n) if n == 0 => assert_eq!(n, 0), "o noes {value:?}", value=a); + } + + fn panic_message(f: F) -> String + where F: FnOnce() + UnwindSafe { + let err = catch_unwind(f) + .expect_err("function did not panic"); + + *err.downcast::() + .expect("function panicked with non-String value") + } + + #[test] + fn test_panic_message() { + let a = Foo::A(1); + + // expr, pat + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(_)); + }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`"#); + + // expr, pat if cond + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(s) if s == "foo"); + }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`"#); + + // expr, pat => arm + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(_) => {}); + }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`"#); + + // expr, pat if cond => arm + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(s) if s == "foo" => {}); + }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`"#); + + // expr, pat, args + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(_), "msg"); + }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`: msg"#); + + // expr, pat if cond, args + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(s) if s == "foo", "msg"); + }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`: msg"#); + + // expr, pat => arm, args + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(_) => {}, "msg"); + }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`: msg"#); + + // expr, pat if cond => arm, args + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(s) if s == "foo" => {}, "msg"); + }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`: msg"#); + } +} From f040e708a49341880dc1a615fcdd41dc380e52c7 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 30 Jan 2022 13:30:12 +0000 Subject: [PATCH 4/9] Carry output information on visible command all the way to C --- src/animation.rs | 4 +- src/event_loop/mod.rs | 5 ++- src/lib.rs | 3 ++ src/main.rs | 11 ++--- src/outputs.rs | 8 ++-- src/server-context-service.c | 3 +- src/state.rs | 85 ++++++++++++++++++++++++------------ 7 files changed, 77 insertions(+), 42 deletions(-) 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), ); From a4b67c65fffd35334ad72c10d1b8afe1d58025f1 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 30 Jan 2022 13:32:05 +0000 Subject: [PATCH 5/9] Don't reach for globals to choose output This actually removes the size request from panel creation. Incidentally, this still works becuae the following configure event gets the sizes from glib. --- src/server-context-service.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/server-context-service.c b/src/server-context-service.c index a540d0dd..7a90e251 100644 --- a/src/server-context-service.c +++ b/src/server-context-service.c @@ -141,20 +141,18 @@ on_surface_configure(ServerContextService *self, PhoshLayerSurface *surface) } static void -make_window (ServerContextService *self) +make_window (ServerContextService *self, struct wl_output *output) { if (self->window) { g_error("Window already present"); } - struct squeek_output_handle output = squeek_outputs_get_current(squeek_wayland->outputs); - squeek_uiman_set_output(self->manager, output); uint32_t height = squeek_uiman_get_perceptual_height(self->manager); self->window = g_object_new ( PHOSH_TYPE_LAYER_SURFACE, "layer-shell", squeek_wayland->layer_shell, - "wl-output", output.output, + "wl-output", output, "height", height, "anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT @@ -209,7 +207,7 @@ void server_context_service_real_show_keyboard (ServerContextService *self, struct wl_output *output) { if (!self->window) { - make_window (self); + make_window (self, output); } if (!self->widget) { make_widget (self); From 697be64418b410ddfd6d1e5ec0368e33be8c9821 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 30 Jan 2022 16:19:57 +0000 Subject: [PATCH 6/9] visibility: Forward panel height information to window creation --- src/animation.rs | 5 ++- src/event_loop/mod.rs | 2 +- src/main.rs | 11 +++--- src/server-context-service.c | 8 ++--- src/state.rs | 67 +++++++++++++++++++++++++----------- 5 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/animation.rs b/src/animation.rs index ec7fc100..8a602fbd 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -14,6 +14,9 @@ pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200); /// The outwardly visible state of visibility #[derive(PartialEq, Debug, Clone)] pub enum Outcome { - Visible(OutputId), + Visible { + output: OutputId, + height: u32, + }, Hidden, } diff --git a/src/event_loop/mod.rs b/src/event_loop/mod.rs index 99f7cdc9..118bb082 100644 --- a/src/event_loop/mod.rs +++ b/src/event_loop/mod.rs @@ -176,7 +176,7 @@ mod test { let l = State::new(state, now); let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), now); - assert_matches!(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/main.rs b/src/main.rs index b1186fac..76266258 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,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, output: WlOutput); + fn server_context_service_real_show_keyboard(service: *const UIManager, output: WlOutput, height: u32); 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, @@ -149,8 +149,8 @@ mod c { dbus_handler: *const DBusHandler, ) { match msg.panel_visibility { - Some(PanelCommand::Show(output)) => unsafe { - server_context_service_real_show_keyboard(ui_manager, output.0); + Some(PanelCommand::Show { output, height }) => unsafe { + server_context_service_real_show_keyboard(ui_manager, output.0, height); }, Some(PanelCommand::Hide) => unsafe { server_context_service_real_hide_keyboard(ui_manager); @@ -176,7 +176,10 @@ mod c { #[derive(Clone, PartialEq, Debug)] pub enum PanelCommand { - Show(OutputId), + Show { + output: OutputId, + height: u32, + }, Hide, } diff --git a/src/server-context-service.c b/src/server-context-service.c index 7a90e251..23fe42d7 100644 --- a/src/server-context-service.c +++ b/src/server-context-service.c @@ -141,14 +141,12 @@ on_surface_configure(ServerContextService *self, PhoshLayerSurface *surface) } static void -make_window (ServerContextService *self, struct wl_output *output) +make_window (ServerContextService *self, struct wl_output *output, uint32_t height) { if (self->window) { g_error("Window already present"); } - uint32_t height = squeek_uiman_get_perceptual_height(self->manager); - self->window = g_object_new ( PHOSH_TYPE_LAYER_SURFACE, "layer-shell", squeek_wayland->layer_shell, @@ -204,10 +202,10 @@ make_widget (ServerContextService *self) // Called from rust void -server_context_service_real_show_keyboard (ServerContextService *self, struct wl_output *output) +server_context_service_real_show_keyboard (ServerContextService *self, struct wl_output *output, uint32_t height) { if (!self->window) { - make_window (self, output); + make_window (self, output, height); } if (!self->widget) { make_widget (self); diff --git a/src/state.rs b/src/state.rs index e2085aa5..b4927bca 100644 --- a/src/state.rs +++ b/src/state.rs @@ -10,6 +10,7 @@ use crate::imservice::{ ContentHint, ContentPurpose }; use crate::main::{ Commands, PanelCommand }; use crate::outputs; use crate::outputs::{OutputId, OutputState}; +use std::cmp; use std::collections::HashMap; use std::time::Instant; @@ -93,12 +94,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, @@ -112,7 +113,8 @@ impl Outcome { }; // FIXME: handle switching outputs let (dbus_visible_set, panel_visibility) = match new_state.visibility { - animation::Outcome::Visible(output) => (Some(true), Some(PanelCommand::Show(output))), + animation::Outcome::Visible{output, height} + => (Some(true), Some(PanelCommand::Show{output, height})), animation::Outcome::Hidden => (Some(false), Some(PanelCommand::Hide)), }; @@ -236,23 +238,48 @@ impl Application { } } + fn get_preferred_height(output: &OutputState) -> Option { + output.get_pixel_size() + .map(|px_size| { + if px_size.width > px_size.height { + px_size.width / 5 + } else { + if (px_size.width < 540) & (px_size.width > 0) { + px_size.width * 7 / 12 // to match 360×210 + } else { + // Here we switch to wide layout, less height needed + px_size.width * 7 / 22 + } + } + }) + } + pub fn get_outcome(&self, now: Instant) -> Outcome { // FIXME: include physical keyboard presence Outcome { 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 } + Some(output) => { + // Hoping that this will get optimized out on branches not using `visible`. + let height = Self::get_preferred_height(self.outputs.get(&output).unwrap()) + .unwrap_or(0); + // TODO: Instead of setting size to 0 when the output is invalid, + // simply go invisible. + let visible = animation::Outcome::Visible{output, height}; + + match (self.physical_keyboard, self.visibility_override) { + (_, visibility::State::ForcedHidden) => animation::Outcome::Hidden, + (_, visibility::State::ForcedVisible) => visible, + (Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden, + (Presence::Missing, visibility::State::NotForced) => match self.im { + InputMethod::Active(_) => visible, + InputMethod::InactiveSince(since) => { + if now < since + animation::HIDING_TIMEOUT { visible } + else { animation::Outcome::Hidden } + }, }, - }, - }, + } + } }, im: self.im.clone(), } @@ -331,7 +358,7 @@ pub mod test { now += Duration::from_millis(1); assert_matches!( state.get_outcome(now).visibility, - animation::Outcome::Visible(_), + animation::Outcome::Visible{..}, "Hidden when it should remain visible: {:?}", now.saturating_duration_since(start), ) @@ -339,7 +366,7 @@ pub mod test { let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now); - assert_matches!(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 @@ -356,7 +383,7 @@ pub mod test { 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), @@ -386,7 +413,7 @@ pub 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), @@ -422,7 +449,7 @@ pub mod test { let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now); assert_matches!( state.get_outcome(now).visibility, - animation::Outcome::Visible(_), + animation::Outcome::Visible{..}, "Failed to show: {:?}", now.saturating_duration_since(start), ); @@ -478,7 +505,7 @@ pub mod test { assert_matches!( state.get_outcome(now).visibility, - animation::Outcome::Visible(_), + animation::Outcome::Visible{..}, "Failed to appear: {:?}", now.saturating_duration_since(start), ); From a3f91701d00f640703093d8fba0ba68d6f5a282b Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 30 Jan 2022 16:54:32 +0000 Subject: [PATCH 7/9] outputs: Remove ui manager --- src/lib.rs | 1 - src/outputs.rs | 39 ----------------- src/server-context-service.c | 4 +- src/server-context-service.h | 3 +- src/server-main.c | 6 --- src/submission.h | 1 - src/ui_manager.h | 19 --------- src/ui_manager.rs | 82 ------------------------------------ 8 files changed, 2 insertions(+), 153 deletions(-) delete mode 100644 src/ui_manager.h delete mode 100644 src/ui_manager.rs diff --git a/src/lib.rs b/src/lib.rs index 6051250d..9fe36843 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,5 @@ mod style; mod submission; pub mod tests; pub mod util; -mod ui_manager; mod vkeyboard; mod xdg; diff --git a/src/outputs.rs b/src/outputs.rs index 327923d3..13c1b5d3 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -118,26 +118,6 @@ pub mod c { /// Wrapping Outputs is required for calling its methods from C type COutputs = Wrapped; - /// A stable reference to an output. - #[derive(Clone)] - #[repr(C)] - pub struct OutputHandle { - wl_output: WlOutput, - outputs: COutputs, - } - - impl OutputHandle { - // Cannot return an Output reference - // because COutputs is too deeply wrapped - pub fn get_state(&self) -> Option { - let outputs = self.outputs.clone_ref(); - let outputs = outputs.borrow(); - outputs - .find_output(self.wl_output.clone()) - .map(|o| o.current.clone()) - } - } - // Defined in Rust // Callbacks from the output listener follow @@ -303,17 +283,6 @@ pub mod c { .unwrap_or(WlOutput::null()) } - #[no_mangle] - pub extern "C" - fn squeek_outputs_get_current(raw_collection: COutputs) -> OutputHandle { - let collection = raw_collection.clone_ref(); - let collection = collection.borrow(); - OutputHandle { - wl_output: collection.outputs[0].0.output.clone(), - outputs: raw_collection.clone(), - } - } - // TODO: handle unregistration } @@ -437,14 +406,6 @@ impl Outputs { } } - fn find_output(&self, wl_output: c::WlOutput) -> Option<&Output> { - self.outputs - .iter() - .find_map(|(o, _global)| - if o.output == wl_output { Some(o) } else { None } - ) - } - fn find_output_mut(&mut self, wl_output: c::WlOutput) -> Option<&mut Output> { diff --git a/src/server-context-service.c b/src/server-context-service.c index 23fe42d7..6e65aa11 100644 --- a/src/server-context-service.c +++ b/src/server-context-service.c @@ -42,7 +42,6 @@ struct _ServerContextService { /// Needed for instantiating the widget struct submission *submission; // unowned struct squeek_layout_state *layout; - struct ui_manager *manager; // unowned struct squeek_state_manager *state_manager; // shared reference PhoshLayerSurface *window; @@ -315,13 +314,12 @@ init (ServerContextService *self) { } ServerContextService * -server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman, struct squeek_state_manager *state_manager) +server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager) { ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL); ui->submission = submission; ui->state = self; ui->layout = layout; - ui->manager = uiman; ui->state_manager = state_manager; init(ui); return ui; diff --git a/src/server-context-service.h b/src/server-context-service.h index 588aeb5d..599da818 100644 --- a/src/server-context-service.h +++ b/src/server-context-service.h @@ -20,7 +20,6 @@ #include "src/layout.h" #include "src/submission.h" -#include "ui_manager.h" G_BEGIN_DECLS @@ -29,7 +28,7 @@ G_BEGIN_DECLS /** Manages the lifecycle of the window displaying layouts. */ G_DECLARE_FINAL_TYPE (ServerContextService, server_context_service, SERVER, CONTEXT_SERVICE, GObject) -ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman, struct squeek_state_manager *state_manager); +ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager); enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *); G_END_DECLS #endif /* SERVER_CONTEXT_SERVICE_H */ diff --git a/src/server-main.c b/src/server-main.c index ec72aecc..57381ccf 100644 --- a/src/server-main.c +++ b/src/server-main.c @@ -33,7 +33,6 @@ #include "outputs.h" #include "submission.h" #include "server-context-service.h" -#include "ui_manager.h" #include "wayland.h" #include @@ -56,8 +55,6 @@ struct squeekboard { ServerContextService *ui_context; // mess, includes the entire UI /// Currently wanted layout. TODO: merge into state::Application struct squeek_layout_state layout_choice; - /// UI shape tracker/chooser. TODO: merge into state::Application - struct ui_manager *ui_manager; }; @@ -400,8 +397,6 @@ main (int argc, char **argv) // Also initializes wayland struct rsobjects rsobjects = squeek_init(); - instance.ui_manager = squeek_uiman_new(); - instance.settings_context = eekboard_context_service_new(&instance.layout_choice); // set up dbus @@ -446,7 +441,6 @@ main (int argc, char **argv) instance.settings_context, rsobjects.submission, &instance.layout_choice, - instance.ui_manager, rsobjects.state_manager); if (!ui_context) { g_error("Could not initialize GUI"); diff --git a/src/submission.h b/src/submission.h index ed1093f8..5ee2f727 100644 --- a/src/submission.h +++ b/src/submission.h @@ -5,7 +5,6 @@ #include "virtual-keyboard-unstable-v1-client-protocol.h" #include "eek/eek-types.h" #include "main.h" -#include "src/ui_manager.h" struct squeek_layout; diff --git a/src/ui_manager.h b/src/ui_manager.h deleted file mode 100644 index b5f1734e..00000000 --- a/src/ui_manager.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef UI_MANAGER__ -#define UI_MANAGER__ - -#include - -#include "eek/eek-types.h" -#include "outputs.h" -#include "main.h" - -struct ui_manager; - -struct ui_manager *squeek_uiman_new(void); -void squeek_uiman_set_output(struct ui_manager *uiman, struct squeek_output_handle output); -uint32_t squeek_uiman_get_perceptual_height(struct ui_manager *uiman); - -struct vis_manager; - -struct vis_manager *squeek_visman_new(struct squeek_state_manager *state_manager); -#endif diff --git a/src/ui_manager.rs b/src/ui_manager.rs deleted file mode 100644 index b189a479..00000000 --- a/src/ui_manager.rs +++ /dev/null @@ -1,82 +0,0 @@ -/* Copyright (C) 2020, 2021 Purism SPC - * SPDX-License-Identifier: GPL-3.0+ - */ - -/*! Centrally manages the shape of the UI widgets, and the choice of layout. - * - * Coordinates this based on information collated from all possible sources. - */ - -use std::cmp::min; -use ::outputs::c::OutputHandle; - - -pub mod c { - use super::*; - use ::util::c::Wrapped; - - #[no_mangle] - pub extern "C" - fn squeek_uiman_new() -> Wrapped { - Wrapped::new(Manager { output: None }) - } - - /// Used to size the layer surface containing all the OSK widgets. - #[no_mangle] - pub extern "C" - fn squeek_uiman_get_perceptual_height( - uiman: Wrapped, - ) -> u32 { - let uiman = uiman.clone_ref(); - let uiman = uiman.borrow(); - // TODO: what to do when there's no output? - uiman.get_perceptual_height().unwrap_or(0) - } - - #[no_mangle] - pub extern "C" - fn squeek_uiman_set_output( - uiman: Wrapped, - output: OutputHandle, - ) { - let uiman = uiman.clone_ref(); - let mut uiman = uiman.borrow_mut(); - uiman.output = Some(output); - } -} - -/// Stores current state of all things influencing what the UI should look like. -pub struct Manager { - /// Shared output handle, current state updated whenever it's needed. - // TODO: Stop assuming that the output never changes. - // (There's no way for the output manager to update the ui manager.) - // FIXME: Turn into an OutputState and apply relevant connections elsewhere. - // Otherwise testability and predictablity is low. - output: Option, - //// Pixel size of the surface. Needs explicit updating. - //surface_size: Option, -} - -impl Manager { - fn get_perceptual_height(&self) -> Option { - let output_info = (&self.output).as_ref() - .and_then(|o| o.get_state()) - .map(|os| (os.scale as u32, os.get_pixel_size())); - match output_info { - Some((scale, Some(px_size))) => Some({ - let height = if (px_size.width < 720) & (px_size.width > 0) { - px_size.width * 7 / 12 // to match 360×210 - } else if px_size.width < 1080 { - 360 + (1080 - px_size.width) * 60 / 360 // smooth transition - } else { - 360 - }; - - // Don't exceed half the display size - min(height, px_size.height / 2) / scale - }), - Some((scale, None)) => Some(360 / scale), - None => None, - } - } -} From 78ff02e255a365f111fd03f02f71b200e47ece26 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 30 Jan 2022 17:43:52 +0000 Subject: [PATCH 8/9] output: Use new source of panel height information This removed duplicate calculation of ideal height as well. --- src/main.rs | 4 +- src/server-context-service.c | 127 ++++++++++++----------------------- 2 files changed, 44 insertions(+), 87 deletions(-) diff --git a/src/main.rs b/src/main.rs index 76266258..1ae99a5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,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, output: WlOutput, height: u32); + fn server_context_service_update_keyboard(service: *const UIManager, output: WlOutput, height: u32); 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, @@ -150,7 +150,7 @@ mod c { ) { match msg.panel_visibility { Some(PanelCommand::Show { output, height }) => unsafe { - server_context_service_real_show_keyboard(ui_manager, output.0, height); + server_context_service_update_keyboard(ui_manager, output.0, height); }, Some(PanelCommand::Hide) => unsafe { server_context_service_real_hide_keyboard(ui_manager); diff --git a/src/server-context-service.c b/src/server-context-service.c index 6e65aa11..aa88ed6e 100644 --- a/src/server-context-service.c +++ b/src/server-context-service.c @@ -46,6 +46,8 @@ struct _ServerContextService { PhoshLayerSurface *window; GtkWidget *widget; // nullable + + struct wl_output *current_output; guint last_requested_height; }; @@ -64,81 +66,6 @@ on_destroy (ServerContextService *self, GtkWidget *widget) //eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context)); } -static uint32_t -calculate_height(int32_t width, GdkRectangle *geometry) -{ - uint32_t height; - if (geometry->width > geometry->height) { - // 1:5 ratio works fine on lanscape mode, and makes sure there's - // room left for the app window - height = width / 5; - } else { - if (width < 540 && width > 0) { - height = ((unsigned)width * 7 / 12); // to match 360×210 - } else { - // Here we switch to wide layout, less height needed - height = ((unsigned)width * 7 / 22); - } - } - return height; -} - -static void -on_surface_configure(ServerContextService *self, PhoshLayerSurface *surface) -{ - GdkDisplay *display = NULL; - GdkWindow *window = NULL; - GdkMonitor *monitor = NULL; - GdkRectangle geometry; - gint width; - gint height; - - g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self)); - g_return_if_fail (PHOSH_IS_LAYER_SURFACE (surface)); - - g_object_get(G_OBJECT(surface), - "configured-width", &width, - "configured-height", &height, - NULL); - - // In order to improve height calculation, we need the monitor geometry so - // we can use different algorithms for portrait and landscape mode. - // Note: this is a temporary fix until the size manager is complete. - display = gdk_display_get_default (); - if (display) { - window = gtk_widget_get_window (GTK_WIDGET (surface)); - } - if (window) { - monitor = gdk_display_get_monitor_at_window (display, window); - } - if (monitor) { - gdk_monitor_get_geometry (monitor, &geometry); - } else { - geometry.width = geometry.height = 0; - } - - // When the geometry event comes after surface.configure, - // this entire height calculation does nothing. - // guint desired_height = squeek_uiman_get_perceptual_height(context->manager); - // Temporarily use old method, until the size manager is complete. - guint desired_height = calculate_height(width, &geometry); - - guint configured_height = (guint)height; - // if height was already requested once but a different one was given - // (for the same set of surrounding properties), - // then it's probably not reasonable to ask for it again, - // as it's likely to create pointless loops - // of request->reject->request_again->... - if (desired_height != configured_height - && self->last_requested_height != desired_height) { - self->last_requested_height = desired_height; - phosh_layer_surface_set_size(surface, 0, - (gint)desired_height); - phosh_layer_surface_set_exclusive_zone(surface, (gint)desired_height); - phosh_layer_surface_wl_surface_commit (surface); - } -} - static void make_window (ServerContextService *self, struct wl_output *output, uint32_t height) { @@ -163,7 +90,7 @@ make_window (ServerContextService *self, struct wl_output *output, uint32_t heig g_object_connect (self->window, "swapped-signal::destroy", G_CALLBACK(on_destroy), self, - "swapped-signal::configured", G_CALLBACK(on_surface_configure), self, + //"swapped-signal::configured", G_CALLBACK(on_surface_configure), self, NULL); // The properties below are just to make hacking easier. @@ -200,9 +127,47 @@ make_widget (ServerContextService *self) } // Called from rust +/// Updates the type of hiddenness void -server_context_service_real_show_keyboard (ServerContextService *self, struct wl_output *output, uint32_t height) +server_context_service_real_hide_keyboard (ServerContextService *self) { + //self->desired_height = 0; + self->current_output = NULL; + if (self->window) { + gtk_widget_hide (GTK_WIDGET(self->window)); + } +} + +// Called from rust +/// Updates the type of visibility +void +server_context_service_update_keyboard (ServerContextService *self, struct wl_output *output, uint32_t height) +{ + if (output != self->current_output) { + // Recreate on a new output + server_context_service_real_hide_keyboard(self); + } else { + gint h; + PhoshLayerSurface *surface = self->window; + g_object_get(G_OBJECT(surface), + "configured-height", &h, + NULL); + + if ((uint32_t)h != height) { + + //TODO: make sure that redrawing happens in the correct place (it doesn't now). + phosh_layer_surface_set_size(self->window, 0, height); + phosh_layer_surface_set_exclusive_zone(self->window, height); + phosh_layer_surface_wl_surface_commit(self->window); + + self->current_output = output; + + return; + } + } + + self->current_output = output; + if (!self->window) { make_window (self, output, height); } @@ -212,14 +177,6 @@ server_context_service_real_show_keyboard (ServerContextService *self, struct wl gtk_widget_show (GTK_WIDGET(self->window)); } -// Called from rust -void -server_context_service_real_hide_keyboard (ServerContextService *self) -{ - if (self->window) { - gtk_widget_hide (GTK_WIDGET(self->window)); - } -} static void server_context_service_set_property (GObject *object, From 16d6871422c09bd674f9ab105e3a964d07ab1e2e Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Wed, 2 Feb 2022 17:39:43 +0000 Subject: [PATCH 9/9] panel: Apply a hard limit of 1/2 height --- src/state.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/state.rs b/src/state.rs index b4927bca..c65033df 100644 --- a/src/state.rs +++ b/src/state.rs @@ -241,16 +241,19 @@ impl Application { fn get_preferred_height(output: &OutputState) -> Option { output.get_pixel_size() .map(|px_size| { - if px_size.width > px_size.height { - px_size.width / 5 - } else { - if (px_size.width < 540) & (px_size.width > 0) { - px_size.width * 7 / 12 // to match 360×210 + let height = { + if px_size.width > px_size.height { + px_size.width / 5 } else { - // Here we switch to wide layout, less height needed - px_size.width * 7 / 22 + if (px_size.width < 540) & (px_size.width > 0) { + px_size.width * 7 / 12 // to match 360×210 + } else { + // Here we switch to wide layout, less height needed + px_size.width * 7 / 22 + } } - } + }; + cmp::min(height, px_size.height / 2) }) }