pages: Add Accessibility/Magnifier page
This commit is contained in:
parent
186698ff5b
commit
8e7ed01fe6
13 changed files with 773 additions and 1 deletions
|
|
@ -21,6 +21,7 @@ cosmic-config.workspace = true
|
|||
cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
cosmic-idle-config.workspace = true
|
||||
cosmic-panel-config = { workspace = true, optional = true }
|
||||
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", optional = true }
|
||||
cosmic-randr-shell.workspace = true
|
||||
cosmic-randr = { workspace = true, optional = true }
|
||||
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
|
||||
|
|
@ -55,6 +56,7 @@ once_cell = "1.20.3"
|
|||
regex = "1.11.1"
|
||||
ron = "0.8"
|
||||
rust-embed = "8.5.0"
|
||||
sctk = { workspace = true, optional = true }
|
||||
secure-string = "0.3.0"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
slab = "0.4.9"
|
||||
|
|
@ -110,6 +112,7 @@ gettext = ["dep:gettext-rs"]
|
|||
|
||||
# Default features for Linux
|
||||
linux = [
|
||||
"page-accessibility",
|
||||
"page-about",
|
||||
"page-bluetooth",
|
||||
"page-date",
|
||||
|
|
@ -127,6 +130,7 @@ linux = [
|
|||
]
|
||||
|
||||
# Pages
|
||||
page-accessibility = ["dep:cosmic-protocols", "dep:sctk"]
|
||||
page-about = ["dep:cosmic-settings-system", "dep:hostname1-zbus", "dep:zbus"]
|
||||
page-bluetooth = ["dep:bluez-zbus", "dep:zbus", "dep:cosmic-settings-subscriptions"]
|
||||
page-date = ["dep:timedate-zbus", "dep:zbus"]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Config;
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
use crate::pages::accessibility;
|
||||
#[cfg(feature = "page-bluetooth")]
|
||||
use crate::pages::bluetooth;
|
||||
use crate::pages::desktop::{self, appearance};
|
||||
|
|
@ -74,6 +76,12 @@ pub struct SettingsApp {
|
|||
impl SettingsApp {
|
||||
fn subtask_to_page(&self, cmd: &PageCommands) -> Option<Entity> {
|
||||
match cmd {
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
PageCommands::Accessibility => self.pages.page_id::<accessibility::Page>(),
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
PageCommands::AccessibilityMagnifier => {
|
||||
self.pages.page_id::<accessibility::magnifier::Page>()
|
||||
}
|
||||
#[cfg(feature = "page-about")]
|
||||
PageCommands::About => self.pages.page_id::<system::about::Page>(),
|
||||
PageCommands::Appearance => self.pages.page_id::<desktop::appearance::Page>(),
|
||||
|
|
@ -194,6 +202,8 @@ impl cosmic::Application for SettingsApp {
|
|||
app.insert_page::<networking::Page>();
|
||||
#[cfg(feature = "page-bluetooth")]
|
||||
app.insert_page::<bluetooth::Page>();
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
app.insert_page::<accessibility::Page>();
|
||||
let desktop_id = app.insert_page::<desktop::Page>().id();
|
||||
app.insert_page::<display::Page>();
|
||||
#[cfg(feature = "page-sound")]
|
||||
|
|
@ -368,6 +378,18 @@ impl cosmic::Application for SettingsApp {
|
|||
}
|
||||
|
||||
Message::PageMessage(message) => match message {
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
crate::pages::Message::Accessibility(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<accessibility::Page>() {
|
||||
return page.update(message).map(Into::into);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
crate::pages::Message::AccessibilityMagnifier(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<accessibility::magnifier::Page>() {
|
||||
return page.update(self.active_page, message).map(Into::into);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "page-about")]
|
||||
crate::pages::Message::About(message) => {
|
||||
page::update!(self.pages, message, system::about::Page);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ pub struct Args {
|
|||
|
||||
#[derive(Subcommand, Debug, Serialize, Deserialize, Clone)]
|
||||
pub enum PageCommands {
|
||||
/// Accessibility settings page
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
Accessibility,
|
||||
/// Accessibility Magnifier settings page
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
AccessibilityMagnifier,
|
||||
/// About settings page
|
||||
#[cfg(feature = "page-about")]
|
||||
About,
|
||||
|
|
|
|||
339
cosmic-settings/src/pages/accessibility/magnifier.rs
Normal file
339
cosmic-settings/src/pages/accessibility/magnifier.rs
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use cosmic::{
|
||||
iced::{Element, Length},
|
||||
iced_core::text::Wrapping,
|
||||
widget::{self, icon, settings, svg, text},
|
||||
Apply,
|
||||
};
|
||||
use cosmic_comp_config::{ZoomConfig, ZoomMovement};
|
||||
use cosmic_config::{ConfigGet, ConfigSet};
|
||||
use cosmic_settings_page::{
|
||||
self as page,
|
||||
section::{self, Section},
|
||||
Entity,
|
||||
};
|
||||
use slotmap::SlotMap;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::error;
|
||||
|
||||
use super::{wayland, AccessibilityEvent, AccessibilityRequest};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Page {
|
||||
entity: Entity,
|
||||
|
||||
accessibility_config: cosmic_config::Config,
|
||||
zoom_config: ZoomConfig,
|
||||
increment_values: Vec<String>,
|
||||
increment_idx: Option<usize>,
|
||||
|
||||
wayland_thread: Option<wayland::Sender>,
|
||||
magnifier_state: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
Event(wayland::AccessibilityEvent),
|
||||
ProtocolUnavailable,
|
||||
SetMagnifier(bool),
|
||||
SetIncrement(usize),
|
||||
SetSignin(bool),
|
||||
SetMovement(ZoomMovement),
|
||||
}
|
||||
|
||||
impl Default for Page {
|
||||
fn default() -> Self {
|
||||
let comp_config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap();
|
||||
let zoom_config: ZoomConfig = comp_config
|
||||
.get("accessibility_zoom")
|
||||
.inspect_err(|err| {
|
||||
if err.is_err() {
|
||||
error!(?err, "Failed to read config 'accessibility_zoom'");
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut values = HashSet::<u32>::from_iter([25, 50, 100, 150, 200, zoom_config.increment])
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
values.sort();
|
||||
let increment_values = values
|
||||
.into_iter()
|
||||
.map(|val| {
|
||||
format!(
|
||||
"{}%{}",
|
||||
val,
|
||||
if val == ZoomConfig::default().increment {
|
||||
" (Default)"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let increment_idx = increment_values.iter().position(|s| {
|
||||
s.split("%").next().and_then(|val| str::parse(val).ok()) == Some(zoom_config.increment)
|
||||
});
|
||||
|
||||
Page {
|
||||
entity: Entity::default(),
|
||||
|
||||
accessibility_config: comp_config,
|
||||
zoom_config,
|
||||
increment_values,
|
||||
increment_idx,
|
||||
|
||||
wayland_thread: None,
|
||||
magnifier_state: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn set_id(&mut self, entity: Entity) {
|
||||
self.entity = entity;
|
||||
}
|
||||
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new(
|
||||
"accessibility_magnifier",
|
||||
"preferences-desktop-accessibility",
|
||||
)
|
||||
.title(fl!("magnifier"))
|
||||
}
|
||||
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut SlotMap<section::Entity, page::Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
Some(vec![
|
||||
sections.insert(magnifier()),
|
||||
sections.insert(tip()),
|
||||
sections.insert(view_movement()),
|
||||
])
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
sender: mpsc::Sender<crate::pages::Message>,
|
||||
) -> cosmic::Task<crate::pages::Message> {
|
||||
if self.wayland_thread.is_none() {
|
||||
match wayland::spawn_wayland_connection() {
|
||||
Ok((tx, mut rx)) => {
|
||||
self.wayland_thread = Some(tx);
|
||||
tokio::task::spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
let _ = sender
|
||||
.send(crate::pages::Message::AccessibilityMagnifier(
|
||||
Message::Event(event),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
let _ = sender
|
||||
.send(crate::pages::Message::AccessibilityMagnifier(
|
||||
Message::ProtocolUnavailable,
|
||||
))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"Failed to spawn wayland connection for magnifier page: {}",
|
||||
err
|
||||
);
|
||||
return cosmic::Task::done(crate::pages::Message::Accessibility(
|
||||
super::Message::Return,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cosmic::Task::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> cosmic::Task<crate::pages::Message> {
|
||||
let _ = self.wayland_thread.take();
|
||||
|
||||
cosmic::Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
pub fn magnifier() -> section::Section<crate::pages::Message> {
|
||||
crate::slab!(descriptions {
|
||||
magnifier = fl!("magnifier");
|
||||
controls = fl!("magnifier", "controls");
|
||||
increment = fl!("magnifier", "increment");
|
||||
signin = fl!("magnifier", "signin");
|
||||
});
|
||||
|
||||
Section::default()
|
||||
.title(&descriptions[magnifier])
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::section()
|
||||
.title(§ion.title)
|
||||
.add(
|
||||
settings::item::builder(&descriptions[magnifier])
|
||||
.description(&descriptions[controls])
|
||||
.control(
|
||||
widget::toggler(page.magnifier_state).on_toggle(Message::SetMagnifier),
|
||||
),
|
||||
)
|
||||
.add(settings::item(
|
||||
&descriptions[increment],
|
||||
widget::dropdown(
|
||||
&page.increment_values,
|
||||
page.increment_idx,
|
||||
Message::SetIncrement,
|
||||
),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[signin],
|
||||
widget::toggler(page.zoom_config.start_on_login).on_toggle(Message::SetSignin),
|
||||
))
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::AccessibilityMagnifier)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tip() -> section::Section<crate::pages::Message> {
|
||||
crate::slab!(descriptions {
|
||||
applet = fl!("magnifier", "applet");
|
||||
});
|
||||
let applet_illustration = icon::from_name("illustration-accessibility-magnifier-applet")
|
||||
.icon()
|
||||
.into_svg_handle();
|
||||
|
||||
Section::default()
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, _page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
let mut items = vec![text::body(&descriptions[applet])
|
||||
.wrapping(Wrapping::Word)
|
||||
.width(Length::Shrink)
|
||||
.into()];
|
||||
if let Some(illustration) = applet_illustration.clone() {
|
||||
items.push(svg(illustration).width(Length::Fill).into());
|
||||
}
|
||||
|
||||
settings::section()
|
||||
.add(settings::flex_item_row(items))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn view_movement() -> section::Section<crate::pages::Message> {
|
||||
crate::slab!(descriptions {
|
||||
movement = fl!("magnifier", "movement");
|
||||
continuous = fl!("magnifier", "continuous");
|
||||
onedge = fl!("magnifier", "onedge");
|
||||
centered = fl!("magnifier", "centered");
|
||||
});
|
||||
Section::default()
|
||||
.title(&descriptions[movement])
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::section()
|
||||
.title(§ion.title)
|
||||
.add(widget::settings::item_row(vec![widget::radio(
|
||||
text::body(&descriptions[continuous]),
|
||||
ZoomMovement::Continuously,
|
||||
Some(page.zoom_config.view_moves),
|
||||
Message::SetMovement,
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.into()]))
|
||||
.add(widget::settings::item_row(vec![widget::radio(
|
||||
text::body(&descriptions[onedge]),
|
||||
ZoomMovement::OnEdge,
|
||||
Some(page.zoom_config.view_moves),
|
||||
Message::SetMovement,
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.into()]))
|
||||
.add(widget::settings::item_row(vec![widget::radio(
|
||||
text::body(&descriptions[centered]),
|
||||
ZoomMovement::Centered,
|
||||
Some(page.zoom_config.view_moves),
|
||||
Message::SetMovement,
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.into()]))
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::AccessibilityMagnifier)
|
||||
})
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(
|
||||
&mut self,
|
||||
active_page: page::Entity,
|
||||
message: Message,
|
||||
) -> cosmic::iced::Task<crate::app::Message> {
|
||||
match message {
|
||||
Message::Event(AccessibilityEvent::Magnifier(value)) => {
|
||||
self.magnifier_state = value;
|
||||
}
|
||||
Message::SetMagnifier(value) => {
|
||||
if let Some(sender) = self.wayland_thread.as_ref() {
|
||||
let _ = sender.send(AccessibilityRequest::Magnifier(value));
|
||||
}
|
||||
}
|
||||
Message::SetIncrement(idx) => {
|
||||
self.increment_idx = Some(idx);
|
||||
let value = self.increment_values[idx]
|
||||
.split("%")
|
||||
.next()
|
||||
.unwrap()
|
||||
.parse::<u32>()
|
||||
.unwrap();
|
||||
self.zoom_config.increment = value;
|
||||
|
||||
if let Err(err) = self
|
||||
.accessibility_config
|
||||
.set("accessibility_zoom", self.zoom_config)
|
||||
{
|
||||
error!(?err, "Failed to set config 'accessibility_zoom'");
|
||||
}
|
||||
}
|
||||
Message::SetSignin(value) => {
|
||||
self.zoom_config.start_on_login = value;
|
||||
|
||||
if let Err(err) = self
|
||||
.accessibility_config
|
||||
.set("accessibility_zoom", self.zoom_config)
|
||||
{
|
||||
error!(?err, "Failed to set config 'accessibility_zoom'");
|
||||
}
|
||||
}
|
||||
Message::SetMovement(zoom_movement) => {
|
||||
self.zoom_config.view_moves = zoom_movement;
|
||||
|
||||
if let Err(err) = self
|
||||
.accessibility_config
|
||||
.set("accessibility_zoom", self.zoom_config)
|
||||
{
|
||||
error!(?err, "Failed to set config 'accessibility_zoom'");
|
||||
}
|
||||
}
|
||||
// We shouldn't have gotten into this page in that case
|
||||
Message::Event(AccessibilityEvent::Closed) | Message::ProtocolUnavailable => {
|
||||
if active_page == self.entity {
|
||||
return cosmic::iced::Task::done(crate::app::Message::PageMessage(
|
||||
crate::pages::Message::Accessibility(super::Message::Return),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cosmic::iced::Task::none()
|
||||
}
|
||||
}
|
||||
175
cosmic-settings/src/pages/accessibility/mod.rs
Normal file
175
cosmic-settings/src/pages/accessibility/mod.rs
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
use cosmic::{
|
||||
iced_core::text::Wrapping,
|
||||
theme,
|
||||
widget::{button, container, horizontal_space, icon, settings, text},
|
||||
Apply,
|
||||
};
|
||||
pub use cosmic_comp_config::ZoomMovement;
|
||||
use cosmic_settings_page::{
|
||||
self as page,
|
||||
section::{self, Section},
|
||||
Insert,
|
||||
};
|
||||
use slotmap::SlotMap;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub mod magnifier;
|
||||
mod wayland;
|
||||
pub use wayland::{AccessibilityEvent, AccessibilityRequest};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Page {
|
||||
entity: page::Entity,
|
||||
magnifier_state: bool,
|
||||
|
||||
wayland_available: bool,
|
||||
wayland_thread: Option<wayland::Sender>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
Event(wayland::AccessibilityEvent),
|
||||
ProtocolUnavailable,
|
||||
Return,
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn set_id(&mut self, entity: page::Entity) {
|
||||
self.entity = entity;
|
||||
}
|
||||
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new(
|
||||
"accessibility",
|
||||
"preferences-desktop-accessibility-symbolic",
|
||||
)
|
||||
.title(fl!("accessibility"))
|
||||
}
|
||||
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut SlotMap<section::Entity, page::Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
Some(vec![sections.insert(vision())])
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
sender: mpsc::Sender<crate::pages::Message>,
|
||||
) -> cosmic::Task<crate::pages::Message> {
|
||||
if self.wayland_thread.is_none() {
|
||||
match wayland::spawn_wayland_connection() {
|
||||
Ok((tx, mut rx)) => {
|
||||
self.wayland_available = true;
|
||||
self.wayland_thread = Some(tx);
|
||||
tokio::task::spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
let _ = sender
|
||||
.send(crate::pages::Message::Accessibility(Message::Event(event)))
|
||||
.await;
|
||||
}
|
||||
let _ = sender
|
||||
.send(crate::pages::Message::Accessibility(
|
||||
Message::ProtocolUnavailable,
|
||||
))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"Failed to spawn wayland connection for accessibility page: {}",
|
||||
err
|
||||
);
|
||||
self.wayland_available = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cosmic::Task::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> cosmic::Task<crate::pages::Message> {
|
||||
let _ = self.wayland_thread.take();
|
||||
|
||||
cosmic::Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {
|
||||
fn sub_pages(page: Insert<crate::pages::Message>) -> Insert<crate::pages::Message> {
|
||||
page.sub_page::<magnifier::Page>()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vision() -> section::Section<crate::pages::Message> {
|
||||
crate::slab!(descriptions {
|
||||
magnifier = fl!("magnifier");
|
||||
vision = fl!("accessibility", "vision");
|
||||
on = fl!("accessibility", "on");
|
||||
off = fl!("accessibility", "off");
|
||||
unavailable = fl!("accessibility", "unavailable");
|
||||
});
|
||||
|
||||
Section::default()
|
||||
.title(&descriptions[vision])
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |binder, page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::section()
|
||||
.title(§ion.title)
|
||||
.add({
|
||||
let (magnifier_entity, _magnifier_info) = binder
|
||||
.info
|
||||
.iter()
|
||||
.find(|(_, v)| v.id == "accessibility_magnifier")
|
||||
.expect("magnifier page not found");
|
||||
|
||||
let status_text = if page.wayland_available {
|
||||
if page.magnifier_state {
|
||||
&descriptions[on]
|
||||
} else {
|
||||
&descriptions[off]
|
||||
}
|
||||
} else {
|
||||
&descriptions[unavailable]
|
||||
};
|
||||
|
||||
settings::item_row(vec![
|
||||
text::body(&descriptions[magnifier])
|
||||
.wrapping(Wrapping::Word)
|
||||
.into(),
|
||||
horizontal_space().into(),
|
||||
text::body(status_text).wrapping(Wrapping::Word).into(),
|
||||
icon::from_name("go-next-symbolic").size(16).into(),
|
||||
])
|
||||
.apply(container)
|
||||
.class(cosmic::theme::Container::List)
|
||||
.apply(button::custom)
|
||||
.class(theme::Button::Transparent)
|
||||
.on_press_maybe(
|
||||
page.wayland_available
|
||||
.then_some(crate::pages::Message::Page(magnifier_entity)),
|
||||
)
|
||||
})
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) -> cosmic::iced::Task<crate::app::Message> {
|
||||
match message {
|
||||
Message::Event(AccessibilityEvent::Magnifier(value)) => {
|
||||
self.magnifier_state = value;
|
||||
}
|
||||
Message::Event(AccessibilityEvent::Closed) | Message::ProtocolUnavailable => {
|
||||
self.wayland_available = false;
|
||||
}
|
||||
Message::Return => {
|
||||
return cosmic::iced::Task::done(crate::app::Message::Page(self.entity))
|
||||
}
|
||||
}
|
||||
|
||||
cosmic::iced::Task::none()
|
||||
}
|
||||
}
|
||||
152
cosmic-settings/src/pages/accessibility/wayland.rs
Normal file
152
cosmic-settings/src/pages/accessibility/wayland.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
use cosmic_protocols::a11y::v1::client::cosmic_a11y_manager_v1;
|
||||
use sctk::{
|
||||
reexports::{
|
||||
calloop::{self, channel, LoopSignal},
|
||||
calloop_wayland_source::WaylandSource,
|
||||
client::{
|
||||
globals::{registry_queue_init, GlobalListContents},
|
||||
protocol::wl_registry,
|
||||
ConnectError, Connection, Dispatch, Proxy,
|
||||
},
|
||||
},
|
||||
registry::RegistryState,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AccessibilityEvent {
|
||||
Magnifier(bool),
|
||||
Closed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AccessibilityRequest {
|
||||
Magnifier(bool),
|
||||
}
|
||||
|
||||
pub type Sender = calloop::channel::Sender<AccessibilityRequest>;
|
||||
|
||||
pub fn spawn_wayland_connection() -> Result<
|
||||
(
|
||||
channel::Sender<AccessibilityRequest>,
|
||||
mpsc::Receiver<AccessibilityEvent>,
|
||||
),
|
||||
ConnectError,
|
||||
> {
|
||||
let (event_tx, event_rx) = mpsc::channel(10);
|
||||
let (request_tx, request_rx) = channel::channel();
|
||||
let conn = Connection::connect_to_env()?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = wayland_thread(conn, event_tx.clone(), request_rx) {
|
||||
tracing::warn!("Accessibility protocol wayland thread crashed: {}", err);
|
||||
let _ = event_tx.send(AccessibilityEvent::Closed);
|
||||
}
|
||||
});
|
||||
|
||||
Ok((request_tx, event_rx))
|
||||
}
|
||||
|
||||
fn wayland_thread(
|
||||
conn: Connection,
|
||||
tx: mpsc::Sender<AccessibilityEvent>,
|
||||
rx: channel::Channel<AccessibilityRequest>,
|
||||
) -> anyhow::Result<()> {
|
||||
struct State {
|
||||
loop_signal: LoopSignal,
|
||||
tx: mpsc::Sender<AccessibilityEvent>,
|
||||
global: cosmic_a11y_manager_v1::CosmicA11yManagerV1,
|
||||
|
||||
magnifier: bool,
|
||||
}
|
||||
|
||||
impl Dispatch<cosmic_a11y_manager_v1::CosmicA11yManagerV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_proxy: &cosmic_a11y_manager_v1::CosmicA11yManagerV1,
|
||||
event: <cosmic_a11y_manager_v1::CosmicA11yManagerV1 as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qhandle: &sctk::reexports::client::QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
cosmic_a11y_manager_v1::Event::Magnifier { active } => {
|
||||
let magnifier = active
|
||||
.into_result()
|
||||
.unwrap_or(cosmic_a11y_manager_v1::ActiveState::Disabled)
|
||||
== cosmic_a11y_manager_v1::ActiveState::Enabled;
|
||||
if magnifier != state.magnifier {
|
||||
if state
|
||||
.tx
|
||||
.blocking_send(AccessibilityEvent::Magnifier(magnifier))
|
||||
.is_err()
|
||||
{
|
||||
state.loop_signal.stop();
|
||||
state.loop_signal.wakeup();
|
||||
};
|
||||
state.magnifier = magnifier;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &wl_registry::WlRegistry,
|
||||
_event: <wl_registry::WlRegistry as Proxy>::Event,
|
||||
_data: &GlobalListContents,
|
||||
_conn: &Connection,
|
||||
_qhandle: &sctk::reexports::client::QueueHandle<Self>,
|
||||
) {
|
||||
// We don't care about any dynamic globals
|
||||
}
|
||||
}
|
||||
|
||||
let mut event_loop = calloop::EventLoop::<State>::try_new().unwrap();
|
||||
|
||||
let loop_handle = event_loop.handle();
|
||||
let (globals, event_queue) = registry_queue_init(&conn).unwrap();
|
||||
let qhandle = event_queue.handle();
|
||||
|
||||
WaylandSource::new(conn, event_queue)
|
||||
.insert(loop_handle.clone())
|
||||
.map_err(|err| err.error)?;
|
||||
|
||||
let registry_state = RegistryState::new(&globals);
|
||||
let Ok(global) = registry_state.bind_one::<cosmic_a11y_manager_v1::CosmicA11yManagerV1, _, _>(
|
||||
&qhandle,
|
||||
1..=1,
|
||||
(),
|
||||
) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
loop_handle
|
||||
.insert_source(rx, |request, _, state| match request {
|
||||
channel::Event::Msg(AccessibilityRequest::Magnifier(val)) => {
|
||||
state.global.set_magnifier(if val {
|
||||
cosmic_a11y_manager_v1::ActiveState::Enabled
|
||||
} else {
|
||||
cosmic_a11y_manager_v1::ActiveState::Disabled
|
||||
});
|
||||
}
|
||||
channel::Event::Closed => {
|
||||
state.loop_signal.stop();
|
||||
state.loop_signal.wakeup();
|
||||
}
|
||||
})
|
||||
.map_err(|err| err.error)?;
|
||||
|
||||
let mut state = State {
|
||||
loop_signal: event_loop.get_signal(),
|
||||
tx,
|
||||
global,
|
||||
|
||||
magnifier: false,
|
||||
};
|
||||
|
||||
event_loop.run(None, &mut state, |_| {})?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
use cosmic_settings_page::Entity;
|
||||
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
pub mod accessibility;
|
||||
#[cfg(feature = "page-bluetooth")]
|
||||
pub mod bluetooth;
|
||||
pub mod desktop;
|
||||
|
|
@ -20,6 +22,10 @@ pub mod time;
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
Accessibility(accessibility::Message),
|
||||
#[cfg(feature = "page-accessibility")]
|
||||
AccessibilityMagnifier(accessibility::magnifier::Message),
|
||||
#[cfg(feature = "page-about")]
|
||||
About(system::about::Message),
|
||||
Appearance(desktop::appearance::Message),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue