mundy/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
#![forbid(
clippy::dbg_macro,
clippy::missing_safety_doc,
clippy::unnecessary_safety_comment,
clippy::unnecessary_safety_doc,
unsafe_op_in_unsafe_fn
)]
#![deny(clippy::unwrap_used)]
//! Your friendly neighbourhood ~~whale~~ crate for reading various system-level
//! accessibility and UI preferences across platforms 🐋
//!
//! The following preferences are supported:
//! * [`AccentColor`]—The user's current system wide accent color preference.
//! * [`ColorScheme`]—The user's preference for either light or dark mode.
//! * [`Contrast`]—The user's preferred contrast level.
//! * [`ReducedMotion`]—The user's reduced motion preference.
//! * [`ReducedTransparency`]—The user's reduced transparency preference.
//! * [`DoubleClickInterval`]—The maximum amount of time allowed between the first and second click.
//!
//! ## Example
//! The easiest way to get the preferences is to use the
//! [`Preferences::stream`] function to create a stream that is continually
//! updated when things change:
//!
//! ```no_run
//! use mundy::{Preferences, Interest};
//! use futures_lite::StreamExt as _;
//!
//! // Interest tells mundy which preferences it should monitor for you.
//! // use `Interest::All` if you're interested in all preferences.
//! let mut stream = Preferences::stream(Interest::AccentColor);
//! # let _ = async move {
//! while let Some(preferences) = stream.next().await {
//! eprintln!("accent color: {:?}", preferences.accent_color);
//! }
//! # };
//! ```
//!
//! Alternatively, there's [`Preferences::subscribe`] which
//! accepts a simple callback function instead.
//!
//! ## Errors
//! Most errors (except some fatal errors at startup) are simply ignored
//! and the default value for the preference (which is usually `NoPreference`) is returned.
//! It can be useful to turn on the `log` feature to find out what's going on.
//!
//! <br>
//!
//! <small>«*I believe in a universe that doesn't care and people
//! who do. [...] but this whale is pretty cool.* ― Angus</small>
use futures_lite::Stream;
use pin_project_lite::pin_project;
use stream_utils::Dedup;
#[macro_use]
mod impls;
mod interest;
pub use interest::*;
#[cfg(feature = "callback")]
mod async_rt;
#[cfg(feature = "callback")]
mod callback;
#[cfg(feature = "callback")]
pub use callback::*;
#[cfg(feature = "accent-color")]
mod color;
#[cfg(feature = "accent-color")]
pub use color::*;
mod stream_utils;
/// # Feature Flags
///
/// * `epaint`—Enable converting from [`Srgba`] to [`epaint::Color32`].
/// * `bevy_color`—Enable converting from [`Srgba`] to [`bevy_color::Srgba`].
/// * `log`—Enable logging.
/// * `callback`—Enable the synchronous [`Preferences::subscribe`] function (*default*).
/// * `color-scheme`—Enable support for [`ColorScheme`] (*default*).
/// * `contrast`—Enable support for [`Contrast`] (*default*).
/// * `reduced-motion`—Enable support for [`ReducedMotion`] (*default*).
/// * `reduced-transparency`—Enable support for [`ReducedTransparency`] (*default*).
/// * `accent-color`—Enable support for [`AccentColor`] (*default*).
/// * `double-click-interval`—Enable support for [`DoubleClickInterval`] (*default*).
/// * (Linux) `async-io`—Use `zbus` with `async-io` (*default*).
/// * (Linux) `tokio`—Use `zbus` with `tokio` instead of `async-io`.
#[cfg(doc)]
#[cfg_attr(docsrs, doc(cfg(doc)))]
pub mod feature_flags {}
#[cfg(doctest)]
#[doc = include_str!("../readme.md")]
pub mod readme_doctest {}
/// A collection of preferences retrieved by calling either
/// [`Preferences::stream`] or [`Preferences::subscribe`].
///
/// Which fields are filled in is determined by the [`Interest`]
/// you provide when creating a stream or subscription.
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct Preferences {
/// The user's preference for either light or dark mode.
#[cfg(feature = "color-scheme")]
pub color_scheme: ColorScheme,
/// The user's preferred contrast level.
#[cfg(feature = "contrast")]
pub contrast: Contrast,
/// The user's reduced motion preference.
#[cfg(feature = "reduced-motion")]
pub reduced_motion: ReducedMotion,
/// The user's reduced transparency preference.
#[cfg(feature = "reduced-transparency")]
pub reduced_transparency: ReducedTransparency,
/// The user's current system wide accent color preference.
#[cfg(feature = "accent-color")]
pub accent_color: AccentColor,
/// The maximum amount of time that may occur between the first and second click
/// event for it to count as double click.
#[cfg(feature = "double-click-interval")]
pub double_click_interval: DoubleClickInterval,
}
impl Preferences {
/// Creates a new stream for a selection of system preferences given by `interests`.
/// Should be called from the main thread *after* setting up an event loop (e.g. using winit).
///
/// The stream is guaranteed to contain at least one item with the initial preferences.
///
/// You can use [`Preferences::subscribe`] if you don't want to manage
/// spawning an async runtime yourself.
///
#[doc = include_str!("doc/caveats.md")]
pub fn stream(interest: Interest) -> PreferencesStream {
// TODO: handle empty interest
PreferencesStream {
inner: Dedup::new(imp::stream(interest)),
}
}
}
pin_project! {
/// A stream that continually yields preferences
/// whenever they are changed. Created by [`Preferences::stream()`].
pub struct PreferencesStream {
#[pin] inner: Dedup<imp::PreferencesStream>,
}
}
#[cfg(test)]
static_assertions::assert_impl_all!(PreferencesStream: Send);
impl Stream for PreferencesStream {
type Item = Preferences;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
let this = self.project();
this.inner.poll_next(cx).map(|o| o.map(Preferences::from))
}
}
impls! {
#[cfg(target_os = "linux")]
mod freedesktop supports {
"color-scheme" color_scheme,
"contrast" contrast,
"reduced-motion" reduced_motion,
"accent-color" accent_color,
"double-click-interval" double_click_interval,
};
#[cfg(windows)]
mod windows supports {
"color-scheme" color_scheme,
"contrast" contrast,
"reduced-motion" reduced_motion,
"accent-color" accent_color,
"reduced-transparency" reduced_transparency,
"double-click-interval" double_click_interval,
};
#[cfg(target_os = "macos")]
mod macos supports {
"color-scheme" color_scheme,
"contrast" contrast,
"reduced-motion" reduced_motion,
"reduced-transparency" reduced_transparency,
"accent-color" accent_color,
"double-click-interval" double_click_interval,
};
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
mod web supports {
"color-scheme" color_scheme,
"contrast" contrast,
"reduced-motion" reduced_motion,
"accent-color" accent_color,
"reduced-transparency" reduced_transparency,
};
}
/// The user's preference for either light or dark mode.
///
/// See also <https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme>.
///
/// ## Sources
/// * Linux: `org.freedesktop.appearance color-scheme` from the [XDG Settings portal][xdg].
/// * Windows: [`UISettings.GetColorValue(UIColorType::Foreground)`](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/ui/apply-windows-themes#know-when-dark-mode-is-enabled)
/// * macOS: `NSApplication.effectiveAppearance`
/// * Web: `@media (prefers-color-scheme: ...)`
///
/// [xdg]: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.Settings.html
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg(feature = "color-scheme")]
pub enum ColorScheme {
/// Indicates that the user has not expressed an active preference,
/// that the current platform doesn't support a color scheme preference
/// or that an error occurred while trying to retrieve the preference.
#[default]
NoPreference,
/// Indicates that the user prefers an interface with a light appearance.
Light,
/// Indicates that the user prefers an interface with a dark appearance.
Dark,
}
/// The user's preferred contrast level.
///
/// See also <https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast>
///
/// ## Sources
/// * Linux: `org.freedesktop.appearance contrast` from the [XDG Settings portal][xdg].
/// * Windows: [`AccessibilitySettings.HighContrast`](https://learn.microsoft.com/en-us/uwp/api/windows.ui.viewmanagement.accessibilitysettings.highcontrast)
/// * macOS: [`accessibilityDisplayShouldIncreaseContrast`](https://developer.apple.com/documentation/appkit/nsworkspace/1526290-accessibilitydisplayshouldincrea)
/// * Web: `@media (prefers-contrast: ...)`
///
/// [xdg]: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.Settings.html
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg(feature = "contrast")]
pub enum Contrast {
/// Indicates that the user has not expressed an active preference,
/// that the current platform doesn't support a contrast preference
/// or that an error occurred while trying to retrieve the preference.
#[default]
NoPreference,
/// Indicates that the user prefers an interface with a higher level of contrast.
More,
/// Indicates that the user prefers an interface with a lower level of contrast.
Less,
/// Indicates that the user has configured a specific set of colors (forced color mode)
/// and the contrast from these colors neither matches [`Contrast::More`] or [`Contrast::Less`].
Custom,
}
/// The user prefers to have a minimal amount
/// of motion. Especially motion that simulates the third dimension.
///
/// Such motion can cause discomfort to people with [vestibular disorders](https://www.a11yproject.com/posts/understanding-vestibular-disorders/).
///
/// See also <https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion>.
///
/// ## Sources
/// * Linux (GNOME-only): `org.gnome.desktop.interface enable-animations` from the [XDG Settings portal][xdg].
/// * Windows: [`UISettings.AnimationsEnabled`](https://learn.microsoft.com/en-us/uwp/api/windows.ui.viewmanagement.uisettings.animationsenabled)
/// * macOS: [`accessibilityDisplayShouldReduceMotion`](https://developer.apple.com/documentation/appkit/nsworkspace/1644069-accessibilitydisplayshouldreduce)
/// * Web: `@media (prefers-reduced-motion: ...)`
///
/// [xdg]: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.Settings.html
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg(feature = "reduced-motion")]
pub enum ReducedMotion {
/// Indicates that the user has not expressed an active preference,
/// that the current platform doesn't support a reduced motion preference
/// or that an error occurred while trying to retrieve the preference.
#[default]
NoPreference,
/// Indicates that the user prefers a minimal amount of motion.
Reduce,
}
/// Indicates that applications should not use transparent or semitransparent backgrounds.
///
/// See also <https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-transparency>.
///
/// ## Sources
/// * Windows: [`UISettings.AdvancedEffectsEnabled`](https://learn.microsoft.com/en-us/uwp/api/windows.ui.viewmanagement.uisettings.advancedeffectsenabled)
/// * macOS: [`accessibilityDisplayShouldReduceTransparency`](https://developer.apple.com/documentation/appkit/nsworkspace/1533006-accessibilitydisplayshouldreduce)
/// * Web: `@media (prefers-reduced-transparency: ...)`
///
/// [xdg]: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.Settings.html
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg(feature = "reduced-transparency")]
pub enum ReducedTransparency {
/// Indicates that the user has not expressed an active preference,
/// that the current platform doesn't support a reduced transparency preference
/// or that an error occurred while trying to retrieve the preference.
#[default]
NoPreference,
/// Indicates that the user prefers an interface with no transparent
/// or semitransparent backgrounds.
Reduce,
}
/// The user's current system wide accent color preference.
///
/// ## Sources
/// * Linux: `org.freedesktop.appearance accent-color` from the [XDG Settings portal][xdg].
/// * Windows: [`UISettings.GetColorValue(UIColorType::Accent)`](https://learn.microsoft.com/en-us/uwp/api/windows.ui.viewmanagement.uisettings)
/// * macOS: [`NSColor.controlAccentColor`](https://developer.apple.com/documentation/appkit/nscolor/3000782-controlaccentcolor)
/// * Web: The [`AccentColor`](https://developer.mozilla.org/en-US/docs/Web/CSS/system-color#accentcolor) system color.
///
/// [xdg]: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.Settings.html
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg(feature = "accent-color")]
pub struct AccentColor(pub Option<Srgba>);
/// The maximum amount of time that may occur between the first and second click
/// event for it to count as double click.
///
/// A typical value for this setting is ~500 ms.
///
/// ## Sources
/// * Linux (GNOME-only): `org.gnome.desktop.peripherals.mouse double-click` from the [XDG Settings portal][xdg].
/// * Windows: [`GetDoubleClickTime`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdoubleclicktime)
/// * macOS: [`NSEvent.doubleClickInterval`](https://developer.apple.com/documentation/appkit/nsevent/1528384-doubleclickinterval)
/// * Web: Unsupported
///
/// [xdg]: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.Settings.html
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg(feature = "double-click-interval")]
pub struct DoubleClickInterval(pub Option<std::time::Duration>);
// TODO: Windows also has a double click size:
// https://github.com/dotnet/winforms/blob/7376e50c5a762131398992def2e76f4586fe5025/src/System.Windows.Forms/src/System/Windows/Forms/SystemInformation.cs#L263
// https://github.com/dotnet/winforms/blob/7376e50c5a762131398992def2e76f4586fe5025/src/System.Windows.Forms/src/System/Windows/Forms/SystemInformation.cs#L263