Add build output dependency file for mouse-test application
Add standard menu fore keybinds
This commit is contained in:
174
README.md
174
README.md
@@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
En terminal-baserad fönsterhanterare med flytande fönster, full mussuport och SSH-kompatibilitet — inspirerad av tmux men med ett modernt UX.
|
En terminal-baserad fönsterhanterare med flytande fönster, full mussuport och SSH-kompatibilitet — inspirerad av tmux men med ett modernt UX.
|
||||||
|
|
||||||
## Funktioner (planerade)
|
## Funktioner
|
||||||
|
|
||||||
- Flytande terminaler med fri positionering och storleksändring
|
- Flytande terminaler med fri positionering och storleksändring
|
||||||
- Full mussuport: vänster/höger klick, scroll, markering
|
- Full mussuport: vänster/höger/mitten-klick, scroll, drag, ANSI-muse-protokoll vidarebefordras till appar
|
||||||
- Topanel och/eller bottenpanel
|
- Panel (topp/botten) med klickbara knappar och dynamiska status-widgets
|
||||||
|
- Dropdown-menyer med undermenyer
|
||||||
|
- Körbar kommandodialog (`ctrl+space`)
|
||||||
- Dynamisk omrendering vid terminalstorlek-ändringar
|
- Dynamisk omrendering vid terminalstorlek-ändringar
|
||||||
- SSH-kompatibel (fungerar via vanliga ANSI escape-koder)
|
- SSH-kompatibel (fungerar via vanliga ANSI escape-koder)
|
||||||
- Kompilerar till fristående binärer för Windows och Linux
|
- Kompilerar till fristående binärer för Linux och Windows
|
||||||
|
|
||||||
## Teknikstack
|
## Teknikstack
|
||||||
|
|
||||||
@@ -18,6 +20,8 @@ En terminal-baserad fönsterhanterare med flytande fönster, full mussuport och
|
|||||||
| Terminal I/O, mus, resize | `crossterm` |
|
| Terminal I/O, mus, resize | `crossterm` |
|
||||||
| TUI-rendering, buffersystem | `ratatui` |
|
| TUI-rendering, buffersystem | `ratatui` |
|
||||||
| Pseudoterminal (PTY/ConPTY) | `portable-pty` |
|
| Pseudoterminal (PTY/ConPTY) | `portable-pty` |
|
||||||
|
| VT100-emulering | `vt100` |
|
||||||
|
| Konfiguration | `toml` + `serde` |
|
||||||
| Språk | Rust |
|
| Språk | Rust |
|
||||||
|
|
||||||
## Bygga projektet
|
## Bygga projektet
|
||||||
@@ -54,9 +58,165 @@ Använd `Terminal > Run Task` och välj:
|
|||||||
```
|
```
|
||||||
TUI-WM/
|
TUI-WM/
|
||||||
├── src/
|
├── src/
|
||||||
│ └── main.rs
|
│ ├── main.rs — Startpunkt, event-loop
|
||||||
|
│ ├── app.rs — All logik: fönsterhantering, mus, event-routing
|
||||||
|
│ ├── config.rs — TOML-konfiguration och dess datatyper
|
||||||
|
│ ├── pty.rs — PTY-instans (startar och kommunicerar med shell/app)
|
||||||
|
│ └── render.rs — Ratatui-rendering av paneler och fönster
|
||||||
|
├── config.toml — Huvud-konfigfil (panels, keybinds)
|
||||||
|
├── panels/
|
||||||
|
│ └── topbar.toml — Panel-definition (knappar, status-widgets)
|
||||||
├── .vscode/
|
├── .vscode/
|
||||||
│ └── tasks.json
|
│ └── tasks.json
|
||||||
├── Cargo.toml
|
└── Cargo.toml
|
||||||
└── README.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
Konfigurationen delas upp i två nivåer:
|
||||||
|
|
||||||
|
1. **`config.toml`** — huvud-konfigfil, definierar paneler och keybinds
|
||||||
|
2. **`panels/<fil>.toml`** — extern panel-fil, definierar knapparna och status-widgets för en specifik panel
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Paneler
|
||||||
|
|
||||||
|
En panel är en topbar eller bottombar med knappar och status-widgets.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# config.toml
|
||||||
|
[[panel]]
|
||||||
|
position = "top" # "top" | "bottom"
|
||||||
|
file = "panels/topbar.toml" # länk till extern panel-definition (valfri)
|
||||||
|
```
|
||||||
|
|
||||||
|
Eller inline utan extern fil:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[panel]]
|
||||||
|
position = "bottom"
|
||||||
|
|
||||||
|
[[panel.item]]
|
||||||
|
label = "Terminal"
|
||||||
|
action = { type = "spawn_terminal" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Knappar (items)
|
||||||
|
|
||||||
|
Varje knapp i en panel kopplas till en `action`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# panels/topbar.toml
|
||||||
|
[[item]]
|
||||||
|
label = "Terminal"
|
||||||
|
action = { type = "spawn_terminal" }
|
||||||
|
|
||||||
|
[[item]]
|
||||||
|
label = "Exit"
|
||||||
|
action = { type = "exit" }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Tillgängliga actions
|
||||||
|
|
||||||
|
| Action | Beskrivning |
|
||||||
|
|---|---|
|
||||||
|
| `{ type = "exit" }` | Avslutar TUI-WM |
|
||||||
|
| `{ type = "spawn_terminal" }` | Startar nytt terminalfönster med systemets standard-shell |
|
||||||
|
| `{ type = "spawn_terminal", shell = "/bin/bash" }` | Startar terminal med specifikt shell/program |
|
||||||
|
| `{ type = "spawn_run_dialog" }` | Öppnar körbar kommandodialog |
|
||||||
|
| `{ type = "run_program", command = "htop" }` | Kör ett program i terminalfönster |
|
||||||
|
| `{ type = "run_script", path = "/path/to/script.sh" }` | Kör ett skript i terminalfönster |
|
||||||
|
| `{ type = "submenu", item = [...] }` | Öppnar en dropdown-undermeny |
|
||||||
|
|
||||||
|
#### Dropdown-undermeny
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[item]]
|
||||||
|
label = "Apps"
|
||||||
|
action = { type = "submenu", item = [
|
||||||
|
{ label = "htop", action = { type = "run_program", command = "htop" } },
|
||||||
|
{ label = "vim", action = { type = "run_program", command = "vim" } },
|
||||||
|
] }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Status-widgets
|
||||||
|
|
||||||
|
Status-widgets är dynamiska textblock i panelen som kör ett kommando periodiskt.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# panels/topbar.toml
|
||||||
|
[[status]]
|
||||||
|
command = "date '+%H:%M:%S'" # shell-kommando vars stdout visas
|
||||||
|
interval = 1 # uppdateringsintervall i sekunder (default: 10)
|
||||||
|
width = 10 # teckenbredd i panelen
|
||||||
|
align = "right" # "right" (default) | "left"
|
||||||
|
```
|
||||||
|
|
||||||
|
Flera status-widgets kan definieras. Höger-dockade renderas från höger kant inåt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Keybinds
|
||||||
|
|
||||||
|
Keybinds definieras i `config.toml` och kopplas till samme `action`-typ som knappar.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[keybind]]
|
||||||
|
key = "ctrl+space"
|
||||||
|
scope = "global" # "global" | "wm" (default)
|
||||||
|
action = { type = "spawn_run_dialog" }
|
||||||
|
|
||||||
|
[[keybind]]
|
||||||
|
key = "alt+enter"
|
||||||
|
scope = "global"
|
||||||
|
action = { type = "spawn_terminal" }
|
||||||
|
|
||||||
|
[[keybind]]
|
||||||
|
key = "ctrl+q"
|
||||||
|
scope = "wm"
|
||||||
|
action = { type = "exit" }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Scope
|
||||||
|
|
||||||
|
| Scope | Beskrivning |
|
||||||
|
|---|---|
|
||||||
|
| `global` | Aktiveras alltid, även när ett terminalfönster har fokus |
|
||||||
|
| `wm` | Aktiveras bara när inget terminalfönster är fokuserat (default) |
|
||||||
|
|
||||||
|
#### Tangenter
|
||||||
|
|
||||||
|
Format: `"modifier+modifier+key"`. Tillgängliga modifiers: `ctrl`, `alt`, `shift`.
|
||||||
|
Exempelnycklar: `a`–`z`, `0`–`9`, `space`, `enter`, `esc`, `tab`, `backspace`, `delete`, `up`, `down`, `left`, `right`, `home`, `end`, `pageup`, `pagedown`, `f1`–`f12`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mushantering
|
||||||
|
|
||||||
|
| Åtgärd | Effekt |
|
||||||
|
|---|---|
|
||||||
|
| Vänsterklick på titel-rad | Fokuserar och börjar flytta fönstret |
|
||||||
|
| Vänsterklick på `[x]` | Stänger fönstret |
|
||||||
|
| Vänsterklick på kant/hörn | Börjar storleksändra fönstret |
|
||||||
|
| Klick i innehållsyta | Fokuserar fönstret och skickar musklick till appen i terminalen |
|
||||||
|
| Scroll inuti terminal | Skickar scroll-event till appen (med ANSI mus-protokoll) eller pilar som fallback |
|
||||||
|
| Höger/mitten-klick i terminal | Vidarebefordras direkt till appen |
|
||||||
|
|
||||||
|
Möss-tracking fungerar automatiskt — om appen i terminalen aktiverar ANSI mus-protokoll (t.ex. vim, htop, ncurses-appar) vidarebefordras alla mus-events korrekt med rätt encoding (SGR, UTF-8 eller default X10).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Standardtangenter
|
||||||
|
|
||||||
|
| Tangent | Funktion |
|
||||||
|
|---|---|
|
||||||
|
| `ctrl+space` | Öppna körbar kommandodialog |
|
||||||
|
| `alt+enter` | Nytt terminalfönster |
|
||||||
|
| `ctrl+q` | Avsluta (bara när inget terminalfönster fokuseras) |
|
||||||
|
|||||||
@@ -12,16 +12,19 @@ file = "panels/topbar.toml"
|
|||||||
[[keybind]]
|
[[keybind]]
|
||||||
key = "ctrl+space"
|
key = "ctrl+space"
|
||||||
scope = "global"
|
scope = "global"
|
||||||
|
label = "Öppna kommandodialog"
|
||||||
action = { type = "spawn_run_dialog" }
|
action = { type = "spawn_run_dialog" }
|
||||||
|
|
||||||
# Starta ny terminal med Alt+Enter (global)
|
# Starta ny terminal med Alt+Enter
|
||||||
[[keybind]]
|
[[keybind]]
|
||||||
key = "alt+enter"
|
key = "alt+enter"
|
||||||
scope = "global"
|
scope = "global"
|
||||||
|
label = "Ny terminal"
|
||||||
action = { type = "spawn_terminal" }
|
action = { type = "spawn_terminal" }
|
||||||
|
|
||||||
# Avsluta TUI-WM när skrivbordet är fokuserat
|
# Avsluta TUI-WM när skrivbordet är fokuserat
|
||||||
[[keybind]]
|
[[keybind]]
|
||||||
key = "ctrl+q"
|
key = "ctrl+q"
|
||||||
scope = "wm"
|
scope = "wm"
|
||||||
|
label = "Avsluta TUI-WM"
|
||||||
action = { type = "exit" }
|
action = { type = "exit" }
|
||||||
|
|||||||
3
dist/linux/config.toml
vendored
3
dist/linux/config.toml
vendored
@@ -12,16 +12,19 @@ file = "panels/topbar.toml"
|
|||||||
[[keybind]]
|
[[keybind]]
|
||||||
key = "ctrl+space"
|
key = "ctrl+space"
|
||||||
scope = "global"
|
scope = "global"
|
||||||
|
label = "Öppna kommandodialog"
|
||||||
action = { type = "spawn_run_dialog" }
|
action = { type = "spawn_run_dialog" }
|
||||||
|
|
||||||
# Starta ny terminal med Alt+Enter
|
# Starta ny terminal med Alt+Enter
|
||||||
[[keybind]]
|
[[keybind]]
|
||||||
key = "alt+enter"
|
key = "alt+enter"
|
||||||
scope = "global"
|
scope = "global"
|
||||||
|
label = "Ny terminal"
|
||||||
action = { type = "spawn_terminal" }
|
action = { type = "spawn_terminal" }
|
||||||
|
|
||||||
# Avsluta TUI-WM när skrivbordet är fokuserat
|
# Avsluta TUI-WM när skrivbordet är fokuserat
|
||||||
[[keybind]]
|
[[keybind]]
|
||||||
key = "ctrl+q"
|
key = "ctrl+q"
|
||||||
scope = "wm"
|
scope = "wm"
|
||||||
|
label = "Avsluta TUI-WM"
|
||||||
action = { type = "exit" }
|
action = { type = "exit" }
|
||||||
|
|||||||
3
dist/win/config.toml
vendored
3
dist/win/config.toml
vendored
@@ -12,16 +12,19 @@ file = "panels/topbar.toml"
|
|||||||
[[keybind]]
|
[[keybind]]
|
||||||
key = "ctrl+space"
|
key = "ctrl+space"
|
||||||
scope = "global"
|
scope = "global"
|
||||||
|
label = "Öppna kommandodialog"
|
||||||
action = { type = "spawn_run_dialog" }
|
action = { type = "spawn_run_dialog" }
|
||||||
|
|
||||||
# Starta ny PowerShell-terminal
|
# Starta ny PowerShell-terminal
|
||||||
[[keybind]]
|
[[keybind]]
|
||||||
key = "alt+enter"
|
key = "alt+enter"
|
||||||
scope = "global"
|
scope = "global"
|
||||||
|
label = "Ny terminal"
|
||||||
action = { type = "spawn_terminal", shell = "powershell.exe" }
|
action = { type = "spawn_terminal", shell = "powershell.exe" }
|
||||||
|
|
||||||
# Avsluta TUI-WM när skrivbordet är fokuserat
|
# Avsluta TUI-WM när skrivbordet är fokuserat
|
||||||
[[keybind]]
|
[[keybind]]
|
||||||
key = "ctrl+q"
|
key = "ctrl+q"
|
||||||
scope = "wm"
|
scope = "wm"
|
||||||
|
label = "Avsluta TUI-WM"
|
||||||
action = { type = "exit" }
|
action = { type = "exit" }
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
label = "Terminal"
|
label = "Terminal"
|
||||||
action = { type = "spawn_terminal" }
|
action = { type = "spawn_terminal" }
|
||||||
|
|
||||||
|
[[item]]
|
||||||
|
label = "mouse-test"
|
||||||
|
action = { type = "run_program", command = "testapps/mouse-test/target/release/mouse-test" }
|
||||||
|
|
||||||
[[item]]
|
[[item]]
|
||||||
label = "Exit"
|
label = "Exit"
|
||||||
action = { type = "exit" }
|
action = { type = "exit" }
|
||||||
|
|||||||
441
src/app.rs
441
src/app.rs
@@ -108,6 +108,10 @@ pub struct App {
|
|||||||
|
|
||||||
// Parsade keybinds från config
|
// Parsade keybinds från config
|
||||||
pub parsed_keybinds: Vec<ParsedKeybind>,
|
pub parsed_keybinds: Vec<ParsedKeybind>,
|
||||||
|
|
||||||
|
// TUI-WM logo-knapp rects (en per panel), för klick och hover
|
||||||
|
pub tui_wm_btn_rects: Vec<Rect>,
|
||||||
|
pub hovered_tui_btn: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DropdownState {
|
pub struct DropdownState {
|
||||||
@@ -116,6 +120,8 @@ pub struct DropdownState {
|
|||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
pub item_rects: Vec<Rect>,
|
pub item_rects: Vec<Rect>,
|
||||||
pub hovered: Option<usize>,
|
pub hovered: Option<usize>,
|
||||||
|
/// Explicit ankar-X vid layout-uppdatering (används för TUI-WM-menyn)
|
||||||
|
pub anchor_x: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FloatingWindow {
|
pub struct FloatingWindow {
|
||||||
@@ -129,6 +135,13 @@ pub struct FloatingWindow {
|
|||||||
pub resizing: Option<ResizeState>,
|
pub resizing: Option<ResizeState>,
|
||||||
/// Om false: inga resize-kanter, kan inte storleksändras
|
/// Om false: inga resize-kanter, kan inte storleksändras
|
||||||
pub resizable: bool,
|
pub resizable: bool,
|
||||||
|
/// Mus-tracking-läge (PressRelease / AnyMotion osv.) begärt av appen.
|
||||||
|
/// Lagras oberoende av vt100-parser så att det överlever parser-återskapning vid resize.
|
||||||
|
pub mouse_mode: vt100::MouseProtocolMode,
|
||||||
|
/// Mus-encoding begärd av appen (Default / Sgr / Utf8).
|
||||||
|
pub mouse_encoding: vt100::MouseProtocolEncoding,
|
||||||
|
/// Delvis mottagen escape-sekvens från PTY (för sekvenser som klippts tvärs genom en chunk-gräns).
|
||||||
|
mouse_seq_carry: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum WindowContent {
|
pub enum WindowContent {
|
||||||
@@ -324,12 +337,15 @@ impl App {
|
|||||||
focused_id: None,
|
focused_id: None,
|
||||||
status_states,
|
status_states,
|
||||||
parsed_keybinds,
|
parsed_keybinds,
|
||||||
|
tui_wm_btn_rects: Vec::new(),
|
||||||
|
hovered_tui_btn: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_layout(&mut self, area: Rect) {
|
pub fn update_layout(&mut self, area: Rect) {
|
||||||
self.panel_rects.clear();
|
self.panel_rects.clear();
|
||||||
self.panel_item_rects.clear();
|
self.panel_item_rects.clear();
|
||||||
|
self.tui_wm_btn_rects.clear();
|
||||||
|
|
||||||
let mut top_used = 0u16;
|
let mut top_used = 0u16;
|
||||||
let mut bottom_used = 0u16;
|
let mut bottom_used = 0u16;
|
||||||
@@ -349,7 +365,8 @@ impl App {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut item_rects = Vec::new();
|
let mut item_rects = Vec::new();
|
||||||
let mut x = rect.x + 10;
|
// Items börjar efter logo (9) + fokus-dot (1) + mellanrum (1) = x+12
|
||||||
|
let mut x = rect.x + 12;
|
||||||
for item in &panel.items {
|
for item in &panel.items {
|
||||||
let w = item.label.len() as u16 + 2;
|
let w = item.label.len() as u16 + 2;
|
||||||
item_rects.push(Rect::new(x, rect.y, w, 1));
|
item_rects.push(Rect::new(x, rect.y, w, 1));
|
||||||
@@ -358,6 +375,8 @@ impl App {
|
|||||||
|
|
||||||
self.panel_rects.push(rect);
|
self.panel_rects.push(rect);
|
||||||
self.panel_item_rects.push(item_rects);
|
self.panel_item_rects.push(item_rects);
|
||||||
|
// Logo-knapp rect: " TUI-WM " (9 tecken bred) vid x+1
|
||||||
|
self.tui_wm_btn_rects.push(Rect::new(rect.x + 1, rect.y, 9, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.content_area = Rect::new(
|
self.content_area = Rect::new(
|
||||||
@@ -372,7 +391,9 @@ impl App {
|
|||||||
if let (Some(pr), Some(ir)) =
|
if let (Some(pr), Some(ir)) =
|
||||||
(self.panel_rects.get(pi), self.panel_item_rects.get(pi))
|
(self.panel_rects.get(pi), self.panel_item_rects.get(pi))
|
||||||
{
|
{
|
||||||
let x = ir.first().map(|r| r.x).unwrap_or(pr.x);
|
let x = dd.anchor_x
|
||||||
|
.or_else(|| ir.first().map(|r| r.x))
|
||||||
|
.unwrap_or(pr.x);
|
||||||
let y = pr.y + 1;
|
let y = pr.y + 1;
|
||||||
let width = dd
|
let width = dd
|
||||||
.items
|
.items
|
||||||
@@ -382,7 +403,7 @@ impl App {
|
|||||||
.unwrap_or(12)
|
.unwrap_or(12)
|
||||||
.max(12);
|
.max(12);
|
||||||
dd.item_rects = (0..dd.items.len())
|
dd.item_rects = (0..dd.items.len())
|
||||||
.map(|i| Rect::new(x, y + i as u16, width, 1))
|
.map(|i| Rect::new(x, y + 1 + i as u16, width, 1))
|
||||||
.collect();
|
.collect();
|
||||||
dd.rect = Rect::new(x, y, width, dd.items.len() as u16);
|
dd.rect = Rect::new(x, y, width, dd.items.len() as u16);
|
||||||
}
|
}
|
||||||
@@ -390,7 +411,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
// ── PTY-data → vt100-parser ──────────────────────────────────────────
|
// ── PTY-data → vt100-parser + mus-tracking-skanning ──────────────────────────────────────
|
||||||
for window in &mut self.windows {
|
for window in &mut self.windows {
|
||||||
let WindowContent::Terminal { rx, parser, alive, .. } = &mut window.content else { continue };
|
let WindowContent::Terminal { rx, parser, alive, .. } = &mut window.content else { continue };
|
||||||
if !*alive {
|
if !*alive {
|
||||||
@@ -398,7 +419,17 @@ impl App {
|
|||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
match rx.try_recv() {
|
match rx.try_recv() {
|
||||||
Ok(data) => parser.process(&data),
|
Ok(data) => {
|
||||||
|
// Scanna för mus-escape-sekvenser FÖRE vi ger data till vt100-parsern.
|
||||||
|
// På så sätt överlever mus-läget parser-återskapning vid resize.
|
||||||
|
scan_mouse_tracking(
|
||||||
|
&data,
|
||||||
|
&mut window.mouse_mode,
|
||||||
|
&mut window.mouse_encoding,
|
||||||
|
&mut window.mouse_seq_carry,
|
||||||
|
);
|
||||||
|
parser.process(&data);
|
||||||
|
}
|
||||||
Err(mpsc::TryRecvError::Empty) => break,
|
Err(mpsc::TryRecvError::Empty) => break,
|
||||||
Err(mpsc::TryRecvError::Disconnected) => {
|
Err(mpsc::TryRecvError::Disconnected) => {
|
||||||
*alive = false;
|
*alive = false;
|
||||||
@@ -418,8 +449,10 @@ impl App {
|
|||||||
}
|
}
|
||||||
let (cur_rows, cur_cols) = parser.screen().size();
|
let (cur_rows, cur_cols) = parser.screen().size();
|
||||||
if cur_rows != new_rows || cur_cols != new_cols {
|
if cur_rows != new_rows || cur_cols != new_cols {
|
||||||
|
crate::log::log(&format!("PTY resize: {}x{} -> {}x{} (mouse mode was {:?})", cur_cols, cur_rows, new_cols, new_rows, window.mouse_mode));
|
||||||
let _ = pty.resize(new_rows, new_cols);
|
let _ = pty.resize(new_rows, new_cols);
|
||||||
*parser = vt100::Parser::new(new_rows, new_cols, 0);
|
*parser = vt100::Parser::new(new_rows, new_cols, 0);
|
||||||
|
// OBS: window.mouse_mode / window.mouse_encoding berörs inte — överlever restet.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,16 +627,58 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_mouse(&mut self, col: u16, row: u16, kind: MouseEventKind) {
|
fn handle_mouse(&mut self, col: u16, row: u16, kind: MouseEventKind) {
|
||||||
|
// Logga alla mushändelser utom Moved (för mycker brus)
|
||||||
|
if !matches!(kind, MouseEventKind::Moved) {
|
||||||
|
crate::log::log(&format!("MOUSE {:?} col={} row={}", kind, col, row));
|
||||||
|
}
|
||||||
match kind {
|
match kind {
|
||||||
MouseEventKind::Moved => self.update_hover(col, row),
|
MouseEventKind::Moved => {
|
||||||
MouseEventKind::Drag(MouseButton::Left) => self.handle_drag(col, row),
|
self.update_hover(col, row);
|
||||||
MouseEventKind::Up(MouseButton::Left) => {
|
self.forward_mouse_to_focused_terminal(kind, col, row);
|
||||||
|
}
|
||||||
|
MouseEventKind::Drag(MouseButton::Left) => {
|
||||||
|
self.handle_drag(col, row);
|
||||||
|
// Vidarebefordra drag-rörelse till terminal om vi inte håller på med WM-drag/resize
|
||||||
|
let doing_wm = self.windows.iter().any(|w| w.dragging.is_some() || w.resizing.is_some());
|
||||||
|
if !doing_wm {
|
||||||
|
self.forward_mouse_to_focused_terminal(kind, col, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseEventKind::Up(btn) => {
|
||||||
for w in &mut self.windows {
|
for w in &mut self.windows {
|
||||||
w.dragging = None;
|
w.dragging = None;
|
||||||
w.resizing = None;
|
w.resizing = None;
|
||||||
}
|
}
|
||||||
|
self.forward_mouse_to_focused_terminal(MouseEventKind::Up(btn), col, row);
|
||||||
}
|
}
|
||||||
MouseEventKind::Down(MouseButton::Left) => self.handle_click(col, row),
|
MouseEventKind::Down(MouseButton::Left) => self.handle_click(col, row),
|
||||||
|
MouseEventKind::Down(btn) => {
|
||||||
|
// Höger/mitten-klick: fokusera fönster och vidarebefordra till terminal
|
||||||
|
let hit = self.windows.iter().rev().find(|w| w.in_content(col, row)).and_then(|w| {
|
||||||
|
if let WindowContent::Terminal { alive, .. } = &w.content {
|
||||||
|
if !*alive { return None; }
|
||||||
|
Some((w.id, w.content_rect(), w.mouse_mode, w.mouse_encoding))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some((id, content_rect, mode, encoding)) = hit {
|
||||||
|
crate::log::log(&format!(" -> right/middle hit window={} mode={:?} enc={:?} content_rect={:?}", id, mode, encoding, content_rect));
|
||||||
|
self.focus_window(id);
|
||||||
|
if let Some(bytes) = encode_mouse_event(MouseEventKind::Down(btn), col, row, content_rect, mode, encoding) {
|
||||||
|
crate::log::log(&format!(" -> sending {} bytes: {:?}", bytes.len(), bytes));
|
||||||
|
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
|
||||||
|
if let WindowContent::Terminal { pty, .. } = &mut w.content {
|
||||||
|
let _ = pty.write_input(&bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
crate::log::log(" -> encode_mouse_event returned None (mode=None or filtered)");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
crate::log::log(" -> no terminal window hit");
|
||||||
|
}
|
||||||
|
}
|
||||||
MouseEventKind::ScrollUp => self.handle_scroll(col, row, true),
|
MouseEventKind::ScrollUp => self.handle_scroll(col, row, true),
|
||||||
MouseEventKind::ScrollDown => self.handle_scroll(col, row, false),
|
MouseEventKind::ScrollDown => self.handle_scroll(col, row, false),
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -614,6 +689,7 @@ impl App {
|
|||||||
self.hovered_panel_item = None;
|
self.hovered_panel_item = None;
|
||||||
self.hovered_window_close = None;
|
self.hovered_window_close = None;
|
||||||
self.hovered_resize = None;
|
self.hovered_resize = None;
|
||||||
|
self.hovered_tui_btn = false;
|
||||||
|
|
||||||
// Dropdown-hover
|
// Dropdown-hover
|
||||||
if let Some(dd) = &mut self.dropdown {
|
if let Some(dd) = &mut self.dropdown {
|
||||||
@@ -654,6 +730,14 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TUI-WM logo-knapp
|
||||||
|
for &r in &self.tui_wm_btn_rects {
|
||||||
|
if in_rect(col, row, r) {
|
||||||
|
self.hovered_tui_btn = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_drag(&mut self, col: u16, row: u16) {
|
fn handle_drag(&mut self, col: u16, row: u16) {
|
||||||
@@ -735,14 +819,42 @@ impl App {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Innehållsyta → fokus
|
// 5. Innehållsyta → fokus + vidarebefordra musklick till terminal
|
||||||
let content_id = self.windows.iter().rev().find(|w| w.in_content(col, row)).map(|w| w.id);
|
let hit = self.windows.iter().rev().find(|w| w.in_content(col, row)).and_then(|w| {
|
||||||
if let Some(id) = content_id {
|
let (mode, encoding) = match &w.content {
|
||||||
|
WindowContent::Terminal { alive, .. } if *alive => (w.mouse_mode, w.mouse_encoding),
|
||||||
|
_ => (vt100::MouseProtocolMode::None, vt100::MouseProtocolEncoding::Default),
|
||||||
|
};
|
||||||
|
Some((w.id, w.content_rect(), mode, encoding))
|
||||||
|
});
|
||||||
|
if let Some((id, content_rect, mode, encoding)) = hit {
|
||||||
|
crate::log::log(&format!(" click step5: window={} mode={:?} enc={:?} col={} row={} content_rect={:?}", id, mode, encoding, col, row, content_rect));
|
||||||
self.focus_window(id);
|
self.focus_window(id);
|
||||||
|
if let Some(bytes) = encode_mouse_event(
|
||||||
|
MouseEventKind::Down(MouseButton::Left), col, row, content_rect, mode, encoding,
|
||||||
|
) {
|
||||||
|
crate::log::log(&format!(" -> sending {} bytes: {:?}", bytes.len(), bytes));
|
||||||
|
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
|
||||||
|
if let WindowContent::Terminal { pty, .. } = &mut w.content {
|
||||||
|
let _ = pty.write_input(&bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
crate::log::log(" -> encode_mouse_event returned None");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Panel-knappar
|
// 6. TUI-WM logo → keybind-meny
|
||||||
|
for pi in 0..self.tui_wm_btn_rects.len() {
|
||||||
|
let r = self.tui_wm_btn_rects[pi];
|
||||||
|
if in_rect(col, row, r) {
|
||||||
|
self.open_tui_wm_dropdown(pi);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Panel-knappar
|
||||||
for (pi, item_rects) in self.panel_item_rects.clone().iter().enumerate() {
|
for (pi, item_rects) in self.panel_item_rects.clone().iter().enumerate() {
|
||||||
for (ii, r) in item_rects.iter().enumerate() {
|
for (ii, r) in item_rects.iter().enumerate() {
|
||||||
if in_rect(col, row, *r) {
|
if in_rect(col, row, *r) {
|
||||||
@@ -756,11 +868,43 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 8. Inget träffat → skrivbordet fokuseras
|
||||||
|
self.focused_id = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_scroll(&mut self, col: u16, row: u16, up: bool) {
|
fn handle_scroll(&mut self, col: u16, row: u16, up: bool) {
|
||||||
let id = self.windows.iter().rev().find(|w| in_rect(col, row, w.rect())).map(|w| w.id);
|
let id = self.windows.iter().rev().find(|w| in_rect(col, row, w.rect())).map(|w| w.id);
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
|
// Hämta mus-tracking-info utan mutable lån
|
||||||
|
let info = self.windows.iter().find(|w| w.id == id).and_then(|w| {
|
||||||
|
if let WindowContent::Terminal { alive, .. } = &w.content {
|
||||||
|
if !*alive { return None; }
|
||||||
|
if w.mouse_mode == vt100::MouseProtocolMode::None { return None; }
|
||||||
|
Some((w.content_rect(), w.mouse_mode, w.mouse_encoding))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some((content_rect, mode, encoding)) = info {
|
||||||
|
let kind = if up { MouseEventKind::ScrollUp } else { MouseEventKind::ScrollDown };
|
||||||
|
crate::log::log(&format!(" scroll up={} window={} mode={:?} enc={:?}", up, id, mode, encoding));
|
||||||
|
if let Some(bytes) = encode_mouse_event(kind, col, row, content_rect, mode, encoding) {
|
||||||
|
crate::log::log(&format!(" -> scroll sending {} bytes: {:?}", bytes.len(), bytes));
|
||||||
|
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
|
||||||
|
if let WindowContent::Terminal { pty, .. } = &mut w.content {
|
||||||
|
let _ = pty.write_input(&bytes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
crate::log::log(" -> scroll encode returned None");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: piltangenter om ingen mus-tracking är aktiv
|
||||||
|
crate::log::log(&format!(" scroll fallback arrows up={} window={}", up, id));
|
||||||
let bytes: &[u8] = if up { b"\x1b[A\x1b[A\x1b[A" } else { b"\x1b[B\x1b[B\x1b[B" };
|
let bytes: &[u8] = if up { b"\x1b[A\x1b[A\x1b[A" } else { b"\x1b[B\x1b[B\x1b[B" };
|
||||||
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
|
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
|
||||||
if let WindowContent::Terminal { pty, .. } = &mut w.content {
|
if let WindowContent::Terminal { pty, .. } = &mut w.content {
|
||||||
@@ -770,6 +914,33 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Vidarebefordrar ett mushändelse till det fokuserade terminalfönstret
|
||||||
|
/// om musen befinner sig i fönstrets innehållsyta och mus-tracking är aktivt.
|
||||||
|
fn forward_mouse_to_focused_terminal(&mut self, kind: MouseEventKind, col: u16, row: u16) {
|
||||||
|
let Some(id) = self.focused_id else { return };
|
||||||
|
|
||||||
|
let info = self.windows.iter().find(|w| w.id == id).and_then(|w| {
|
||||||
|
if !w.in_content(col, row) { return None; }
|
||||||
|
if let WindowContent::Terminal { alive, .. } = &w.content {
|
||||||
|
if !*alive { return None; }
|
||||||
|
Some((w.content_rect(), w.mouse_mode, w.mouse_encoding))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some((content_rect, mode, encoding)) = info {
|
||||||
|
if let Some(bytes) = encode_mouse_event(kind, col, row, content_rect, mode, encoding) {
|
||||||
|
crate::log::log(&format!(" forward {:?} -> window={} {} bytes: {:?}", kind, id, bytes.len(), bytes));
|
||||||
|
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
|
||||||
|
if let WindowContent::Terminal { pty, .. } = &mut w.content {
|
||||||
|
let _ = pty.write_input(&bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn execute_action(&mut self, action: MenuAction) {
|
fn execute_action(&mut self, action: MenuAction) {
|
||||||
match action {
|
match action {
|
||||||
MenuAction::Exit => self.should_quit = true,
|
MenuAction::Exit => self.should_quit = true,
|
||||||
@@ -781,6 +952,7 @@ impl App {
|
|||||||
MenuAction::RunProgram { command, .. } => self.spawn_terminal(&command),
|
MenuAction::RunProgram { command, .. } => self.spawn_terminal(&command),
|
||||||
MenuAction::SpawnRunDialog => self.spawn_run_dialog(),
|
MenuAction::SpawnRunDialog => self.spawn_run_dialog(),
|
||||||
MenuAction::Submenu { .. } => {}
|
MenuAction::Submenu { .. } => {}
|
||||||
|
MenuAction::NoOp => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -803,6 +975,9 @@ impl App {
|
|||||||
dragging: None,
|
dragging: None,
|
||||||
resizing: None,
|
resizing: None,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
|
mouse_mode: vt100::MouseProtocolMode::None,
|
||||||
|
mouse_encoding: vt100::MouseProtocolEncoding::Default,
|
||||||
|
mouse_seq_carry: Vec::new(),
|
||||||
});
|
});
|
||||||
self.focus_window(id);
|
self.focus_window(id);
|
||||||
}
|
}
|
||||||
@@ -821,8 +996,9 @@ impl App {
|
|||||||
let x = ir.x;
|
let x = ir.x;
|
||||||
let y = pr.y + 1;
|
let y = pr.y + 1;
|
||||||
|
|
||||||
|
// item_rects peker på den faktiska synliga raden (y+1 = innanför övre kant)
|
||||||
let item_rects: Vec<Rect> =
|
let item_rects: Vec<Rect> =
|
||||||
(0..items.len()).map(|i| Rect::new(x, y + i as u16, width, 1)).collect();
|
(0..items.len()).map(|i| Rect::new(x, y + 1 + i as u16, width, 1)).collect();
|
||||||
|
|
||||||
self.dropdown = Some(DropdownState {
|
self.dropdown = Some(DropdownState {
|
||||||
panel_idx,
|
panel_idx,
|
||||||
@@ -830,9 +1006,51 @@ impl App {
|
|||||||
item_rects,
|
item_rects,
|
||||||
items,
|
items,
|
||||||
hovered: None,
|
hovered: None,
|
||||||
|
anchor_x: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Öppnar en dropdown med alla keybinds, förankrad vid TUI-WM-logotypen.
|
||||||
|
fn open_tui_wm_dropdown(&mut self, panel_idx: usize) {
|
||||||
|
let pr = match self.panel_rects.get(panel_idx) {
|
||||||
|
Some(r) => *r,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
let btn_r = self.tui_wm_btn_rects.get(panel_idx).copied().unwrap_or(pr);
|
||||||
|
let items = self.build_keybind_menu_items();
|
||||||
|
if items.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let width = items.iter().map(|i| i.label.len() as u16 + 2).max().unwrap_or(20).max(20);
|
||||||
|
let x = btn_r.x;
|
||||||
|
let y = pr.y + 1;
|
||||||
|
// item_rects peker på den faktiska synliga raden (y+1 = innanför övre kant)
|
||||||
|
let item_rects: Vec<Rect> =
|
||||||
|
(0..items.len()).map(|i| Rect::new(x, y + 1 + i as u16, width, 1)).collect();
|
||||||
|
self.dropdown = Some(DropdownState {
|
||||||
|
panel_idx,
|
||||||
|
rect: Rect::new(x, y, width, items.len() as u16),
|
||||||
|
item_rects,
|
||||||
|
items,
|
||||||
|
hovered: None,
|
||||||
|
anchor_x: Some(x),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bygger meny-items från config.keybinds för visning i TUI-WM-menyn.
|
||||||
|
/// Varje item kör keybindens faktiska action vid klick.
|
||||||
|
fn build_keybind_menu_items(&self) -> Vec<MenuItem> {
|
||||||
|
use crate::config::KeybindScope;
|
||||||
|
self.config.keybinds.iter().map(|kb| {
|
||||||
|
let desc = kb.label.as_deref().unwrap_or_else(|| action_display(&kb.action));
|
||||||
|
let scope = if kb.scope == KeybindScope::Global { " [global]" } else { "" };
|
||||||
|
MenuItem {
|
||||||
|
label: format!("{:<14} {}{}", kb.key, desc, scope),
|
||||||
|
action: kb.action.clone(),
|
||||||
|
}
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn_terminal(&mut self, shell: &str) {
|
pub fn spawn_terminal(&mut self, shell: &str) {
|
||||||
let ca = self.content_area;
|
let ca = self.content_area;
|
||||||
let offset = (self.windows.len() as i32) * 2;
|
let offset = (self.windows.len() as i32) * 2;
|
||||||
@@ -859,6 +1077,9 @@ impl App {
|
|||||||
dragging: None,
|
dragging: None,
|
||||||
resizing: None,
|
resizing: None,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
mouse_mode: vt100::MouseProtocolMode::None,
|
||||||
|
mouse_encoding: vt100::MouseProtocolEncoding::Default,
|
||||||
|
mouse_seq_carry: Vec::new(),
|
||||||
});
|
});
|
||||||
self.focus_window(id);
|
self.focus_window(id);
|
||||||
}
|
}
|
||||||
@@ -884,6 +1105,187 @@ impl App {
|
|||||||
|
|
||||||
// ─── Hjälpfunktioner ─────────────────────────────────────────────────────────
|
// ─── Hjälpfunktioner ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Kodar ett crossterm-mushändelse till ANSI-byte-sekvens enligt terminalen
|
||||||
|
/// begärda mus-tracking-protokoll. Returnerar None om protokollet är None eller
|
||||||
|
/// om händelsetypen inte täcks av det aktiva protokollet.
|
||||||
|
fn encode_mouse_event(
|
||||||
|
kind: MouseEventKind,
|
||||||
|
col: u16,
|
||||||
|
row: u16,
|
||||||
|
content_rect: Rect,
|
||||||
|
mode: vt100::MouseProtocolMode,
|
||||||
|
encoding: vt100::MouseProtocolEncoding,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
if mode == vt100::MouseProtocolMode::None {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1-baserade terminalkoordinater relativt innehållsytan
|
||||||
|
let term_col = col.saturating_sub(content_rect.x) + 1;
|
||||||
|
let term_row = row.saturating_sub(content_rect.y) + 1;
|
||||||
|
|
||||||
|
let (button, is_release): (u8, bool) = match kind {
|
||||||
|
MouseEventKind::Down(MouseButton::Left) => (0, false),
|
||||||
|
MouseEventKind::Down(MouseButton::Middle) => (1, false),
|
||||||
|
MouseEventKind::Down(MouseButton::Right) => (2, false),
|
||||||
|
MouseEventKind::Up(_) => (3, true),
|
||||||
|
MouseEventKind::Drag(MouseButton::Left) => (32, false),
|
||||||
|
MouseEventKind::Drag(MouseButton::Middle) => (33, false),
|
||||||
|
MouseEventKind::Drag(MouseButton::Right) => (34, false),
|
||||||
|
MouseEventKind::Moved => (35, false),
|
||||||
|
MouseEventKind::ScrollUp => (64, false),
|
||||||
|
MouseEventKind::ScrollDown => (65, false),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filtrera händelsetyp mot aktivt protokoll
|
||||||
|
match mode {
|
||||||
|
vt100::MouseProtocolMode::None => return None,
|
||||||
|
vt100::MouseProtocolMode::Press => {
|
||||||
|
if !matches!(kind,
|
||||||
|
MouseEventKind::Down(_) |
|
||||||
|
MouseEventKind::ScrollUp |
|
||||||
|
MouseEventKind::ScrollDown
|
||||||
|
) { return None; }
|
||||||
|
}
|
||||||
|
vt100::MouseProtocolMode::PressRelease => {
|
||||||
|
if !matches!(kind,
|
||||||
|
MouseEventKind::Down(_) |
|
||||||
|
MouseEventKind::Up(_) |
|
||||||
|
MouseEventKind::ScrollUp |
|
||||||
|
MouseEventKind::ScrollDown
|
||||||
|
) { return None; }
|
||||||
|
}
|
||||||
|
vt100::MouseProtocolMode::ButtonMotion => {
|
||||||
|
if !matches!(kind,
|
||||||
|
MouseEventKind::Down(_) |
|
||||||
|
MouseEventKind::Up(_) |
|
||||||
|
MouseEventKind::Drag(_) |
|
||||||
|
MouseEventKind::ScrollUp |
|
||||||
|
MouseEventKind::ScrollDown
|
||||||
|
) { return None; }
|
||||||
|
}
|
||||||
|
vt100::MouseProtocolMode::AnyMotion => {} // alla händelsetyper passerar
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(match encoding {
|
||||||
|
vt100::MouseProtocolEncoding::Default => {
|
||||||
|
if term_col > 223 || term_row > 223 { return None; }
|
||||||
|
vec![0x1b, b'[', b'M', button + 32, term_col as u8 + 32, term_row as u8 + 32]
|
||||||
|
}
|
||||||
|
vt100::MouseProtocolEncoding::Sgr => {
|
||||||
|
let suffix = if is_release { 'm' } else { 'M' };
|
||||||
|
format!("\x1b[<{};{};{}{}", button, term_col, term_row, suffix).into_bytes()
|
||||||
|
}
|
||||||
|
vt100::MouseProtocolEncoding::Utf8 => {
|
||||||
|
let mut bytes = vec![0x1b, b'[', b'M', button + 32];
|
||||||
|
for coord in [term_col, term_row] {
|
||||||
|
let code_point = coord as u32 + 32;
|
||||||
|
if let Some(c) = char::from_u32(code_point) {
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
bytes.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skannar ett chunk av r\u00e5 PTY-data efter DEC private mode escape-sekvenser som
|
||||||
|
/// styr mus-tracking (\x1b[?<n>h / \x1b[?<n>l) och uppdaterar `mode` och `encoding`.
|
||||||
|
/// En liten "carry"-buffer anv\u00e4nds f\u00f6r att hantera sekvenser som klippts mitt i
|
||||||
|
/// en chunk-gr\u00e4ns.\n///
|
||||||
|
/// Hanterar b\u00e5de enkla sekvenser (\x1b[?1003h) och kombinerade sekvenser (\x1b[?1003;1006h).
|
||||||
|
fn scan_mouse_tracking(
|
||||||
|
data: &[u8],
|
||||||
|
mode: &mut vt100::MouseProtocolMode,
|
||||||
|
encoding: &mut vt100::MouseProtocolEncoding,
|
||||||
|
carry: &mut Vec<u8>,
|
||||||
|
) {
|
||||||
|
// Kombinera carry med ny data
|
||||||
|
let mut buf = std::mem::take(carry);
|
||||||
|
buf.extend_from_slice(data);
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i < buf.len() {
|
||||||
|
if buf[i] != 0x1b {
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Kolla om vi har ESC [ ?
|
||||||
|
if i + 2 < buf.len() && buf[i + 1] == b'[' && buf[i + 2] == b'?' {
|
||||||
|
let num_start = i + 3;
|
||||||
|
let mut j = num_start;
|
||||||
|
// Scanna siffror och semikolon tills h eller l (eller slut p\u00e5 buffer)
|
||||||
|
while j < buf.len() && (buf[j].is_ascii_digit() || buf[j] == b';') {
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
if j >= buf.len() {
|
||||||
|
// Ofullst\u00e4ndig sekvens \u2014 spara resten som carry
|
||||||
|
*carry = buf[i..].to_vec();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if buf[j] == b'h' || buf[j] == b'l' {
|
||||||
|
let enable = buf[j] == b'h';
|
||||||
|
if let Ok(s) = std::str::from_utf8(&buf[num_start..j]) {
|
||||||
|
let old_mode = *mode;
|
||||||
|
let old_enc = *encoding;
|
||||||
|
for part in s.split(';') {
|
||||||
|
if let Ok(n) = part.trim().parse::<u16>() {
|
||||||
|
apply_mouse_dec_mode(n, enable, mode, encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *mode != old_mode || *encoding != old_enc {
|
||||||
|
crate::log::log(&format!(
|
||||||
|
"PTY mus-lage uppdaterat: mode={:?} enc={:?} via ESC[?{}{}",
|
||||||
|
mode, encoding, s, if enable { 'h' } else { 'l' }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = j + 1;
|
||||||
|
} else {
|
||||||
|
// Inte h/l \u2014 bara en ESC vi inte k\u00e4nner
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
} else if i + 1 >= buf.len() {
|
||||||
|
// M\u00f6jlig ofullst\u00e4ndig ESC sekvens vid bufferkanten
|
||||||
|
*carry = buf[i..].to_vec();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Till\u00e4mpar en DEC private mode-\u00e4ndring p\u00e5 det laggrade mus-l\u00e4get.
|
||||||
|
fn apply_mouse_dec_mode(
|
||||||
|
n: u16,
|
||||||
|
enable: bool,
|
||||||
|
mode: &mut vt100::MouseProtocolMode,
|
||||||
|
encoding: &mut vt100::MouseProtocolEncoding,
|
||||||
|
) {
|
||||||
|
if enable {
|
||||||
|
match n {
|
||||||
|
9 => *mode = vt100::MouseProtocolMode::Press, // X10
|
||||||
|
1000 => *mode = vt100::MouseProtocolMode::PressRelease, // VT200
|
||||||
|
1002 => *mode = vt100::MouseProtocolMode::ButtonMotion,
|
||||||
|
1003 => *mode = vt100::MouseProtocolMode::AnyMotion,
|
||||||
|
1005 => *encoding = vt100::MouseProtocolEncoding::Utf8,
|
||||||
|
1006 => *encoding = vt100::MouseProtocolEncoding::Sgr,
|
||||||
|
1015 => {} // URXVT \u2014 behandla som Default (ignorera)
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match n {
|
||||||
|
9 | 1000 | 1002 | 1003 => *mode = vt100::MouseProtocolMode::None,
|
||||||
|
1005 | 1006 => *encoding = vt100::MouseProtocolEncoding::Default,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn in_rect(col: u16, row: u16, rect: Rect) -> bool {
|
fn in_rect(col: u16, row: u16, rect: Rect) -> bool {
|
||||||
col >= rect.x
|
col >= rect.x
|
||||||
&& col < rect.x + rect.width
|
&& col < rect.x + rect.width
|
||||||
@@ -929,6 +1331,19 @@ fn run_command(command: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returnerar ett kortfattat namn för en MenuAction (används som fallback i keybind-menyn).
|
||||||
|
fn action_display(action: &MenuAction) -> &'static str {
|
||||||
|
match action {
|
||||||
|
MenuAction::Exit => "Avsluta",
|
||||||
|
MenuAction::SpawnTerminal { .. } => "Ny terminal",
|
||||||
|
MenuAction::RunScript { .. } => "Kör skript",
|
||||||
|
MenuAction::RunProgram { .. } => "Kör program",
|
||||||
|
MenuAction::Submenu { .. } => "Undermeny",
|
||||||
|
MenuAction::SpawnRunDialog => "Kommandodialog",
|
||||||
|
MenuAction::NoOp => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn key_to_bytes(key: KeyEvent) -> Option<Vec<u8>> {
|
fn key_to_bytes(key: KeyEvent) -> Option<Vec<u8>> {
|
||||||
use KeyCode::*;
|
use KeyCode::*;
|
||||||
let bytes: Vec<u8> = match key.code {
|
let bytes: Vec<u8> = match key.code {
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ pub struct KeybindConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub scope: KeybindScope,
|
pub scope: KeybindScope,
|
||||||
pub action: MenuAction,
|
pub action: MenuAction,
|
||||||
|
/// Visningsnamn som visas i TUI-WM-menyn (valfritt)
|
||||||
|
#[serde(default)]
|
||||||
|
pub label: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone, Default, PartialEq)]
|
#[derive(Deserialize, Debug, Clone, Default, PartialEq)]
|
||||||
@@ -77,6 +80,8 @@ pub enum MenuAction {
|
|||||||
},
|
},
|
||||||
/// Öppnar Tui-run kommandodialog
|
/// Öppnar Tui-run kommandodialog
|
||||||
SpawnRunDialog,
|
SpawnRunDialog,
|
||||||
|
/// Gör ingenting – används för display-only rader i menyer
|
||||||
|
NoOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// En status-widget i panelen: kör ett kommando periodiskt och renderar output
|
/// En status-widget i panelen: kör ett kommando periodiskt och renderar output
|
||||||
@@ -106,6 +111,29 @@ fn default_interval() -> u64 {
|
|||||||
10
|
10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_keybinds() -> Vec<KeybindConfig> {
|
||||||
|
vec![
|
||||||
|
KeybindConfig {
|
||||||
|
key: "ctrl+space".to_string(),
|
||||||
|
scope: KeybindScope::Global,
|
||||||
|
action: MenuAction::SpawnRunDialog,
|
||||||
|
label: Some("Öppna kommandodialog".to_string()),
|
||||||
|
},
|
||||||
|
KeybindConfig {
|
||||||
|
key: "alt+enter".to_string(),
|
||||||
|
scope: KeybindScope::Global,
|
||||||
|
action: MenuAction::SpawnTerminal { shell: None },
|
||||||
|
label: Some("Ny terminal".to_string()),
|
||||||
|
},
|
||||||
|
KeybindConfig {
|
||||||
|
key: "ctrl+q".to_string(),
|
||||||
|
scope: KeybindScope::Wm,
|
||||||
|
action: MenuAction::Exit,
|
||||||
|
label: Some("Avsluta TUI-WM".to_string()),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load(path: &str) -> anyhow::Result<Config> {
|
pub fn load(path: &str) -> anyhow::Result<Config> {
|
||||||
let content = fs::read_to_string(path)?;
|
let content = fs::read_to_string(path)?;
|
||||||
@@ -128,7 +156,10 @@ impl Config {
|
|||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Varning: kunde inte ladda {}: {}", path, e);
|
eprintln!("Varning: kunde inte ladda {}: {}", path, e);
|
||||||
Config::default()
|
Config {
|
||||||
|
keybinds: default_keybinds(),
|
||||||
|
..Config::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
src/log.rs
Normal file
98
src/log.rs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/// Enkel fil-logger. Loggar till <exe_dir>/logs/<datum>.log
|
||||||
|
/// Trådsäker via en global Mutex<File>.
|
||||||
|
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
static LOG: Mutex<Option<std::fs::File>> = Mutex::new(None);
|
||||||
|
|
||||||
|
/// Initialisera loggern. Anropas en gång från main().
|
||||||
|
pub fn init() {
|
||||||
|
let dir = log_dir();
|
||||||
|
if let Err(e) = fs::create_dir_all(&dir) {
|
||||||
|
eprintln!("log::init: kunde inte skapa logg-mapp {:?}: {}", dir, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let name = today_filename();
|
||||||
|
let path = dir.join(name);
|
||||||
|
match OpenOptions::new().create(true).append(true).open(&path) {
|
||||||
|
Ok(f) => {
|
||||||
|
*LOG.lock().unwrap() = Some(f);
|
||||||
|
log(&format!("=== TUI-WM startad ==="));
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("log::init: kunde inte öppna {:?}: {}", path, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skriv en logg-rad med tidsstämpel.
|
||||||
|
pub fn log(msg: &str) {
|
||||||
|
let ts = timestamp();
|
||||||
|
let line = format!("[{}] {}\n", ts, msg);
|
||||||
|
if let Ok(mut guard) = LOG.lock() {
|
||||||
|
if let Some(f) = guard.as_mut() {
|
||||||
|
let _ = f.write_all(line.as_bytes());
|
||||||
|
let _ = f.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makro för bekväm formatering — används som log!("foo {}", bar)
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! log {
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
$crate::log::log(&format!($($arg)*))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Interna hjälpfunktioner ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn log_dir() -> PathBuf {
|
||||||
|
// Lägg logs/ bredvid den körande binären
|
||||||
|
let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("."));
|
||||||
|
exe.parent().unwrap_or(std::path::Path::new(".")).join("logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn today_filename() -> String {
|
||||||
|
// Bygg YYYY-MM-DD från SystemTime utan externa beroenden
|
||||||
|
let secs = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let (y, mo, d) = unix_to_ymd(secs);
|
||||||
|
format!("{:04}-{:02}-{:02}.log", y, mo, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamp() -> String {
|
||||||
|
let secs = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let subsec_ms = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.map(|d| d.subsec_millis())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let (y, mo, d) = unix_to_ymd(secs);
|
||||||
|
let h = (secs % 86400) / 3600;
|
||||||
|
let m = (secs % 3600) / 60;
|
||||||
|
let s = secs % 60;
|
||||||
|
format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}", y, mo, d, h, m, s, subsec_ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Konverterar Unix-sekunder till (år, månad, dag). Ingen extern crate nödvändig.
|
||||||
|
fn unix_to_ymd(secs: u64) -> (u32, u32, u32) {
|
||||||
|
// Algoritm: https://howardhinnant.github.io/date_algorithms.html
|
||||||
|
let z = (secs / 86400) as i64 + 719468;
|
||||||
|
let era = if z >= 0 { z } else { z - 146096 } / 146097;
|
||||||
|
let doe = (z - era * 146097) as u32;
|
||||||
|
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
||||||
|
let y = yoe as i64 + era * 400;
|
||||||
|
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
||||||
|
let mp = (5 * doy + 2) / 153;
|
||||||
|
let d = doy - (153 * mp + 2) / 5 + 1;
|
||||||
|
let m = if mp < 10 { mp + 3 } else { mp - 9 };
|
||||||
|
let y = if m <= 2 { y + 1 } else { y };
|
||||||
|
(y as u32, m, d)
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
mod app;
|
mod app;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod log;
|
||||||
mod pty;
|
mod pty;
|
||||||
mod render;
|
mod render;
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ use ratatui::{backend::CrosstermBackend, Terminal};
|
|||||||
use std::{io, time::Duration};
|
use std::{io, time::Duration};
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
|
log::init();
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ pub fn render(frame: &mut Frame, app: &App) {
|
|||||||
pi,
|
pi,
|
||||||
&panel_cfg.status_widgets,
|
&panel_cfg.status_widgets,
|
||||||
status_states,
|
status_states,
|
||||||
|
app.focused_id.is_none(),
|
||||||
|
app.hovered_tui_btn,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +63,8 @@ fn render_panel(
|
|||||||
panel_idx: usize,
|
panel_idx: usize,
|
||||||
status_widgets: &[crate::config::StatusWidget],
|
status_widgets: &[crate::config::StatusWidget],
|
||||||
status_states: &[crate::app::StatusWidgetState],
|
status_states: &[crate::app::StatusWidgetState],
|
||||||
|
desktop_focused: bool,
|
||||||
|
hovered_tui: bool,
|
||||||
) {
|
) {
|
||||||
// Panelens bakgrund
|
// Panelens bakgrund
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
@@ -68,15 +72,24 @@ fn render_panel(
|
|||||||
rect,
|
rect,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Logotyp
|
// Logotyp – klickbar meny-knapp med hover-highlight
|
||||||
|
let logo_style = if hovered_tui {
|
||||||
|
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||||
|
};
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(Span::styled(
|
Paragraph::new(Span::styled(" TUI-WM ", logo_style)),
|
||||||
" TUI-WM ",
|
|
||||||
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
|
|
||||||
)),
|
|
||||||
Rect::new(rect.x + 1, rect.y, 9, 1),
|
Rect::new(rect.x + 1, rect.y, 9, 1),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Fokus-indikator: grön ● = skrivbord fokuserat, grå ● = fönster fokuserat
|
||||||
|
let dot_color = if desktop_focused { Color::Green } else { Color::Indexed(240) };
|
||||||
|
frame.render_widget(
|
||||||
|
Paragraph::new(Span::styled("●", Style::default().fg(dot_color).bg(Color::Indexed(238)))),
|
||||||
|
Rect::new(rect.x + 10, rect.y, 1, 1),
|
||||||
|
);
|
||||||
|
|
||||||
// Vänsterjusterade knappar
|
// Vänsterjusterade knappar
|
||||||
for (ii, item) in items.iter().enumerate() {
|
for (ii, item) in items.iter().enumerate() {
|
||||||
let Some(&ir) = item_rects.get(ii) else { continue };
|
let Some(&ir) = item_rects.get(ii) else { continue };
|
||||||
@@ -117,7 +130,7 @@ fn render_panel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vänster-dockade status-widgets renderas efter knapparna
|
// Vänster-dockade status-widgets renderas efter knapparna
|
||||||
let left_start = item_rects.last().map(|r| r.x + r.width + 1).unwrap_or(rect.x + 10);
|
let left_start = item_rects.last().map(|r| r.x + r.width + 1).unwrap_or(rect.x + 12);
|
||||||
let mut left_x = left_start;
|
let mut left_x = left_start;
|
||||||
for (si, sw_cfg) in status_widgets.iter().enumerate() {
|
for (si, sw_cfg) in status_widgets.iter().enumerate() {
|
||||||
if sw_cfg.align != StatusAlign::Left {
|
if sw_cfg.align != StatusAlign::Left {
|
||||||
@@ -154,7 +167,8 @@ fn render_dropdown(frame: &mut Frame, dd: &DropdownState) {
|
|||||||
} else {
|
} else {
|
||||||
Style::default().fg(Color::White).bg(Color::Indexed(236))
|
Style::default().fg(Color::White).bg(Color::Indexed(236))
|
||||||
};
|
};
|
||||||
let draw_rect = Rect::new(ir.x + 1, ir.y + 1, ir.width.saturating_sub(2), 1);
|
// ir pekar redan på den faktiska raden innanför kanten
|
||||||
|
let draw_rect = Rect::new(ir.x + 1, ir.y, ir.width.saturating_sub(2), 1);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(Span::styled(format!(" {} ", item.label), style)),
|
Paragraph::new(Span::styled(format!(" {} ", item.label), style)),
|
||||||
draw_rect,
|
draw_rect,
|
||||||
|
|||||||
307
testapps/mouse-test/Cargo.lock
generated
Normal file
307
testapps/mouse-test/Cargo.lock
generated
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.28.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"futures-core",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"rustix",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.183"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mouse-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
11
testapps/mouse-test/Cargo.toml
Normal file
11
testapps/mouse-test/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "mouse-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "mouse-test"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
crossterm = { version = "0.28", features = ["event-stream"] }
|
||||||
262
testapps/mouse-test/src/main.rs
Normal file
262
testapps/mouse-test/src/main.rs
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
use crossterm::{
|
||||||
|
cursor,
|
||||||
|
event::{
|
||||||
|
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, MouseButton, MouseEventKind,
|
||||||
|
},
|
||||||
|
execute, queue,
|
||||||
|
style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor},
|
||||||
|
terminal::{
|
||||||
|
self, disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen,
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
// ─── Logging ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn log_path() -> PathBuf {
|
||||||
|
let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("."));
|
||||||
|
let dir = exe.parent().unwrap_or(std::path::Path::new(".")).to_path_buf();
|
||||||
|
let filename = today_filename();
|
||||||
|
dir.join(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn today_filename() -> String {
|
||||||
|
let secs = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let (y, mo, d) = unix_to_ymd(secs);
|
||||||
|
format!("mouse-test-{:04}-{:02}-{:02}.log", y, mo, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamp() -> String {
|
||||||
|
let dur = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let secs = dur.as_secs();
|
||||||
|
let ms = dur.subsec_millis();
|
||||||
|
let h = (secs % 86400) / 3600;
|
||||||
|
let m = (secs % 3600) / 60;
|
||||||
|
let s = secs % 60;
|
||||||
|
format!("{:02}:{:02}:{:02}.{:03}", h, m, s, ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unix_to_ymd(secs: u64) -> (u32, u32, u32) {
|
||||||
|
let z = (secs / 86400) as i64 + 719468;
|
||||||
|
let era = if z >= 0 { z } else { z - 146096 } / 146097;
|
||||||
|
let doe = (z - era * 146097) as u32;
|
||||||
|
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
||||||
|
let y = yoe as i64 + era * 400;
|
||||||
|
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
||||||
|
let mp = (5 * doy + 2) / 153;
|
||||||
|
let d = doy - (153 * mp + 2) / 5 + 1;
|
||||||
|
let m = if mp < 10 { mp + 3 } else { mp - 9 };
|
||||||
|
let y = if m <= 2 { y + 1 } else { y };
|
||||||
|
(y as u32, m, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_log(log_file: &mut std::fs::File, msg: &str) {
|
||||||
|
let line = format!("[{}] {}\n", timestamp(), msg);
|
||||||
|
let _ = log_file.write_all(line.as_bytes());
|
||||||
|
let _ = log_file.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Shapes ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Shape {
|
||||||
|
col: u16,
|
||||||
|
row: u16,
|
||||||
|
color: ShapeColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
enum ShapeColor {
|
||||||
|
Blue, // left click
|
||||||
|
Red, // right click
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Main ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
// Öppna loggfil
|
||||||
|
let path = log_path();
|
||||||
|
let _ = fs::create_dir_all(path.parent().unwrap());
|
||||||
|
let mut log_file = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&path)?;
|
||||||
|
write_log(&mut log_file, &format!("=== mouse-test startad, logg: {:?} ===", path));
|
||||||
|
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
execute!(
|
||||||
|
stdout,
|
||||||
|
EnterAlternateScreen,
|
||||||
|
EnableMouseCapture,
|
||||||
|
cursor::Hide,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Aktivera utökad mus-tracking: button events + drag + any motion + SGR encoding
|
||||||
|
// ?1002 = button-motion, ?1003 = any motion, ?1006 = SGR encoding
|
||||||
|
execute!(stdout, Print("\x1b[?1003h\x1b[?1006h"))?;
|
||||||
|
write_log(&mut log_file, "Aktiverade mus-tracking: ?1003h ?1006h");
|
||||||
|
|
||||||
|
let result = run(&mut stdout, &mut log_file);
|
||||||
|
|
||||||
|
// Stäng av allt
|
||||||
|
execute!(stdout, Print("\x1b[?1003l\x1b[?1006l"))?;
|
||||||
|
execute!(stdout, cursor::Show, LeaveAlternateScreen, DisableMouseCapture)?;
|
||||||
|
disable_raw_mode()?;
|
||||||
|
|
||||||
|
write_log(&mut log_file, "=== mouse-test avslutad ===");
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(stdout: &mut io::Stdout, log_file: &mut std::fs::File) -> io::Result<()> {
|
||||||
|
let mut shapes: Vec<Shape> = Vec::new();
|
||||||
|
let mut log_lines: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
// Initialdragning
|
||||||
|
redraw(stdout, &shapes, &log_lines)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if event::poll(Duration::from_millis(50))? {
|
||||||
|
let ev = event::read()?;
|
||||||
|
match &ev {
|
||||||
|
Event::Key(k) => {
|
||||||
|
let msg = format!("KEY {:?} modifiers={:?}", k.code, k.modifiers);
|
||||||
|
write_log(log_file, &msg);
|
||||||
|
if k.code == KeyCode::Char('q') || k.code == KeyCode::Esc {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if k.code == KeyCode::Char('c') {
|
||||||
|
shapes.clear();
|
||||||
|
log_lines.push("Rensat alla shapes".to_string());
|
||||||
|
cap_log(&mut log_lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Mouse(m) => {
|
||||||
|
let col = m.column;
|
||||||
|
let row = m.row;
|
||||||
|
let kind = m.kind;
|
||||||
|
|
||||||
|
let msg = format!("MOUSE {:?} col={} row={} mods={:?}", kind, col, row, m.modifiers);
|
||||||
|
write_log(log_file, &msg);
|
||||||
|
log_lines.push(msg.clone());
|
||||||
|
cap_log(&mut log_lines);
|
||||||
|
|
||||||
|
match kind {
|
||||||
|
MouseEventKind::Down(MouseButton::Left) => {
|
||||||
|
shapes.push(Shape { col, row, color: ShapeColor::Blue });
|
||||||
|
}
|
||||||
|
MouseEventKind::Down(MouseButton::Right) => {
|
||||||
|
shapes.push(Shape { col, row, color: ShapeColor::Red });
|
||||||
|
}
|
||||||
|
MouseEventKind::ScrollUp => {
|
||||||
|
for s in &mut shapes {
|
||||||
|
s.row = s.row.saturating_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseEventKind::ScrollDown => {
|
||||||
|
for s in &mut shapes {
|
||||||
|
s.row = s.row.saturating_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Crossterm skickar horisontell scroll som ScrollLeft/ScrollRight
|
||||||
|
MouseEventKind::ScrollLeft => {
|
||||||
|
for s in &mut shapes {
|
||||||
|
s.col = s.col.saturating_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseEventKind::ScrollRight => {
|
||||||
|
for s in &mut shapes {
|
||||||
|
s.col = s.col.saturating_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Resize(w, h) => {
|
||||||
|
let msg = format!("RESIZE {}x{}", w, h);
|
||||||
|
write_log(log_file, &msg);
|
||||||
|
log_lines.push(msg);
|
||||||
|
cap_log(&mut log_lines);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
redraw(stdout, &shapes, &log_lines)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_LOG_LINES: usize = 20;
|
||||||
|
|
||||||
|
fn cap_log(lines: &mut Vec<String>) {
|
||||||
|
while lines.len() > MAX_LOG_LINES {
|
||||||
|
lines.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redraw(stdout: &mut io::Stdout, shapes: &[Shape], log_lines: &[String]) -> io::Result<()> {
|
||||||
|
let (cols, rows) = terminal::size()?;
|
||||||
|
|
||||||
|
queue!(stdout, cursor::MoveTo(0, 0), Clear(ClearType::All))?;
|
||||||
|
|
||||||
|
// Rubrik
|
||||||
|
queue!(
|
||||||
|
stdout,
|
||||||
|
cursor::MoveTo(0, 0),
|
||||||
|
SetForegroundColor(Color::Cyan),
|
||||||
|
Print("mouse-test | vänsterklick=blå ruta högerklick=röd ruta scroll=flytta q=avsluta c=rensa"),
|
||||||
|
ResetColor,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
let sep: String = "─".repeat(cols as usize);
|
||||||
|
queue!(stdout, cursor::MoveTo(0, 1), SetForegroundColor(Color::DarkGrey), Print(&sep), ResetColor)?;
|
||||||
|
|
||||||
|
// Rita shapes (2x4 tecken block)
|
||||||
|
for s in shapes {
|
||||||
|
let bg = match s.color {
|
||||||
|
ShapeColor::Blue => Color::Blue,
|
||||||
|
ShapeColor::Red => Color::Red,
|
||||||
|
};
|
||||||
|
// Gör ett 2-radshögt, 4-kolumnsbredt block
|
||||||
|
for dr in 0u16..2 {
|
||||||
|
let r = s.row.saturating_add(dr);
|
||||||
|
if r < 2 || r >= rows { continue; }
|
||||||
|
queue!(
|
||||||
|
stdout,
|
||||||
|
cursor::MoveTo(s.col, r),
|
||||||
|
SetBackgroundColor(bg),
|
||||||
|
Print(" "),
|
||||||
|
ResetColor,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logg-panel längst ner
|
||||||
|
let log_start_row = rows.saturating_sub(MAX_LOG_LINES as u16 + 1);
|
||||||
|
queue!(stdout, cursor::MoveTo(0, log_start_row), SetForegroundColor(Color::DarkGrey), Print(&sep), ResetColor)?;
|
||||||
|
for (i, line) in log_lines.iter().enumerate() {
|
||||||
|
let r = log_start_row + 1 + i as u16;
|
||||||
|
if r >= rows { break; }
|
||||||
|
// Trunkera om för lång
|
||||||
|
let display: &str = if line.len() > cols as usize { &line[..cols as usize] } else { line };
|
||||||
|
queue!(
|
||||||
|
stdout,
|
||||||
|
cursor::MoveTo(0, r),
|
||||||
|
SetForegroundColor(Color::Grey),
|
||||||
|
Print(display),
|
||||||
|
ResetColor,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.flush()
|
||||||
|
}
|
||||||
1
testapps/mouse-test/target/.rustc_info.json
Normal file
1
testapps/mouse-test/target/.rustc_info.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"rustc_fingerprint":14215568240061937964,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.92.0 (ded5c06cf 2025-12-08)\nbinary: rustc\ncommit-hash: ded5c06cf21d2b93bffd5d884aa6e96934ee4234\ncommit-date: 2025-12-08\nhost: x86_64-unknown-linux-gnu\nrelease: 1.92.0\nLLVM version: 21.1.3\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/brasse/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}
|
||||||
3
testapps/mouse-test/target/CACHEDIR.TAG
Normal file
3
testapps/mouse-test/target/CACHEDIR.TAG
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Signature: 8a477f597d28d172789f06886806bc55
|
||||||
|
# This file is a cache directory tag created by cargo.
|
||||||
|
# For information about cache directory tags see https://bford.info/cachedir/
|
||||||
0
testapps/mouse-test/target/release/.cargo-lock
Normal file
0
testapps/mouse-test/target/release/.cargo-lock
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
8d45f0d559d66bc1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"std\"]","declared_features":"[\"arbitrary\", \"bytemuck\", \"example_generated\", \"serde\", \"serde_core\", \"std\"]","target":7691312148208718491,"profile":2040997289075261528,"path":9779765049526801347,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/bitflags-2025c01cf01ab8b5/dep-lib-bitflags","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
760f7b2d392f0023
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[]","declared_features":"[\"core\", \"rustc-dep-of-std\"]","target":13840298032947503755,"profile":2040997289075261528,"path":1596534247721701504,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/cfg-if-ebc73076c81e0e49/dep-lib-cfg_if","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
83dddb9730e5bee5
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"bracketed-paste\", \"default\", \"event-stream\", \"events\", \"windows\"]","declared_features":"[\"bracketed-paste\", \"default\", \"event-stream\", \"events\", \"filedescriptor\", \"libc\", \"serde\", \"use-dev-tty\", \"windows\"]","target":7162149947039624270,"profile":2040997289075261528,"path":252103811417717214,"deps":[[302948626015856208,"futures_core",false,6398869625909783990],[3430646239657634944,"rustix",false,1020948293971461092],[4627466251042474366,"signal_hook_mio",false,8465342173122801562],[5675930438384443948,"mio",false,7238060262000704430],[12459942763388630573,"parking_lot",false,9436988536820532313],[16909888598953886583,"bitflags",false,13937469153157858701],[17154765528929363175,"signal_hook",false,15812921453296546932]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/crossterm-4ccfa59f247e5176/dep-lib-crossterm","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
aec685458e1d732a
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":17743456753391690785,"profile":8944999695620513791,"path":16775741776141748,"deps":[[17159683253194042242,"libc",false,3234931940414782003]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/errno-203b80682fe3787d/dep-lib-errno","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
b6ad63a8055acd58
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[]","declared_features":"[\"alloc\", \"cfg-target-has-atomic\", \"default\", \"portable-atomic\", \"std\", \"unstable\"]","target":9453135960607436725,"profile":18348216721672176038,"path":3303830916469115557,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/futures-core-9c361e3ce6f35a97/dep-lib-futures_core","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
18ecfdbc182b57f0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[17159683253194042242,"build_script_build",false,13798190032261323108]],"local":[{"RerunIfChanged":{"output":"release/build/libc-bea32bb21c7eb060/output","paths":["build.rs"]}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_FREEBSD_VERSION","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_MUSL_V1_2_3","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_LINUX_TIME_BITS64","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_GNU_FILE_OFFSET_BITS","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_GNU_TIME_BITS","val":null}}],"rustflags":[],"config":0,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
33fafcdc75c9e42c
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"default\", \"std\"]","declared_features":"[\"align\", \"const-extern-fn\", \"default\", \"extra_traits\", \"rustc-dep-of-std\", \"rustc-std-workspace-core\", \"std\", \"use_std\"]","target":17682796336736096309,"profile":7322064999780386650,"path":16729691517801958497,"deps":[[17159683253194042242,"build_script_build",false,17318358277326498840]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/libc-dcb47aa695b31b42/dep-lib-libc","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
64994578ba047dbf
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"default\", \"std\"]","declared_features":"[\"align\", \"const-extern-fn\", \"default\", \"extra_traits\", \"rustc-dep-of-std\", \"rustc-std-workspace-core\", \"std\", \"use_std\"]","target":5408242616063297496,"profile":8928907579149787682,"path":5278319571169746718,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/libc-ea5ad1246bf51071/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
46431ec6cd1fb4ad
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"elf\", \"errno\", \"general\", \"ioctl\", \"no_std\"]","declared_features":"[\"bootparam\", \"btrfs\", \"compiler_builtins\", \"core\", \"default\", \"elf\", \"elf_uapi\", \"errno\", \"general\", \"if_arp\", \"if_ether\", \"if_packet\", \"io_uring\", \"ioctl\", \"landlock\", \"loop_device\", \"mempolicy\", \"net\", \"netlink\", \"no_std\", \"prctl\", \"ptrace\", \"rustc-dep-of-std\", \"std\", \"system\", \"xdp\"]","target":5772965225213482929,"profile":4314370921045452772,"path":4746728906714955721,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/linux-raw-sys-076fa98919044f20/dep-lib-linux_raw_sys","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
664ed3c38ee6fd6b
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"atomic_usize\", \"default\"]","declared_features":"[\"arc_lock\", \"atomic_usize\", \"default\", \"nightly\", \"owning_ref\", \"serde\"]","target":16157403318809843794,"profile":2040997289075261528,"path":1929112552281581564,"deps":[[15358414700195712381,"scopeguard",false,8543138611106819891]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/lock_api-e68e7a0598a1d524/dep-lib-lock_api","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
63fef27575bbac30
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[]","declared_features":"[\"kv\", \"kv_serde\", \"kv_std\", \"kv_sval\", \"kv_unstable\", \"kv_unstable_serde\", \"kv_unstable_std\", \"kv_unstable_sval\", \"max_level_debug\", \"max_level_error\", \"max_level_info\", \"max_level_off\", \"max_level_trace\", \"max_level_warn\", \"release_max_level_debug\", \"release_max_level_error\", \"release_max_level_info\", \"release_max_level_off\", \"release_max_level_trace\", \"release_max_level_warn\", \"serde\", \"serde_core\", \"std\", \"sval\", \"sval_ref\", \"value-bag\"]","target":6550155848337067049,"profile":2040997289075261528,"path":8084957827879210201,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/log-4fe4b0f6afd99737/dep-lib-log","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ae3b6c9b75c17264
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"default\", \"log\", \"net\", \"os-ext\", \"os-poll\"]","declared_features":"[\"default\", \"log\", \"net\", \"os-ext\", \"os-poll\"]","target":5157902839847266895,"profile":13712647568182654241,"path":14303346305037773922,"deps":[[10630857666389190470,"log",false,3507384322979200611],[17159683253194042242,"libc",false,3234931940414782003]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/mio-66370afe95e742fb/dep-lib-mio","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
57dcba89296f220d
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[]","declared_features":"[]","target":1147146474800544312,"profile":2040997289075261528,"path":4942398508502643691,"deps":[[17030156879047273469,"crossterm",false,16554921277129481603]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/mouse-test-2cea95c9260905bc/dep-bin-mouse-test","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
59049f2e13ebf682
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"default\"]","declared_features":"[\"arc_lock\", \"deadlock_detection\", \"default\", \"hardware-lock-elision\", \"nightly\", \"owning_ref\", \"send_guard\", \"serde\"]","target":9887373948397848517,"profile":2040997289075261528,"path":16678798856705028838,"deps":[[2555121257709722468,"lock_api",false,7781629232011234918],[6545091685033313457,"parking_lot_core",false,6180226835031802948]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/parking_lot-e7da0e36674f8d8e/dep-lib-parking_lot","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
68cdafa682385e53
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[]","declared_features":"[\"backtrace\", \"deadlock_detection\", \"nightly\", \"petgraph\"]","target":5408242616063297496,"profile":1369601567987815722,"path":6064203699951004343,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/parking_lot_core-0bd745cf7da85f56/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
bbbd0551d4d737b6
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[6545091685033313457,"build_script_build",false,6007301086752263528]],"local":[{"RerunIfChanged":{"output":"release/build/parking_lot_core-c5610ccd4b03c707/output","paths":["build.rs"]}}],"rustflags":[],"config":0,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
444468ed8f93c455
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[]","declared_features":"[\"backtrace\", \"deadlock_detection\", \"nightly\", \"petgraph\"]","target":12558056885032795287,"profile":2040997289075261528,"path":3875254373442465110,"deps":[[3666196340704888985,"smallvec",false,6633531385642205446],[6545091685033313457,"build_script_build",false,13130200545514339771],[7667230146095136825,"cfg_if",false,2522067713950158710],[17159683253194042242,"libc",false,3234931940414782003]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/parking_lot_core-fcfcc085aa0b6787/dep-lib-parking_lot_core","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
e4c3356c10232b0e
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"alloc\", \"libc-extra-traits\", \"std\", \"stdio\", \"termios\"]","declared_features":"[\"all-apis\", \"alloc\", \"cc\", \"compiler_builtins\", \"core\", \"default\", \"event\", \"fs\", \"io_uring\", \"itoa\", \"libc\", \"libc-extra-traits\", \"libc_errno\", \"linux_4_11\", \"linux_latest\", \"mm\", \"mount\", \"net\", \"once_cell\", \"param\", \"pipe\", \"process\", \"procfs\", \"pty\", \"rand\", \"runtime\", \"rustc-dep-of-std\", \"rustc-std-workspace-alloc\", \"shm\", \"std\", \"stdio\", \"system\", \"termios\", \"thread\", \"time\", \"try_close\", \"use-explicitly-provided-auxv\", \"use-libc\", \"use-libc-auxv\"]","target":16221545317719767766,"profile":10474043801839359757,"path":6497363289159846858,"deps":[[3430646239657634944,"build_script_build",false,15241173649152273119],[5036304442846774733,"linux_raw_sys",false,12516664233022079814],[16909888598953886583,"bitflags",false,13937469153157858701]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/rustix-1beafe4d470c510e/dep-lib-rustix","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0550ec861e7d8ca6
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"alloc\", \"libc-extra-traits\", \"std\", \"stdio\", \"termios\"]","declared_features":"[\"all-apis\", \"alloc\", \"cc\", \"compiler_builtins\", \"core\", \"default\", \"event\", \"fs\", \"io_uring\", \"itoa\", \"libc\", \"libc-extra-traits\", \"libc_errno\", \"linux_4_11\", \"linux_latest\", \"mm\", \"mount\", \"net\", \"once_cell\", \"param\", \"pipe\", \"process\", \"procfs\", \"pty\", \"rand\", \"runtime\", \"rustc-dep-of-std\", \"rustc-std-workspace-alloc\", \"shm\", \"std\", \"stdio\", \"system\", \"termios\", \"thread\", \"time\", \"try_close\", \"use-explicitly-provided-auxv\", \"use-libc\", \"use-libc-auxv\"]","target":5408242616063297496,"profile":8123407633567502970,"path":9161315508553989719,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/rustix-51ac11c8d745d068/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dfeac3c5ae8683d3
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[3430646239657634944,"build_script_build",false,12001104677101654021]],"local":[{"RerunIfChanged":{"output":"release/build/rustix-d99f030b6af4839f/output","paths":["build.rs"]}},{"RerunIfEnvChanged":{"var":"CARGO_CFG_RUSTIX_USE_EXPERIMENTAL_ASM","val":null}},{"RerunIfEnvChanged":{"var":"CARGO_CFG_RUSTIX_USE_LIBC","val":null}},{"RerunIfEnvChanged":{"var":"CARGO_FEATURE_USE_LIBC","val":null}},{"RerunIfEnvChanged":{"var":"CARGO_FEATURE_RUSTC_DEP_OF_STD","val":null}},{"RerunIfEnvChanged":{"var":"CARGO_CFG_MIRI","val":null}}],"rustflags":[],"config":0,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
332ff2a54d538f76
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[]","declared_features":"[\"default\", \"use_std\"]","target":3556356971060988614,"profile":2040997289075261528,"path":759260240459071725,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/scopeguard-a960f03e2280f9b8/dep-lib-scopeguard","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
74f4e40448c872db
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"channel\", \"default\", \"iterator\"]","declared_features":"[\"cc\", \"channel\", \"default\", \"extended-siginfo\", \"extended-siginfo-raw\", \"iterator\"]","target":831277710805360288,"profile":2040997289075261528,"path":9405517761606597848,"deps":[[6684496268350303357,"signal_hook_registry",false,11826222428890880962],[17154765528929363175,"build_script_build",false,13787227434378246565],[17159683253194042242,"libc",false,3234931940414782003]],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/signal-hook-5a5707a97e68ad5f/dep-lib-signal_hook","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
9a0485be756ca8db
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":4758242423518056681,"features":"[\"channel\", \"default\", \"iterator\"]","declared_features":"[\"cc\", \"channel\", \"default\", \"extended-siginfo\", \"extended-siginfo-raw\", \"iterator\"]","target":17883862002600103897,"profile":1369601567987815722,"path":10794713951714513156,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"release/.fingerprint/signal-hook-d0c592f645e4d7a4/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user