gardesk/garclip / 5c000fa

Browse files

untrack PLAN.md

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
5c000fab84fee686de2639e2d241fddf573a2669
Parents
6bfc2c4
Tree
7170ef6

2 changed files

StatusFile+-
M .gitignore 1 1
D PLAN.md 0 703
.gitignoremodified
@@ -2,4 +2,4 @@ docs/
2
 .fackr/
2
 .fackr/
3
 .vscode/
3
 .vscode/
4
 CLAUDE.md
4
 CLAUDE.md
5
-/target/
5
+/target/PLAN.md
PLAN.mddeleted
@@ -1,703 +0,0 @@
1
-# garclip Implementation Plan
2
-
3
-## Overview
4
-
5
-A bespoke X11 clipboard manager for the gardesk suite. Implements the X11 selection protocol directly using x11rb without any third-party clipboard crates. Supports clipboard history, PRIMARY/CLIPBOARD selections, and integrates with the gardesk ecosystem.
6
-
7
----
8
-
9
-## X11 Clipboard Protocol (The Bespoke Implementation)
10
-
11
-### How X11 Clipboard Works
12
-
13
-X11 uses a **selection ownership model** - the clipboard doesn't store data. Instead:
14
-
15
-1. When an app copies text, it becomes the **selection owner**
16
-2. When another app wants to paste, it sends a **SelectionRequest** to the owner
17
-3. The owner responds with **SelectionNotify** containing the data
18
-4. Problem: When the owner app closes, clipboard data is lost
19
-
20
-### Clipboard Manager's Role
21
-
22
-A clipboard manager solves this by:
23
-1. **Monitoring** selection ownership changes
24
-2. **Grabbing** clipboard contents before the owner releases
25
-3. **Becoming** the new owner to persist the data
26
-4. **Responding** to selection requests from other apps
27
-
28
-### Key X11 Atoms
29
-
30
-```
31
-CLIPBOARD          - Standard copy/paste selection
32
-PRIMARY            - Middle-click paste (highlighted text)
33
-TARGETS            - Query supported data formats
34
-UTF8_STRING        - UTF-8 encoded text
35
-STRING             - Latin-1 text (fallback)
36
-TEXT               - Generic text
37
-ATOM               - List of atoms
38
-TIMESTAMP          - Selection timestamp
39
-MULTIPLE           - Multiple target request
40
-INCR               - Incremental transfer (large data)
41
-CLIPBOARD_MANAGER  - Clipboard manager registration
42
-SAVE_TARGETS       - Request to save clipboard
43
-```
44
-
45
-### X11 Event Flow
46
-
47
-```
48
-┌─────────────────────────────────────────────────────────────────┐
49
-│                    CLIPBOARD MONITORING                         │
50
-├─────────────────────────────────────────────────────────────────┤
51
-│                                                                 │
52
-│  1. App copies text → SetSelectionOwner(CLIPBOARD)              │
53
-│                       ↓                                         │
54
-│  2. garclip receives SelectionClear (lost ownership)            │
55
-│     OR detects new owner via polling                            │
56
-│                       ↓                                         │
57
-│  3. garclip requests content: ConvertSelection(CLIPBOARD)       │
58
-│                       ↓                                         │
59
-│  4. Owner sends SelectionNotify with data                       │
60
-│                       ↓                                         │
61
-│  5. garclip stores in history                                   │
62
-│                       ↓                                         │
63
-│  6. When owner closes → garclip becomes new owner               │
64
-│                                                                 │
65
-└─────────────────────────────────────────────────────────────────┘
66
-
67
-┌─────────────────────────────────────────────────────────────────┐
68
-│                    SERVING PASTE REQUESTS                       │
69
-├─────────────────────────────────────────────────────────────────┤
70
-│                                                                 │
71
-│  1. App requests paste: ConvertSelection(CLIPBOARD, target)     │
72
-│                       ↓                                         │
73
-│  2. garclip receives SelectionRequest event                     │
74
-│                       ↓                                         │
75
-│  3. Check requested target (TARGETS, UTF8_STRING, etc.)         │
76
-│                       ↓                                         │
77
-│  4. Set property on requestor window with data                  │
78
-│                       ↓                                         │
79
-│  5. Send SelectionNotify to requestor                           │
80
-│                                                                 │
81
-└─────────────────────────────────────────────────────────────────┘
82
-```
83
-
84
----
85
-
86
-## Architecture
87
-
88
-### Project Structure
89
-
90
-```
91
-garclip/
92
-├── Cargo.toml                    # Workspace definition
93
-├── PLAN.md                       # This file
94
-├── garclip/                      # Main daemon crate
95
-│   ├── Cargo.toml
96
-│   └── src/
97
-│       ├── main.rs               # Entry point, CLI parsing
98
-│       ├── lib.rs                # Library exports
99
-│       ├── error.rs              # Error types (thiserror)
100
-│       │
101
-│       ├── x11/                  # X11 clipboard implementation
102
-│       │   ├── mod.rs
103
-│       │   ├── atoms.rs          # Atom interning and caching
104
-│       │   ├── selection.rs      # Selection ownership management
105
-│       │   └── transfer.rs       # Data transfer (request/response)
106
-│       │
107
-│       ├── clipboard/            # Clipboard logic
108
-│       │   ├── mod.rs
109
-│       │   ├── manager.rs        # Main clipboard manager
110
-│       │   ├── entry.rs          # Clipboard entry type
111
-│       │   └── history.rs        # History storage
112
-│       │
113
-│       ├── daemon/               # Daemon infrastructure
114
-│       │   ├── mod.rs
115
-│       │   └── state.rs          # Daemon state machine
116
-│       │
117
-│       ├── ipc/                  # IPC layer
118
-│       │   ├── mod.rs
119
-│       │   ├── protocol.rs       # Command/Response/Event types
120
-│       │   ├── server.rs         # Unix socket server
121
-│       │   └── client.rs         # Client helpers
122
-│       │
123
-│       └── config/               # Configuration
124
-│           ├── mod.rs
125
-│           └── types.rs          # Config structs
126
-│
127
-└── garclipctl/                   # CLI control tool
128
-    ├── Cargo.toml
129
-    └── src/
130
-        └── main.rs               # CLI commands
131
-```
132
-
133
-### Core Types
134
-
135
-```rust
136
-// clipboard/entry.rs
137
-pub struct ClipboardEntry {
138
-    pub id: u64,                          // Unique ID
139
-    pub content: ClipboardContent,        // The actual data
140
-    pub source: Option<String>,           // Source application (if known)
141
-    pub timestamp: SystemTime,            // When captured
142
-    pub pinned: bool,                      // Pinned entries persist
143
-}
144
-
145
-pub enum ClipboardContent {
146
-    Text(String),                         // UTF-8 text
147
-    // Future: Image, Html, Files, etc.
148
-}
149
-
150
-// clipboard/history.rs
151
-pub struct ClipboardHistory {
152
-    entries: VecDeque<ClipboardEntry>,
153
-    max_entries: usize,
154
-    next_id: u64,
155
-}
156
-
157
-impl ClipboardHistory {
158
-    pub fn push(&mut self, content: ClipboardContent, source: Option<String>);
159
-    pub fn get(&self, id: u64) -> Option<&ClipboardEntry>;
160
-    pub fn current(&self) -> Option<&ClipboardEntry>;
161
-    pub fn list(&self, limit: usize) -> Vec<&ClipboardEntry>;
162
-    pub fn remove(&mut self, id: u64) -> bool;
163
-    pub fn clear(&mut self);
164
-    pub fn pin(&mut self, id: u64) -> bool;
165
-    pub fn unpin(&mut self, id: u64) -> bool;
166
-}
167
-```
168
-
169
-### X11 Selection Manager
170
-
171
-```rust
172
-// x11/selection.rs
173
-pub struct SelectionManager {
174
-    conn: Arc<RustConnection>,
175
-    window: Window,                       // Our window for selection ownership
176
-    atoms: Atoms,                         // Cached atoms
177
-    owned_selections: HashSet<Atom>,      // Currently owned selections
178
-}
179
-
180
-impl SelectionManager {
181
-    pub fn new(conn: Arc<RustConnection>) -> Result<Self>;
182
-
183
-    // Ownership
184
-    pub fn claim_ownership(&mut self, selection: Atom) -> Result<()>;
185
-    pub fn release_ownership(&mut self, selection: Atom) -> Result<()>;
186
-    pub fn is_owner(&self, selection: Atom) -> Result<bool>;
187
-
188
-    // Requesting data from current owner
189
-    pub fn request_targets(&self, selection: Atom) -> Result<Vec<Atom>>;
190
-    pub fn request_text(&self, selection: Atom) -> Result<Option<String>>;
191
-
192
-    // Handling incoming requests (when we're the owner)
193
-    pub fn handle_selection_request(&self, event: SelectionRequestEvent, data: &[u8]) -> Result<()>;
194
-    pub fn handle_selection_clear(&mut self, event: SelectionClearEvent);
195
-}
196
-
197
-// x11/atoms.rs
198
-pub struct Atoms {
199
-    pub clipboard: Atom,
200
-    pub primary: Atom,
201
-    pub targets: Atom,
202
-    pub utf8_string: Atom,
203
-    pub string: Atom,
204
-    pub text: Atom,
205
-    pub atom: Atom,
206
-    pub timestamp: Atom,
207
-    pub multiple: Atom,
208
-    pub incr: Atom,
209
-    pub clipboard_manager: Atom,
210
-    pub save_targets: Atom,
211
-    // Property atoms
212
-    pub garclip_data: Atom,               // Our property for storing data
213
-}
214
-
215
-impl Atoms {
216
-    pub fn intern(conn: &RustConnection) -> Result<Self>;
217
-}
218
-```
219
-
220
-### IPC Protocol
221
-
222
-```rust
223
-// ipc/protocol.rs
224
-
225
-#[derive(Debug, Clone, Serialize, Deserialize)]
226
-#[serde(tag = "command", rename_all = "snake_case")]
227
-pub enum Command {
228
-    // Clipboard operations
229
-    Copy { text: String },                 // Programmatic copy
230
-    Paste,                                 // Get current clipboard
231
-
232
-    // History operations
233
-    History {
234
-        limit: Option<usize>,              // Max entries to return (default: 50)
235
-        #[serde(default)]
236
-        include_pinned: bool,              // Include pinned entries
237
-    },
238
-    Select { id: u64 },                    // Select entry from history (makes it current)
239
-    Delete { id: u64 },                    // Delete entry from history
240
-    Clear,                                 // Clear clipboard
241
-    ClearHistory { keep_pinned: bool },    // Clear all history
242
-
243
-    // Pin management
244
-    Pin { id: u64 },                       // Pin an entry
245
-    Unpin { id: u64 },                     // Unpin an entry
246
-    ListPinned,                            // List pinned entries
247
-
248
-    // Search
249
-    Search {
250
-        query: String,
251
-        limit: Option<usize>,
252
-    },
253
-
254
-    // Daemon control
255
-    Status,                                // Get daemon status
256
-    Reload,                                // Reload configuration
257
-    Quit,                                  // Shutdown daemon
258
-
259
-    // Subscriptions
260
-    Subscribe { events: Vec<String> },     // Subscribe to events
261
-}
262
-
263
-#[derive(Debug, Clone, Serialize, Deserialize)]
264
-pub struct Response {
265
-    pub success: bool,
266
-    #[serde(skip_serializing_if = "Option::is_none")]
267
-    pub data: Option<serde_json::Value>,
268
-    #[serde(skip_serializing_if = "Option::is_none")]
269
-    pub error: Option<String>,
270
-}
271
-
272
-#[derive(Debug, Clone, Serialize, Deserialize)]
273
-#[serde(tag = "event", rename_all = "snake_case")]
274
-pub enum Event {
275
-    ClipboardChanged {
276
-        id: u64,
277
-        preview: String,                   // First ~100 chars
278
-        source: Option<String>,
279
-    },
280
-    HistoryCleared,
281
-    EntryPinned { id: u64 },
282
-    EntryUnpinned { id: u64 },
283
-    EntryDeleted { id: u64 },
284
-}
285
-```
286
-
287
-### Configuration
288
-
289
-```toml
290
-# ~/.config/garclip/config.toml
291
-
292
-[history]
293
-max_entries = 1000                # Maximum history entries
294
-persist = true                    # Persist history across restarts
295
-persist_path = ""                 # Custom path (default: ~/.local/share/garclip/history.json)
296
-
297
-[behavior]
298
-watch_primary = true              # Also track PRIMARY selection (middle-click)
299
-watch_clipboard = true            # Track CLIPBOARD selection (Ctrl+C)
300
-deduplicate = true                # Don't add duplicate entries
301
-ignore_empty = true               # Ignore empty clipboard
302
-min_length = 1                    # Minimum text length to record
303
-max_length = 1048576              # Maximum text length (1MB)
304
-
305
-[filters]
306
-# Regex patterns to ignore
307
-ignore_patterns = [
308
-    "^\\s*$",                     # Whitespace only
309
-]
310
-# Window classes to ignore
311
-ignore_classes = [
312
-    "keepassxc",                  # Password managers
313
-    "1password",
314
-]
315
-
316
-[daemon]
317
-socket_path = ""                  # Custom socket path (default: $XDG_RUNTIME_DIR/garclip.sock)
318
-log_level = "info"                # Logging level
319
-```
320
-
321
----
322
-
323
-## Implementation Phases
324
-
325
-### Phase 1: Foundation (Core X11 + Basic Daemon)
326
-
327
-**Goal**: Get the daemon running and monitoring clipboard
328
-
329
-1. **Project scaffolding**
330
-   - Create workspace structure
331
-   - Set up Cargo.toml with dependencies
332
-   - Basic error types
333
-
334
-2. **X11 atom management** (`x11/atoms.rs`)
335
-   - Intern all required atoms
336
-   - Atom caching
337
-
338
-3. **Selection ownership** (`x11/selection.rs`)
339
-   - Create hidden window for selection
340
-   - Claim/release ownership
341
-   - Check current owner
342
-
343
-4. **Basic clipboard read** (`x11/transfer.rs`)
344
-   - Request TARGETS from owner
345
-   - Request UTF8_STRING/STRING data
346
-   - Handle SelectionNotify response
347
-
348
-5. **Daemon skeleton** (`daemon/state.rs`)
349
-   - X11 event loop
350
-   - Detect clipboard changes
351
-   - Log clipboard content
352
-
353
-**Deliverable**: Daemon that prints clipboard changes to stdout
354
-
355
-### Phase 2: Clipboard Manager (Persistence + History)
356
-
357
-**Goal**: Persist clipboard data and maintain history
358
-
359
-1. **Clipboard entry types** (`clipboard/entry.rs`)
360
-   - ClipboardEntry struct
361
-   - ClipboardContent enum
362
-
363
-2. **History storage** (`clipboard/history.rs`)
364
-   - In-memory VecDeque
365
-   - Push/get/list/remove operations
366
-   - Deduplication
367
-
368
-3. **Selection serving** (`x11/transfer.rs`)
369
-   - Handle SelectionRequest events
370
-   - Respond with stored data
371
-   - TARGETS response
372
-
373
-4. **Become clipboard owner**
374
-   - Take ownership when original owner releases
375
-   - Serve clipboard content to requestors
376
-
377
-5. **Persistence** (`clipboard/history.rs`)
378
-   - Save history to JSON file
379
-   - Load on startup
380
-
381
-**Deliverable**: Daemon that persists clipboard across app closes
382
-
383
-### Phase 3: IPC Layer
384
-
385
-**Goal**: Control daemon via Unix socket
386
-
387
-1. **Protocol types** (`ipc/protocol.rs`)
388
-   - Command, Response, Event enums
389
-   - Serde serialization
390
-
391
-2. **IPC server** (`ipc/server.rs`)
392
-   - Unix socket listener
393
-   - Accept connections
394
-   - JSON message handling
395
-
396
-3. **Command handlers** (`daemon/state.rs`)
397
-   - Implement all commands
398
-   - Wire to clipboard manager
399
-
400
-4. **Event subscriptions**
401
-   - Track subscribed clients
402
-   - Broadcast events
403
-
404
-**Deliverable**: Daemon controllable via JSON over socket
405
-
406
-### Phase 4: CLI Tool (garclipctl)
407
-
408
-**Goal**: User-friendly CLI interface
409
-
410
-1. **CLI structure**
411
-   - clap argument parsing
412
-   - Subcommands for each operation
413
-
414
-2. **Commands**
415
-   - `garclipctl paste` - Print current clipboard
416
-   - `garclipctl copy <text>` - Copy text
417
-   - `garclipctl history` - List history
418
-   - `garclipctl select <id>` - Select from history
419
-   - `garclipctl clear` - Clear clipboard
420
-   - `garclipctl search <query>` - Search history
421
-   - `garclipctl status` - Show daemon status
422
-
423
-3. **Output formatting**
424
-   - Human-readable default
425
-   - `--json` flag for scripting
426
-
427
-**Deliverable**: Full CLI control tool
428
-
429
-### Phase 5: Configuration + Polish
430
-
431
-**Goal**: Configurable and robust
432
-
433
-1. **Configuration loading**
434
-   - Parse TOML config
435
-   - Defaults for all values
436
-   - Config reload via IPC
437
-
438
-2. **Filtering**
439
-   - Ignore patterns
440
-   - Ignore window classes
441
-
442
-3. **PRIMARY selection**
443
-   - Monitor PRIMARY in addition to CLIPBOARD
444
-   - Configurable
445
-
446
-4. **Signal handling**
447
-   - SIGTERM graceful shutdown
448
-   - SIGHUP config reload
449
-
450
-5. **Logging**
451
-   - tracing integration
452
-   - Log file support
453
-
454
-**Deliverable**: Production-ready clipboard manager
455
-
456
----
457
-
458
-## Dependencies
459
-
460
-```toml
461
-[dependencies]
462
-# X11
463
-x11rb = { version = "0.13", features = ["allow-unsafe-code"] }
464
-
465
-# Async runtime
466
-tokio = { version = "1", features = ["full", "signal"] }
467
-
468
-# Serialization
469
-serde = { version = "1.0", features = ["derive"] }
470
-serde_json = "1.0"
471
-toml = "0.8"
472
-
473
-# Error handling
474
-thiserror = "2.0"
475
-anyhow = "1.0"
476
-
477
-# CLI
478
-clap = { version = "4.5", features = ["derive"] }
479
-
480
-# Utilities
481
-dirs = "6.0"
482
-tracing = "0.1"
483
-tracing-subscriber = { version = "0.3", features = ["env-filter"] }
484
-regex = "1.10"      # For ignore patterns
485
-```
486
-
487
----
488
-
489
-## Key Implementation Details
490
-
491
-### Detecting Clipboard Changes
492
-
493
-Two approaches:
494
-
495
-1. **Polling** (simpler, works reliably)
496
-   ```rust
497
-   loop {
498
-       let owner = conn.get_selection_owner(atoms.clipboard)?.reply()?.owner;
499
-       if owner != last_owner && owner != our_window {
500
-           // New owner - request content
501
-           request_clipboard_content()?;
502
-           last_owner = owner;
503
-       }
504
-       tokio::time::sleep(Duration::from_millis(100)).await;
505
-   }
506
-   ```
507
-
508
-2. **XFixes extension** (more efficient, event-driven)
509
-   ```rust
510
-   // Request SelectionNotify events
511
-   xfixes::select_selection_input(
512
-       &conn,
513
-       our_window,
514
-       atoms.clipboard,
515
-       xfixes::SelectionEventMask::SET_SELECTION_OWNER,
516
-   )?;
517
-   ```
518
-
519
-**Recommendation**: Start with polling (simpler), add XFixes later if needed.
520
-
521
-### Requesting Clipboard Content
522
-
523
-```rust
524
-async fn request_text(&self, selection: Atom) -> Result<Option<String>> {
525
-    // 1. Request conversion to our property
526
-    self.conn.convert_selection(
527
-        self.window,
528
-        selection,
529
-        self.atoms.utf8_string,
530
-        self.atoms.garclip_data,
531
-        x11rb::CURRENT_TIME,
532
-    )?;
533
-    self.conn.flush()?;
534
-
535
-    // 2. Wait for SelectionNotify event
536
-    let event = self.wait_for_selection_notify(selection).await?;
537
-
538
-    if event.property == x11rb::NONE {
539
-        return Ok(None); // Conversion failed
540
-    }
541
-
542
-    // 3. Read property data
543
-    let reply = self.conn.get_property(
544
-        true,  // Delete after reading
545
-        self.window,
546
-        self.atoms.garclip_data,
547
-        self.atoms.utf8_string,
548
-        0,
549
-        u32::MAX / 4,
550
-    )?.reply()?;
551
-
552
-    Ok(Some(String::from_utf8_lossy(&reply.value).into_owned()))
553
-}
554
-```
555
-
556
-### Serving Clipboard Requests
557
-
558
-```rust
559
-fn handle_selection_request(&self, event: SelectionRequestEvent, data: &str) -> Result<()> {
560
-    let target = event.target;
561
-    let property = if event.property == x11rb::NONE {
562
-        event.target  // Old clients
563
-    } else {
564
-        event.property
565
-    };
566
-
567
-    if target == self.atoms.targets {
568
-        // Respond with supported targets
569
-        let targets = [
570
-            self.atoms.targets,
571
-            self.atoms.utf8_string,
572
-            self.atoms.string,
573
-            self.atoms.timestamp,
574
-        ];
575
-        self.conn.change_property32(
576
-            PropMode::REPLACE,
577
-            event.requestor,
578
-            property,
579
-            self.atoms.atom,
580
-            &targets.iter().map(|a| a.resource_id()).collect::<Vec<_>>(),
581
-        )?;
582
-    } else if target == self.atoms.utf8_string || target == self.atoms.string {
583
-        // Respond with text
584
-        self.conn.change_property8(
585
-            PropMode::REPLACE,
586
-            event.requestor,
587
-            property,
588
-            target,
589
-            data.as_bytes(),
590
-        )?;
591
-    } else {
592
-        // Unsupported target - send failure
593
-        property = x11rb::NONE;
594
-    }
595
-
596
-    // Send SelectionNotify
597
-    let notify = SelectionNotifyEvent {
598
-        response_type: SELECTION_NOTIFY_EVENT,
599
-        sequence: 0,
600
-        time: event.time,
601
-        requestor: event.requestor,
602
-        selection: event.selection,
603
-        target: event.target,
604
-        property,
605
-    };
606
-    self.conn.send_event(false, event.requestor, EventMask::NO_EVENT, notify)?;
607
-    self.conn.flush()?;
608
-
609
-    Ok(())
610
-}
611
-```
612
-
613
----
614
-
615
-## Integration Points
616
-
617
-### With gar (Window Manager)
618
-
619
-- Could query gar IPC for focused window class (for source tracking)
620
-- Future: Workspace-aware clipboard history
621
-
622
-### With garlaunch
623
-
624
-- garlaunch could have a clipboard history picker mode
625
-- Or garclip could have its own popup (using gartk)
626
-
627
----
628
-
629
-## Testing Strategy
630
-
631
-1. **Unit tests**
632
-   - History operations
633
-   - Config parsing
634
-   - IPC protocol serialization
635
-
636
-2. **Integration tests (Xephyr)**
637
-   ```bash
638
-   Xephyr -br -ac -noreset -screen 1280x720 :1 &
639
-   DISPLAY=:1 cargo run --bin garclip -- daemon
640
-   DISPLAY=:1 garclipctl status
641
-   ```
642
-
643
-3. **Manual testing**
644
-   - Copy in various apps
645
-   - Close source app, verify paste works
646
-   - History persistence across daemon restart
647
-
648
----
649
-
650
-## Progress & TODO
651
-
652
-### Phase 1: Foundation (COMPLETE)
653
-- [x] Project scaffolding (workspace, Cargo.toml)
654
-- [x] Error types
655
-- [x] X11 atom interning
656
-- [x] Selection ownership management
657
-- [x] Data transfer (request/response)
658
-- [x] Clipboard entry types (text + images)
659
-- [x] History storage with persistence
660
-- [x] Clipboard manager
661
-- [x] Configuration (TOML)
662
-- [x] IPC protocol (JSON over Unix socket)
663
-- [x] IPC server/client
664
-- [x] Daemon state machine
665
-- [x] CLI entry point
666
-- [x] garclipctl control tool
667
-
668
-### Phase 2: Daemon Command Handler (COMPLETE)
669
-- [x] Refactor client handler to use channels for daemon state access
670
-- [x] Proper async message passing between IPC and daemon loop
671
-- [x] Handle all commands through the daemon via CommandRequest + oneshot
672
-- [x] Handle Quit command for graceful shutdown
673
-- [x] Handle Subscribe for event streaming to clients
674
-
675
-### Phase 3: Selection Monitoring (COMPLETE)
676
-- [x] XFixes extension for event-driven clipboard monitoring
677
-- [x] Claim ownership when original owner releases
678
-- [x] Reduce polling overhead
679
-
680
-### Phase 4: Filtering
681
-- [ ] Regex patterns to ignore content
682
-- [ ] Window class filtering (ignore password managers, etc.)
683
-- [ ] Configurable min/max content length enforcement
684
-
685
-### Phase 5: Signals & Lifecycle
686
-- [ ] SIGHUP for config reload
687
-- [ ] Graceful SIGTERM handling
688
-- [ ] PID file for single-instance enforcement
689
-
690
-### Phase 6: Integration
691
-- [ ] Query gar IPC for focused window class (source tracking)
692
-- [ ] Optional garlaunch integration for history picker UI
693
-- [ ] Systemd user service file
694
-
695
----
696
-
697
-## Open Questions
698
-
699
-1. **Encryption** - Should sensitive clipboard data be encrypted at rest?
700
-   - Recommendation: Optional, not in initial implementation
701
-
702
-2. **Popup UI** - Should garclip have its own history picker UI, or rely on garlaunch?
703
-   - Recommendation: Defer UI to later, CLI-first approach