mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-13 12:23:38 +01:00
refactor(macos): the animation maths, the arrival record and the enhanced-UI rules move into komorebi-macos-core, which needs no macOS
This commit is contained in:
1 parent
b83cf06cc6
commit
8e1a2b261c
21 files changed
+324
-245
No files matched your search
Generated
+11
@@ -3872,6 +3872,7 @@ dependencies = [
|
||||
"komorebi-config",
|
||||
"komorebi-core",
|
||||
"komorebi-layouts",
|
||||
"komorebi-macos-core",
|
||||
"komorebi-protocol",
|
||||
"komorebi-settings",
|
||||
"komorebi-themes",
|
||||
@@ -3905,6 +3906,16 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komorebi-macos-core"
|
||||
version = "0.2.0-dev"
|
||||
dependencies = [
|
||||
"komorebi-layouts",
|
||||
"komorebi-protocol",
|
||||
"parking_lot 0.12.5",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "komorebi-protocol"
|
||||
version = "0.2.0-dev"
|
||||
|
||||
@@ -28,6 +28,7 @@ members = [
|
||||
# `default-members` below is the Windows set that a bare cargo command builds
|
||||
"crates/komorebi-linux",
|
||||
"crates/komorebi-macos",
|
||||
"crates/komorebi-macos-core",
|
||||
"crates/komorebi-hotkeys",
|
||||
"crates/komorebi-version",
|
||||
"komorebi-recorder",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "komorebi-macos-core"
|
||||
version.workspace = true
|
||||
description = "The platform-neutral parts of komorebi-macos (animation maths, arrival record, accessibility rules), tested on every platform"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
komorebi-layouts = { path = "../komorebi-layouts" }
|
||||
komorebi-protocol = { path = "../komorebi-protocol", default-features = false }
|
||||
parking_lot = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
use crate::core::Rect;
|
||||
use crate::core::animation::AnimationStyle;
|
||||
use komorebi_layouts::Rect;
|
||||
use komorebi_protocol::animation::AnimationStyle;
|
||||
|
||||
use super::style::apply_ease_func;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
//! The pure part of the window animations: the easing functions, the motion of a frame, the tracks a
|
||||
//! window follows, and the throttle on the frames. Nothing here reads a clock it was not given or
|
||||
//! talks to the window server.
|
||||
|
||||
pub mod lerp;
|
||||
pub mod stats;
|
||||
pub mod step;
|
||||
pub mod style;
|
||||
pub mod throttle;
|
||||
pub mod tracks;
|
||||
pub mod transition;
|
||||
File renamed without changes.
+2
-2
@@ -4,8 +4,8 @@
|
||||
//! can be tested deterministically and the animator is free to decide when a frame happens.
|
||||
|
||||
use super::style::apply_ease_func;
|
||||
use crate::core::Rect;
|
||||
use crate::core::animation::AnimationStyle;
|
||||
use komorebi_layouts::Rect;
|
||||
use komorebi_protocol::animation::AnimationStyle;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::core::animation::AnimationStyle;
|
||||
use komorebi_protocol::animation::AnimationStyle;
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
File renamed without changes.
+2
-2
@@ -10,8 +10,8 @@ use super::step::FrameF;
|
||||
use super::step::Motion;
|
||||
use super::step::MotionSpec;
|
||||
use super::step::step;
|
||||
use crate::core::Rect;
|
||||
use crate::core::animation::AnimationStyle;
|
||||
use komorebi_layouts::Rect;
|
||||
use komorebi_protocol::animation::AnimationStyle;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
+3
-3
@@ -6,9 +6,9 @@
|
||||
//! rects, so that it can be tested without a screen.
|
||||
|
||||
use crate::animation::style::apply_ease_func;
|
||||
use crate::core::OperationDirection;
|
||||
use crate::core::Rect;
|
||||
use crate::core::animation::AnimationStyle;
|
||||
use komorebi_layouts::OperationDirection;
|
||||
use komorebi_layouts::Rect;
|
||||
use komorebi_protocol::animation::AnimationStyle;
|
||||
|
||||
/// Which side of the monitor the workspace that is entered comes in from. The workspace that is
|
||||
/// left goes out on the other side.
|
||||
@@ -10,7 +10,7 @@
|
||||
//! this module waits for anything or takes the lock of the window manager, so it can be called
|
||||
//! from any thread. The layout picks up what has been said the next time that it runs.
|
||||
|
||||
use crate::core::Rect;
|
||||
use komorebi_layouts::Rect;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
@@ -0,0 +1,243 @@
|
||||
//! The rules for stopping apps from animating the moves and resizes we make
|
||||
//!
|
||||
//! This is the state machine of `komorebi-macos`'s `accessibility::enhanced_ui`: the attribute is read and
|
||||
//! written through [`Flag`], which the macOS side implements over the accessibility API, so nothing here
|
||||
//! needs macOS.
|
||||
//!
|
||||
//! When `AXEnhancedUserInterface` is on for an application, the app animates every move and resize
|
||||
//! it is asked to make over AX, which turns each of our frames into a slow animation of its own. The
|
||||
//! attribute belongs to the *application* element, not to a window, and it is shared by all of that
|
||||
//! application's windows, so turning it off has to be reference counted per process: the last window
|
||||
//! to finish puts it back.
|
||||
//!
|
||||
//! Almost no app has the attribute on, so the common case must cost nothing: once an app has been
|
||||
//! seen not to have it, it is left alone for a while without asking again.
|
||||
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
/// How long an app that does not have the attribute on is left alone before it is asked again
|
||||
pub const QUIET_PERIOD: Duration = Duration::from_secs(10);
|
||||
|
||||
/// The attribute, as seen by the state machine
|
||||
pub trait Flag {
|
||||
/// The current value, or `None` if the app has no such attribute or did not answer
|
||||
fn read(&self) -> Option<bool>;
|
||||
|
||||
/// Returns whether the write succeeded
|
||||
fn write(&self, value: bool) -> bool;
|
||||
}
|
||||
|
||||
/// What is known about one process: who relies on the attribute being off, and what is owed back to the app
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State {
|
||||
/// How many windows are currently relying on the attribute being off
|
||||
pub active: u32,
|
||||
/// The attribute may be off because of us, so it is owed back to the app
|
||||
///
|
||||
/// Setters can change app state before they report an error, so this is set before the attribute
|
||||
/// is touched rather than after. It also stops us reading our own `false` back later and
|
||||
/// mistaking it for the app's own setting.
|
||||
pub restore_pending: bool,
|
||||
pub quiet_until: Option<Instant>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Returns whether the caller now holds a reference that it has to [`State::release`]
|
||||
pub fn acquire(&mut self, flag: &impl Flag, now: Instant) -> bool {
|
||||
if self.active > 0 {
|
||||
self.active += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if !self.restore_pending {
|
||||
if self.quiet_until.is_some_and(|until| now < until) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if flag.read() != Some(true) {
|
||||
self.quiet_until = Some(now + QUIET_PERIOD);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self.restore_pending = true;
|
||||
flag.write(false);
|
||||
self.active = 1;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn release(&mut self, flag: &impl Flag) {
|
||||
self.active = self.active.saturating_sub(1);
|
||||
|
||||
if self.active == 0 && self.restore_pending && flag.write(true) {
|
||||
self.restore_pending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::cell::Cell;
|
||||
|
||||
struct Fake {
|
||||
value: Cell<Option<bool>>,
|
||||
reads: Cell<u32>,
|
||||
writes: Cell<u32>,
|
||||
fail_writes: Cell<bool>,
|
||||
}
|
||||
|
||||
impl Fake {
|
||||
fn new(value: Option<bool>) -> Self {
|
||||
Self {
|
||||
value: Cell::new(value),
|
||||
reads: Cell::new(0),
|
||||
writes: Cell::new(0),
|
||||
fail_writes: Cell::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Flag for Fake {
|
||||
fn read(&self) -> Option<bool> {
|
||||
self.reads.set(self.reads.get() + 1);
|
||||
self.value.get()
|
||||
}
|
||||
|
||||
fn write(&self, value: bool) -> bool {
|
||||
self.writes.set(self.writes.get() + 1);
|
||||
|
||||
if self.fail_writes.get() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.value.set(Some(value));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_app_with_the_attribute_on_has_it_turned_off_and_back_on() {
|
||||
let flag = Fake::new(Some(true));
|
||||
let mut state = State::default();
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(state.acquire(&flag, now));
|
||||
assert_eq!(flag.value.get(), Some(false));
|
||||
|
||||
state.release(&flag);
|
||||
assert_eq!(flag.value.get(), Some(true));
|
||||
assert!(!state.restore_pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_windows_share_one_disable_and_one_restore() {
|
||||
let flag = Fake::new(Some(true));
|
||||
let mut state = State::default();
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(state.acquire(&flag, now));
|
||||
assert!(state.acquire(&flag, now));
|
||||
assert_eq!(flag.writes.get(), 1);
|
||||
assert_eq!(flag.reads.get(), 1);
|
||||
|
||||
state.release(&flag);
|
||||
assert_eq!(flag.value.get(), Some(false));
|
||||
|
||||
state.release(&flag);
|
||||
assert_eq!(flag.value.get(), Some(true));
|
||||
assert_eq!(flag.writes.get(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_app_without_the_attribute_costs_nothing_after_the_first_look() {
|
||||
let flag = Fake::new(None);
|
||||
let mut state = State::default();
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(!state.acquire(&flag, now));
|
||||
assert!(!state.acquire(&flag, now + Duration::from_secs(1)));
|
||||
assert!(!state.acquire(&flag, now + Duration::from_secs(9)));
|
||||
|
||||
assert_eq!(flag.reads.get(), 1);
|
||||
assert_eq!(flag.writes.get(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_app_with_the_attribute_off_is_left_alone() {
|
||||
let flag = Fake::new(Some(false));
|
||||
let mut state = State::default();
|
||||
|
||||
assert!(!state.acquire(&flag, Instant::now()));
|
||||
assert_eq!(flag.writes.get(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_app_is_asked_again_after_the_quiet_period() {
|
||||
let flag = Fake::new(Some(false));
|
||||
let mut state = State::default();
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(!state.acquire(&flag, now));
|
||||
|
||||
// somebody turned on VoiceOver in the meantime
|
||||
flag.value.set(Some(true));
|
||||
assert!(!state.acquire(&flag, now + QUIET_PERIOD - Duration::from_secs(1)));
|
||||
assert!(state.acquire(&flag, now + QUIET_PERIOD + Duration::from_secs(1)));
|
||||
assert_eq!(flag.value.get(), Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_restore_is_owed_and_never_mistaken_for_the_apps_own_setting() {
|
||||
let flag = Fake::new(Some(true));
|
||||
let mut state = State::default();
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(state.acquire(&flag, now));
|
||||
|
||||
flag.fail_writes.set(true);
|
||||
state.release(&flag);
|
||||
|
||||
assert!(state.restore_pending);
|
||||
assert_eq!(flag.value.get(), Some(false));
|
||||
|
||||
// the next window must not read the false we left behind and conclude the app never had it on
|
||||
flag.fail_writes.set(false);
|
||||
let reads = flag.reads.get();
|
||||
assert!(state.acquire(&flag, now + Duration::from_secs(1)));
|
||||
assert_eq!(flag.reads.get(), reads);
|
||||
|
||||
state.release(&flag);
|
||||
assert_eq!(flag.value.get(), Some(true));
|
||||
assert!(!state.restore_pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_disable_is_still_owed_back() {
|
||||
let flag = Fake::new(Some(true));
|
||||
flag.fail_writes.set(true);
|
||||
let mut state = State::default();
|
||||
|
||||
// the setter may have changed the app before it reported the error
|
||||
assert!(state.acquire(&flag, Instant::now()));
|
||||
assert!(state.restore_pending);
|
||||
|
||||
flag.fail_writes.set(false);
|
||||
state.release(&flag);
|
||||
|
||||
assert_eq!(flag.value.get(), Some(true));
|
||||
assert!(!state.restore_pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn releasing_more_than_was_acquired_is_harmless() {
|
||||
let flag = Fake::new(Some(true));
|
||||
let mut state = State::default();
|
||||
|
||||
state.release(&flag);
|
||||
assert_eq!(flag.writes.get(), 0);
|
||||
assert_eq!(state.active, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//! The parts of `komorebi-macos` that need nothing from macOS.
|
||||
//!
|
||||
//! The animation maths, the record of what windows turned out to be, and the rules for the
|
||||
//! accessibility attribute that makes apps animate on their own live here so that their tests run on
|
||||
//! every platform (a Linux CI runner included), not only on a Mac: `komorebi-macos` pulls in the
|
||||
//! Objective-C runtime crates, which refuse to compile anywhere else.
|
||||
//!
|
||||
//! The rule of the crate is that it has no dependency on `objc2`, `block2`, `dispatch2` or any
|
||||
//! binding of an Apple framework, so a module that would need one does not belong in it.
|
||||
//! `komorebi-macos` re-exports each module at the path it had before, so nothing that uses them
|
||||
//! changed.
|
||||
|
||||
pub mod animation;
|
||||
pub mod arrival;
|
||||
pub mod enhanced_ui;
|
||||
@@ -9,6 +9,7 @@ komorebi-themes = { path = "../komorebi-themes" }
|
||||
komorebi-backend = { path = "../komorebi-backend" }
|
||||
komorebi-collections = { path = "../komorebi-collections" }
|
||||
komorebi-core = { path = "../komorebi-core" }
|
||||
komorebi-macos-core = { path = "../komorebi-macos-core" }
|
||||
komorebi-layouts = { path = "../komorebi-layouts", features = [
|
||||
"darwin",
|
||||
"schemars",
|
||||
|
||||
@@ -12,73 +12,14 @@
|
||||
use crate::AccessibilityUiElement;
|
||||
use crate::accessibility::private::get_enhanced_user_interface_if_present;
|
||||
use crate::accessibility::private::set_enhanced_user_interface;
|
||||
use komorebi_macos_core::enhanced_ui::Flag;
|
||||
use komorebi_macos_core::enhanced_ui::State;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::LazyLock;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
/// How long an app that does not have the attribute on is left alone before it is asked again
|
||||
const QUIET_PERIOD: Duration = Duration::from_secs(10);
|
||||
|
||||
/// The attribute, as seen by the state machine
|
||||
pub trait Flag {
|
||||
/// The current value, or `None` if the app has no such attribute or did not answer
|
||||
fn read(&self) -> Option<bool>;
|
||||
|
||||
/// Returns whether the write succeeded
|
||||
fn write(&self, value: bool) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct State {
|
||||
/// How many windows are currently relying on the attribute being off
|
||||
active: u32,
|
||||
/// The attribute may be off because of us, so it is owed back to the app
|
||||
///
|
||||
/// Setters can change app state before they report an error, so this is set before the attribute
|
||||
/// is touched rather than after. It also stops us reading our own `false` back later and
|
||||
/// mistaking it for the app's own setting.
|
||||
restore_pending: bool,
|
||||
quiet_until: Option<Instant>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Returns whether the caller now holds a reference that it has to [`State::release`]
|
||||
fn acquire(&mut self, flag: &impl Flag, now: Instant) -> bool {
|
||||
if self.active > 0 {
|
||||
self.active += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if !self.restore_pending {
|
||||
if self.quiet_until.is_some_and(|until| now < until) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if flag.read() != Some(true) {
|
||||
self.quiet_until = Some(now + QUIET_PERIOD);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self.restore_pending = true;
|
||||
flag.write(false);
|
||||
self.active = 1;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn release(&mut self, flag: &impl Flag) {
|
||||
self.active = self.active.saturating_sub(1);
|
||||
|
||||
if self.active == 0 && self.restore_pending && flag.write(true) {
|
||||
self.restore_pending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AxFlag(AccessibilityUiElement);
|
||||
|
||||
impl Flag for AxFlag {
|
||||
@@ -127,168 +68,3 @@ pub fn disable(pid: i32, app: &AccessibilityUiElement) -> Option<EnhancedUiGuard
|
||||
pub fn forget(pid: i32) {
|
||||
STATES.lock().remove(&pid);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::cell::Cell;
|
||||
|
||||
struct Fake {
|
||||
value: Cell<Option<bool>>,
|
||||
reads: Cell<u32>,
|
||||
writes: Cell<u32>,
|
||||
fail_writes: Cell<bool>,
|
||||
}
|
||||
|
||||
impl Fake {
|
||||
fn new(value: Option<bool>) -> Self {
|
||||
Self {
|
||||
value: Cell::new(value),
|
||||
reads: Cell::new(0),
|
||||
writes: Cell::new(0),
|
||||
fail_writes: Cell::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Flag for Fake {
|
||||
fn read(&self) -> Option<bool> {
|
||||
self.reads.set(self.reads.get() + 1);
|
||||
self.value.get()
|
||||
}
|
||||
|
||||
fn write(&self, value: bool) -> bool {
|
||||
self.writes.set(self.writes.get() + 1);
|
||||
|
||||
if self.fail_writes.get() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.value.set(Some(value));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_app_with_the_attribute_on_has_it_turned_off_and_back_on() {
|
||||
let flag = Fake::new(Some(true));
|
||||
let mut state = State::default();
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(state.acquire(&flag, now));
|
||||
assert_eq!(flag.value.get(), Some(false));
|
||||
|
||||
state.release(&flag);
|
||||
assert_eq!(flag.value.get(), Some(true));
|
||||
assert!(!state.restore_pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_windows_share_one_disable_and_one_restore() {
|
||||
let flag = Fake::new(Some(true));
|
||||
let mut state = State::default();
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(state.acquire(&flag, now));
|
||||
assert!(state.acquire(&flag, now));
|
||||
assert_eq!(flag.writes.get(), 1);
|
||||
assert_eq!(flag.reads.get(), 1);
|
||||
|
||||
state.release(&flag);
|
||||
assert_eq!(flag.value.get(), Some(false));
|
||||
|
||||
state.release(&flag);
|
||||
assert_eq!(flag.value.get(), Some(true));
|
||||
assert_eq!(flag.writes.get(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_app_without_the_attribute_costs_nothing_after_the_first_look() {
|
||||
let flag = Fake::new(None);
|
||||
let mut state = State::default();
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(!state.acquire(&flag, now));
|
||||
assert!(!state.acquire(&flag, now + Duration::from_secs(1)));
|
||||
assert!(!state.acquire(&flag, now + Duration::from_secs(9)));
|
||||
|
||||
assert_eq!(flag.reads.get(), 1);
|
||||
assert_eq!(flag.writes.get(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_app_with_the_attribute_off_is_left_alone() {
|
||||
let flag = Fake::new(Some(false));
|
||||
let mut state = State::default();
|
||||
|
||||
assert!(!state.acquire(&flag, Instant::now()));
|
||||
assert_eq!(flag.writes.get(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_app_is_asked_again_after_the_quiet_period() {
|
||||
let flag = Fake::new(Some(false));
|
||||
let mut state = State::default();
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(!state.acquire(&flag, now));
|
||||
|
||||
// somebody turned on VoiceOver in the meantime
|
||||
flag.value.set(Some(true));
|
||||
assert!(!state.acquire(&flag, now + QUIET_PERIOD - Duration::from_secs(1)));
|
||||
assert!(state.acquire(&flag, now + QUIET_PERIOD + Duration::from_secs(1)));
|
||||
assert_eq!(flag.value.get(), Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_restore_is_owed_and_never_mistaken_for_the_apps_own_setting() {
|
||||
let flag = Fake::new(Some(true));
|
||||
let mut state = State::default();
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(state.acquire(&flag, now));
|
||||
|
||||
flag.fail_writes.set(true);
|
||||
state.release(&flag);
|
||||
|
||||
assert!(state.restore_pending);
|
||||
assert_eq!(flag.value.get(), Some(false));
|
||||
|
||||
// the next window must not read the false we left behind and conclude the app never had it on
|
||||
flag.fail_writes.set(false);
|
||||
let reads = flag.reads.get();
|
||||
assert!(state.acquire(&flag, now + Duration::from_secs(1)));
|
||||
assert_eq!(flag.reads.get(), reads);
|
||||
|
||||
state.release(&flag);
|
||||
assert_eq!(flag.value.get(), Some(true));
|
||||
assert!(!state.restore_pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_disable_is_still_owed_back() {
|
||||
let flag = Fake::new(Some(true));
|
||||
flag.fail_writes.set(true);
|
||||
let mut state = State::default();
|
||||
|
||||
// the setter may have changed the app before it reported the error
|
||||
assert!(state.acquire(&flag, Instant::now()));
|
||||
assert!(state.restore_pending);
|
||||
|
||||
flag.fail_writes.set(false);
|
||||
state.release(&flag);
|
||||
|
||||
assert_eq!(flag.value.get(), Some(true));
|
||||
assert!(!state.restore_pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn releasing_more_than_was_acquired_is_harmless() {
|
||||
let flag = Fake::new(Some(true));
|
||||
let mut state = State::default();
|
||||
|
||||
state.release(&flag);
|
||||
assert_eq!(flag.writes.get(), 0);
|
||||
assert_eq!(state.active, 0);
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,18 @@ use step::MotionSpec;
|
||||
pub mod animator;
|
||||
pub mod capture;
|
||||
pub mod ghost;
|
||||
pub mod lerp;
|
||||
pub mod prefix;
|
||||
pub mod stats;
|
||||
pub mod step;
|
||||
pub mod style;
|
||||
pub mod throttle;
|
||||
pub mod tracks;
|
||||
pub mod transition;
|
||||
pub mod workspace_transition;
|
||||
|
||||
// the pure part of the animations lives in komorebi-macos-core, and is where it always was
|
||||
pub use komorebi_macos_core::animation::lerp;
|
||||
pub use komorebi_macos_core::animation::stats;
|
||||
pub use komorebi_macos_core::animation::step;
|
||||
pub use komorebi_macos_core::animation::style;
|
||||
pub use komorebi_macos_core::animation::throttle;
|
||||
pub use komorebi_macos_core::animation::tracks;
|
||||
pub use komorebi_macos_core::animation::transition;
|
||||
|
||||
pub const DEFAULT_ANIMATION_ENABLED: bool = false;
|
||||
pub const DEFAULT_ANIMATION_STYLE: AnimationStyle = AnimationStyle::Linear;
|
||||
pub const DEFAULT_ANIMATION_DURATION: u64 = 250;
|
||||
|
||||
@@ -47,7 +47,7 @@ pub mod accessibility;
|
||||
pub mod animation;
|
||||
pub mod app_kit_notification_constants;
|
||||
pub mod application;
|
||||
pub mod arrival;
|
||||
pub use komorebi_macos_core::arrival;
|
||||
pub mod ax_registry;
|
||||
pub mod border_manager;
|
||||
pub mod container;
|
||||
|
||||
@@ -73,6 +73,11 @@ crate = "komorebi-macos"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-macos-core"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
license-files = []
|
||||
|
||||
[[licenses.clarify]]
|
||||
crate = "komorebi-recorder"
|
||||
expression = "LicenseRef-Komorebi-2.0"
|
||||
|
||||
@@ -617,6 +617,7 @@
|
||||
"komorebi-layouts 0.2.0-dev",
|
||||
"komorebi-linux 0.2.0-dev",
|
||||
"komorebi-macos 0.2.0-dev",
|
||||
"komorebi-macos-core 0.2.0-dev",
|
||||
"komorebi-protocol 0.2.0-dev",
|
||||
"komorebi-recorder 0.1.0-dev",
|
||||
"komorebi-scrolling 0.2.0-dev",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"komorebi-collections",
|
||||
"komorebi-core",
|
||||
"komorebi-layouts",
|
||||
"komorebi-macos-core",
|
||||
"komorebi-scrolling",
|
||||
"komorebi-settings",
|
||||
"komorebi-themes",
|
||||
|
||||
Reference in new issue
Block a user