diff --git a/Cargo.lock b/Cargo.lock index 5c51c74b..d5460b15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 1829ea61..b792e765 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/crates/komorebi-macos-core/Cargo.toml b/crates/komorebi-macos-core/Cargo.toml new file mode 100644 index 00000000..b237c94e --- /dev/null +++ b/crates/komorebi-macos-core/Cargo.toml @@ -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 } diff --git a/crates/komorebi-macos/src/animation/lerp.rs b/crates/komorebi-macos-core/src/animation/lerp.rs similarity index 96% rename from crates/komorebi-macos/src/animation/lerp.rs rename to crates/komorebi-macos-core/src/animation/lerp.rs index 2ee9e15d..cc456215 100644 --- a/crates/komorebi-macos/src/animation/lerp.rs +++ b/crates/komorebi-macos-core/src/animation/lerp.rs @@ -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; diff --git a/crates/komorebi-macos-core/src/animation/mod.rs b/crates/komorebi-macos-core/src/animation/mod.rs new file mode 100644 index 00000000..e3b3d398 --- /dev/null +++ b/crates/komorebi-macos-core/src/animation/mod.rs @@ -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; diff --git a/crates/komorebi-macos/src/animation/stats.rs b/crates/komorebi-macos-core/src/animation/stats.rs similarity index 100% rename from crates/komorebi-macos/src/animation/stats.rs rename to crates/komorebi-macos-core/src/animation/stats.rs diff --git a/crates/komorebi-macos/src/animation/step.rs b/crates/komorebi-macos-core/src/animation/step.rs similarity index 99% rename from crates/komorebi-macos/src/animation/step.rs rename to crates/komorebi-macos-core/src/animation/step.rs index dc0edc81..c0f1ed13 100644 --- a/crates/komorebi-macos/src/animation/step.rs +++ b/crates/komorebi-macos-core/src/animation/step.rs @@ -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; diff --git a/crates/komorebi-macos/src/animation/style.rs b/crates/komorebi-macos-core/src/animation/style.rs similarity index 99% rename from crates/komorebi-macos/src/animation/style.rs rename to crates/komorebi-macos-core/src/animation/style.rs index 021004d5..d2b7bbef 100644 --- a/crates/komorebi-macos/src/animation/style.rs +++ b/crates/komorebi-macos-core/src/animation/style.rs @@ -1,4 +1,4 @@ -use crate::core::animation::AnimationStyle; +use komorebi_protocol::animation::AnimationStyle; use std::f64::consts::PI; diff --git a/crates/komorebi-macos/src/animation/throttle.rs b/crates/komorebi-macos-core/src/animation/throttle.rs similarity index 100% rename from crates/komorebi-macos/src/animation/throttle.rs rename to crates/komorebi-macos-core/src/animation/throttle.rs diff --git a/crates/komorebi-macos/src/animation/tracks.rs b/crates/komorebi-macos-core/src/animation/tracks.rs similarity index 99% rename from crates/komorebi-macos/src/animation/tracks.rs rename to crates/komorebi-macos-core/src/animation/tracks.rs index 2bcab0e5..29ae9f9e 100644 --- a/crates/komorebi-macos/src/animation/tracks.rs +++ b/crates/komorebi-macos-core/src/animation/tracks.rs @@ -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; diff --git a/crates/komorebi-macos/src/animation/transition.rs b/crates/komorebi-macos-core/src/animation/transition.rs similarity index 98% rename from crates/komorebi-macos/src/animation/transition.rs rename to crates/komorebi-macos-core/src/animation/transition.rs index 11de9414..e0f11eda 100644 --- a/crates/komorebi-macos/src/animation/transition.rs +++ b/crates/komorebi-macos-core/src/animation/transition.rs @@ -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. diff --git a/crates/komorebi-macos/src/arrival.rs b/crates/komorebi-macos-core/src/arrival.rs similarity index 99% rename from crates/komorebi-macos/src/arrival.rs rename to crates/komorebi-macos-core/src/arrival.rs index 1a410f79..c7983d17 100644 --- a/crates/komorebi-macos/src/arrival.rs +++ b/crates/komorebi-macos-core/src/arrival.rs @@ -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; diff --git a/crates/komorebi-macos-core/src/enhanced_ui.rs b/crates/komorebi-macos-core/src/enhanced_ui.rs new file mode 100644 index 00000000..a571503d --- /dev/null +++ b/crates/komorebi-macos-core/src/enhanced_ui.rs @@ -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; + + /// 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, +} + +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>, + reads: Cell, + writes: Cell, + fail_writes: Cell, + } + + impl Fake { + fn new(value: Option) -> 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 { + 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); + } +} diff --git a/crates/komorebi-macos-core/src/lib.rs b/crates/komorebi-macos-core/src/lib.rs new file mode 100644 index 00000000..df1895ab --- /dev/null +++ b/crates/komorebi-macos-core/src/lib.rs @@ -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; diff --git a/crates/komorebi-macos/Cargo.toml b/crates/komorebi-macos/Cargo.toml index 1c0032b5..0e7301b3 100644 --- a/crates/komorebi-macos/Cargo.toml +++ b/crates/komorebi-macos/Cargo.toml @@ -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", diff --git a/crates/komorebi-macos/src/accessibility/enhanced_ui.rs b/crates/komorebi-macos/src/accessibility/enhanced_ui.rs index 22cb3ba7..8e7ca379 100644 --- a/crates/komorebi-macos/src/accessibility/enhanced_ui.rs +++ b/crates/komorebi-macos/src/accessibility/enhanced_ui.rs @@ -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; - - /// 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, -} - -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>, - reads: Cell, - writes: Cell, - fail_writes: Cell, - } - - impl Fake { - fn new(value: Option) -> 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 { - 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); - } -} diff --git a/crates/komorebi-macos/src/animation/mod.rs b/crates/komorebi-macos/src/animation/mod.rs index 104ba7ca..b3dd5ee2 100644 --- a/crates/komorebi-macos/src/animation/mod.rs +++ b/crates/komorebi-macos/src/animation/mod.rs @@ -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; diff --git a/crates/komorebi-macos/src/lib.rs b/crates/komorebi-macos/src/lib.rs index e91cca6c..17af2b72 100644 --- a/crates/komorebi-macos/src/lib.rs +++ b/crates/komorebi-macos/src/lib.rs @@ -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; diff --git a/deny.toml b/deny.toml index 4e065b1a..4fd261ce 100644 --- a/deny.toml +++ b/deny.toml @@ -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" diff --git a/dependencies.json b/dependencies.json index 40bd94d8..2fa84d88 100644 --- a/dependencies.json +++ b/dependencies.json @@ -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", diff --git a/tools/test-partition.json b/tools/test-partition.json index 2798e147..fdc93466 100644 --- a/tools/test-partition.json +++ b/tools/test-partition.json @@ -5,6 +5,7 @@ "komorebi-collections", "komorebi-core", "komorebi-layouts", + "komorebi-macos-core", "komorebi-scrolling", "komorebi-settings", "komorebi-themes",