diff --git a/eek/eek-gtk-keyboard.c b/eek/eek-gtk-keyboard.c index d057179f..44a21cc4 100644 --- a/eek/eek-gtk-keyboard.c +++ b/eek/eek-gtk-keyboard.c @@ -53,7 +53,7 @@ typedef struct _EekGtkKeyboardPrivate struct squeek_state_manager *state_manager; // shared reference struct submission *submission; // unowned reference - LevelKeyboard *keyboard; // unowned reference; it's kept in server-context + Layout *keyboard; // unowned reference; it's kept in server-context GdkEventSequence *sequence; // unowned reference LfbEvent *event; diff --git a/eek/eek-keyboard.c b/eek/eek-keyboard.c index 189098a9..d2570e28 100644 --- a/eek/eek-keyboard.c +++ b/eek/eek-keyboard.c @@ -86,15 +86,15 @@ struct keymap squeek_key_map_from_str(const char *keymap_str) { return km; } -void level_keyboard_free(LevelKeyboard *self) { +void layout_free(Layout *self) { squeek_layout_free(self->layout); g_free(self); } -LevelKeyboard* -level_keyboard_new (char *style_name, struct squeek_layout *layout) +Layout* +layout_new (char *style_name, struct squeek_layout *layout) { - LevelKeyboard *keyboard = g_new0(LevelKeyboard, 1); + Layout *keyboard = g_new0(Layout, 1); if (!keyboard) { g_error("Failed to create a keyboard"); } diff --git a/eek/eek-keyboard.h b/eek/eek-keyboard.h index afe4a019..398fa615 100644 --- a/eek/eek-keyboard.h +++ b/eek/eek-keyboard.h @@ -39,17 +39,14 @@ struct keymap { }; /// Keyboard info holder -struct _LevelKeyboard { +struct _Layout { char style_name[20]; // The name of the css class on layout struct squeek_layout *layout; // owned }; -typedef struct _LevelKeyboard LevelKeyboard; -gchar *eek_keyboard_get_keymap(LevelKeyboard *keyboard); - -LevelKeyboard* -level_keyboard_new (char *style_name, struct squeek_layout *layout); -void level_keyboard_free(LevelKeyboard *self); +Layout* +layout_new (char *style_name, struct squeek_layout *layout); +void layout_free(Layout *self); G_END_DECLS #endif /* EEK_KEYBOARD_H */ diff --git a/eek/eek-renderer.c b/eek/eek-renderer.c index ed8107b1..57f582d7 100644 --- a/eek/eek-renderer.c +++ b/eek/eek-renderer.c @@ -206,7 +206,7 @@ eek_renderer_render_keyboard (EekRenderer *self, struct render_geometry geometry, struct submission *submission, cairo_t *cr, - LevelKeyboard *keyboard) + Layout *keyboard) { g_return_if_fail (geometry.allocation_width > 0.0); g_return_if_fail (geometry.allocation_height > 0.0); @@ -316,7 +316,7 @@ renderer_init (EekRenderer *self) } EekRenderer * -eek_renderer_new (LevelKeyboard *keyboard, +eek_renderer_new (Layout *keyboard, PangoContext *pcontext) { EekRenderer *renderer = calloc(1, sizeof(EekRenderer)); diff --git a/eek/eek-renderer.h b/eek/eek-renderer.h index a50a11f8..0de06415 100644 --- a/eek/eek-renderer.h +++ b/eek/eek-renderer.h @@ -58,7 +58,7 @@ struct render_geometry { }; GType eek_renderer_get_type (void) G_GNUC_CONST; -EekRenderer *eek_renderer_new (LevelKeyboard *keyboard, +EekRenderer *eek_renderer_new (Layout *keyboard, PangoContext *pcontext); void eek_renderer_set_scale_factor (EekRenderer *renderer, gint scale); @@ -68,7 +68,7 @@ cairo_surface_t *eek_renderer_get_icon_surface(const gchar *icon_name, gint scale); void eek_renderer_render_keyboard (EekRenderer *renderer, struct render_geometry geometry, struct submission *submission, - cairo_t *cr, LevelKeyboard *keyboard); + cairo_t *cr, Layout *keyboard); void eek_renderer_free (EekRenderer *self); diff --git a/eek/eek-types.h b/eek/eek-types.h index 565e3a11..225ef2e5 100644 --- a/eek/eek-types.h +++ b/eek/eek-types.h @@ -39,7 +39,7 @@ typedef struct _EekBounds EekBounds; typedef struct _EekboardContextService EekboardContextService; typedef struct _ServerContextService ServerContextService; -typedef struct _LevelKeyboard LevelKeyboard; +typedef struct _Layout Layout; /** * EekPoint: diff --git a/eekboard/eekboard-context-service.c b/eekboard/eekboard-context-service.c index dbb0ca6a..f11989f6 100644 --- a/eekboard/eekboard-context-service.c +++ b/eekboard/eekboard-context-service.c @@ -57,7 +57,7 @@ struct _EekboardContextService { GObject parent; struct squeek_state_manager *state_manager; // shared reference - LevelKeyboard *keyboard; // currently used keyboard + Layout *keyboard; // currently used keyboard GSettings *settings; // Owned reference /// Needed for keymap changes after keyboard updates. @@ -127,9 +127,9 @@ settings_get_layout(GSettings *settings, char **type, char **layout) } void eekboard_context_service_set_layout(EekboardContextService *context, char *style_name, struct squeek_layout *layout, uint32_t timestamp) { - LevelKeyboard *keyboard = level_keyboard_new(style_name, layout); + Layout *keyboard = layout_new(style_name, layout); // set as current - LevelKeyboard *previous_keyboard = context->keyboard; + Layout *previous_keyboard = context->keyboard; context->keyboard = keyboard; // Update the keymap if necessary. // TODO: Update submission on change event @@ -142,7 +142,7 @@ void eekboard_context_service_set_layout(EekboardContextService *context, char * // replacing the keyboard above will cause the previous keyboard to get destroyed from the UI side (eek_gtk_keyboard_dispose) if (previous_keyboard) { - level_keyboard_free(previous_keyboard); + layout_free(previous_keyboard); } } @@ -264,7 +264,7 @@ eekboard_context_service_destroy (EekboardContextService *context) * Get keyboard currently active in @context. * Returns: (transfer none): an #EekKeyboard */ -LevelKeyboard * +Layout * eekboard_context_service_get_keyboard (EekboardContextService *context) { return context->keyboard; diff --git a/eekboard/eekboard-context-service.h b/eekboard/eekboard-context-service.h index 50525d9f..1ba28fae 100644 --- a/eekboard/eekboard-context-service.h +++ b/eekboard/eekboard-context-service.h @@ -41,10 +41,10 @@ G_DECLARE_FINAL_TYPE(EekboardContextService, eekboard_context_service, EEKBOARD, EekboardContextService *eekboard_context_service_new(struct squeek_state_manager *state_manager); void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission); void eekboard_context_service_destroy (EekboardContextService *context); -LevelKeyboard *eekboard_context_service_get_keyboard(EekboardContextService *context); +Layout *eekboard_context_service_get_keyboard(EekboardContextService *context); void eekboard_context_service_set_keymap(EekboardContextService *context, - const LevelKeyboard *keyboard); + const Layout *keyboard); G_END_DECLS #endif /* EEKBOARD_CONTEXT_SERVICE_H */ diff --git a/src/debug.rs b/src/debug.rs index b6cc50ed..bac4fa31 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -6,7 +6,7 @@ use std::thread; use zbus::{Connection, ObjectServer, dbus_interface, fdo}; -use crate::event_loop; +use crate::main; use crate::state; @@ -15,7 +15,7 @@ use std::convert::TryInto; /// Accepts commands controlling the debug mode struct Manager { - sender: event_loop::driver::Threaded, + sender: main::EventLoop, enabled: bool, } @@ -54,7 +54,7 @@ fn start(mgr: Manager) -> Result<(), Box> { } } -pub fn init(sender: event_loop::driver::Threaded) { +pub fn init(sender: main::EventLoop) { let mgr = Manager { sender, enabled: false, diff --git a/src/event_loop/driver.rs b/src/event_loop/driver.rs index dcc22da5..4fb57ff0 100644 --- a/src/event_loop/driver.rs +++ b/src/event_loop/driver.rs @@ -18,21 +18,22 @@ use crate::event_loop; use crate::logging; -use crate::main::Commands; -use crate::state::{ Application, Event }; use glib; use std::sync::mpsc; use std::thread; use std::time::Instant; +use super::{ActorState, Outcome}; // Traits use crate::logging::Warn; +use super::Event; -/// Type of the sender that waits for external events -type Sender = mpsc::Sender; -/// Type of the sender that waits for internal state changes -type UISender = glib::Sender; +type UISender = glib::Sender< + < + ::Outcome as Outcome + >::Commands +>; /// This loop driver spawns a new thread which updates the state in a loop, /// in response to incoming events. @@ -43,12 +44,27 @@ type UISender = glib::Sender; // This can/should be abstracted over Event and Commands, // so that the C call-ins can be thrown away from here and defined near events. #[derive(Clone)] -pub struct Threaded { - thread: Sender, +pub struct Threaded +where + S: ActorState + Send, + S::Event: Send, + ::Commands: Send, +{ + /// Waits for external events + thread: mpsc::Sender, } -impl Threaded { - pub fn new(ui: UISender, initial_state: Application) -> Self { +impl Threaded +where + // Not sure why this needs 'static. It's already owned. + S: ActorState + Send + 'static, + S::Event: Send, + ::Commands: Send, +{ + pub fn new( + ui: UISender, + initial_state: S, + ) -> Self { let (sender, receiver) = mpsc::channel(); let saved_sender = sender.clone(); thread::spawn(move || { @@ -71,13 +87,16 @@ impl Threaded { } } - pub fn send(&self, event: Event) -> Result<(), mpsc::SendError> { + pub fn send(&self, event: S::Event) -> Result<(), mpsc::SendError> { self.thread.send(event) } - fn handle_loop_event(loop_sender: &Sender, state: event_loop::State, event: Event, ui: &UISender) - -> event_loop::State - { + fn handle_loop_event( + loop_sender: &mpsc::Sender, + state: event_loop::State, + event: S::Event, + ui: &UISender, + ) -> event_loop::State { let now = Instant::now(); let (new_state, commands) = event_loop::handle_event(state.clone(), event, now); @@ -94,79 +113,16 @@ impl Threaded { new_state } - fn schedule_timeout_wake(loop_sender: &Sender, when: Instant) { + fn schedule_timeout_wake( + loop_sender: &mpsc::Sender, + when: Instant, + ) { let sender = loop_sender.clone(); thread::spawn(move || { let now = Instant::now(); thread::sleep(when - now); - sender.send(Event::TimeoutReached(when)) - .or_warn(&mut logging::Print, logging::Problem::Warning, "Can't wake visibility manager"); + sender.send(S::Event::new_timeout_reached(when)) + .or_warn(&mut logging::Print, logging::Problem::Warning, "Can't wake manager"); }); } } - -/// For calling in only -mod c { - use super::*; - - use crate::state::Presence; - use crate::state::LayoutChoice; - use crate::state::visibility; - use crate::util; - use crate::util::c::Wrapped; - use std::os::raw::c_char; - - #[no_mangle] - pub extern "C" - fn squeek_state_send_force_visible(mgr: Wrapped) { - 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) { - 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, 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, - 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"); - } -} diff --git a/src/event_loop/mod.rs b/src/event_loop/mod.rs index 7a494aca..05af69a7 100644 --- a/src/event_loop/mod.rs +++ b/src/event_loop/mod.rs @@ -38,27 +38,49 @@ pub mod driver; -// This module is tightly coupled to the shape of data passed around in this project. -// That's not a problem as long as there's only one loop. -// They can still be abstracted into Traits, -// and the loop parametrized over them. -use crate::main::Commands; -use crate::state; -use crate::state::Event; use std::cmp; use std::time::{ Duration, Instant }; +/// Carries the incoming data to affect the actor state, +/// plus an event to help schedule timed events. +pub trait Event: Clone { + fn new_timeout_reached(when: Instant) -> Self; + /// Returns the value of the reached timeout, if this event carries the timeout. + fn get_timeout_reached(&self) -> Option; +} + +/// 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; +} + /// This keeps the state of the tracker loop between iterations #[derive(Clone)] -struct State { - state: state::Application, +struct State { + state: S, scheduled_wakeup: Option, last_update: Instant, } -impl State { - fn new(initial_state: state::Application, now: Instant) -> Self { +impl State { + fn new(initial_state: S, now: Instant) -> Self { Self { state: initial_state, scheduled_wakeup: None, @@ -73,11 +95,11 @@ impl State { /// - determines next scheduled animation wakeup, /// and because this is a pure function, it's easily testable. /// It returns the new state, and the message to send onwards. -fn handle_event( - mut loop_state: State, - event: Event, +fn handle_event( + mut loop_state: State, + event: S::Event, now: Instant, -) -> (State, Commands) { +) -> (State, ::Commands) { // Calculate changes to send to the consumer, // based on publicly visible state. // The internal state may change more often than the publicly visible one, @@ -93,8 +115,8 @@ fn handle_event( .get_commands_to_reach(&new_outcome); // Timeout events are special: they affect the scheduled timeout. - loop_state.scheduled_wakeup = match event { - Event::TimeoutReached(when) => { + loop_state.scheduled_wakeup = match event.get_timeout_reached() { + Some(when) => { if when > now { // Special handling for scheduled events coming in early. // Wait at least 10 ms to avoid Zeno's paradox. @@ -112,7 +134,7 @@ fn handle_event( None } }, - _ => loop_state.scheduled_wakeup.clone(), + None => loop_state.scheduled_wakeup.clone(), }; // Reschedule timeout if the new state calls for it. @@ -152,6 +174,7 @@ mod test { use crate::animation; use crate::imservice::{ ContentHint, ContentPurpose }; use crate::panel; + use crate::state; use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility }; use crate::state::test::application_with_fake_output; @@ -162,6 +185,9 @@ mod test { } } + // TODO: This should only test the scheduling in handle_event. + // This means it should be separated from actual application logic, + // and use a mock state instead. #[test] fn schedule_hide() { let start = Instant::now(); // doesn't matter when. It would be better to have a reproducible value though @@ -181,7 +207,7 @@ mod test { now += animation::HIDING_TIMEOUT; - let (l, commands) = handle_event(l, Event::TimeoutReached(now), now); + let (l, commands) = handle_event(l, state::Event::TimeoutReached(now), now); assert_eq!(commands.panel_visibility, Some(panel::Command::Hide)); assert_eq!(l.scheduled_wakeup, None); } diff --git a/src/imservice.rs b/src/imservice.rs index 3c52a7e7..395481ee 100644 --- a/src/imservice.rs +++ b/src/imservice.rs @@ -10,7 +10,7 @@ use std::num::Wrapping; use std::string::String; use std::time::Instant; -use crate::event_loop::driver; +use crate::main; use crate::state; use crate::state::Event; use ::logging; @@ -322,7 +322,7 @@ impl Default for IMProtocolState { pub struct IMService { /// Owned reference (still created and destroyed in C) pub im: c::InputMethod, - sender: driver::Threaded, + sender: main::EventLoop, pending: IMProtocolState, current: IMProtocolState, // turn current into an idiomatic representation? @@ -338,7 +338,7 @@ pub enum SubmitError { impl IMService { pub fn new( im: c::InputMethod, - sender: driver::Threaded, + sender: main::EventLoop, ) -> Box { // IMService will be referenced to by C, // so it needs to stay in the same place in memory via Box diff --git a/src/main.rs b/src/main.rs index 261cd6e9..c56b1053 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,9 @@ use crate::actors; use crate::animation; use crate::debug; use crate::data::loading; +use crate::event_loop; use crate::panel; +use crate::state; use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver}; @@ -46,7 +48,7 @@ mod c { /// The handle to which Commands should be sent /// for processing in the main loop. receiver: Wrapped>, - state_manager: Wrapped, + state_manager: Wrapped, submission: Wrapped, /// Not wrapped, because C needs to access this. wayland: *mut Wayland, @@ -89,6 +91,8 @@ mod c { // given that dbus handler is using glib. fn dbus_handler_set_visible(dbus: *const DBusHandler, visible: u8); } + + // INITIALIZATION /// Creates what's possible in Rust to eliminate as many FFI calls as possible, /// because types aren't getting checked across their boundaries, @@ -202,8 +206,76 @@ mod c { } } } + + // EVENT PASSING + + use crate::logging; + use crate::state::{Event, Presence}; + use crate::state::LayoutChoice; + use crate::state::visibility; + use crate::util; + + use logging::Warn; + + #[no_mangle] + pub extern "C" + fn squeek_state_send_force_visible(mgr: Wrapped) { + 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) { + 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, 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, + 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; + + pub mod commands { use crate::animation; #[derive(Clone, Debug)] diff --git a/src/outputs.rs b/src/outputs.rs index a6a8944d..d52ac681 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -6,8 +6,8 @@ use std::ops; use std::vec::Vec; -use crate::event_loop; -use ::logging; +use crate::logging; +use crate::main; use crate::util::DivCeil; // traits @@ -438,11 +438,11 @@ type GlobalId = u32; /// The outputs manager pub struct Outputs { outputs: Vec<(Output, GlobalId)>, - sender: event_loop::driver::Threaded, + sender: main::EventLoop, } impl Outputs { - pub fn new(sender: event_loop::driver::Threaded) -> Outputs { + pub fn new(sender: main::EventLoop) -> Outputs { Outputs { outputs: Vec::new(), sender, diff --git a/src/receiver.rs b/src/receiver.rs index de0e95d9..653711fb 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -1,15 +1,15 @@ /*! Defines the application-wide message bus for updating state.*/ -use crate::event_loop::driver::Threaded; +use crate::main; pub mod c { use super::*; use crate::util::c::Wrapped; - pub type State = Wrapped; + pub type State = Wrapped; } // The state receiver is an endpoint of a channel, so it's safely cloneable. // There's no need to keep it in a Rc. // The C version uses Wrapped with an underlying Rc, // because Wrapped is well-tested already. -pub type State = Threaded; \ No newline at end of file +pub type State = main::EventLoop; \ No newline at end of file diff --git a/src/state.rs b/src/state.rs index dbead0c2..5030a5d4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,6 +7,8 @@ use crate::animation; use crate::debug; +use crate::event_loop; +use crate::event_loop::ActorState; use crate::imservice::{ ContentHint, ContentPurpose }; use crate::layout::ArrangementKind; use crate::main; @@ -80,6 +82,19 @@ pub enum Event { TimeoutReached(Instant), } +impl event_loop::Event for Event { + fn new_timeout_reached(when: Instant) -> Self { + Self::TimeoutReached(when) + } + + fn get_timeout_reached(&self) -> Option { + match self { + Self::TimeoutReached(when) => Some(*when), + _ => None, + } + } +} + impl From for Event { fn from(im: InputMethod) -> Self { Self::InputMethod(im) @@ -119,13 +134,14 @@ pub struct Outcome { pub im: InputMethod, } -impl Outcome { +impl event_loop::Outcome for Outcome { + type Commands = Commands; /// Returns the commands needed to apply changes as required by the new state. /// This implementation doesn't actually take the old state into account, /// instead issuing all the commands as needed to reach the new state. /// The receivers of the commands bear the burden /// of checking if the commands end up being no-ops. - pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands { + fn get_commands_to_reach(&self, new_state: &Self) -> Commands { // FIXME: handle switching outputs let (dbus_visible_set, panel_visibility) = match new_state.panel { animation::Outcome::Visible{output, height, ..} @@ -425,8 +441,17 @@ Outcome: }, ) } +} - pub fn get_outcome(&self, now: Instant) -> Outcome { +impl ActorState for Application { + type Event = Event; + type Outcome = Outcome; + + fn apply_event(self, e: Self::Event, time: Instant) -> Self { + Self::apply_event(self, e, time) + } + + fn get_outcome(&self, now: Instant) -> Outcome { // FIXME: include physical keyboard presence Outcome { panel: match self.preferred_output { @@ -474,7 +499,7 @@ Outcome: } /// Returns the next time to update the outcome. - pub fn get_next_wake(&self, now: Instant) -> Option { + fn get_next_wake(&self, now: Instant) -> Option { match self { Self { visibility_override: visibility::State::NotForced,