Skip to content

Commit 6c50e9e

Browse files
✨ Add Mark
1 parent 3f986ac commit 6c50e9e

12 files changed

Lines changed: 416 additions & 6 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "piri"
3-
version = "0.1.5"
3+
version = "0.1.6"
44
edition = "2021"
55
authors = ["Starfall"]
66
description = "Extend niri compositor capabilities with extensible command system and plugins"

README.en.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,22 @@ Piri is a high-performance [Niri](https://github.com/YaLTeR/niri) extension tool
1313
- 🎯 **Window Rule**: Powerful rule engine. Automatically places windows based on regex matching and provides focus-triggered command execution with a built-in de-duplication mechanism (see [Window Rule Docs](docs/en/plugins/window_rule.md))
1414
- 📐 **Workspace Rule**: Workspace window layout management. Provides automatic width adjustment, automatic tiling, automatic alignment, and automatic maximization. Integrates original Autofill functionality (see [Workspace Rule Docs](docs/en/plugins/workspace_rule.md))
1515
- 🔒 **Singleton**: Single-instance assurance. Ensures specific applications remain globally unique, supporting quick focus or automatic process launching (see [Singleton Docs](docs/en/plugins/singleton.md))
16+
- 📌 **Mark**: Named window marks for quick focus. Bind the focused window to a name and jump back later; bindings are in-memory only (see [Mark Docs](docs/en/plugins/mark.md))
1617
- 📋 **Window Order**: Intelligent reordering. Automatically reorders tiled windows based on configured weights, preserving relative positions for identical weights to minimize movement (see [Window Order Docs](docs/en/plugins/window_order.md))
1718
- 🍽️ **Swallow**: Window swallowing mechanism. Automatically hides parent windows when child windows are opened, allowing child windows to replace parent windows in the layout (see [Swallow Docs](docs/en/plugins/swallow.md))
1819

20+
## Window Matching
21+
22+
Piri uses a unified window matching mechanism: regex on `app_id` and/or `title`. Plugins such as `window_rule`, `singleton`, and `scratchpads` use it to find windows.
23+
24+
**Supported matching**:
25+
- Full regular expression syntax
26+
- Match `app_id` and/or `title`
27+
- If both are set, **either** match can satisfy the rule (OR)
28+
29+
> **Note**: The Window Rule plugin also supports list matching for `app_id` and `title`; see [Window Rule Docs](docs/en/plugins/window_rule.md).
30+
31+
**Details**: [Window matching](docs/en/window_matching.md)
1932

2033
## Quick Start
2134

@@ -325,6 +338,34 @@ piri singleton {name} toggle
325338

326339
For detailed documentation, please refer to the [Singleton documentation](docs/en/plugins/singleton.md).
327340

341+
### Mark
342+
343+
Assign **named marks** (e.g. letters `a`, `b`) to windows for quick focus. Marks are kept in the daemon’s memory and are **cleared when the daemon restarts**. You only enable the plugin in `piri.toml` and bind `spawn` commands in Niri for marks you use often.
344+
345+
**Configuration example**:
346+
347+
```toml
348+
[piri.plugins]
349+
mark = true
350+
```
351+
352+
**Quick usage**:
353+
354+
```bash
355+
# No valid binding: bind focused window to name; binding exists and window lives: focus it
356+
piri mark {name} toggle
357+
358+
# Force-bind focused window to name (overwrites previous binding)
359+
piri mark {name} add
360+
361+
# Remove this mark
362+
piri mark {name} delete
363+
```
364+
365+
**Note**: Piri cannot capture the “next key” globally. To save shortcut slots, you can use a launcher (e.g. `fuzzel`) to pick a letter, then run the commands above. If Niri adds multi-key sequences or binding modes, you can group `piri mark …` calls under one prefix.
366+
367+
For detailed documentation, see the [Mark documentation](docs/en/plugins/mark.md).
368+
328369
### Window Order
329370

330371
![Window Order - Manual Trigger](./assets/window_order.mp4)

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Piri 是基于 Rust 的 [Niri](https://github.com/YaLTeR/niri) 高性能功能
1313
- 🎯 **Window Rule**: 强大规则引擎。基于正则匹配实现窗口自动归位,并提供带去重机制的焦点触发命令执行(详见 [Window Rule 文档](docs/zh/plugins/window_rule.md)
1414
- 📐 **Workspace Rule**: 工作区窗口布局管理。提供自动宽度调整、自动平铺、自动对齐和自动最大化等功能,整合了原 Autofill 功能(详见 [Workspace Rule 文档](docs/zh/plugins/workspace_rule.md)
1515
- 🔒 **Singleton**: 单实例保障。确保特定应用全局唯一,支持快速聚焦现有实例或自动拉起新进程(详见 [Singleton 文档](docs/zh/plugins/singleton.md)
16+
- 📌 **Mark**: 窗口标记与快速聚焦。用名称绑定当前窗口,再次触发可跳回该窗口;绑定仅存于内存(详见 [Mark 文档](docs/zh/plugins/mark.md)
1617
- 📋 **Window Order**: 智能窗口排序。根据配置权重自动重排平铺窗口,相同权重窗口保持相对位置以最小化移动损耗(详见 [Window Order 文档](docs/zh/plugins/window_order.md)
1718
- 🍽️ **Swallow**: 窗口吞噬机制。当子窗口打开时自动隐藏父窗口,让子窗口在布局中替换父窗口的位置(详见 [Swallow 文档](docs/zh/plugins/swallow.md)
1819

@@ -345,6 +346,34 @@ piri singleton {name} toggle
345346

346347
详细说明请参考 [Singleton 文档](docs/zh/plugins/singleton.md)
347348

349+
### Mark
350+
351+
为窗口设置**命名标记**(如字母 `a``b`),用于快速聚焦。标记在守护进程内存中维护,**重启 daemon 后会清空**;无需在 `piri.toml` 里为每个标记写配置,只需启用插件并在 Niri 里为常用标记绑定 `spawn` 命令。
352+
353+
**配置示例**
354+
355+
```toml
356+
[piri.plugins]
357+
mark = true
358+
```
359+
360+
**快速使用**
361+
362+
```bash
363+
# 无有效绑定时:把当前焦点窗口绑定到名称;已有且窗口仍在:聚焦该窗口
364+
piri mark {name} toggle
365+
366+
# 强制把当前窗口绑定到名称(覆盖原绑定)
367+
piri mark {name} add
368+
369+
# 删除该标记
370+
piri mark {name} delete
371+
```
372+
373+
**说明**:Piri 无法全局捕获「下一个按键」;若希望少占快捷键,可配合启动器(如 `fuzzel`)选字母后再调用上述命令。Niri 若支持多键序列或绑定模式,可将多条 `piri mark …` 收拢在同一前缀下。
374+
375+
详细说明请参考 [Mark 文档](docs/zh/plugins/mark.md)
376+
348377
### Window Order
349378

350379
![Window Order - 手动触发](./assets/window_order.mp4)

docs/en/plugins/mark.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Mark Plugin
2+
3+
The Mark plugin provides **named quick-focus targets**: bind a string (often a single letter, e.g. `a`) to the current window, then jump back to that window using the same name. Bindings live in the daemon’s memory only; **nothing is written to the config file**.
4+
5+
## Configuration
6+
7+
Enable the plugin:
8+
9+
```toml
10+
[piri.plugins]
11+
mark = true
12+
```
13+
14+
No extra `[mark.*]` tables are required; all marks are created or removed at runtime via the CLI.
15+
16+
## Command Line
17+
18+
```bash
19+
# If the mark points to a window that still exists → focus it; else bind the focused window to this mark
20+
piri mark {name} toggle
21+
22+
# Remove this mark (success even if it did not exist)
23+
piri mark {name} delete
24+
25+
# Bind the focused window to this mark (replaces any previous binding)
26+
piri mark {name} add
27+
```
28+
29+
Examples:
30+
31+
```bash
32+
piri mark a toggle # First time: mark current window as a; later: jump to a
33+
piri mark a add # Force re-bind current window to a
34+
piri mark a delete # Clear a
35+
```
36+
37+
## Behavior
38+
39+
1. **`toggle`**: If `name` is bound and the window still exists, **focus** that window; otherwise (unbound or window closed) **bind** the currently focused window to `name`.
40+
2. **`add`**: Always sets `name` to the current focus, overwriting any previous binding without checking the old window.
41+
3. **`delete`**: Removes the binding for `name`; idempotent.
42+
43+
To **change** which window a mark points to while the old binding is still valid, use **`add`**, or **`delete`** then **`toggle`**.
44+
45+
## Niri Keybindings
46+
47+
Piri cannot listen for the “next key” by itself. In Niri you typically add one `spawn` per mark you care about, for example:
48+
49+
```kdl
50+
binds {
51+
Mod+Shift+A { spawn "piri" "mark" "a" "toggle"; }
52+
Mod+Shift+B { spawn "piri" "mark" "b" "toggle"; }
53+
}
54+
```
55+
56+
If Niri gains multi-key sequences or binding modes, you can group these under one prefix. See the main README for context.
57+
58+
## Limitations
59+
60+
- **Not persistent**: All marks are lost when `piri daemon` restarts.
61+
- **No on-window labels**: Niri IPC does not provide drawing mark letters on window decorations; use a bar, notifications, or an external script if you need a visible list.
62+
- **Requires focus**: `toggle` / `add` use the **currently focused** window; focus the target before invoking.
63+
64+
## Use Cases
65+
66+
- Short-lived bookmarks for a few windows you switch between often.
67+
- Pairing with a launcher (e.g. `fuzzel` listing `a``z` then calling `piri mark …`) so you do not type the mark name manually every time.

docs/en/plugins/plugins.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ Executes commands when switching to specific empty workspaces. Useful for automa
2222
- Workspace-based configuration
2323
- Similar to Hyprland's `on-created-empty` workspace rule
2424

25+
### [Mark Plugin](mark.md)
26+
27+
Named marks for windows via `piri mark …`: bind the focused window or jump back to a marked window. Bindings are kept in daemon memory only; no per-mark tables in the config.
28+
29+
**Key features**:
30+
- `toggle`, `add`, and `delete` operations
31+
- Works well with Niri `spawn` keybindings or a launcher
32+
2533
### [Window Rule Plugin](window_rule.md)
2634

2735
Automatically moves windows to specified workspaces based on their `app_id` or `title`. Useful for automating window management and organizing applications.
@@ -50,10 +58,14 @@ You can control which plugins are enabled or disabled in the configuration file:
5058
scratchpads = true
5159
empty = true
5260
window_rule = true
53-
autofill = true
61+
workspace_rule = true
62+
singleton = true
63+
window_order = true
64+
swallow = true
65+
mark = true
5466
```
5567

5668
**Default Behavior**:
5769
- If not explicitly specified, plugins are **disabled** by default (`false`)
58-
- You must explicitly set `scratchpads = true`, `empty = true`, `window_rule = true`, or `autofill = true` to enable plugins
59-
- Exception: `window_rule` plugin is enabled by default if window rules are configured (unless explicitly set to `false`)
70+
- Set each plugin to `true` explicitly to enable it
71+
- Exception: `window_rule` is enabled by default if window rules are configured (unless explicitly set to `false`)

docs/zh/plugins/mark.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Mark 插件
2+
3+
Mark 插件为窗口提供**可命名的快捷焦点**:把一个字符串(通常是一个字母,如 `a`)绑定到当前窗口,之后用同一名称即可快速切回该窗口。绑定关系保存在守护进程内存中,**不写配置文件**
4+
5+
## 配置
6+
7+
只需在插件列表中启用:
8+
9+
```toml
10+
[piri.plugins]
11+
mark = true
12+
```
13+
14+
无需额外的 `[mark.*]` 表;所有标记均在运行时通过 CLI 创建或删除。
15+
16+
## 命令行
17+
18+
```bash
19+
# 若该标记已指向仍存在窗口 → 聚焦该窗口;否则将当前焦点窗口绑定到该标记
20+
piri mark {name} toggle
21+
22+
# 删除该标记(不存在也视为成功)
23+
piri mark {name} delete
24+
25+
# 将当前焦点窗口绑定到该标记(若已有绑定则直接覆盖)
26+
piri mark {name} add
27+
```
28+
29+
示例:
30+
31+
```bash
32+
piri mark a toggle # 第一次:把当前窗口标为 a;之后:跳到 a
33+
piri mark a add # 强制把当前窗口重新标为 a
34+
piri mark a delete # 取消 a
35+
```
36+
37+
## 行为说明
38+
39+
1. **`toggle`**:若 `name` 已有绑定且对应窗口仍存在,则**聚焦**该窗口;否则(无绑定或窗口已关闭)将**当前焦点窗口**写入 `name`
40+
2. **`add`**:始终用当前焦点窗口覆盖 `name`,不先判断旧窗口是否仍存在。
41+
3. **`delete`**:移除 `name` 的绑定,幂等。
42+
43+
若要在**已有有效绑定**时改绑到别的窗口,请使用 **`add`**,或先 **`delete`****`toggle`**
44+
45+
## 与 Niri 快捷键配合
46+
47+
Piri 无法单独「监听下一个按键」;在 Niri 中一般为每个要用的标记配置一条 `spawn`,例如:
48+
49+
```kdl
50+
binds {
51+
Mod+Shift+A { spawn "piri" "mark" "a" "toggle"; }
52+
Mod+Shift+B { spawn "piri" "mark" "b" "toggle"; }
53+
}
54+
```
55+
56+
若 Niri 将来支持多键序列或绑定模式,可把多条 `piri mark …` 收拢到同一前缀下,减少与全局快捷键的冲突。详见项目 README 中的相关说明。
57+
58+
## 限制与提示
59+
60+
- **非持久化**:重启 `piri daemon` 后所有标记丢失。
61+
- **无窗口装饰**:Niri IPC 当前无法在窗口上绘制标记字母;若需可视化,可考虑状态栏、通知或外部脚本自行维护列表。
62+
- **依赖焦点**`toggle` / `add` 依赖「当前聚焦窗口」;调用前请确保目标窗口已聚焦。
63+
64+
## 使用场景
65+
66+
- 临时「书签」几个频繁切换的窗口(终端、浏览器、编辑器等)。
67+
- 与脚本或菜单(如 `fuzzel` 列出 `a``z` 后调用 `piri mark …`)组合,减少手写标记名。

docs/zh/plugins/plugins.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ Piri 支持插件系统,允许你扩展功能。插件在守护进程模式下
2222
- 基于工作区的配置
2323
- 类似于 Hyprland 的 `on-created-empty` 工作区规则
2424

25+
### [Mark 插件](mark.md)
26+
27+
为窗口设置命名标记,通过 `piri mark …` 绑定当前焦点窗口或跳回已标记窗口。绑定仅存于守护进程内存,无需在配置中为每个标记单独建表。
28+
29+
**主要特性**
30+
- `toggle` / `add` / `delete` 三种操作
31+
- 适合与 Niri `spawn` 或启动器组合使用
32+
2533
### [Window Rule 插件](window_rule.md)
2634

2735
根据窗口的 `app_id``title` 自动将窗口移动到指定的工作区。用于自动化窗口管理和组织应用程序。
@@ -72,10 +80,14 @@ Piri 支持插件系统,允许你扩展功能。插件在守护进程模式下
7280
scratchpads = true
7381
empty = true
7482
window_rule = true
75-
autofill = true
83+
workspace_rule = true
84+
singleton = true
85+
window_order = true
86+
swallow = true
87+
mark = true
7688
```
7789

7890
**默认行为**
7991
- 如果未明确指定,插件默认**禁用**`false`
80-
- 必须显式设置 `scratchpads = true``empty = true``window_rule = true``autofill = true` 来启用插件
92+
- 必须显式将对应项设为 `true` 来启用插件
8193
- `window_rule` 插件例外:如果配置了窗口规则,默认启用(除非显式设置为 `false`

src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ pub struct PluginsConfig {
182182
pub swallow: Option<bool>,
183183
#[serde(default)]
184184
pub workspace_rule: Option<bool>,
185+
#[serde(default)]
186+
pub mark: Option<bool>,
185187
#[serde(rename = "empty_config", default)]
186188
pub empty_config: Option<EmptyPluginConfig>,
187189
}
@@ -197,6 +199,7 @@ impl Default for PluginsConfig {
197199
window_order: None,
198200
swallow: None,
199201
workspace_rule: None,
202+
mark: None,
200203
empty_config: None,
201204
}
202205
}
@@ -379,6 +382,7 @@ impl PluginsConfig {
379382
"window_order" => self.window_order.unwrap_or(false),
380383
"swallow" => self.swallow.unwrap_or(false),
381384
"workspace_rule" => self.workspace_rule.unwrap_or(false),
385+
"mark" => self.mark.unwrap_or(false),
382386
_ => false,
383387
}
384388
}

src/ipc.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ pub enum IpcRequest {
1919
name: String,
2020
},
2121
WindowOrderToggle,
22+
/// Mark: focus if bound window exists, else bind current focus to `name`.
23+
MarkToggle {
24+
name: String,
25+
},
26+
/// Remove mark `name` (no-op if missing).
27+
MarkDelete {
28+
name: String,
29+
},
30+
/// Bind current focus to `name`, replacing any previous binding.
31+
MarkAdd {
32+
name: String,
33+
},
2234
Ping,
2335
Shutdown,
2436
}
@@ -261,6 +273,22 @@ pub async fn handle_request(
261273
IpcResponse::Error("WindowOrder plugin is not enabled. Please enable it in the configuration file (piri.plugins.window_order = true).".to_string())
262274
}
263275
}
276+
IpcRequest::MarkToggle { .. }
277+
| IpcRequest::MarkDelete { .. }
278+
| IpcRequest::MarkAdd { .. } => {
279+
let config = handler.config();
280+
if config.piri.plugins.is_enabled("mark") {
281+
IpcResponse::Error(
282+
"Mark plugin is enabled but not initialized. Please restart the daemon."
283+
.to_string(),
284+
)
285+
} else {
286+
IpcResponse::Error(
287+
"Mark plugin is not enabled. Set piri.plugins.mark = true in the config."
288+
.to_string(),
289+
)
290+
}
291+
}
264292
}
265293
}
266294
};

0 commit comments

Comments
 (0)