deadbyte-mcp 0.11.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +126 -54
- package/R41-PARENT.json +15 -0
- package/README.txt +5 -4
- package/acceptance/r40/desktop-live-drift.mjs +106 -0
- package/acceptance/r40/desktop-live-ground.mjs +110 -0
- package/acceptance/r40/desktop-live-input.mjs +128 -0
- package/acceptance/r40/desktop-live-observe.mjs +100 -0
- package/acceptance/r40/desktop-live-uia.mjs +121 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.exe +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.exe +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.exe +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +7 -5
- package/controller/deadbyte-controller.config.psd1.example +3 -0
- package/controller/deadbyte-controller.ps1 +149 -4
- package/controller/deadbyte-desktop-policy.json +41 -0
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/ARCHITECTURE.md +6 -1
- package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
- package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
- package/native/deadbyte-desktop-input.cpp +455 -0
- package/native/deadbyte-desktop-observer.cpp +253 -0
- package/native/deadbyte-desktop-uia.cpp +307 -0
- package/package.json +12 -8
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/build-containment.ps1 +24 -14
- package/scripts/build-desktop-r40.ps1 +112 -0
- package/scripts/deferred-slot-operation-r40.mjs +126 -0
- package/scripts/deferred-slot-operation-r41.mjs +127 -0
- package/scripts/final-closure-r40.mjs +114 -0
- package/scripts/final-closure-r41.mjs +116 -0
- package/scripts/final-closure-verify-r40.mjs +212 -0
- package/scripts/final-closure-verify-r41.mjs +215 -0
- package/scripts/gate-windows-r40.ps1 +43 -0
- package/scripts/gate-windows-r41.ps1 +48 -0
- package/scripts/init-desktop-policy-r40.ps1 +85 -0
- package/scripts/r40-compact-ledger-probe.mjs +4 -0
- package/scripts/release-parity-r40.mjs +212 -0
- package/scripts/release-parity-r41.mjs +221 -0
- package/scripts/release-parity-tests-r40.ps1 +9 -0
- package/scripts/release-parity-tests-r41.ps1 +9 -0
- package/scripts/verify-memory-r41.mjs +88 -0
- package/scripts/verify-r41-parent.mjs +61 -0
- package/scripts/windows-gate-evidence-r40.mjs +52 -0
- package/scripts/windows-gate-evidence-r41.mjs +53 -0
- package/src/autonomous-context-engine.mjs +3 -1
- package/src/autonomous-planner-contracts.mjs +19 -1
- package/src/autonomous-planner.mjs +24 -0
- package/src/autonomous-runtime.mjs +164 -25
- package/src/capability-ledger.mjs +6 -1
- package/src/compact-gateway-mcp-tools.mjs +14 -2
- package/src/deadbyte-cli.mjs +72 -44
- package/src/desktop-contract.mjs +263 -0
- package/src/desktop-input-r40.mjs +148 -0
- package/src/desktop-mcp-tools-r40.mjs +126 -0
- package/src/desktop-memory-r40.mjs +272 -0
- package/src/desktop-observer-r40.mjs +231 -0
- package/src/desktop-policy-r40.mjs +110 -0
- package/src/desktop-runtime-r40.mjs +318 -0
- package/src/desktop-uia-r40.mjs +123 -0
- package/src/mcp-server.mjs +87 -19
- package/src/memory-r41.mjs +692 -0
- package/src/r40-compact-disarmed-probe.mjs +71 -0
- package/src/r40-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +38 -5
- package/src/remote-console.mjs +41 -0
- package/src/remote-live-log.mjs +208 -45
- package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
- package/src/remote-mcp-r40-parity-client.mjs +200 -0
- package/src/remote-mcp-smoke-client.mjs +20 -6
- package/src/remote-result-journal.mjs +10 -2
- package/src/runtime-update.mjs +55 -17
- package/src/tool-audit.mjs +29 -2
- package/src/version.mjs +1 -1
- package/test/autonomous-capability-r36.test.mjs +43 -1
- package/test/autonomous-desktop-r40.test.mjs +188 -0
- package/test/capability-ledger-r36.test.mjs +1 -0
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/compact-catalog-r36.test.mjs +2 -2
- package/test/compact-gateway-r36.test.mjs +56 -4
- package/test/compact-max-catalog-r40.test.mjs +95 -0
- package/test/controller-desktop-r40.test.mjs +86 -0
- package/test/core.test.mjs +1 -1
- package/test/desktop-adversarial-r40.test.mjs +62 -0
- package/test/desktop-contract-r40.test.mjs +200 -0
- package/test/desktop-input-r40.test.mjs +63 -0
- package/test/desktop-mcp-tools-r40.test.mjs +61 -0
- package/test/desktop-memory-r40.test.mjs +141 -0
- package/test/desktop-observer-r40.test.mjs +119 -0
- package/test/desktop-policy-r40.test.mjs +111 -0
- package/test/desktop-runtime-r40.test.mjs +278 -0
- package/test/desktop-uia-r40.test.mjs +42 -0
- package/test/full-max-catalog-r40.test.mjs +91 -0
- package/test/helpers/autonomous-r34-fixture.mjs +3 -3
- package/test/memory-autonomous-integration-r41.test.mjs +31 -0
- package/test/memory-history-r41.test.mjs +126 -0
- package/test/memory-projections-r41.test.mjs +92 -0
- package/test/memory-retrieval-r41.test.mjs +124 -0
- package/test/memory-snapshot-r41.test.mjs +106 -0
- package/test/r33-closeout-regression.test.mjs +7 -7
- package/test/r33-finalization.test.mjs +15 -15
- package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
- package/test/r40-finalization.test.mjs +118 -0
- package/test/r41-finalization.test.mjs +38 -0
- package/test/release-generation.test.mjs +41 -9
- package/test/release-version.test.mjs +20 -61
- package/test/remote-console-r40.test.mjs +38 -0
- package/test/remote-live-log-r37.test.mjs +89 -32
- package/test/remote-r24-catalog.test.mjs +12 -3
- package/test/remote-result-journal.test.mjs +15 -0
- package/test/remote-session-cli.test.mjs +69 -46
- package/test/runtime-self-update.test.mjs +44 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# R41 DETERMINISTIC MEMORY ARCHITECTURE
|
|
2
|
+
|
|
3
|
+
Status: approved implementation contract
|
|
4
|
+
Date: 2026-09-20
|
|
5
|
+
Predecessor: R40 PUBLIC FINAL / deadbyte-mcp 0.12.0
|
|
6
|
+
Development root: `D:\Projects\deadbyte-mcp-r41-memory-dev`
|
|
7
|
+
|
|
8
|
+
R40 release, source baseline, generation, npm artifact, and final evidence are immutable. R41 starts from parent manifest `70906ae0a7e4f0627efbb5087b40b85677c81ed6d64ed89c0ba2af846e390f39` recorded in `R41-PARENT.json`.
|
|
9
|
+
|
|
10
|
+
## Execution contract
|
|
11
|
+
|
|
12
|
+
- TASK_ID: `R41-MEMORY-ARCHITECTURE`
|
|
13
|
+
- OBJECTIVE: production deterministic long-term memory derived from DEADBYTE's signed autonomous journals.
|
|
14
|
+
- ACTIVE_SKILL: `implement-controlled-software-changes`.
|
|
15
|
+
- LEAD_ROLE: systems implementation.
|
|
16
|
+
- REVIEW_ROLE: independent machine verifier and adversarial tests; no human reviewer is available in this execution surface.
|
|
17
|
+
- FIXED: journal authority, Ed25519 evidence, immutable releases, fail-closed mutation, Host never auto-arms, no blind replay.
|
|
18
|
+
- TARGETS: deterministic snapshots, episodic/decision/incident memory, bounded working memory, rebuildable exact/FTS/symbol indexes, disabled semantic adapter.
|
|
19
|
+
- BOUNDS: internal runtime and existing compact gateways only; no new public MCP tools; no neural authority; no new dependency.
|
|
20
|
+
- CLASS_2_DISCRETION: bounded schemas, limits, file layout, and compaction thresholds that preserve the approved architecture.
|
|
21
|
+
- CLASS_3_DISCRETION: helper structure, names, pure data structures, and test organization.
|
|
22
|
+
- PROHIBITED: modifying R40 bytes/evidence, overwriting or republishing 0.12.0, embeddings as truth, memory-based authorization, silent decision overwrite, unverified provenance.
|
|
23
|
+
- METHOD_LOCK: project lock; signed journal is canonical, memory is a deterministic projection, snapshot plus verified suffix must equal full replay, indexes are disposable.
|
|
24
|
+
- ASSURANCE_MODE: security-critical deterministic state and release regression.
|
|
25
|
+
- ACCEPTANCE_ORACLES: byte equality, content hashes, Ed25519 verification, full-replay equivalence, actual planner context, focused/adversarial tests, full Windows release gate.
|
|
26
|
+
- STOP_CONDITION: source conflict, inability to verify canonical provenance, authority widening, or unknown side-effect state.
|
|
27
|
+
- ROLLBACK: discard the separate R41 directory; R40 remains active and untouched.
|
|
28
|
+
|
|
29
|
+
## Authority model
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
signed goal + signed event journal
|
|
33
|
+
|
|
|
34
|
+
+--> verified full replay
|
|
35
|
+
+--> signed deterministic snapshot + verified suffix replay
|
|
36
|
+
+--> episodic projection
|
|
37
|
+
+--> decision projection
|
|
38
|
+
+--> incident projection
|
|
39
|
+
+--> disposable retrieval indexes
|
|
40
|
+
+--> bounded verified working set
|
|
41
|
+
|
|
|
42
|
+
v
|
|
43
|
+
planner
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Only the signed goal and journal events establish facts. Snapshots, memories, indexes, and planner working sets are derived state. They never arm authority, authorize an operation, or prove a side effect.
|
|
47
|
+
|
|
48
|
+
## Journal identity
|
|
49
|
+
|
|
50
|
+
R41 reuses the existing autonomous journal contract:
|
|
51
|
+
|
|
52
|
+
- goal envelope: `deadbyte.autonomous-goal.v1`, Ed25519 domain-bound;
|
|
53
|
+
- event envelope: `deadbyte.autonomous-event.v1`, contiguous per-goal sequence;
|
|
54
|
+
- each event binds `goal_id`, `seq`, `prev_sha256`, payload, and `event_sha256`;
|
|
55
|
+
- event files are immutable `000001.json`, `000002.json`, ...;
|
|
56
|
+
- verification fails on a gap, substitution, signature failure, wrong goal, wrong predecessor hash, or schema drift.
|
|
57
|
+
|
|
58
|
+
Canonical memory references use `{goal_id, seq, event_sha256}`. Artifacts and receipts are references by SHA-256, never copied as untraceable claims.
|
|
59
|
+
|
|
60
|
+
## Deterministic snapshots
|
|
61
|
+
|
|
62
|
+
Schema `deadbyte.memory-snapshot.v1` contains:
|
|
63
|
+
|
|
64
|
+
- `schema_version` and snapshot schema;
|
|
65
|
+
- `snapshot_sequence`;
|
|
66
|
+
- `goal_id`;
|
|
67
|
+
- `last_event_seq`;
|
|
68
|
+
- `journal_prefix_hash` (the exact event head at `last_event_seq`);
|
|
69
|
+
- `state` and `state_hash` over canonical JSON;
|
|
70
|
+
- `created_at_utc` as metadata excluded from derived-state identity;
|
|
71
|
+
- `source_release_manifest`;
|
|
72
|
+
- `writer_version`;
|
|
73
|
+
- Ed25519 attestation and a snapshot self-hash.
|
|
74
|
+
|
|
75
|
+
Load verifies the signature, schema, state hash, source release binding, exact journal prefix, and sequence bounds. A corrupt/deleted snapshot is never trusted; the provider explicitly falls back to full verified replay and may replace the projection atomically. Snapshot plus suffix replay must be byte-equal to full replay.
|
|
76
|
+
|
|
77
|
+
## Episodic memory
|
|
78
|
+
|
|
79
|
+
Each goal is one episode. The projection records goal, observations, decisions, actions, results, verifications, outcome, event range, artifact/receipt references, and timestamps. Every item retains canonical event references. Open episodes remain open; only terminal journal evidence closes them.
|
|
80
|
+
|
|
81
|
+
## Decision memory
|
|
82
|
+
|
|
83
|
+
Planner decisions and explicit decision/supersession events project to stable decision records. Supersession is an edge, not an overwrite. Missing targets and cycles fail closed. Before any planner mutation, the bounded working set contains relevant active decisions and their source event references.
|
|
84
|
+
|
|
85
|
+
## Failure and incident memory
|
|
86
|
+
|
|
87
|
+
Failures project into deterministic fingerprints from safe structured fields: tool/operation, platform/architecture, exit/signal/timeout, diagnostic codes, output hashes, bounded stack frames, runtime/profile/symbol identity, artifacts, and environment fingerprint. Raw secrets and unbounded output are forbidden. Incidents link symptoms, attempts, root cause/fix evidence when present, status, and source event range.
|
|
88
|
+
|
|
89
|
+
## Bounded working memory
|
|
90
|
+
|
|
91
|
+
The working set contains the current goal/plan/files/symbols, recent events, open failures, active constraints, relevant decisions/incidents, current authority observation, and active episode. It is deterministically ranked and compacted to a byte budget. Each retained fact carries canonical event or artifact provenance. Deleting it loses no truth.
|
|
92
|
+
|
|
93
|
+
## Retrieval indexes
|
|
94
|
+
|
|
95
|
+
Indexes are canonical JSON projections rebuilt from verified memory:
|
|
96
|
+
|
|
97
|
+
- exact canonical ID;
|
|
98
|
+
- goal event range;
|
|
99
|
+
- exact normalized token and bounded full-text token;
|
|
100
|
+
- structured fields;
|
|
101
|
+
- symbol;
|
|
102
|
+
- artifact/content SHA-256;
|
|
103
|
+
- decision scope/status;
|
|
104
|
+
- incident fingerprint.
|
|
105
|
+
|
|
106
|
+
The index binds the source memory hash. Poisoned or stale bindings fail closed. Deleting and rebuilding an index must produce byte-identical content.
|
|
107
|
+
|
|
108
|
+
## Optional semantic adapter
|
|
109
|
+
|
|
110
|
+
`memory.semantic.enabled` defaults to `false`. The adapter may return candidate canonical IDs only. Every candidate is resolved from the verified exact index and re-read from canonical memory before planner use. It cannot authorize, mutate, establish truth, or replace receipts.
|
|
111
|
+
|
|
112
|
+
## Production integration
|
|
113
|
+
|
|
114
|
+
R41 adds an internal memory provider to the autonomous runtime. `plannerView` receives only its bounded verified projection. Authority checks remain in the existing execution path immediately before effects. No public MCP tool is added. Local inspection and verification use CLI/internal APIs and signed evidence.
|
|
115
|
+
|
|
116
|
+
## Acceptance
|
|
117
|
+
|
|
118
|
+
Focused and adversarial tests must prove:
|
|
119
|
+
|
|
120
|
+
1. full replay equals snapshot plus suffix replay;
|
|
121
|
+
2. schema/sequence/prefix/state/signature tampering fails closed;
|
|
122
|
+
3. deleted/corrupt snapshots explicitly fall back to full verified replay;
|
|
123
|
+
4. nonexistent episode references and decision cycles fail;
|
|
124
|
+
5. incident spoofing and index poisoning fail;
|
|
125
|
+
6. stale/cross-release working memory is rejected;
|
|
126
|
+
7. semantic candidates require valid canonical provenance;
|
|
127
|
+
8. indexes rebuild byte-identically after deletion;
|
|
128
|
+
9. retrieved memory cannot arm or authorize any execution;
|
|
129
|
+
10. planner mutation sees verified decision memory;
|
|
130
|
+
11. R40 manifest, release tree, and final evidence hashes remain unchanged.
|
|
131
|
+
|
|
132
|
+
R41 release identity is a new version/manifest and cannot reuse or overwrite 0.12.0. Publication remains forbidden until focused tests, full regression, Windows gate, local/public parity, rollback/restore, final closure, independent verification, and final all-DISARMED authority pass.
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
#define WIN32_LEAN_AND_MEAN
|
|
2
|
+
#include <windows.h>
|
|
3
|
+
#include <io.h>
|
|
4
|
+
#include <fcntl.h>
|
|
5
|
+
#include <cstdio>
|
|
6
|
+
#include <cstdint>
|
|
7
|
+
#include <cstdlib>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <vector>
|
|
10
|
+
#include <sstream>
|
|
11
|
+
#include <iomanip>
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
|
|
14
|
+
static constexpr size_t kMaxRequestBytes = 128u * 1024u;
|
|
15
|
+
static constexpr size_t kMaxTextBytes = 64u * 1024u;
|
|
16
|
+
static constexpr size_t kMaxKeys = 5u;
|
|
17
|
+
|
|
18
|
+
struct Request {
|
|
19
|
+
std::string actionSha256;
|
|
20
|
+
std::string kind;
|
|
21
|
+
std::string expectedDesktop;
|
|
22
|
+
uintptr_t expectedHwnd = 0;
|
|
23
|
+
DWORD expectedPid = 0;
|
|
24
|
+
LONG left = 0;
|
|
25
|
+
LONG top = 0;
|
|
26
|
+
LONG width = 0;
|
|
27
|
+
LONG height = 0;
|
|
28
|
+
LONG x = 0;
|
|
29
|
+
LONG y = 0;
|
|
30
|
+
LONG scrollDelta = 0;
|
|
31
|
+
std::vector<std::string> keys;
|
|
32
|
+
std::string textUtf8;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
static std::string Utf8(const wchar_t* value) {
|
|
36
|
+
if (!value) return {};
|
|
37
|
+
const int needed = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value, -1, nullptr, 0, nullptr, nullptr);
|
|
38
|
+
if (needed <= 0) return {};
|
|
39
|
+
std::string out(static_cast<size_t>(needed), '\0');
|
|
40
|
+
if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value, -1, out.data(), needed, nullptr, nullptr) <= 0) return {};
|
|
41
|
+
if (!out.empty() && out.back() == '\0') out.pop_back();
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static std::string JsonEscape(const std::string& input) {
|
|
46
|
+
std::ostringstream out;
|
|
47
|
+
for (unsigned char ch : input) {
|
|
48
|
+
switch (ch) {
|
|
49
|
+
case '"': out << "\\\""; break;
|
|
50
|
+
case '\\': out << "\\\\"; break;
|
|
51
|
+
case '\b': out << "\\b"; break;
|
|
52
|
+
case '\f': out << "\\f"; break;
|
|
53
|
+
case '\n': out << "\\n"; break;
|
|
54
|
+
case '\r': out << "\\r"; break;
|
|
55
|
+
case '\t': out << "\\t"; break;
|
|
56
|
+
default:
|
|
57
|
+
if (ch < 0x20) out << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<unsigned int>(ch) << std::dec;
|
|
58
|
+
else out << static_cast<char>(ch);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out.str();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static std::string HwndHex(HWND hwnd) {
|
|
65
|
+
std::ostringstream out;
|
|
66
|
+
out << "0x" << std::hex << std::nouppercase << reinterpret_cast<uintptr_t>(hwnd);
|
|
67
|
+
return out.str();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static bool InputDesktopName(std::string& name) {
|
|
71
|
+
HDESK desktop = OpenInputDesktop(0, FALSE, DESKTOP_READOBJECTS);
|
|
72
|
+
if (!desktop) return false;
|
|
73
|
+
wchar_t buffer[512]{};
|
|
74
|
+
DWORD needed = 0;
|
|
75
|
+
const BOOL ok = GetUserObjectInformationW(desktop, UOI_NAME, buffer, sizeof(buffer), &needed);
|
|
76
|
+
CloseDesktop(desktop);
|
|
77
|
+
if (!ok) return false;
|
|
78
|
+
name = Utf8(buffer);
|
|
79
|
+
return !name.empty();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static bool Hex64(const std::string& value) {
|
|
83
|
+
if (value.size() != 64u) return false;
|
|
84
|
+
return std::all_of(value.begin(), value.end(), [](char c) {
|
|
85
|
+
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static bool ParseLong(const std::string& value, LONG& out) {
|
|
90
|
+
if (value.empty()) return false;
|
|
91
|
+
char* end = nullptr;
|
|
92
|
+
errno = 0;
|
|
93
|
+
const long parsed = std::strtol(value.c_str(), &end, 10);
|
|
94
|
+
if (errno != 0 || end == value.c_str() || *end != '\0') return false;
|
|
95
|
+
out = static_cast<LONG>(parsed);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static bool ParseDword(const std::string& value, DWORD& out) {
|
|
100
|
+
if (value.empty()) return false;
|
|
101
|
+
char* end = nullptr;
|
|
102
|
+
errno = 0;
|
|
103
|
+
const unsigned long parsed = std::strtoul(value.c_str(), &end, 10);
|
|
104
|
+
if (errno != 0 || end == value.c_str() || *end != '\0') return false;
|
|
105
|
+
out = static_cast<DWORD>(parsed);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static bool ParseUintptrHex(const std::string& value, uintptr_t& out) {
|
|
110
|
+
if (value.size() < 3u || value[0] != '0' || value[1] != 'x') return false;
|
|
111
|
+
char* end = nullptr;
|
|
112
|
+
errno = 0;
|
|
113
|
+
const unsigned long long parsed = std::strtoull(value.c_str() + 2, &end, 16);
|
|
114
|
+
if (errno != 0 || end == value.c_str() + 2 || *end != '\0') return false;
|
|
115
|
+
out = static_cast<uintptr_t>(parsed);
|
|
116
|
+
return static_cast<unsigned long long>(out) == parsed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static bool ReadAllStdin(std::string& bytes) {
|
|
120
|
+
if (_setmode(_fileno(stdin), _O_BINARY) == -1) return false;
|
|
121
|
+
std::vector<char> buffer(4096u);
|
|
122
|
+
for (;;) {
|
|
123
|
+
const size_t got = std::fread(buffer.data(), 1, buffer.size(), stdin);
|
|
124
|
+
if (got > 0u) {
|
|
125
|
+
if (bytes.size() + got > kMaxRequestBytes) return false;
|
|
126
|
+
bytes.append(buffer.data(), got);
|
|
127
|
+
}
|
|
128
|
+
if (got < buffer.size()) {
|
|
129
|
+
if (std::ferror(stdin)) return false;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return !bytes.empty();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static bool NextLine(const std::string& bytes, size_t& offset, std::string& line) {
|
|
137
|
+
if (offset >= bytes.size()) return false;
|
|
138
|
+
const size_t end = bytes.find('\n', offset);
|
|
139
|
+
if (end == std::string::npos) return false;
|
|
140
|
+
line.assign(bytes, offset, end - offset);
|
|
141
|
+
if (!line.empty() && line.back() == '\r') line.pop_back();
|
|
142
|
+
offset = end + 1u;
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static bool SplitField(const std::string& line, std::string& key, std::string& value) {
|
|
147
|
+
const size_t eq = line.find('=');
|
|
148
|
+
if (eq == std::string::npos || eq == 0u) return false;
|
|
149
|
+
key = line.substr(0u, eq);
|
|
150
|
+
value = line.substr(eq + 1u);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static bool SupportedKind(const std::string& kind) {
|
|
155
|
+
static const char* const kinds[] = {
|
|
156
|
+
"pointer_move","click_primary","click_secondary","double_click_primary",
|
|
157
|
+
"scroll","key_chord","text_input"
|
|
158
|
+
};
|
|
159
|
+
for (const char* value : kinds) if (kind == value) return true;
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static WORD VirtualKey(const std::string& key) {
|
|
164
|
+
if (key.size() == 1u && key[0] >= 'A' && key[0] <= 'Z') return static_cast<WORD>(key[0]);
|
|
165
|
+
if (key.size() == 1u && key[0] >= '0' && key[0] <= '9') return static_cast<WORD>(key[0]);
|
|
166
|
+
if (key.size() >= 2u && key[0] == 'F') {
|
|
167
|
+
const int n = std::atoi(key.c_str() + 1);
|
|
168
|
+
if (n >= 1 && n <= 12) return static_cast<WORD>(VK_F1 + n - 1);
|
|
169
|
+
}
|
|
170
|
+
if (key == "ENTER") return VK_RETURN;
|
|
171
|
+
if (key == "ESC") return VK_ESCAPE;
|
|
172
|
+
if (key == "TAB") return VK_TAB;
|
|
173
|
+
if (key == "SPACE") return VK_SPACE;
|
|
174
|
+
if (key == "BACKSPACE") return VK_BACK;
|
|
175
|
+
if (key == "DELETE") return VK_DELETE;
|
|
176
|
+
if (key == "INSERT") return VK_INSERT;
|
|
177
|
+
if (key == "HOME") return VK_HOME;
|
|
178
|
+
if (key == "END") return VK_END;
|
|
179
|
+
if (key == "PAGEUP") return VK_PRIOR;
|
|
180
|
+
if (key == "PAGEDOWN") return VK_NEXT;
|
|
181
|
+
if (key == "LEFT") return VK_LEFT;
|
|
182
|
+
if (key == "RIGHT") return VK_RIGHT;
|
|
183
|
+
if (key == "UP") return VK_UP;
|
|
184
|
+
if (key == "DOWN") return VK_DOWN;
|
|
185
|
+
if (key == "CTRL") return VK_CONTROL;
|
|
186
|
+
if (key == "ALT") return VK_MENU;
|
|
187
|
+
if (key == "SHIFT") return VK_SHIFT;
|
|
188
|
+
if (key == "WIN") return VK_LWIN;
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static bool ParseRequest(const std::string& bytes, Request& request, std::string& reason) {
|
|
193
|
+
size_t offset = 0u;
|
|
194
|
+
std::string line;
|
|
195
|
+
if (!NextLine(bytes, offset, line) || line != "DEADBYTE_DESKTOP_INPUT_V1") {
|
|
196
|
+
reason = "magic mismatch"; return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
bool seenAction=false,seenKind=false,seenDesktop=false,seenHwnd=false,seenPid=false;
|
|
200
|
+
bool seenLeft=false,seenTop=false,seenWidth=false,seenHeight=false,seenX=false,seenY=false;
|
|
201
|
+
bool seenDelta=false,seenKeyCount=false,seenTextBytes=false;
|
|
202
|
+
size_t declaredKeyCount=0u, declaredTextBytes=0u;
|
|
203
|
+
|
|
204
|
+
while (NextLine(bytes, offset, line)) {
|
|
205
|
+
std::string key,value;
|
|
206
|
+
if (!SplitField(line,key,value)) { reason="field malformed"; return false; }
|
|
207
|
+
if (key=="action_sha256") {
|
|
208
|
+
if (seenAction || !Hex64(value)) { reason="action_sha256 invalid"; return false; }
|
|
209
|
+
seenAction=true; request.actionSha256=value;
|
|
210
|
+
} else if (key=="kind") {
|
|
211
|
+
if (seenKind || !SupportedKind(value)) { reason="kind invalid"; return false; }
|
|
212
|
+
seenKind=true; request.kind=value;
|
|
213
|
+
} else if (key=="expected_desktop") {
|
|
214
|
+
if (seenDesktop || value.empty() || value.size()>128u || value.find('\0')!=std::string::npos) { reason="expected_desktop invalid"; return false; }
|
|
215
|
+
seenDesktop=true; request.expectedDesktop=value;
|
|
216
|
+
} else if (key=="expected_hwnd") {
|
|
217
|
+
if (seenHwnd || !ParseUintptrHex(value,request.expectedHwnd)) { reason="expected_hwnd invalid"; return false; }
|
|
218
|
+
seenHwnd=true;
|
|
219
|
+
} else if (key=="expected_pid") {
|
|
220
|
+
if (seenPid || !ParseDword(value,request.expectedPid) || request.expectedPid>0x7fffffffu) { reason="expected_pid invalid"; return false; }
|
|
221
|
+
seenPid=true;
|
|
222
|
+
} else if (key=="left") {
|
|
223
|
+
if (seenLeft || !ParseLong(value,request.left)) { reason="left invalid"; return false; } seenLeft=true;
|
|
224
|
+
} else if (key=="top") {
|
|
225
|
+
if (seenTop || !ParseLong(value,request.top)) { reason="top invalid"; return false; } seenTop=true;
|
|
226
|
+
} else if (key=="width") {
|
|
227
|
+
if (seenWidth || !ParseLong(value,request.width)) { reason="width invalid"; return false; } seenWidth=true;
|
|
228
|
+
} else if (key=="height") {
|
|
229
|
+
if (seenHeight || !ParseLong(value,request.height)) { reason="height invalid"; return false; } seenHeight=true;
|
|
230
|
+
} else if (key=="x") {
|
|
231
|
+
if (seenX || !ParseLong(value,request.x)) { reason="x invalid"; return false; } seenX=true;
|
|
232
|
+
} else if (key=="y") {
|
|
233
|
+
if (seenY || !ParseLong(value,request.y)) { reason="y invalid"; return false; } seenY=true;
|
|
234
|
+
} else if (key=="scroll_delta") {
|
|
235
|
+
if (seenDelta || !ParseLong(value,request.scrollDelta)) { reason="scroll_delta invalid"; return false; } seenDelta=true;
|
|
236
|
+
} else if (key=="key_count") {
|
|
237
|
+
if (seenKeyCount) { reason="key_count duplicate"; return false; }
|
|
238
|
+
DWORD count=0; if (!ParseDword(value,count) || count>kMaxKeys) { reason="key_count invalid"; return false; }
|
|
239
|
+
seenKeyCount=true; declaredKeyCount=static_cast<size_t>(count);
|
|
240
|
+
} else if (key=="key") {
|
|
241
|
+
if (request.keys.size()>=kMaxKeys || VirtualKey(value)==0) { reason="key invalid"; return false; }
|
|
242
|
+
if (std::find(request.keys.begin(),request.keys.end(),value)!=request.keys.end()) { reason="key duplicate"; return false; }
|
|
243
|
+
request.keys.push_back(value);
|
|
244
|
+
} else if (key=="text_bytes") {
|
|
245
|
+
if (seenTextBytes) { reason="text_bytes duplicate"; return false; }
|
|
246
|
+
DWORD count=0; if (!ParseDword(value,count) || count>kMaxTextBytes) { reason="text_bytes invalid"; return false; }
|
|
247
|
+
seenTextBytes=true; declaredTextBytes=static_cast<size_t>(count);
|
|
248
|
+
if (bytes.size()-offset != declaredTextBytes) { reason="text payload length mismatch"; return false; }
|
|
249
|
+
request.textUtf8.assign(bytes.data()+offset,declaredTextBytes);
|
|
250
|
+
offset=bytes.size();
|
|
251
|
+
break;
|
|
252
|
+
} else {
|
|
253
|
+
reason="unknown field"; return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!(seenAction&&seenKind&&seenDesktop&&seenHwnd&&seenPid&&seenLeft&&seenTop&&seenWidth&&seenHeight&&seenX&&seenY&&seenDelta&&seenKeyCount&&seenTextBytes)) {
|
|
258
|
+
reason="required field missing"; return false;
|
|
259
|
+
}
|
|
260
|
+
if (request.keys.size()!=declaredKeyCount) { reason="key_count mismatch"; return false; }
|
|
261
|
+
if (request.width<=0 || request.height<=0 || request.width>131072 || request.height>131072) { reason="bounds invalid"; return false; }
|
|
262
|
+
if (request.left < -131072 || request.top < -131072 || request.left+request.width > 262144 || request.top+request.height > 262144) { reason="bounds extent invalid"; return false; }
|
|
263
|
+
|
|
264
|
+
const bool pointer = request.kind=="pointer_move" || request.kind=="click_primary" || request.kind=="click_secondary" ||
|
|
265
|
+
request.kind=="double_click_primary" || request.kind=="scroll";
|
|
266
|
+
if (pointer) {
|
|
267
|
+
if (request.x<request.left || request.y<request.top || request.x>=request.left+request.width || request.y>=request.top+request.height) {
|
|
268
|
+
reason="point outside expected desktop"; return false;
|
|
269
|
+
}
|
|
270
|
+
} else if (request.x!=0 || request.y!=0) { reason="non-pointer coordinate must be zero"; return false; }
|
|
271
|
+
|
|
272
|
+
if (request.kind=="scroll") {
|
|
273
|
+
if (request.scrollDelta==0 || request.scrollDelta < -12000 || request.scrollDelta > 12000) { reason="scroll delta invalid"; return false; }
|
|
274
|
+
} else if (request.scrollDelta!=0) { reason="non-scroll delta must be zero"; return false; }
|
|
275
|
+
|
|
276
|
+
if (request.kind=="key_chord") {
|
|
277
|
+
if (request.keys.empty()) { reason="key chord empty"; return false; }
|
|
278
|
+
} else if (!request.keys.empty()) { reason="keys supplied for non-key action"; return false; }
|
|
279
|
+
|
|
280
|
+
if (request.kind=="text_input") {
|
|
281
|
+
if (request.textUtf8.empty()) { reason="text input empty"; return false; }
|
|
282
|
+
} else if (!request.textUtf8.empty()) { reason="text supplied for non-text action"; return false; }
|
|
283
|
+
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
static bool ValidateEnvironment(const Request& request, std::string& currentDesktop, HWND& foreground, DWORD& foregroundPid, std::string& reason) {
|
|
288
|
+
if (!InputDesktopName(currentDesktop)) { reason="input desktop unavailable"; return false; }
|
|
289
|
+
if (currentDesktop != request.expectedDesktop) { reason="input desktop changed"; return false; }
|
|
290
|
+
|
|
291
|
+
const LONG left=GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
292
|
+
const LONG top=GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
293
|
+
const LONG width=GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
294
|
+
const LONG height=GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
295
|
+
if (left!=request.left || top!=request.top || width!=request.width || height!=request.height) {
|
|
296
|
+
reason="virtual desktop bounds changed"; return false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
foreground=GetForegroundWindow();
|
|
300
|
+
foregroundPid=0;
|
|
301
|
+
if (foreground) GetWindowThreadProcessId(foreground,&foregroundPid);
|
|
302
|
+
if (reinterpret_cast<uintptr_t>(foreground)!=request.expectedHwnd || foregroundPid!=request.expectedPid) {
|
|
303
|
+
reason="foreground window changed"; return false;
|
|
304
|
+
}
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
static LONG NormalizeAbsolute(LONG value,LONG origin,LONG extent) {
|
|
309
|
+
if (extent<=1) return 0;
|
|
310
|
+
const long long numerator=static_cast<long long>(value-origin)*65535ll;
|
|
311
|
+
return static_cast<LONG>(numerator/static_cast<long long>(extent-1));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
static INPUT MouseInput(DWORD flags,LONG x,LONG y,DWORD data=0) {
|
|
315
|
+
INPUT input{};
|
|
316
|
+
input.type=INPUT_MOUSE;
|
|
317
|
+
input.mi.dx=x;
|
|
318
|
+
input.mi.dy=y;
|
|
319
|
+
input.mi.mouseData=data;
|
|
320
|
+
input.mi.dwFlags=flags;
|
|
321
|
+
return input;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
static INPUT KeyInput(WORD vk,DWORD flags=0) {
|
|
325
|
+
INPUT input{};
|
|
326
|
+
input.type=INPUT_KEYBOARD;
|
|
327
|
+
input.ki.wVk=vk;
|
|
328
|
+
input.ki.dwFlags=flags;
|
|
329
|
+
return input;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
static INPUT UnicodeInput(wchar_t ch,DWORD flags=0) {
|
|
333
|
+
INPUT input{};
|
|
334
|
+
input.type=INPUT_KEYBOARD;
|
|
335
|
+
input.ki.wScan=static_cast<WORD>(ch);
|
|
336
|
+
input.ki.dwFlags=KEYEVENTF_UNICODE|flags;
|
|
337
|
+
return input;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
static bool SendExact(const std::vector<INPUT>& inputs,size_t& sent,std::string& reason) {
|
|
341
|
+
size_t offset=0u;
|
|
342
|
+
while (offset<inputs.size()) {
|
|
343
|
+
const size_t remaining=inputs.size()-offset;
|
|
344
|
+
const UINT batch=static_cast<UINT>(remaining>512u?512u:remaining);
|
|
345
|
+
const UINT result=SendInput(batch,const_cast<INPUT*>(inputs.data()+offset),sizeof(INPUT));
|
|
346
|
+
if (result!=batch) {
|
|
347
|
+
sent+=result;
|
|
348
|
+
std::ostringstream out; out<<"SendInput partial result "<<result<<"/"<<batch<<" error="<<GetLastError();
|
|
349
|
+
reason=out.str(); return false;
|
|
350
|
+
}
|
|
351
|
+
sent+=result; offset+=batch;
|
|
352
|
+
}
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
static bool BuildInputs(const Request& request,std::vector<INPUT>& inputs,std::string& reason) {
|
|
357
|
+
const bool pointer=request.kind=="pointer_move" || request.kind=="click_primary" || request.kind=="click_secondary" ||
|
|
358
|
+
request.kind=="double_click_primary" || request.kind=="scroll";
|
|
359
|
+
LONG nx=0,ny=0;
|
|
360
|
+
if (pointer) {
|
|
361
|
+
nx=NormalizeAbsolute(request.x,request.left,request.width);
|
|
362
|
+
ny=NormalizeAbsolute(request.y,request.top,request.height);
|
|
363
|
+
inputs.push_back(MouseInput(MOUSEEVENTF_MOVE|MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_VIRTUALDESK,nx,ny));
|
|
364
|
+
}
|
|
365
|
+
if (request.kind=="click_primary") {
|
|
366
|
+
inputs.push_back(MouseInput(MOUSEEVENTF_LEFTDOWN,0,0));
|
|
367
|
+
inputs.push_back(MouseInput(MOUSEEVENTF_LEFTUP,0,0));
|
|
368
|
+
} else if (request.kind=="click_secondary") {
|
|
369
|
+
inputs.push_back(MouseInput(MOUSEEVENTF_RIGHTDOWN,0,0));
|
|
370
|
+
inputs.push_back(MouseInput(MOUSEEVENTF_RIGHTUP,0,0));
|
|
371
|
+
} else if (request.kind=="double_click_primary") {
|
|
372
|
+
for (int i=0;i<2;i++) {
|
|
373
|
+
inputs.push_back(MouseInput(MOUSEEVENTF_LEFTDOWN,0,0));
|
|
374
|
+
inputs.push_back(MouseInput(MOUSEEVENTF_LEFTUP,0,0));
|
|
375
|
+
}
|
|
376
|
+
} else if (request.kind=="scroll") {
|
|
377
|
+
inputs.push_back(MouseInput(MOUSEEVENTF_WHEEL,0,0,static_cast<DWORD>(request.scrollDelta)));
|
|
378
|
+
} else if (request.kind=="key_chord") {
|
|
379
|
+
for (const auto& key:request.keys) inputs.push_back(KeyInput(VirtualKey(key)));
|
|
380
|
+
for (auto it=request.keys.rbegin();it!=request.keys.rend();++it) inputs.push_back(KeyInput(VirtualKey(*it),KEYEVENTF_KEYUP));
|
|
381
|
+
} else if (request.kind=="text_input") {
|
|
382
|
+
if (request.textUtf8.size()>static_cast<size_t>(INT_MAX)) { reason="text too large"; return false; }
|
|
383
|
+
const int needed=MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,request.textUtf8.data(),static_cast<int>(request.textUtf8.size()),nullptr,0);
|
|
384
|
+
if (needed<=0) { reason="text UTF-8 invalid"; return false; }
|
|
385
|
+
std::wstring wide(static_cast<size_t>(needed),L'\0');
|
|
386
|
+
if (MultiByteToWideChar(CP_UTF8,MB_ERR_INVALID_CHARS,request.textUtf8.data(),static_cast<int>(request.textUtf8.size()),wide.data(),needed)!=needed) {
|
|
387
|
+
reason="text UTF-8 conversion failed"; return false;
|
|
388
|
+
}
|
|
389
|
+
if (std::find(wide.begin(),wide.end(),L'\0')!=wide.end()) { reason="text contains NUL"; return false; }
|
|
390
|
+
if (wide.size()>32768u) { reason="text UTF-16 event bound exceeded"; return false; }
|
|
391
|
+
inputs.reserve(wide.size()*2u);
|
|
392
|
+
for (wchar_t ch:wide) {
|
|
393
|
+
inputs.push_back(UnicodeInput(ch));
|
|
394
|
+
inputs.push_back(UnicodeInput(ch,KEYEVENTF_KEYUP));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (inputs.empty() || inputs.size()>65536u) { reason="input event count invalid"; return false; }
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
static int SelfTest() {
|
|
402
|
+
if (VirtualKey("A")!='A' || VirtualKey("F12")!=VK_F12 || VirtualKey("CTRL")!=VK_CONTROL || VirtualKey("NOPE")!=0) return 31;
|
|
403
|
+
if (NormalizeAbsolute(-1920,-1920,3840)!=0) return 32;
|
|
404
|
+
if (NormalizeAbsolute(1919,-1920,3840)!=65535) return 33;
|
|
405
|
+
std::printf("{\"schema\":\"deadbyte.desktop-native-input-selftest.v1\",\"status\":\"passed\"}\n");
|
|
406
|
+
return 0;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
int wmain(int argc,wchar_t** argv) {
|
|
410
|
+
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
|
411
|
+
if (argc==2 && std::wstring(argv[1])==L"--selftest") return SelfTest();
|
|
412
|
+
if (argc!=1) {
|
|
413
|
+
std::fprintf(stderr,"deadbyte-desktop-input: arguments are not accepted\n");
|
|
414
|
+
return 2;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
std::string bytes;
|
|
418
|
+
if (!ReadAllStdin(bytes)) {
|
|
419
|
+
std::fprintf(stderr,"deadbyte-desktop-input: request read failed or exceeded bound\n");
|
|
420
|
+
return 10;
|
|
421
|
+
}
|
|
422
|
+
Request request;
|
|
423
|
+
std::string reason;
|
|
424
|
+
if (!ParseRequest(bytes,request,reason)) {
|
|
425
|
+
std::fprintf(stderr,"deadbyte-desktop-input: request rejected: %s\n",reason.c_str());
|
|
426
|
+
return 11;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
std::string currentDesktop;
|
|
430
|
+
HWND foreground=nullptr;
|
|
431
|
+
DWORD foregroundPid=0;
|
|
432
|
+
if (!ValidateEnvironment(request,currentDesktop,foreground,foregroundPid,reason)) {
|
|
433
|
+
std::fprintf(stderr,"deadbyte-desktop-input: environment rejected: %s\n",reason.c_str());
|
|
434
|
+
return 12;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
std::vector<INPUT> inputs;
|
|
438
|
+
if (!BuildInputs(request,inputs,reason)) {
|
|
439
|
+
std::fprintf(stderr,"deadbyte-desktop-input: input build rejected: %s\n",reason.c_str());
|
|
440
|
+
return 13;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
size_t sent=0u;
|
|
444
|
+
if (!SendExact(inputs,sent,reason)) {
|
|
445
|
+
std::fprintf(stderr,"deadbyte-desktop-input: injection failed after %zu events: %s\n",sent,reason.c_str());
|
|
446
|
+
return 14;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
std::printf("{\"schema\":\"deadbyte.desktop-native-input.v1\",\"injected\":true,\"backend\":\"win32-sendinput\","
|
|
450
|
+
"\"action_sha256\":\"%s\",\"kind\":\"%s\",\"event_count\":%zu,\"input_desktop\":\"%s\","
|
|
451
|
+
"\"foreground_hwnd\":\"%s\",\"foreground_pid\":%lu}\n",
|
|
452
|
+
request.actionSha256.c_str(),request.kind.c_str(),sent,JsonEscape(currentDesktop).c_str(),
|
|
453
|
+
HwndHex(foreground).c_str(),static_cast<unsigned long>(foregroundPid));
|
|
454
|
+
return 0;
|
|
455
|
+
}
|