Add build output dependency file for mouse-test application

Add standard menu fore keybinds
This commit is contained in:
2026-03-28 03:18:41 +01:00
parent 4440ad79bd
commit 3a9e292088
203 changed files with 3797 additions and 29 deletions

174
README.md
View File

@@ -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.
## Funktioner (planerade)
## Funktioner
- Flytande terminaler med fri positionering och storleksändring
- Full mussuport: vänster/höger klick, scroll, markering
- Topanel och/eller bottenpanel
- Full mussuport: vänster/höger/mitten-klick, scroll, drag, ANSI-muse-protokoll vidarebefordras till appar
- Panel (topp/botten) med klickbara knappar och dynamiska status-widgets
- Dropdown-menyer med undermenyer
- Körbar kommandodialog (`ctrl+space`)
- Dynamisk omrendering vid terminalstorlek-ändringar
- 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
@@ -18,6 +20,8 @@ En terminal-baserad fönsterhanterare med flytande fönster, full mussuport och
| Terminal I/O, mus, resize | `crossterm` |
| TUI-rendering, buffersystem | `ratatui` |
| Pseudoterminal (PTY/ConPTY) | `portable-pty` |
| VT100-emulering | `vt100` |
| Konfiguration | `toml` + `serde` |
| Språk | Rust |
## Bygga projektet
@@ -54,9 +58,165 @@ Använd `Terminal > Run Task` och välj:
```
TUI-WM/
├── 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/
│ └── tasks.json
── Cargo.toml
└── README.md
── Cargo.toml
```
---
## 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) |

View File

@@ -12,16 +12,19 @@ file = "panels/topbar.toml"
[[keybind]]
key = "ctrl+space"
scope = "global"
label = "Öppna kommandodialog"
action = { type = "spawn_run_dialog" }
# Starta ny terminal med Alt+Enter (global)
# Starta ny terminal med Alt+Enter
[[keybind]]
key = "alt+enter"
scope = "global"
label = "Ny terminal"
action = { type = "spawn_terminal" }
# Avsluta TUI-WM när skrivbordet är fokuserat
[[keybind]]
key = "ctrl+q"
scope = "wm"
label = "Avsluta TUI-WM"
action = { type = "exit" }

View File

@@ -12,16 +12,19 @@ file = "panels/topbar.toml"
[[keybind]]
key = "ctrl+space"
scope = "global"
label = "Öppna kommandodialog"
action = { type = "spawn_run_dialog" }
# Starta ny terminal med Alt+Enter
[[keybind]]
key = "alt+enter"
scope = "global"
label = "Ny terminal"
action = { type = "spawn_terminal" }
# Avsluta TUI-WM när skrivbordet är fokuserat
[[keybind]]
key = "ctrl+q"
scope = "wm"
label = "Avsluta TUI-WM"
action = { type = "exit" }

View File

@@ -12,16 +12,19 @@ file = "panels/topbar.toml"
[[keybind]]
key = "ctrl+space"
scope = "global"
label = "Öppna kommandodialog"
action = { type = "spawn_run_dialog" }
# Starta ny PowerShell-terminal
[[keybind]]
key = "alt+enter"
scope = "global"
label = "Ny terminal"
action = { type = "spawn_terminal", shell = "powershell.exe" }
# Avsluta TUI-WM när skrivbordet är fokuserat
[[keybind]]
key = "ctrl+q"
scope = "wm"
label = "Avsluta TUI-WM"
action = { type = "exit" }

View File

@@ -3,6 +3,10 @@
label = "Terminal"
action = { type = "spawn_terminal" }
[[item]]
label = "mouse-test"
action = { type = "run_program", command = "testapps/mouse-test/target/release/mouse-test" }
[[item]]
label = "Exit"
action = { type = "exit" }

View File

@@ -108,6 +108,10 @@ pub struct App {
// Parsade keybinds från config
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 {
@@ -116,6 +120,8 @@ pub struct DropdownState {
pub rect: Rect,
pub item_rects: Vec<Rect>,
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 {
@@ -129,6 +135,13 @@ pub struct FloatingWindow {
pub resizing: Option<ResizeState>,
/// Om false: inga resize-kanter, kan inte storleksändras
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 {
@@ -324,12 +337,15 @@ impl App {
focused_id: None,
status_states,
parsed_keybinds,
tui_wm_btn_rects: Vec::new(),
hovered_tui_btn: false,
}
}
pub fn update_layout(&mut self, area: Rect) {
self.panel_rects.clear();
self.panel_item_rects.clear();
self.tui_wm_btn_rects.clear();
let mut top_used = 0u16;
let mut bottom_used = 0u16;
@@ -349,7 +365,8 @@ impl App {
};
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 {
let w = item.label.len() as u16 + 2;
item_rects.push(Rect::new(x, rect.y, w, 1));
@@ -358,6 +375,8 @@ impl App {
self.panel_rects.push(rect);
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(
@@ -372,7 +391,9 @@ impl App {
if let (Some(pr), Some(ir)) =
(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 width = dd
.items
@@ -382,7 +403,7 @@ impl App {
.unwrap_or(12)
.max(12);
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();
dd.rect = Rect::new(x, y, width, dd.items.len() as u16);
}
@@ -390,7 +411,7 @@ impl App {
}
pub fn tick(&mut self) {
// ── PTY-data → vt100-parser ──────────────────────────────────────────
// ── PTY-data → vt100-parser + mus-tracking-skanning ──────────────────────────────────────
for window in &mut self.windows {
let WindowContent::Terminal { rx, parser, alive, .. } = &mut window.content else { continue };
if !*alive {
@@ -398,7 +419,17 @@ impl App {
}
loop {
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::Disconnected) => {
*alive = false;
@@ -418,8 +449,10 @@ impl App {
}
let (cur_rows, cur_cols) = parser.screen().size();
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);
*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) {
// 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 {
MouseEventKind::Moved => self.update_hover(col, row),
MouseEventKind::Drag(MouseButton::Left) => self.handle_drag(col, row),
MouseEventKind::Up(MouseButton::Left) => {
MouseEventKind::Moved => {
self.update_hover(col, row);
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 {
w.dragging = 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(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::ScrollDown => self.handle_scroll(col, row, false),
_ => {}
@@ -614,6 +689,7 @@ impl App {
self.hovered_panel_item = None;
self.hovered_window_close = None;
self.hovered_resize = None;
self.hovered_tui_btn = false;
// Dropdown-hover
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) {
@@ -735,14 +819,42 @@ impl App {
return;
}
// 5. Innehållsyta → fokus
let content_id = self.windows.iter().rev().find(|w| w.in_content(col, row)).map(|w| w.id);
if let Some(id) = content_id {
// 5. Innehållsyta → fokus + vidarebefordra musklick till terminal
let hit = self.windows.iter().rev().find(|w| w.in_content(col, row)).and_then(|w| {
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);
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;
}
// 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 (ii, r) in item_rects.iter().enumerate() {
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) {
let id = self.windows.iter().rev().find(|w| in_rect(col, row, w.rect())).map(|w| w.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" };
if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
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) {
match action {
MenuAction::Exit => self.should_quit = true,
@@ -781,6 +952,7 @@ impl App {
MenuAction::RunProgram { command, .. } => self.spawn_terminal(&command),
MenuAction::SpawnRunDialog => self.spawn_run_dialog(),
MenuAction::Submenu { .. } => {}
MenuAction::NoOp => {}
}
}
@@ -803,6 +975,9 @@ impl App {
dragging: None,
resizing: None,
resizable: false,
mouse_mode: vt100::MouseProtocolMode::None,
mouse_encoding: vt100::MouseProtocolEncoding::Default,
mouse_seq_carry: Vec::new(),
});
self.focus_window(id);
}
@@ -821,8 +996,9 @@ impl App {
let x = ir.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 + 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 {
panel_idx,
@@ -830,9 +1006,51 @@ impl App {
item_rects,
items,
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) {
let ca = self.content_area;
let offset = (self.windows.len() as i32) * 2;
@@ -859,6 +1077,9 @@ impl App {
dragging: None,
resizing: None,
resizable: true,
mouse_mode: vt100::MouseProtocolMode::None,
mouse_encoding: vt100::MouseProtocolEncoding::Default,
mouse_seq_carry: Vec::new(),
});
self.focus_window(id);
}
@@ -884,6 +1105,187 @@ impl App {
// ─── 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 {
col >= rect.x
&& 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>> {
use KeyCode::*;
let bytes: Vec<u8> = match key.code {

View File

@@ -18,6 +18,9 @@ pub struct KeybindConfig {
#[serde(default)]
pub scope: KeybindScope,
pub action: MenuAction,
/// Visningsnamn som visas i TUI-WM-menyn (valfritt)
#[serde(default)]
pub label: Option<String>,
}
#[derive(Deserialize, Debug, Clone, Default, PartialEq)]
@@ -77,6 +80,8 @@ pub enum MenuAction {
},
/// Öppnar Tui-run kommandodialog
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
@@ -106,6 +111,29 @@ fn default_interval() -> u64 {
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 {
pub fn load(path: &str) -> anyhow::Result<Config> {
let content = fs::read_to_string(path)?;
@@ -128,7 +156,10 @@ impl Config {
Ok(c) => c,
Err(e) => {
eprintln!("Varning: kunde inte ladda {}: {}", path, e);
Config::default()
Config {
keybinds: default_keybinds(),
..Config::default()
}
}
}
}

98
src/log.rs Normal file
View 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)
}

View File

@@ -1,5 +1,6 @@
mod app;
mod config;
mod log;
mod pty;
mod render;
@@ -14,6 +15,7 @@ use ratatui::{backend::CrosstermBackend, Terminal};
use std::{io, time::Duration};
fn main() -> io::Result<()> {
log::init();
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;

View File

@@ -41,6 +41,8 @@ pub fn render(frame: &mut Frame, app: &App) {
pi,
&panel_cfg.status_widgets,
status_states,
app.focused_id.is_none(),
app.hovered_tui_btn,
);
}
}
@@ -61,6 +63,8 @@ fn render_panel(
panel_idx: usize,
status_widgets: &[crate::config::StatusWidget],
status_states: &[crate::app::StatusWidgetState],
desktop_focused: bool,
hovered_tui: bool,
) {
// Panelens bakgrund
frame.render_widget(
@@ -68,15 +72,24 @@ fn render_panel(
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(
Paragraph::new(Span::styled(
" TUI-WM ",
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
)),
Paragraph::new(Span::styled(" TUI-WM ", logo_style)),
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
for (ii, item) in items.iter().enumerate() {
let Some(&ir) = item_rects.get(ii) else { continue };
@@ -117,7 +130,7 @@ fn render_panel(
}
// 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;
for (si, sw_cfg) in status_widgets.iter().enumerate() {
if sw_cfg.align != StatusAlign::Left {
@@ -154,7 +167,8 @@ fn render_dropdown(frame: &mut Frame, dd: &DropdownState) {
} else {
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(
Paragraph::new(Span::styled(format!(" {} ", item.label), style)),
draw_rect,

307
testapps/mouse-test/Cargo.lock generated Normal file
View 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"

View 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"] }

View 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()
}

View 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":{}}

View 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/

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
8d45f0d559d66bc1

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
760f7b2d392f0023

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
aec685458e1d732a

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -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}

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
33fafcdc75c9e42c

View File

@@ -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}

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
664ed3c38ee6fd6b

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
63fef27575bbac30

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
ae3b6c9b75c17264

View File

@@ -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}

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -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}

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -0,0 +1 @@
e4c3356c10232b0e

View File

@@ -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}

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -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}

View File

@@ -0,0 +1 @@
This file has an mtime of when this was started.

View File

@@ -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}

View File

@@ -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