diff --git a/.gitignore b/.gitignore index ea40b9e..b540dc9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ scribe-tap *.gcno *.gcov .DS_Store + +AGENTS.override.md + +AGENTS.md diff --git a/AGENTS.md b/AGENTS.md index a102a80..638e0f1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,5 @@ + + # Repository Guidelines ## Project Structure & Module Organization @@ -20,4 +22,4 @@ Extend `tests/test_basic.py` when adding behaviours; each scenario should isolat Recent history mixes plain imperative subjects (`Add data-dir flag`) with optional Conventional Commit prefixes (`feat:`). Whichever you choose, keep the subject under 72 characters and describe the user-visible impact in the body when needed. Pull requests should link issues, outline configuration changes, and include `make`/`make check` results (terminal snippets beat screenshots). Mention any flags or environment requirements reviewers must set. ## Environment & Tooling Notes -Hyprland context detection depends on libxkbcommon plus clipboard helpers; verify they are on PATH or rely on `nix develop`. Default log destinations point at `/realm/data/keylog`, so override with `--data-dir` or `--log-dir` during local testing. When embedding inside interception pipelines, keep stdin/stdout unbuffered and use `--context none` for headless runs to avoid compositor calls. +Hyprland context detection depends on libxkbcommon plus clipboard helpers; verify they are on PATH or rely on `nix develop`. Default log destinations point at `/realm/data/captures/keylog`, so override with `--data-dir` or `--log-dir` during local testing. When embedding inside interception pipelines, keep stdin/stdout unbuffered and use `--context none` for headless runs to avoid compositor calls. diff --git a/README.md b/README.md index 0de0ce1..0f4a228 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ scribe-tap [--data-dir DIR] [--log-dir DIR] [--snapshot-dir DIR] [--snapshot-int [--hypr-signature PATH] [--hypr-user USER] ``` -- `--data-dir` – root directory for artefacts (defaults to `/realm/data/keylog`, creating `logs/` and `snapshots/` automatically). +- `--data-dir` – root directory for artefacts (defaults to `/realm/data/captures/keylog`, creating `logs/` and `snapshots/` automatically). - `--log-dir` – directory for JSONL log files (`$data_dir/logs` by default). - `--snapshot-dir` – directory for live snapshots (`$data_dir/snapshots`). - `--snapshot-interval` – write snapshot at most once per window per interval (seconds). @@ -109,10 +109,10 @@ Use the included replay helper to inspect logs (`scribe-tap-replay` when install ```sh # latest snapshots and tail events -python3 tools/replay.py --log-dir /realm/data/keylog/logs --snapshot-dir /realm/data/keylog/snapshots --mode both --window messenger --events-tail 10 --show-clipboard +python3 tools/replay.py --log-dir /realm/data/captures/keylog/logs --snapshot-dir /realm/data/captures/keylog/snapshots --mode both --window messenger --events-tail 10 --show-clipboard # interactive picker -python3 tools/replay.py --snapshot-dir /realm/data/keylog/snapshots --interactive --session 20251003T001711 +python3 tools/replay.py --snapshot-dir /realm/data/captures/keylog/snapshots --interactive --session 20251003T001711 ``` ## License diff --git a/src/main.c b/src/main.c index 45d7fe3..195b9cb 100644 --- a/src/main.c +++ b/src/main.c @@ -248,9 +248,12 @@ static void print_usage(const char *prog) { } int main(int argc, char **argv) { - const char *data_dir = "/realm/data/keylog"; + const char *data_dir = "/realm/data/captures/keylog"; const char *log_dir = NULL; const char *snapshot_dir = NULL; + bool data_dir_explicit = false; + bool log_dir_explicit = false; + bool snapshot_dir_explicit = false; const char *hyprctl_cmd = "hyprctl"; double snapshot_interval = 5.0; double context_refresh = 0.4; @@ -269,10 +272,13 @@ int main(int argc, char **argv) { for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "--log-dir") == 0 && i + 1 < argc) { log_dir = argv[++i]; + log_dir_explicit = true; } else if (strcmp(argv[i], "--snapshot-dir") == 0 && i + 1 < argc) { snapshot_dir = argv[++i]; + snapshot_dir_explicit = true; } else if (strcmp(argv[i], "--data-dir") == 0 && i + 1 < argc) { data_dir = argv[++i]; + data_dir_explicit = true; } else if (strcmp(argv[i], "--snapshot-interval") == 0 && i + 1 < argc) { snapshot_interval = atof(argv[++i]); } else if (strcmp(argv[i], "--context-refresh") == 0 && i + 1 < argc) { @@ -356,7 +362,9 @@ int main(int argc, char **argv) { snapshot_dir = snapshot_dir_buf; } - util_ensure_dir_tree(data_dir); + if (data_dir_explicit || !log_dir_explicit || !snapshot_dir_explicit) { + util_ensure_dir_tree(data_dir); + } util_ensure_dir_tree(log_dir); util_ensure_dir_tree(snapshot_dir); diff --git a/src/state.c b/src/state.c index 3122052..0a59ec5 100644 --- a/src/state.c +++ b/src/state.c @@ -358,6 +358,8 @@ int state_poll_timeout_ms(const State *state) { return (int)interval_ms; } +static char lowercase_char_for_key(int code); /* forward decl for keycode_name */ + static const char *keycode_name(int code) { static char buf[32]; switch (code) { @@ -371,9 +373,15 @@ static const char *keycode_name(int code) { default: break; } - if (code >= KEY_A && code <= KEY_Z) { - snprintf(buf, sizeof(buf), "KEY_%c", 'A' + (code - KEY_A)); - return buf; + /* Use lowercase_char_for_key() to get the correct letter — evdev keycodes + are NOT contiguous A-Z (they follow keyboard rows: Q=16..P=25, A=30..L=38, Z=44..M=50). + The old code assumed contiguous codes and mislabeled most letters. */ + { + char letter = lowercase_char_for_key(code); + if (letter >= 'a' && letter <= 'z') { + snprintf(buf, sizeof(buf), "KEY_%c", letter - 32); /* uppercase */ + return buf; + } } if (code >= KEY_0 && code <= KEY_9) { snprintf(buf, sizeof(buf), "KEY_%c", '0' + (code - KEY_0)); @@ -865,6 +873,11 @@ static void process_key(State *state, int code, const char *key_name, const char buffer_append(buf, clipboard, strlen(clipboard)); changed = true; } + } else if (state->modifiers[MOD_CTRL] || state->modifiers[MOD_ALT]) { + /* Skip buffer append when Ctrl or Alt is held — xkbcommon returns + control codes (0x01-0x1f) that corrupt the buffer. The press event + still logs the keycode for reconstruction from raw events. */ + break; } else { if (utf8_text && *utf8_text) { buffer_append(buf, utf8_text, strlen(utf8_text)); diff --git a/tools/replay.py b/tools/replay.py index 8b83153..56bea39 100755 --- a/tools/replay.py +++ b/tools/replay.py @@ -74,8 +74,8 @@ def load_events(log_path: Path) -> Iterable[dict]: def main() -> None: parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--log-dir", type=Path, default=Path("/realm/data/keylog/logs")) - parser.add_argument("--snapshot-dir", type=Path, default=Path("/realm/data/keylog/snapshots")) + parser.add_argument("--log-dir", type=Path, default=Path("/realm/data/captures/keylog/logs")) + parser.add_argument("--snapshot-dir", type=Path, default=Path("/realm/data/captures/keylog/snapshots")) parser.add_argument( "--date", type=str,