Rift Tiling Window Manager

repository·main·Indexed 24 days ago

https://github.com/acsandmann/rift

A high-performance tiling window manager for macOS that supports multiple layout styles including Tiling, BSP, Master-stack, Scrolling columns, and Stack. It provides macOS-style Mission Control, native trackpad gestures, and hot-reloadable configuration without requiring SIP to be disabled. Rift supports third-party integration via a CLI and Mach port, and features a robust internal architecture using a reactor pattern with EventOutcome for managing window transactions and application events.

Tokens
16.1K
Snippets
32
Records
98
Agent score
84%

What's inside Rift

  1. Overview of Rift tiling window manager

    main

    Rift is a tiling window manager for macOS designed for high performance and usability. It provides several layout styles including:

    • Tiling (i3/sway-like)
    • Binary Space Partitioning (bspwm-like)
    • Master-stack (dwm-like)
    • Scrolling columns (niri-style). Note: When using multiple displays with the scrolling layout, displays must be arranged in a vertical stack to prevent windows from leaking into other displays.
    • Stack (accordion)

    Key features include:

    • MacOS-style Mission Control for visual workspace navigation.
    • Menubar icon for switching workspaces, changing layouts, and accessing controls.
    • Layout Management: Save and restore layouts via the menu bar or CLI from a configurable folder.
    • Mouse/Trackpad Integration: Focus follows mouse with auto-raise, window swapping via dragging, and native-style trackpad gestures for workspace switching.
    • System Compatibility: Works with "Displays have separate Spaces" enabled and does not require disabling SIP.
    • Hot reloadable configuration.
    • Third-party Interop: Supports requests via CLI or Mach port (including a Lua client) and sends signals on startup, workspace switches, and window changes.
  2. Integrate with third-party programs

    main

    Rift supports interop with third-party programs (such as Sketchybar) through two primary mechanisms:

    1. Making Requests to Rift

    You can send requests to Rift using:

    • The CLI.
    • The exposed Mach port (a Lua client is available for this purpose).

    2. Receiving Signals from Rift

    Rift emits signals that can be consumed via a command (CLI) or through a Mach connection. Signals are sent during:

    • Startup.
    • Workspace switches.
    • When windows within a workspace change.
  3. Use OwnedNode for safe root management

    main

    Because nodes must be removed manually (as removal requires a reference to the NodeMap), OwnedNode provides a RAII-style guard for root nodes.

    Warning: If an OwnedNode is dropped without calling OwnedNode::remove(&mut tree), the program will panic in debug builds. This ensures that you don't accidentally leak nodes that were intended to be roots.

    Use OwnedNode::new_root_in(tree, name) to create a new root, or OwnedNode::own(node_id, name) to wrap an existing NodeId.

  4. Understand EventOutcome and the Reactor workflow

    main

    In Rift, workflows mutate the reactor's domain state synchronously and then return an EventOutcome. An EventOutcome describes the ordered integration work (side effects) that must occur after the mutation. This separation allows for testing policy logic without executing actual platform operations.

    Key capabilities of an EventOutcome include:

    • Window Management: Requesting window discoveries, frame writes, title broadcasts, or window closures.
    • Space/Topology Control: Reassigning windows to different spaces, switching native spaces, or recomputing active spaces.
    • Application Interaction: Activating applications, sending app-specific requests, or re-applying app rules.
    • Layout & UI: Triggering layout events, arranging windows, warping the mouse, or dispatching mouse events.
    • System Integration: Updating service configurations, writing to stdout, or performing Mission Control recovery.
  5. Manage window hierarchies with Tree and NodeMap

    main

    The Tree<O> structure manages an N-ary tree of nodes using a NodeMap. The NodeMap acts as the central storage for the tree's structure, allowing multiple trees to coexist and facilitating the movement of branches between them. A Tree is parameterized by an Observer type O, which allows you to react to structural changes (like nodes being added, removed, or moved) via specific lifecycle callbacks.

    To create a basic tree without custom logic, use Tree::new() (which uses the unit type () as a no-op observer). To create a tree with custom reaction logic, use Tree::with_observer(data).

  6. Manage windowing state with RiftState

    main
    In Rift, all mutable domain state is owned by the reactor thread. RiftState serves as the central container for this state, specifically holding the WindowStore which manages window identity, native-space observations, and workspace assignments.
  7. Handle StackLine events

    main

    The StackLine actor processes several types of Events to manage the lifecycle and interaction of UI indicators:

    • GroupsUpdated: Triggered when window groups change. Updates indicators, handles visibility based on fullscreen state, and syncs hit-rects.
    • SpaceStateUpdated: Updates the CoordinateConverter used for mapping coordinates.
    • ConfigUpdated: Updates the internal Config. If the stack line is disabled, it clears all existing indicators.
    • MouseDown: Processes a click at a specific CGPoint. The event tap is expected to have already confirmed the point lands on a visible indicator.
    • MouseMoved: Updates the cursor state (e.g., switching to a pointingHandCursor when hovering over an indicator) and handles hover-based activation if StackLineHoverMode::Hover is enabled.
    pub enum Event {
        GroupsUpdated {
            active_space_ids: Vec<SpaceId>,
            space_id: SpaceId,
            groups: Vec<GroupInfo>,
            active_workspace_for_space_has_fullscreen: bool,
        },
        SpaceStateUpdated(CoordinateConverter, ForwardedSpaceState),
        ConfigUpdated(Config),
        MouseDown(CGPoint),
        MouseMoved {
            point: CGPoint,
            hits_indicator: bool,
        },
    }
  8. How `Timer` integrates with the async executor

    main

    The Timer implementation uses CFRunLoopTimer and is designed to work with a CFRunLoop-based async executor.

    Key behaviors:

    • Thread Locality: Timers are installed on the CFRunLoop of the thread where they are created. The CFRunLoop must be running for the timer to fire.
    • Thread Safety: Timer instances are thread-safe and can be shared between threads.
    • Lifecycle: A Timer can be a one-shot delay (via sleep), a repeating interval (via repeating), or a manually controlled timer (via manual and set_next_fire).
    • Cancellation: Calling .cancel() invalidates the underlying CFRunLoopTimer and wakes any pending tasks, allowing them to complete immediately.
  9. Configure App Workspace Rules

    main

    Rift allows you to define AppWorkspaceRule objects to automate window management. Rules can match windows based on several criteria and dictate whether a window should be managed (assigned to a specific workspace) or floating.

    Supported matching criteria:

    • app_id: The bundle identifier of the application.
    • app_name: The name of the application.
    • title_regex: A regular expression to match against the window title.
    • title_substring: A simple substring match against the window title.
    • ax_role / ax_subrole: Accessibility roles and subroles.

    Rules can specify a target workspace using a WorkspaceSelector:

    • WorkspaceSelector::Index(usize): Selects a workspace by its position in the list.
    • WorkspaceSelector::Name(String): Selects a workspace by its exact name.

    Key fields in AppWorkspaceRule:

    • manage: If true, Rift will attempt to assign the window to a workspace.
    • floating: If true, the window will be treated as a floating window (not tied to a specific workspace).
    • workspace: An optional WorkspaceSelector defining the target workspace.
    // Example: Match a specific app and force it to float
    AppWorkspaceRule {
        app_id: Some("com.example.test".into()),
        workspace: None,
        floating: true,
        manage: true,
        app_name: None,
        title_regex: None,
        title_substring: None,
        ax_role: None,
        ax_subrole: None,
    }
    
    // Example: Match by window title substring and assign to workspace index 1
    AppWorkspaceRule {
        app_id: None,
        workspace: Some(WorkspaceSelector::Index(1)),
        floating: false,
        manage: true,
        app_name: Some("Calendar".into()),
        title_regex: None,
        title_substring: None,
        ax_role: None,
        ax_subrole: None,
    }
  10. Configure Layout Modes

    main

    Rift supports several tiling and stacking layout modes via LayoutMode:

    • traditional: Container-based tiling (i3/sway style).
    • bsp: Binary space partitioning.
    • stack: Dedicated stacked layout.
    • master_stack: Master area + stack area.
    • scrolling: Scrolling column layout (niri-style).
  11. Manage virtual workspaces with WorkspaceStore

    main

    The WorkspaceStore is the central manager for virtual workspace topology across all native macOS spaces. It maintains the mapping between native spaces and their respective virtual workspaces, tracks active workspaces, and handles workspace creation and lifecycle.

    Key behaviors:

    • Single Source of Truth: Membership (which window belongs to which workspace) is authoritative in the WindowStore. WorkspaceStore provides helper methods to query this relationship.
    • Space Initialization: Calling methods like list_workspaces or create_workspace will automatically initialize a native space with the configured number of default virtual workspaces if it doesn't exist yet.
    • Layout Management: Each workspace can have its own LayoutMode (e.g., Bsp, Stack, MasterStack, Scrolling, Traditional) which can be configured via workspace_rules.