这是indexloc提供的服务,不要输入任何密码
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ cargo build --no-default-features

Skew is inspired by and builds upon ideas from:

- **[sway](https://swaywm.org/)**: A Wayland compositor and i3-compatible window manager for Linux
- **[sway](https://swaywm.org/)**: A Wayland compositor and i3 compatible window manager for Linux
- **[yabai](https://github.com/koekeishiya/yabai)**: A tiling window manager for macOS based on binary space partitioning
- **[i3wm](https://i3wm.org/)**: The foundational tiling window manager that influenced many others

Expand Down
81 changes: 81 additions & 0 deletions llms.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Skew - macOS Tiling Window Manager

## Project Overview

Skew is a production-ready tiling window manager for macOS written in Rust. It provides advanced window management through 7 different layout algorithms and deep macOS integration via Accessibility APIs.

## Key Components

- **CLI Interface** (`main.rs`): Command-line entry point
- **Daemon Service** (`daemon.rs`): Background window management service
- **Window Manager** (`window_manager.rs`): Core window control logic
- **Layout Engine** (`layout.rs`): 7 tiling algorithms (BSP, Stack, Grid, Spiral, Column, Monocle, Float)
- **Focus Management** (`focus.rs`): Smart window focus and navigation
- **Hotkey System** (`hotkeys.rs`): Global keyboard shortcuts
- **IPC System** (`ipc.rs`): CLI-daemon communication
- **Plugin System** (`plugins.rs`): Lua scripting integration
- **macOS Integration** (`macos/`): Accessibility API bindings, Core Graphics window ops

## Architecture

**Binaries:** `skew` (CLI), `skewd` (daemon), `skew-cli` (utilities)

**Key Dependencies:**
- macOS APIs: core-graphics, core-foundation, cocoa, objc
- Async: tokio
- Config: serde, toml, serde_json, serde_yaml
- Hotkeys: rdev
- Plugins: mlua (optional)
- CLI: clap

## Development Workflow

**Critical:** After making any changes, always run these commands in sequence:

```bash
cargo fmt # Format code
cargo clippy # Run lints
cargo check # Type check
```

**Testing:**
```bash
cargo test # Run test suite
cargo build # Debug build
cargo build --release # Production build
```

**Features:**
- `default`: Includes scripting support
- `scripting`: Lua plugin system

## macOS-Specific Considerations

- Requires Accessibility API permissions to function
- Uses Core Graphics for window operations
- Integrates with macOS window system through Accessibility bindings
- Hot-key handling works system-wide via rdev

## Code Style

- Standard Rust formatting (cargo fmt)
- Follow existing patterns in the codebase
- Use comprehensive error handling with anyhow/thiserror
- Async code uses tokio runtime
- Configuration supports TOML, JSON, and YAML

## Common Operations

- Window management through Accessibility APIs
- Layout algorithms operate on window geometry
- IPC communication uses Unix sockets
- Plugin system allows Lua scripting extensions
- Hotkey bindings are configurable via TOML

## Important Notes

- This is production code - changes should be thoroughly tested
- Accessibility permissions are critical for functionality
- Multi-display support is built-in
- Configuration is extensively validated
- Error handling and logging are comprehensive throughout
1 change: 0 additions & 1 deletion src/bin/skew-cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use skew::ipc::IpcClient;
use tokio;

#[tokio::main]
async fn main() -> skew::Result<()> {
Expand Down
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ impl HotkeyConfig {
}

let parts: Vec<&str> = key_combo.split('+').collect();
if parts.len() < 1 {
if parts.is_empty() {
return Err(anyhow::anyhow!(
"Invalid key combination format: '{}'",
key_combo
Expand Down
8 changes: 3 additions & 5 deletions src/focus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,12 +338,10 @@ impl FocusManager {
Some(current_index) => {
if forward {
(current_index + 1) % focusable_windows.len()
} else if current_index == 0 {
focusable_windows.len() - 1
} else {
if current_index == 0 {
focusable_windows.len() - 1
} else {
current_index - 1
}
current_index - 1
}
}
None => 0, // No window focused, start with first
Expand Down
62 changes: 34 additions & 28 deletions src/hotkeys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl HotkeyManager {

// Create channel for rdev events
let (event_sender, event_receiver) = std::sync::mpsc::channel();

// Set global sender (this can only be done once)
if GLOBAL_HOTKEY_SENDER.set(event_sender).is_err() {
warn!("Global hotkey sender already initialized");
Expand Down Expand Up @@ -84,7 +84,9 @@ impl HotkeyManager {
drop(running);

// Take the event receiver (can only be done once)
let event_receiver = self.event_receiver.take()
let event_receiver = self
.event_receiver
.take()
.ok_or_else(|| anyhow::anyhow!("Event receiver already taken"))?;

// Clone necessary data for the background tasks
Expand Down Expand Up @@ -179,17 +181,15 @@ impl HotkeyManager {
is_running: Arc<Mutex<bool>>,
) {
info!("Starting hotkey event processing");

while *is_running.lock().unwrap() {
// Use a timeout to periodically check if we should stop
match event_receiver.recv_timeout(std::time::Duration::from_millis(100)) {
Ok(event) => {
if let Err(e) = Self::handle_rdev_event(
event,
&bindings,
&command_sender,
&pressed_keys,
).await {
if let Err(e) =
Self::handle_rdev_event(event, &bindings, &command_sender, &pressed_keys)
.await
{
error!("Error handling hotkey event: {}", e);
}
}
Expand All @@ -203,10 +203,10 @@ impl HotkeyManager {
}
}
}

info!("Hotkey event processing stopped");
}

async fn handle_rdev_event(
event: rdev::Event,
bindings: &HashMap<KeyCombination, String>,
Expand All @@ -218,11 +218,14 @@ impl HotkeyManager {
debug!("Key pressed: {:?}", key);
{
let mut keys = pressed_keys.lock().unwrap();
if !keys.iter().any(|k| std::mem::discriminant(k) == std::mem::discriminant(&key)) {
if !keys
.iter()
.any(|k| std::mem::discriminant(k) == std::mem::discriminant(&key))
{
keys.push(key);
}
}

// Check for matching key combinations
let keys = pressed_keys.lock().unwrap().clone();
if let Some(combination) = Self::match_key_combination(&keys, bindings) {
Expand All @@ -244,17 +247,16 @@ impl HotkeyManager {
}
_ => {} // Ignore other event types
}

Ok(())
}


fn match_key_combination(
pressed_keys: &[Key],
bindings: &HashMap<KeyCombination, String>,
) -> Option<KeyCombination> {
for (combination, _) in bindings {
if Self::is_combination_pressed(&combination, pressed_keys) {
for combination in bindings.keys() {
if Self::is_combination_pressed(combination, pressed_keys) {
return Some(combination.clone());
}
}
Expand All @@ -263,31 +265,36 @@ impl HotkeyManager {

fn is_combination_pressed(combination: &KeyCombination, pressed_keys: &[Key]) -> bool {
fn key_is_pressed(keys: &[Key], target: &Key) -> bool {
keys.iter().any(|k| std::mem::discriminant(k) == std::mem::discriminant(target))
keys.iter()
.any(|k| std::mem::discriminant(k) == std::mem::discriminant(target))
}
for modifier in &combination.modifiers {
match modifier {
ModifierKey::Alt => {
if !key_is_pressed(pressed_keys, &Key::Alt) &&
!key_is_pressed(pressed_keys, &Key::AltGr) {
if !key_is_pressed(pressed_keys, &Key::Alt)
&& !key_is_pressed(pressed_keys, &Key::AltGr)
{
return false;
}
}
ModifierKey::Ctrl => {
if !key_is_pressed(pressed_keys, &Key::ControlLeft) &&
!key_is_pressed(pressed_keys, &Key::ControlRight) {
if !key_is_pressed(pressed_keys, &Key::ControlLeft)
&& !key_is_pressed(pressed_keys, &Key::ControlRight)
{
return false;
}
}
ModifierKey::Shift => {
if !key_is_pressed(pressed_keys, &Key::ShiftLeft) &&
!key_is_pressed(pressed_keys, &Key::ShiftRight) {
if !key_is_pressed(pressed_keys, &Key::ShiftLeft)
&& !key_is_pressed(pressed_keys, &Key::ShiftRight)
{
return false;
}
}
ModifierKey::Cmd => {
if !key_is_pressed(pressed_keys, &Key::MetaLeft) &&
!key_is_pressed(pressed_keys, &Key::MetaRight) {
if !key_is_pressed(pressed_keys, &Key::MetaLeft)
&& !key_is_pressed(pressed_keys, &Key::MetaRight)
{
return false;
}
}
Expand Down Expand Up @@ -405,7 +412,6 @@ impl HotkeyManager {
}

fn parse_action(action: &str) -> Result<Command> {

let parts: Vec<&str> = action.split(':').collect();
let command = parts[0];

Expand Down Expand Up @@ -458,7 +464,7 @@ impl HotkeyManager {
// Global callback function for rdev - must be a function pointer
fn global_hotkey_callback(event: Event) {
if let Some(sender) = GLOBAL_HOTKEY_SENDER.get() {
if let Err(_) = sender.send(event) {
if sender.send(event).is_err() {
// Channel is closed, ignore the error
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ impl IpcServer {

let command = match message.command.as_str() {
"focus" => {
if let Some(id_str) = message.args.get(0) {
if let Some(id_str) = message.args.first() {
match id_str.parse::<u32>() {
Ok(id) => Command::FocusWindow(WindowId(id)),
Err(_) => {
Expand All @@ -170,7 +170,7 @@ impl IpcServer {
}
}
"close" => {
if let Some(id_str) = message.args.get(0) {
if let Some(id_str) = message.args.first() {
match id_str.parse::<u32>() {
Ok(id) => Command::CloseWindow(WindowId(id)),
Err(_) => {
Expand Down
Loading