-
Notifications
You must be signed in to change notification settings - Fork 0
Tiling #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
WalkthroughThis update introduces comprehensive enhancements to the macOS tiling window manager. Major changes include a robust window drag and snapping system, workspace detection, improved accessibility and Core Graphics integration, and a dual logger. New modules for snapping logic and window notifications are added, and significant refactoring occurs in layout, window management, and macOS-specific backends. Documentation and code style are also updated. Changes
Sequence Diagram(s)Window Drag and Snap HandlingsequenceDiagram
participant User
participant WindowManager
participant WindowDragNotificationObserver
participant SnapManager
participant AccessibilityManager
User->>Window: Drag window begins
Window-->>WindowDragNotificationObserver: NSWindowWillMoveNotification
WindowDragNotificationObserver-->>WindowManager: DragStarted event
User->>Window: Drag window ends
Window-->>WindowDragNotificationObserver: NSWindowDidMoveNotification
WindowDragNotificationObserver-->>WindowManager: DragEnded event
WindowManager->>SnapManager: end_window_drag()
alt Snap zone detected
SnapManager-->>WindowManager: SnapToZone or SwapWithWindow
WindowManager->>AccessibilityManager: move_window(s)
else No snap zone
SnapManager-->>WindowManager: ReturnToOriginal
WindowManager->>AccessibilityManager: move_window
end
Layout Application and Window MovementsequenceDiagram
participant WindowManager
participant LayoutManager
participant AccessibilityManager
WindowManager->>LayoutManager: compute_layout()
LayoutManager-->>WindowManager: window_rects
WindowManager->>AccessibilityManager: move_all_windows(window_rects)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90 minutes Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (18)
README.md (1)
234-234: Minor wording nit – consider hyphenating “i3-compatible”.Purely stylistic, but “i3-compatible” is the more common form.
- A Wayland compositor and i3 compatible window manager for Linux + A Wayland compositor and i3-compatible window manager for Linuxsrc/plugins.rs (1)
34-37: Avoid suppressingdead_code; mark unused fields explicitly.Using
#[allow(dead_code)]at field level hides potentially useful warnings project-wide.
If the fields are intentionally unused (for now), prefix them with an underscore instead; that keeps the intent clear without a blanket allow.- #[allow(dead_code)] - name: String, - #[allow(dead_code)] - script_path: PathBuf, + _name: String, + _script_path: PathBuf,src/macos/mod.rs (1)
3-3: Export order nit.Very minor: keeping the module list alphabetically sorted (
accessibility,cgwindow,window_notifications,window_system) is helpful for quick scans in large crates.
No action needed if you don’t enforce alphabetical ordering elsewhere.src/config.rs (1)
308-309: Good idiomatic change; one edge-case still slips through.
parts.is_empty()is clearer thanlen() < 1– nice.
However, a combo like"alt+"yields["alt", ""]; it passes this check but contains an empty key segment.Consider additionally validating that the final part (actual key) is non-empty:
- if parts.is_empty() { + if parts.is_empty() || parts.last().unwrap().is_empty() {That keeps malformed combos from silently entering the config.
src/main.rs (2)
33-82: Well-implemented dual logging system with room for optimization.The
DualLoggerimplementation provides comprehensive logging with timestamps and dual output. The design is solid with proper thread safety and buffering.Consider these potential optimizations:
- Flushing after every log entry (line 72) may impact performance under high log volume. Consider batched flushing or configurable flush intervals.
- Silent error handling for file writes could mask storage issues. Consider logging write failures to stderr.
97-101: Consider using a standard log directory location.The log file is created in the current working directory. Consider using a standard location like
~/.local/share/skew/or/tmp/for better system integration.- let log_file = std::env::current_dir() - .unwrap_or_else(|_| PathBuf::from(".")) - .join("skew.log"); + let log_file = PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".to_string())) + .join(".local/share/skew") + .join("skew.log"); + + // Ensure directory exists + if let Some(parent) = log_file.parent() { + std::fs::create_dir_all(parent)?; + }src/macos/window_system.rs (1)
124-128: Address the TODO comment for configurable window monitoring interval.The TODO indicates this should be configurable via
skew.toml. This would allow users to balance between responsiveness and CPU usage based on their needs.Would you like me to implement the configuration option for
window_monitor_interval_msin the config system and open an issue to track this enhancement?src/macos/window_notifications.rs (1)
422-439: Window owner PID extraction may fail silently.The method attempts to get PID through
window.app.runningApplication, but this chain may be nil at any point. The current implementation returns None gracefully, but consider logging when PID extraction fails for debugging purposes.Add debug logging when PID extraction fails to help diagnose window identification issues.
src/layout.rs (1)
377-398: Consider optimizing BSP tree rebuilding for better performance.Always rebuilding the entire tree from scratch ensures consistency but may impact performance with many windows. Consider implementing incremental updates in the future.
For now, this approach is fine for typical window counts. Monitor performance and consider optimization if users report lag with 10+ windows.
src/snap.rs (2)
58-59: Remove unnecessary#[allow(dead_code)]attribute.The
window_idfield is used in debug logging at line 289, so the dead_code attribute is not needed.- #[allow(dead_code)] window_id: WindowId,
275-279: Update method doesn't track current position during drag.The
update_window_dragmethod receivescurrent_rectbut doesn't use it. Consider tracking the current position for more accurate drag handling.- pub fn update_window_drag(&mut self, window_id: WindowId, _current_rect: Rect) { + pub fn update_window_drag(&mut self, window_id: WindowId, current_rect: Rect) { if let Some(drag_state) = self.window_drag_states.get_mut(&window_id) { drag_state.is_dragging = true; + // Optionally track current position for drag visualization or analytics + // drag_state.current_rect = current_rect; } }src/window_manager.rs (5)
159-165: Track the TODO for configurable intervals.The TODO comment suggests making refresh and monitor intervals configurable, which would be a valuable enhancement for performance tuning.
Would you like me to create an issue to track this configuration enhancement?
603-603: Document or make the resize delay configurable.The 10ms delay seems arbitrary. Consider documenting why this delay is needed or making it configurable.
- // Use a small delay to ensure the resize has completed - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + // Small delay to ensure macOS has completed the resize animation + // before we snap the window back to layout position + tokio::time::sleep(tokio::time::Duration::from_millis( + self.config.snap.resize_settle_delay_ms.unwrap_or(10) + )).await;
643-643: Very low movement threshold may cause performance issues.The 0.5px threshold for detecting movement is extremely sensitive and might trigger too frequently, potentially causing performance issues or unwanted snapping behavior.
- // Very low threshold to catch even small movements (>0.5px) - if distance > 0.5 { + // Reasonable threshold to detect intentional movements (>2.0px) + if distance > self.config.snap.movement_threshold.unwrap_or(2.0) {
885-1066: Consider extracting common swap logic to reduce duplication.Both
swap_windowsandswap_windows_with_rectshave nearly identical bulk move and fallback logic. Consider extracting this to a shared helper method.+ async fn execute_window_swap( + &mut self, + swap_layouts: HashMap<WindowId, Rect>, + windows: Vec<Window>, + ) -> Result<()> { + // Mark all as programmatic moves + for window_id in swap_layouts.keys() { + self.programmatically_moving.insert(*window_id); + } + + // Try bulk move first + match self.macos.move_all_windows(&swap_layouts, &windows).await { + Ok(_) => { + debug!("✅ Successfully swapped windows using bulk move"); + // Update internal state + for (window_id, rect) in &swap_layouts { + if let Some(w) = self.windows.get_mut(window_id) { + w.rect = *rect; + } + self.last_window_positions.insert(*window_id, *rect); + } + } + Err(e) => { + warn!("Bulk swap failed, trying individual moves: {}", e); + // Fallback to individual moves + for (window_id, rect) in &swap_layouts { + match self.macos.move_window(*window_id, *rect).await { + Ok(_) => { + if let Some(w) = self.windows.get_mut(window_id) { + w.rect = *rect; + } + self.last_window_positions.insert(*window_id, *rect); + } + Err(e) => { + warn!("Failed to move window {:?} during swap: {}", window_id, e) + } + } + } + } + } + Ok(()) + }
1315-1315: Consider making batch size configurable.The hardcoded BATCH_SIZE of 2 could be made configurable to allow tuning based on system performance.
- const BATCH_SIZE: usize = 2; // Move 2 windows at a time + let batch_size = self.config.layout.batch_size.unwrap_or(2);src/macos/accessibility.rs (2)
181-183: Consider selective cache refresh for better performance.Refreshing the entire windows cache on every move operation could be expensive. Consider a more targeted refresh approach.
- // Force refresh the cache to ensure we have current windows - if let Err(e) = self.refresh_all_windows_cache() { - warn!("Failed to refresh all windows cache: {}", e); - } + // Try to find in cache first, refresh only if needed + let needs_refresh = !self.window_cache.contains_key(&window_id) || + self.last_cache_update.elapsed() > std::time::Duration::from_secs(1); + + if needs_refresh { + if let Err(e) = self.refresh_all_windows_cache() { + warn!("Failed to refresh all windows cache: {}", e); + } + }
447-447: Document the rationale for 16K PID limit.The cap of 16,384 PIDs seems reasonable but should be documented or made configurable for systems with many processes.
- // Limit buffer growth to prevent excessive memory usage - let new_size = std::cmp::min(buffer.len() * 2, 16384); // Cap at 16K PIDs + // Limit buffer growth to prevent excessive memory usage + // 16K PIDs should be sufficient for most systems (typical: <1000 processes) + const MAX_PIDS: usize = 16384; + let new_size = std::cmp::min(buffer.len() * 2, MAX_PIDS);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (15)
final_improved_test.logis excluded by!**/*.logimproved_fixes_test.logis excluded by!**/*.logmanual_drag_test.logis excluded by!**/*.logskew.logis excluded by!**/*.logskew_debug_test.logis excluded by!**/*.logskew_final_snapping_test.logis excluded by!**/*.logskew_final_test.logis excluded by!**/*.logskew_layout_snap_test.logis excluded by!**/*.logskew_manual_detection_test.logis excluded by!**/*.logskew_manual_test.logis excluded by!**/*.logskew_position_fix_test.logis excluded by!**/*.logskew_test.logis excluded by!**/*.logtest_complete_fix.logis excluded by!**/*.logtest_run.logis excluded by!**/*.logtest_run_fixed.logis excluded by!**/*.log
📒 Files selected for processing (19)
Cargo.toml(1 hunks)README.md(2 hunks)llms.txt(1 hunks)src/bin/skew-cli.rs(0 hunks)src/config.rs(1 hunks)src/focus.rs(1 hunks)src/hotkeys.rs(8 hunks)src/ipc.rs(2 hunks)src/layout.rs(15 hunks)src/lib.rs(1 hunks)src/macos/accessibility.rs(9 hunks)src/macos/cgwindow.rs(8 hunks)src/macos/mod.rs(1 hunks)src/macos/window_notifications.rs(1 hunks)src/macos/window_system.rs(7 hunks)src/main.rs(2 hunks)src/plugins.rs(1 hunks)src/snap.rs(1 hunks)src/window_manager.rs(14 hunks)
💤 Files with no reviewable changes (1)
- src/bin/skew-cli.rs
🧰 Additional context used
🪛 LanguageTool
llms.txt
[style] ~9-~9: This phrase is redundant (‘I’ stands for ‘Interface’). Use simply “CLIInterface”.
Context: ...ssibility APIs. ## Key Components - CLI Interface (main.rs): Command-line entry point...
(ACRONYM_TAUTOLOGY)
🔇 Additional comments (20)
Cargo.toml (1)
26-26: Dependency bump looks good.
objc0.2.7 is still within the semver-compatible 0.2 line, so no API breakage is expected.
Nothing else to flag here.README.md (1)
3-3: Doc tweak LGTM.Switching to “swaywm” clarifies the correct project name. 👍
src/focus.rs (1)
341-344: LGTM! Clean refactor of backward cycling logic.The simplification removes redundant conditional checks while maintaining the correct behavior. The explicit handling of the zero index case makes the logic clearer and more readable.
src/lib.rs (2)
8-8: LGTM! New snap module addition.Adding the
snapmodule exposes the new window snapping management system that's part of the tiling enhancements.
16-16: Good addition of ordering traits to WindowId.Adding
PartialOrdandOrdtraits enables ordering comparisons onWindowIdinstances, which supports consistent window identification and sorting operations across the window management system.src/ipc.rs (1)
153-153: LGTM! More idiomatic argument access.Replacing
message.args.get(0)withmessage.args.first()is more idiomatic and readable while maintaining the same functionality and error handling behavior.Also applies to: 173-173
llms.txt (1)
1-81: Excellent comprehensive project documentation.This documentation provides a thorough overview of the Skew project architecture, components, dependencies, and development workflow. It will be valuable for both contributors and users understanding the system.
src/hotkeys.rs (1)
58-467: Formatting improvements enhance code readability.The formatting changes throughout this file improve consistency and readability without altering functionality. The multi-line expression reformatting and simplified error handling make the code easier to follow.
src/macos/window_system.rs (4)
11-15: Use of private macOS APIs may cause compatibility issues.The
CGSGetActiveSpaceandCGSMainConnectionIDfunctions are private Apple APIs that could change or be removed in future macOS versions, potentially breaking this functionality.Consider documenting this dependency clearly and implementing a fallback mechanism if these APIs become unavailable. Also, check if there are any public APIs that could provide similar functionality.
315-352: Unsafe workspace detection implementation looks correct with proper validation.The unsafe block properly validates the connection ID and workspace ID, with reasonable bounds checking and fallback behavior. The safety comments clearly document the invariants.
82-82: Iterator usage improves code clarity.Using
iter().enumerate().take()is more idiomatic and safer than manual indexing.
299-305: New batch window movement method follows good design patterns.The delegation to
accessibility.move_all_windowsmaintains proper separation of concerns.src/macos/cgwindow.rs (3)
105-108: Enhanced window metadata extraction improves window management capabilities.Extracting
owner_pidandworkspace_idenables better window identification and workspace-aware operations. Good addition for the tiling window manager functionality.Also applies to: 124-125
321-325: Default trait implementation follows Rust best practices.Delegating to
new()in the Default implementation is the standard pattern.
147-159: System application exclusion list verifiedVerified that the entries “Menubar”, “Menu Bar”, and “SystemPreferences” only appear in the
never_tile_appsarray insrc/macos/cgwindow.rs, and that thecontains(&owner.as_str())check uses exact string equality—so no other apps (e.g. “Menu Bar Helper” or similar) will be inadvertently filtered. No changes required.src/macos/window_notifications.rs (1)
69-80: Raw pointer storage requires careful memory management.The instance variable storage using
Box::into_rawis correctly implemented, with corresponding cleanup instop_observing. This pattern safely bridges Rust ownership with Objective-C runtime.src/layout.rs (4)
87-139: Improved split calculation with better space distribution.The enhanced
update_child_rectsmethod ensures minimum sizes are respected while distributing space more fairly. The 30%/70% limits prevent extreme splits that would create unusable panes.
141-181: Spiral insertion pattern creates predictable window placement.The depth-based alternating split direction (horizontal at even depths, vertical at odd) creates an intuitive spiral pattern similar to i3/sway. This is a good UX improvement.
292-304: Window size constraints prevent edge cases.The validation ensures windows have reasonable dimensions (150x100 to 5000x4000). These limits accommodate both small utility windows and large displays.
483-483: Usingdiv_ceilimproves code clarity.The
div_ceilmethod is more explicit than manual ceiling division calculation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Nitpick comments (9)
src/main.rs (2)
84-92: Make log level configurable instead of hardcoding Debug.The current implementation always sets the log level to Debug, which may produce excessive logs in production environments.
Consider making the log level configurable:
-fn setup_dual_logging(log_file: &PathBuf) -> Result<()> { +fn setup_dual_logging(log_file: &PathBuf, log_level: log::LevelFilter) -> Result<()> { let dual_logger = DualLogger::new(log_file)?; log::set_boxed_logger(Box::new(dual_logger)) - .map(|()| log::set_max_level(log::LevelFilter::Debug)) + .map(|()| log::set_max_level(log_level)) .map_err(|e| anyhow::anyhow!("Failed to init dual logger: {}", e))?; Ok(()) }Then in main, determine the log level from environment variable or CLI args:
let log_level = std::env::var("RUST_LOG") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(log::LevelFilter::Info); setup_dual_logging(&log_file, log_level)?;
97-101: Consider more robust log file location handling.The current implementation writes logs to the current working directory, which may not be writable or appropriate in all environments (e.g., system services, read-only filesystems).
Consider using a more appropriate default location:
- let log_file = std::env::current_dir() - .unwrap_or_else(|_| PathBuf::from(".")) - .join("skew.log"); + let log_file = if let Ok(log_dir) = std::env::var("LOG_DIR") { + PathBuf::from(log_dir).join("skew.log") + } else { + // Try standard locations in order + let candidates = [ + dirs::cache_dir().map(|p| p.join("skew")), + dirs::home_dir().map(|p| p.join(".local/share/skew")), + Some(PathBuf::from("/tmp")), + ]; + + candidates.into_iter() + .flatten() + .find(|p| p.exists() || std::fs::create_dir_all(p).is_ok()) + .unwrap_or_else(|| PathBuf::from(".")) + .join("skew.log") + };src/layout.rs (2)
87-87: Complex minimum size enforcement could be simplified.The current logic for enforcing minimum split sizes has magic numbers and complex conditional logic that could be hard to maintain.
Consider extracting constants and simplifying the logic:
+ const MIN_SPLIT_SIZE: f64 = 150.0; + const MIN_SPLIT_RATIO: f64 = 0.3; + const MAX_SPLIT_RATIO: f64 = 0.7; + fn update_child_rects(&mut self) { if let (Some(ref mut left), Some(ref mut right)) = (&mut self.left, &mut self.right) { - const MIN_SPLIT_SIZE: f64 = 150.0; // Reduced minimum for better space usage - let (left_rect, right_rect) = if self.is_horizontal { - let ideal_left_width = self.rect.width * self.split_ratio; - let ideal_right_width = self.rect.width - ideal_left_width; - - // Ensure both sides meet minimum requirements with better distribution - let left_width = if ideal_left_width < MIN_SPLIT_SIZE { - MIN_SPLIT_SIZE.min(self.rect.width * 0.3) // Max 30% for minimum - } else if ideal_right_width < MIN_SPLIT_SIZE { - (self.rect.width - MIN_SPLIT_SIZE).max(self.rect.width * 0.7) - // Min 70% for main - } else { - ideal_left_width - }; + let left_width = self.calculate_split_dimension( + self.rect.width, + self.split_ratio + ); let right_width = self.rect.width - left_width;With a helper method:
fn calculate_split_dimension(&self, total: f64, ratio: f64) -> f64 { let ideal_left = total * ratio; let ideal_right = total - ideal_left; if ideal_left < Self::MIN_SPLIT_SIZE && ideal_right < Self::MIN_SPLIT_SIZE { // Both would be too small, give equal space total / 2.0 } else if ideal_left < Self::MIN_SPLIT_SIZE { Self::MIN_SPLIT_SIZE.min(total * Self::MIN_SPLIT_RATIO) } else if ideal_right < Self::MIN_SPLIT_SIZE { (total - Self::MIN_SPLIT_SIZE).max(total * Self::MAX_SPLIT_RATIO) } else { ideal_left } }Also applies to: 94-101, 118-125
379-393: Consider optimizing BSP tree rebuilding for better performance.The current implementation always rebuilds the entire BSP tree on every layout computation, which could be inefficient for large numbers of windows.
While the comment explains this avoids synchronization issues, consider these alternatives:
- Incremental updates: Track window additions/removals and only rebuild when the window set changes
- Dirty flag: Set a flag when windows are added/removed, rebuild only when dirty
- Immutable tree: Use persistent data structures for lock-free updates
Example approach:
pub struct LayoutManager { current_layout: LayoutType, bsp_root: Option<BSPNode>, split_ratio: f64, + bsp_dirty: bool, + last_window_ids: Vec<WindowId>, } fn compute_bsp_layout(...) -> HashMap<WindowId, Rect> { + let current_ids: Vec<_> = windows.iter().map(|w| w.id).collect(); + if !self.bsp_dirty && self.last_window_ids == current_ids { + // Tree is up to date, just collect rects + if let Some(ref root) = self.bsp_root { + return root.collect_window_rects(general_config.gap); + } + } // Rebuild tree... + self.bsp_dirty = false; + self.last_window_ids = current_ids;src/snap.rs (1)
541-565: Window overlap detection only considers center point.The current implementation only checks if the dragged window's center point is within another window, which might miss cases where windows significantly overlap but centers don't align.
Consider using area overlap for more accurate detection:
pub fn find_window_under_drag( &self, dragged_window_id: WindowId, dragged_rect: Rect, all_windows: &[&Window], ) -> Option<WindowId> { - let center_x = dragged_rect.x + dragged_rect.width / 2.0; - let center_y = dragged_rect.y + dragged_rect.height / 2.0; - - for window in all_windows { - if window.id == dragged_window_id { - continue; - } - - if self.point_in_rect(center_x, center_y, &window.rect) { - debug!( - "Found window {:?} under dragged window {:?}", - window.id, dragged_window_id - ); - return Some(window.id); - } - } + let mut best_overlap = 0.0; + let mut best_window = None; + + for window in all_windows { + if window.id == dragged_window_id { + continue; + } + + let overlap = self.calculate_rect_overlap(&dragged_rect, &window.rect); + if overlap > best_overlap && overlap > 0.25 { // At least 25% overlap + best_overlap = overlap; + best_window = Some(window.id); + } + } + + if let Some(id) = best_window { + debug!( + "Found window {:?} under dragged window {:?} with {:.1}% overlap", + id, dragged_window_id, best_overlap * 100.0 + ); + } + best_window } + +fn calculate_rect_overlap(&self, rect1: &Rect, rect2: &Rect) -> f64 { + let x_overlap = (rect1.x + rect1.width).min(rect2.x + rect2.width) - rect1.x.max(rect2.x); + let y_overlap = (rect1.y + rect1.height).min(rect2.y + rect2.height) - rect1.y.max(rect2.y); + + if x_overlap > 0.0 && y_overlap > 0.0 { + let overlap_area = x_overlap * y_overlap; + let dragged_area = rect1.width * rect1.height; + overlap_area / dragged_area + } else { + 0.0 + } +}src/window_manager.rs (4)
82-89: Add missing imports for collections types.The code uses
std::collections::HashSetandstd::collections::HashMapwith fully qualified paths. Add these imports at the top of the file for better readability.Add these imports at the top of the file:
use std::collections::{HashMap, HashSet};Then simplify the field declarations:
- programmatically_moving: std::collections::HashSet<WindowId>, + programmatically_moving: HashSet<WindowId>, - user_dragging_windows: std::collections::HashSet<WindowId>, + user_dragging_windows: HashSet<WindowId>, - last_window_positions: std::collections::HashMap<WindowId, Rect>, + last_window_positions: HashMap<WindowId, Rect>,
160-166: Address TODO: Make window monitoring intervals configurable.The TODO comment indicates that the window refresh and monitor intervals should be configurable via
skew.toml.Would you like me to create an issue to track implementing configurable intervals for
window_refresh_interval_msandwindow_monitor_interval_ms?
831-835: Consider making overlap detection tolerance configurable.The overlap detection uses a hardcoded 1.0px tolerance which might be too strict for high-DPI displays or cause issues with rounding.
Make the overlap tolerance configurable:
+ let overlap_tolerance = self.config.general.overlap_tolerance.unwrap_or(1.0); let overlaps = !(current_rect.x + current_rect.width <= target_rect.x + overlap_tolerance - || target_rect.x + target_rect.width <= current_rect.x + 1.0 - || current_rect.y + current_rect.height <= target_rect.y + 1.0 - || target_rect.y + target_rect.height <= current_rect.y + 1.0); + || target_rect.x + target_rect.width <= current_rect.x + overlap_tolerance + || current_rect.y + current_rect.height <= target_rect.y + overlap_tolerance + || target_rect.y + target_rect.height <= current_rect.y + overlap_tolerance);
1300-1316: Consider making layout application parameters configurable.The layout throttling (50ms), batch size (2), and inter-batch delay (1ms) are hardcoded. Making these configurable would allow performance tuning for different systems.
Add configuration options:
- if now.duration_since(last_time).as_millis() < 50 { + if now.duration_since(last_time).as_millis() < self.config.layout.throttle_ms.unwrap_or(50) { - const BATCH_SIZE: usize = 2; // Move 2 windows at a time + let batch_size = self.config.layout.batch_size.unwrap_or(2); - tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; + let batch_delay = self.config.layout.batch_delay_ms.unwrap_or(1); + tokio::time::sleep(tokio::time::Duration::from_millis(batch_delay)).await;Also applies to: 1378-1400
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
build.rs(1 hunks)src/config.rs(4 hunks)src/layout.rs(15 hunks)src/macos/accessibility.rs(9 hunks)src/macos/window_notifications.rs(1 hunks)src/main.rs(2 hunks)src/snap.rs(1 hunks)src/window_manager.rs(14 hunks)
✅ Files skipped from review due to trivial changes (1)
- build.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/config.rs
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/macos/accessibility.rs (3)
src/macos/cgwindow.rs (5)
CFArrayGetCount(25-25)CFArrayGetValueAtIndex(26-26)CFRelease(27-27)new(328-334)get_window_info_by_id(289-292)src/lib.rs (1)
new(28-35)src/macos/window_system.rs (3)
new(32-41)move_window(295-297)move_all_windows(299-305)
🔇 Additional comments (9)
src/window_manager.rs (3)
214-255: Well-structured window move detection logic.The implementation correctly differentiates between programmatic moves, user drags, and manual moves with clear control flow and comprehensive debug logging.
439-642: Excellent drag event handling implementation.The drag event handler properly manages window states, integrates with the snap manager, and uses configurable thresholds. The separation between drag and resize events is clear and well-structured.
1160-1194: Robust workspace detection implementation.The workspace detection logic has excellent fallback handling: focused window → most common workspace → stored workspace. The debug logging helps with troubleshooting workspace issues.
src/macos/accessibility.rs (6)
60-67: Good improvement: Constants now follow Rust naming conventions.The change from lowercase to uppercase constants follows Rust's standard naming conventions.
175-215: Well-implemented window movement with proper memory management.The window movement implementation correctly:
- Forces cache refresh to ensure current window state
- Uses AXValue for position and size setting
- Properly releases all Core Foundation objects
- Has comprehensive error handling
Also applies to: 314-371
994-1031: Excellent improvement to window ID generation stability.The window ID generation now uses stable properties (PID, title, rect) with pointer as a last resort, addressing the previous stability concerns. The warning message when using pointer-based IDs is helpful for debugging.
688-692: Proper Core Foundation memory management.The code correctly releases all cached elements before clearing the cache, preventing memory leaks.
Also applies to: 710-714
421-462: Robust process enumeration with proper safeguards.The implementation has good defensive programming:
- Iteration limit prevents infinite loops
- Buffer size cap (16K PIDs) prevents excessive memory usage
- Proper error handling for system calls
1033-1059: Good collision detection and handling for window IDs.The implementation properly detects ID collisions and generates unique fallback IDs using timestamps. The warning logs help identify when collisions occur.
| // For each window element from this PID, try to match with our windows | ||
| for (element_index, window_element) in app_window_elements.iter().enumerate() { | ||
| // Find the corresponding window by matching PID and index within PID | ||
| // This is more reliable than global ordering | ||
| let windows_for_pid: Vec<&crate::Window> = | ||
| windows.iter().filter(|w| w.owner_pid == pid).collect(); | ||
|
|
||
| if element_index < windows_for_pid.len() { | ||
| let window_id = windows_for_pid[element_index].id; | ||
|
|
||
| // Look up the rect for this specific window ID | ||
| if let Some(rect) = window_id_to_rect.get(&window_id) { | ||
| debug!( | ||
| "Moving window {:?} (PID {}, index {}) to {:?}", | ||
| window_id, pid, element_index, rect | ||
| ); | ||
| if let Err(e) = self.set_window_rect(*window_element, *rect) { | ||
| warn!("Failed to move window {:?}: {}", window_id, e); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential issue: Window ordering assumption may not be reliable.
The code assumes that windows for a given PID maintain the same order between enumeration and the provided windows array. This could lead to windows being moved to incorrect positions if the ordering changes.
Consider matching windows by more stable properties:
- if element_index < windows_for_pid.len() {
- let window_id = windows_for_pid[element_index].id;
+ // Match by position or title instead of index
+ if let Ok(Some(elem_rect)) = self.get_window_rect(*window_element) {
+ // Find matching window by comparing positions
+ if let Some(matching_window) = windows_for_pid.iter().find(|w| {
+ (w.rect.x - elem_rect.x).abs() < 5.0 &&
+ (w.rect.y - elem_rect.y).abs() < 5.0
+ }) {
+ let window_id = matching_window.id;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/macos/accessibility.rs around lines 275 to 296, the current code matches
windows by assuming the order of windows for a given PID is consistent between
app_window_elements and the windows array, which may cause incorrect window
positioning. To fix this, replace the index-based matching with a more stable
property such as window ID or another unique identifier to find the
corresponding window element for each window, ensuring accurate matching
regardless of order changes.
| unsafe fn create_observer_class(&self) -> Result<*const Class, Box<dyn std::error::Error>> { | ||
| let superclass = class!(NSObject); | ||
| let mut decl = objc::declare::ClassDecl::new("WindowDragObserver", superclass) | ||
| .ok_or("Failed to create class declaration")?; | ||
|
|
||
| // Add instance variables to store our callback data | ||
| decl.add_ivar::<*const std::ffi::c_void>("dragging_windows"); | ||
| decl.add_ivar::<*const std::ffi::c_void>("resizing_windows"); | ||
| decl.add_ivar::<*const std::ffi::c_void>("event_sender"); | ||
|
|
||
| // Add windowWillMove: method | ||
| decl.add_method( | ||
| sel!(windowWillMove:), | ||
| window_will_move_callback as extern "C" fn(&mut Object, Sel, id), | ||
| ); | ||
|
|
||
| // Add windowDidMove: method | ||
| decl.add_method( | ||
| sel!(windowDidMove:), | ||
| window_did_move_callback as extern "C" fn(&mut Object, Sel, id), | ||
| ); | ||
|
|
||
| // Add windowWillStartLiveResize: method | ||
| decl.add_method( | ||
| sel!(windowWillStartLiveResize:), | ||
| window_will_start_live_resize_callback as extern "C" fn(&mut Object, Sel, id), | ||
| ); | ||
|
|
||
| // Add windowDidEndLiveResize: method | ||
| decl.add_method( | ||
| sel!(windowDidEndLiveResize:), | ||
| window_did_end_live_resize_callback as extern "C" fn(&mut Object, Sel, id), | ||
| ); | ||
|
|
||
| Ok(decl.register()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition in Objective-C class creation.
The create_observer_class method creates a new Objective-C class at runtime, but multiple instances could race to register the same class name, causing a panic.
Add a check to see if the class already exists:
unsafe fn create_observer_class(&self) -> Result<*const Class, Box<dyn std::error::Error>> {
+ // Check if class already exists
+ if let Some(existing_class) = Class::get("WindowDragObserver") {
+ return Ok(existing_class);
+ }
+
let superclass = class!(NSObject);
let mut decl = objc::declare::ClassDecl::new("WindowDragObserver", superclass)
.ok_or("Failed to create class declaration")?;Alternatively, use a unique class name per instance or a static registration with std::sync::Once.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| unsafe fn create_observer_class(&self) -> Result<*const Class, Box<dyn std::error::Error>> { | |
| let superclass = class!(NSObject); | |
| let mut decl = objc::declare::ClassDecl::new("WindowDragObserver", superclass) | |
| .ok_or("Failed to create class declaration")?; | |
| // Add instance variables to store our callback data | |
| decl.add_ivar::<*const std::ffi::c_void>("dragging_windows"); | |
| decl.add_ivar::<*const std::ffi::c_void>("resizing_windows"); | |
| decl.add_ivar::<*const std::ffi::c_void>("event_sender"); | |
| // Add windowWillMove: method | |
| decl.add_method( | |
| sel!(windowWillMove:), | |
| window_will_move_callback as extern "C" fn(&mut Object, Sel, id), | |
| ); | |
| // Add windowDidMove: method | |
| decl.add_method( | |
| sel!(windowDidMove:), | |
| window_did_move_callback as extern "C" fn(&mut Object, Sel, id), | |
| ); | |
| // Add windowWillStartLiveResize: method | |
| decl.add_method( | |
| sel!(windowWillStartLiveResize:), | |
| window_will_start_live_resize_callback as extern "C" fn(&mut Object, Sel, id), | |
| ); | |
| // Add windowDidEndLiveResize: method | |
| decl.add_method( | |
| sel!(windowDidEndLiveResize:), | |
| window_did_end_live_resize_callback as extern "C" fn(&mut Object, Sel, id), | |
| ); | |
| Ok(decl.register()) | |
| } | |
| unsafe fn create_observer_class(&self) -> Result<*const Class, Box<dyn std::error::Error>> { | |
| // Check if class already exists | |
| if let Some(existing_class) = Class::get("WindowDragObserver") { | |
| return Ok(existing_class); | |
| } | |
| let superclass = class!(NSObject); | |
| let mut decl = objc::declare::ClassDecl::new("WindowDragObserver", superclass) | |
| .ok_or("Failed to create class declaration")?; | |
| // Add instance variables to store our callback data | |
| decl.add_ivar::<*const std::ffi::c_void>("dragging_windows"); | |
| decl.add_ivar::<*const std::ffi::c_void>("resizing_windows"); | |
| decl.add_ivar::<*const std::ffi::c_void>("event_sender"); | |
| // Add windowWillMove: method | |
| decl.add_method( | |
| sel!(windowWillMove:), | |
| window_will_move_callback as extern "C" fn(&mut Object, Sel, id), | |
| ); | |
| // Add windowDidMove: method | |
| decl.add_method( | |
| sel!(windowDidMove:), | |
| window_did_move_callback as extern "C" fn(&mut Object, Sel, id), | |
| ); | |
| // Add windowWillStartLiveResize: method | |
| decl.add_method( | |
| sel!(windowWillStartLiveResize:), | |
| window_will_start_live_resize_callback as extern "C" fn(&mut Object, Sel, id), | |
| ); | |
| // Add windowDidEndLiveResize: method | |
| decl.add_method( | |
| sel!(windowDidEndLiveResize:), | |
| window_did_end_live_resize_callback as extern "C" fn(&mut Object, Sel, id), | |
| ); | |
| Ok(decl.register()) | |
| } |
🤖 Prompt for AI Agents
In src/macos/window_notifications.rs around lines 168 to 203, the
create_observer_class method registers an Objective-C class at runtime without
checking if it already exists, which can cause a race condition and panic if
multiple instances try to register the same class. To fix this, first check if
the class with the given name already exists using objc::runtime::Class::get and
return it if found. If not, proceed to create and register the class.
Alternatively, implement a static registration using std::sync::Once to ensure
the class is registered only once safely.
| if let Err(e) = event_sender.try_send(event) { | ||
| warn!("Failed to send drag started event: {}", e); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Events may be silently dropped on channel overflow.
Using try_send can drop important window events if the channel is full, potentially causing the window manager to miss drag/resize operations.
Consider using bounded channel with backpressure or logging dropped events:
- if let Err(e) = event_sender.try_send(event) {
- warn!("Failed to send drag started event: {}", e);
- }
+ match event_sender.try_send(event) {
+ Ok(()) => {},
+ Err(mpsc::error::TrySendError::Full(event)) => {
+ warn!("Event channel full, dropping event: {:?}", event);
+ // Consider metrics/alerting here
+ },
+ Err(mpsc::error::TrySendError::Closed(_)) => {
+ warn!("Event channel closed, observer should stop");
+ // Consider stopping the observer
+ }
+ }Also applies to: 292-294, 339-341, 389-391
🤖 Prompt for AI Agents
In src/macos/window_notifications.rs around lines 246 to 248 (and similarly at
292-294, 339-341, 389-391), the use of try_send on the event_sender channel can
silently drop events if the channel is full. To fix this, replace try_send with
a send operation that awaits or blocks until there is capacity, implementing
backpressure to prevent event loss. Alternatively, if using a bounded channel,
add explicit logging for dropped events to ensure no important window events are
missed.
| unsafe fn get_window_owner_pid(window: id) -> Option<i32> { | ||
| let window_number: i32 = msg_send![window, windowNumber]; | ||
| if window_number <= 0 { | ||
| return None; | ||
| } | ||
|
|
||
| // Use NSRunningApplication to get the PID from the window's owning app | ||
| let app: id = msg_send![window, app]; | ||
| if app != nil { | ||
| let running_app: id = msg_send![app, runningApplication]; | ||
| if running_app != nil { | ||
| let pid: i32 = msg_send![running_app, processIdentifier]; | ||
| return Some(pid); | ||
| } | ||
| } | ||
|
|
||
| None | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Using potentially unstable private APIs to get window app.
The msg_send![window, app] call appears to use undocumented NSWindow methods that may not be stable across macOS versions.
Consider using CGWindowListCopyWindowInfo with kCGWindowOwnerPID as a more stable alternative if the private API breaks.
🏁 Script executed:
#!/bin/bash
# Check if this API usage is documented or if there are alternatives
echo "Searching for NSWindow app method usage..."
rg -A 5 -B 5 "msg_send.*window.*app"
# Look for alternative approaches to get window owner
echo "Searching for alternative window owner detection..."
rg -A 10 "processIdentifier|owningApplication|NSRunningApplication"Length of output: 32812
Avoid undocumented ObjC selectors for window owner PID
The current use of
let app: id = msg_send![window, app];
let running_app: id = msg_send![app, runningApplication];
let pid: i32 = msg_send![running_app, processIdentifier];relies on private/undocumented NSWindow APIs that may break in future macOS releases. Please replace this with a stable CoreGraphics-based lookup.
• File: src/macos/window_notifications.rs
• Lines: ~440–457
Suggested diff:
- // Use NSRunningApplication to get the PID from the window's owning app
- let app: id = msg_send![window, app];
- if app != nil {
- let running_app: id = msg_send![app, runningApplication];
- if running_app != nil {
- let pid: i32 = msg_send![running_app, processIdentifier];
- return Some(pid);
- }
- }
-
- None
+ // Use CGWindowListCopyWindowInfo to get the PID from this window
+ if let Some(pid) = get_pid_from_window_number(window_number) {
+ return Some(pid);
+ }
+
+ NoneImplement get_pid_from_window_number(window_number: i32) -> Option<i32> to call CGWindowListCopyWindowInfo and extract the kCGWindowOwnerPID field. This ensures compatibility with public, stable macOS APIs.
🤖 Prompt for AI Agents
In src/macos/window_notifications.rs around lines 440 to 457, the current
implementation of get_window_owner_pid uses undocumented Objective-C selectors
on NSWindow, which risks breaking on future macOS versions. Replace this by
implementing a new function get_pid_from_window_number that uses the public
CoreGraphics API CGWindowListCopyWindowInfo to retrieve window info for the
given window number and extract the kCGWindowOwnerPID field. Then modify
get_window_owner_pid to call this new function with the window number to obtain
the PID, ensuring the code relies only on stable, documented macOS APIs.
| fn log(&self, record: &Record) { | ||
| if self.enabled(record.metadata()) { | ||
| let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); | ||
| let log_line = format!( | ||
| "[{} {} {}:{}] {}\n", | ||
| timestamp, | ||
| record.level(), | ||
| record.file().unwrap_or("unknown"), | ||
| record.line().unwrap_or(0), | ||
| record.args() | ||
| ); | ||
|
|
||
| // Write to stderr (console) | ||
| eprint!("{}", log_line); | ||
|
|
||
| // Write to file | ||
| if let Ok(mut file) = self.file.lock() { | ||
| let _ = file.write_all(log_line.as_bytes()); | ||
| let _ = file.flush(); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider error handling improvements for logging operations.
The current implementation silently ignores write failures, which could lead to lost logs during critical errors. Additionally, writing to stderr could block if the output pipe is full.
Consider these improvements:
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
let log_line = format!(
"[{} {} {}:{}] {}\n",
timestamp,
record.level(),
record.file().unwrap_or("unknown"),
record.line().unwrap_or(0),
record.args()
);
// Write to stderr (console)
- eprint!("{}", log_line);
+ // Use write! to handle potential blocking
+ use std::io::Write;
+ let _ = std::io::stderr().write_all(log_line.as_bytes());
// Write to file
if let Ok(mut file) = self.file.lock() {
- let _ = file.write_all(log_line.as_bytes());
- let _ = file.flush();
+ if let Err(e) = file.write_all(log_line.as_bytes()) {
+ eprintln!("Failed to write to log file: {}", e);
+ }
+ if let Err(e) = file.flush() {
+ eprintln!("Failed to flush log file: {}", e);
+ }
}
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fn log(&self, record: &Record) { | |
| if self.enabled(record.metadata()) { | |
| let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); | |
| let log_line = format!( | |
| "[{} {} {}:{}] {}\n", | |
| timestamp, | |
| record.level(), | |
| record.file().unwrap_or("unknown"), | |
| record.line().unwrap_or(0), | |
| record.args() | |
| ); | |
| // Write to stderr (console) | |
| eprint!("{}", log_line); | |
| // Write to file | |
| if let Ok(mut file) = self.file.lock() { | |
| let _ = file.write_all(log_line.as_bytes()); | |
| let _ = file.flush(); | |
| } | |
| } | |
| } | |
| fn log(&self, record: &Record) { | |
| if self.enabled(record.metadata()) { | |
| let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); | |
| let log_line = format!( | |
| "[{} {} {}:{}] {}\n", | |
| timestamp, | |
| record.level(), | |
| record.file().unwrap_or("unknown"), | |
| record.line().unwrap_or(0), | |
| record.args() | |
| ); | |
| // Write to stderr (console) | |
| // Use write! to handle potential blocking | |
| use std::io::Write; | |
| let _ = std::io::stderr().write_all(log_line.as_bytes()); | |
| // Write to file | |
| if let Ok(mut file) = self.file.lock() { | |
| if let Err(e) = file.write_all(log_line.as_bytes()) { | |
| eprintln!("Failed to write to log file: {}", e); | |
| } | |
| if let Err(e) = file.flush() { | |
| eprintln!("Failed to flush log file: {}", e); | |
| } | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/main.rs between lines 54 and 75, the log function currently ignores
errors from writing to the log file and writing to stderr, which risks losing
important log messages. Modify the code to handle errors returned by
file.write_all and file.flush by logging or reporting them appropriately. Also,
consider handling potential blocking on eprint by using non-blocking writes or
buffering strategies to avoid blocking the main thread during logging.
| let screen_rect = macos.get_screen_rect().await?; | ||
| let snap_manager = SnapManager::new(screen_rect, 50.0, 20.0, 100.0, 10.0); // 50px snap threshold, 20px edge zone, 100px corner, 10px margin | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Make snap manager parameters configurable.
The snap manager is initialized with hardcoded values. These should be configurable through the Config struct to allow users to customize snapping behavior.
Add snap configuration to your config structure and use it here:
- let snap_manager = SnapManager::new(screen_rect, 50.0, 20.0, 100.0, 10.0); // 50px snap threshold, 20px edge zone, 100px corner, 10px margin
+ let snap_manager = SnapManager::new(
+ screen_rect,
+ self.config.snap.threshold.unwrap_or(50.0),
+ self.config.snap.edge_zone.unwrap_or(20.0),
+ self.config.snap.corner_size.unwrap_or(100.0),
+ self.config.snap.margin.unwrap_or(10.0),
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let screen_rect = macos.get_screen_rect().await?; | |
| let snap_manager = SnapManager::new(screen_rect, 50.0, 20.0, 100.0, 10.0); // 50px snap threshold, 20px edge zone, 100px corner, 10px margin | |
| let screen_rect = macos.get_screen_rect().await?; | |
| let snap_manager = SnapManager::new( | |
| screen_rect, | |
| self.config.snap.threshold.unwrap_or(50.0), | |
| self.config.snap.edge_zone.unwrap_or(20.0), | |
| self.config.snap.corner_size.unwrap_or(100.0), | |
| self.config.snap.margin.unwrap_or(10.0), | |
| ); |
🤖 Prompt for AI Agents
In src/window_manager.rs around lines 115 to 117, the SnapManager is initialized
with hardcoded parameters for snap threshold, edge zone, corner size, and
margin. To fix this, add corresponding snap configuration fields to the Config
struct, then update this code to read those values from the Config instance
instead of using fixed literals. This will allow users to customize the snapping
behavior via configuration.
| if distance > 0.5 { | ||
| debug!( | ||
| "🔄 User move detected for window {:?}: {:.1}px from {:?} to {:?}", | ||
| window_id, distance, prev_rect, new_rect | ||
| ); | ||
|
|
||
| // For manual moves, snap back to proper layout position instead of snap zones | ||
| let layout_target = self.get_layout_target_for_window(window_id).await; | ||
|
|
||
| if let Some(target_rect) = layout_target { | ||
| let layout_distance = ((target_rect.x - new_rect.x).powi(2) | ||
| + (target_rect.y - new_rect.y).powi(2)) | ||
| .sqrt(); | ||
|
|
||
| debug!( | ||
| "🎯 Found layout target for window {:?}: {:?} (distance: {:.1}px)", | ||
| window_id, target_rect, layout_distance | ||
| ); | ||
|
|
||
| // More responsive snapping - reduced threshold | ||
| if layout_distance > 5.0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Make movement detection thresholds configurable.
Several hardcoded thresholds for movement and size detection should be configurable:
- Movement detection threshold (0.5px)
- Layout snapping distance (5.0px)
- Size change detection (5.0px)
Replace hardcoded values with configurable ones:
- if distance > 0.5 {
+ if distance > self.config.general.movement_threshold.unwrap_or(0.5) {
- if layout_distance > 5.0 {
+ if layout_distance > self.config.general.layout_snap_distance.unwrap_or(5.0) {
- if width_change > 5.0 || height_change > 5.0 {
+ let size_threshold = self.config.general.size_change_threshold.unwrap_or(5.0);
+ if width_change > size_threshold || height_change > size_threshold {Also applies to: 709-722
🤖 Prompt for AI Agents
In src/window_manager.rs around lines 665 to 685 and also lines 709 to 722,
replace the hardcoded threshold values for movement detection (0.5px), layout
snapping distance (5.0px), and size change detection (5.0px) with configurable
parameters. Introduce configuration fields or constants for these thresholds,
load or set them appropriately, and use these variables instead of the fixed
numeric literals to allow easy adjustment without code changes.
Summary by cubic
Added event-driven tiling and snapping for windows on macOS, including live drag and resize detection, improved layout algorithms, and a new snapping engine for better window management.
New Features
Refactors
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Documentation
Chores