@midscene/computer 1.12.4 → 1.12.5-beta-20260907080441.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/bin/darwin/rdp-helper +0 -0
- package/bin/linux/rdp-helper +0 -0
- package/dist/es/cli.mjs +12 -7
- package/dist/es/index.mjs +12 -7
- package/dist/lib/cli.js +12 -7
- package/dist/lib/index.js +12 -7
- package/native/rdp/CMakeLists.txt +21 -0
- package/native/rdp/include/rdp_helper_session.hpp +5 -3
- package/native/rdp/src/session.cpp +80 -52
- package/native/rdp/tests/screenshot_test.cpp +225 -0
- package/package.json +3 -3
package/bin/darwin/rdp-helper
CHANGED
|
Binary file
|
package/bin/linux/rdp-helper
CHANGED
|
Binary file
|
package/dist/es/cli.mjs
CHANGED
|
@@ -523,8 +523,10 @@ async function captureScreenshot(options) {
|
|
|
523
523
|
}
|
|
524
524
|
const SMOOTH_MOVE_STEPS_TAP = 8;
|
|
525
525
|
const SMOOTH_MOVE_STEPS_MOUSE_MOVE = 10;
|
|
526
|
+
const SMOOTH_MOVE_STEPS_DRAG = 20;
|
|
526
527
|
const SMOOTH_MOVE_DELAY_TAP = 8;
|
|
527
528
|
const SMOOTH_MOVE_DELAY_MOUSE_MOVE = 10;
|
|
529
|
+
const SMOOTH_MOVE_DELAY_DRAG = 10;
|
|
528
530
|
const MOUSE_MOVE_EFFECT_WAIT = 300;
|
|
529
531
|
const CLICK_SETTLE_DELAY = 50;
|
|
530
532
|
const CLICK_HOLD_DURATION = 100;
|
|
@@ -1151,7 +1153,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
1151
1153
|
}
|
|
1152
1154
|
async healthCheck(displays) {
|
|
1153
1155
|
console.log('[HealthCheck] Starting health check...');
|
|
1154
|
-
console.log("[HealthCheck] @midscene/computer v1.12.
|
|
1156
|
+
console.log("[HealthCheck] @midscene/computer v1.12.5-beta-20260907080441.0");
|
|
1155
1157
|
console.log('[HealthCheck] Taking screenshot...');
|
|
1156
1158
|
const screenshotTimeout = 15000;
|
|
1157
1159
|
let timeoutId;
|
|
@@ -1628,7 +1630,10 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1628
1630
|
await this.moveDisplayPointer(from, 'Mouse did not reach the drag start target');
|
|
1629
1631
|
await this.inputDriver.withMouseButton('left', async ()=>{
|
|
1630
1632
|
await this.inputDriver.delay(100);
|
|
1631
|
-
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target'
|
|
1633
|
+
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target', {
|
|
1634
|
+
smoothSteps: SMOOTH_MOVE_STEPS_DRAG,
|
|
1635
|
+
smoothDelay: SMOOTH_MOVE_DELAY_DRAG
|
|
1636
|
+
});
|
|
1632
1637
|
await this.inputDriver.delay(100);
|
|
1633
1638
|
});
|
|
1634
1639
|
}
|
|
@@ -2067,10 +2072,10 @@ function rdp_device_define_property(obj, key, value) {
|
|
|
2067
2072
|
const device_debug = getDebug('rdp:device');
|
|
2068
2073
|
const device_SMOOTH_MOVE_STEPS_TAP = 8;
|
|
2069
2074
|
const device_SMOOTH_MOVE_STEPS_MOUSE_MOVE = 10;
|
|
2070
|
-
const
|
|
2075
|
+
const device_SMOOTH_MOVE_STEPS_DRAG = 12;
|
|
2071
2076
|
const device_SMOOTH_MOVE_DELAY_TAP = 8;
|
|
2072
2077
|
const device_SMOOTH_MOVE_DELAY_MOUSE_MOVE = 10;
|
|
2073
|
-
const
|
|
2078
|
+
const device_SMOOTH_MOVE_DELAY_DRAG = 10;
|
|
2074
2079
|
const device_MOUSE_MOVE_EFFECT_WAIT = 300;
|
|
2075
2080
|
const device_CLICK_HOLD_DURATION = 50;
|
|
2076
2081
|
const DRAG_HOLD_DURATION = 100;
|
|
@@ -2273,8 +2278,8 @@ class RDPDevice {
|
|
|
2273
2278
|
await this.backend.mouseButton('left', 'down');
|
|
2274
2279
|
await sleep(DRAG_HOLD_DURATION);
|
|
2275
2280
|
await this.movePointer(Math.round(to.x), Math.round(to.y), {
|
|
2276
|
-
steps:
|
|
2277
|
-
stepDelayMs:
|
|
2281
|
+
steps: device_SMOOTH_MOVE_STEPS_DRAG,
|
|
2282
|
+
stepDelayMs: device_SMOOTH_MOVE_DELAY_DRAG
|
|
2278
2283
|
});
|
|
2279
2284
|
await sleep(DRAG_HOLD_DURATION);
|
|
2280
2285
|
await this.backend.mouseButton('left', 'up');
|
|
@@ -2601,7 +2606,7 @@ const tools = new ComputerMidsceneTools({
|
|
|
2601
2606
|
});
|
|
2602
2607
|
runToolsCLI(tools, 'midscene-computer', {
|
|
2603
2608
|
stripPrefix: 'computer_',
|
|
2604
|
-
version: "1.12.
|
|
2609
|
+
version: "1.12.5-beta-20260907080441.0",
|
|
2605
2610
|
extraCommands: createReportCliCommands()
|
|
2606
2611
|
}).catch((e)=>{
|
|
2607
2612
|
process.exit(reportCLIError(e));
|
package/dist/es/index.mjs
CHANGED
|
@@ -523,8 +523,10 @@ async function captureScreenshot(options) {
|
|
|
523
523
|
}
|
|
524
524
|
const SMOOTH_MOVE_STEPS_TAP = 8;
|
|
525
525
|
const SMOOTH_MOVE_STEPS_MOUSE_MOVE = 10;
|
|
526
|
+
const SMOOTH_MOVE_STEPS_DRAG = 20;
|
|
526
527
|
const SMOOTH_MOVE_DELAY_TAP = 8;
|
|
527
528
|
const SMOOTH_MOVE_DELAY_MOUSE_MOVE = 10;
|
|
529
|
+
const SMOOTH_MOVE_DELAY_DRAG = 10;
|
|
528
530
|
const MOUSE_MOVE_EFFECT_WAIT = 300;
|
|
529
531
|
const CLICK_SETTLE_DELAY = 50;
|
|
530
532
|
const CLICK_HOLD_DURATION = 100;
|
|
@@ -1151,7 +1153,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
1151
1153
|
}
|
|
1152
1154
|
async healthCheck(displays) {
|
|
1153
1155
|
console.log('[HealthCheck] Starting health check...');
|
|
1154
|
-
console.log("[HealthCheck] @midscene/computer v1.12.
|
|
1156
|
+
console.log("[HealthCheck] @midscene/computer v1.12.5-beta-20260907080441.0");
|
|
1155
1157
|
console.log('[HealthCheck] Taking screenshot...');
|
|
1156
1158
|
const screenshotTimeout = 15000;
|
|
1157
1159
|
let timeoutId;
|
|
@@ -1628,7 +1630,10 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1628
1630
|
await this.moveDisplayPointer(from, 'Mouse did not reach the drag start target');
|
|
1629
1631
|
await this.inputDriver.withMouseButton('left', async ()=>{
|
|
1630
1632
|
await this.inputDriver.delay(100);
|
|
1631
|
-
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target'
|
|
1633
|
+
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target', {
|
|
1634
|
+
smoothSteps: SMOOTH_MOVE_STEPS_DRAG,
|
|
1635
|
+
smoothDelay: SMOOTH_MOVE_DELAY_DRAG
|
|
1636
|
+
});
|
|
1632
1637
|
await this.inputDriver.delay(100);
|
|
1633
1638
|
});
|
|
1634
1639
|
}
|
|
@@ -2110,10 +2115,10 @@ function rdp_device_define_property(obj, key, value) {
|
|
|
2110
2115
|
const device_debug = getDebug('rdp:device');
|
|
2111
2116
|
const device_SMOOTH_MOVE_STEPS_TAP = 8;
|
|
2112
2117
|
const device_SMOOTH_MOVE_STEPS_MOUSE_MOVE = 10;
|
|
2113
|
-
const
|
|
2118
|
+
const device_SMOOTH_MOVE_STEPS_DRAG = 12;
|
|
2114
2119
|
const device_SMOOTH_MOVE_DELAY_TAP = 8;
|
|
2115
2120
|
const device_SMOOTH_MOVE_DELAY_MOUSE_MOVE = 10;
|
|
2116
|
-
const
|
|
2121
|
+
const device_SMOOTH_MOVE_DELAY_DRAG = 10;
|
|
2117
2122
|
const device_MOUSE_MOVE_EFFECT_WAIT = 300;
|
|
2118
2123
|
const device_CLICK_HOLD_DURATION = 50;
|
|
2119
2124
|
const DRAG_HOLD_DURATION = 100;
|
|
@@ -2316,8 +2321,8 @@ class RDPDevice {
|
|
|
2316
2321
|
await this.backend.mouseButton('left', 'down');
|
|
2317
2322
|
await sleep(DRAG_HOLD_DURATION);
|
|
2318
2323
|
await this.movePointer(Math.round(to.x), Math.round(to.y), {
|
|
2319
|
-
steps:
|
|
2320
|
-
stepDelayMs:
|
|
2324
|
+
steps: device_SMOOTH_MOVE_STEPS_DRAG,
|
|
2325
|
+
stepDelayMs: device_SMOOTH_MOVE_DELAY_DRAG
|
|
2321
2326
|
});
|
|
2322
2327
|
await sleep(DRAG_HOLD_DURATION);
|
|
2323
2328
|
await this.backend.mouseButton('left', 'up');
|
|
@@ -2640,7 +2645,7 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
|
|
|
2640
2645
|
}
|
|
2641
2646
|
}
|
|
2642
2647
|
function version() {
|
|
2643
|
-
const currentVersion = "1.12.
|
|
2648
|
+
const currentVersion = "1.12.5-beta-20260907080441.0";
|
|
2644
2649
|
console.log(`@midscene/computer v${currentVersion}`);
|
|
2645
2650
|
return currentVersion;
|
|
2646
2651
|
}
|
package/dist/lib/cli.js
CHANGED
|
@@ -549,8 +549,10 @@ async function captureScreenshot(options) {
|
|
|
549
549
|
}
|
|
550
550
|
const SMOOTH_MOVE_STEPS_TAP = 8;
|
|
551
551
|
const SMOOTH_MOVE_STEPS_MOUSE_MOVE = 10;
|
|
552
|
+
const SMOOTH_MOVE_STEPS_DRAG = 20;
|
|
552
553
|
const SMOOTH_MOVE_DELAY_TAP = 8;
|
|
553
554
|
const SMOOTH_MOVE_DELAY_MOUSE_MOVE = 10;
|
|
555
|
+
const SMOOTH_MOVE_DELAY_DRAG = 10;
|
|
554
556
|
const MOUSE_MOVE_EFFECT_WAIT = 300;
|
|
555
557
|
const CLICK_SETTLE_DELAY = 50;
|
|
556
558
|
const CLICK_HOLD_DURATION = 100;
|
|
@@ -1177,7 +1179,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
1177
1179
|
}
|
|
1178
1180
|
async healthCheck(displays) {
|
|
1179
1181
|
console.log('[HealthCheck] Starting health check...');
|
|
1180
|
-
console.log("[HealthCheck] @midscene/computer v1.12.
|
|
1182
|
+
console.log("[HealthCheck] @midscene/computer v1.12.5-beta-20260907080441.0");
|
|
1181
1183
|
console.log('[HealthCheck] Taking screenshot...');
|
|
1182
1184
|
const screenshotTimeout = 15000;
|
|
1183
1185
|
let timeoutId;
|
|
@@ -1654,7 +1656,10 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1654
1656
|
await this.moveDisplayPointer(from, 'Mouse did not reach the drag start target');
|
|
1655
1657
|
await this.inputDriver.withMouseButton('left', async ()=>{
|
|
1656
1658
|
await this.inputDriver.delay(100);
|
|
1657
|
-
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target'
|
|
1659
|
+
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target', {
|
|
1660
|
+
smoothSteps: SMOOTH_MOVE_STEPS_DRAG,
|
|
1661
|
+
smoothDelay: SMOOTH_MOVE_DELAY_DRAG
|
|
1662
|
+
});
|
|
1658
1663
|
await this.inputDriver.delay(100);
|
|
1659
1664
|
});
|
|
1660
1665
|
}
|
|
@@ -2094,10 +2099,10 @@ function rdp_device_define_property(obj, key, value) {
|
|
|
2094
2099
|
const device_debug = (0, logger_namespaceObject.getDebug)('rdp:device');
|
|
2095
2100
|
const device_SMOOTH_MOVE_STEPS_TAP = 8;
|
|
2096
2101
|
const device_SMOOTH_MOVE_STEPS_MOUSE_MOVE = 10;
|
|
2097
|
-
const
|
|
2102
|
+
const device_SMOOTH_MOVE_STEPS_DRAG = 12;
|
|
2098
2103
|
const device_SMOOTH_MOVE_DELAY_TAP = 8;
|
|
2099
2104
|
const device_SMOOTH_MOVE_DELAY_MOUSE_MOVE = 10;
|
|
2100
|
-
const
|
|
2105
|
+
const device_SMOOTH_MOVE_DELAY_DRAG = 10;
|
|
2101
2106
|
const device_MOUSE_MOVE_EFFECT_WAIT = 300;
|
|
2102
2107
|
const device_CLICK_HOLD_DURATION = 50;
|
|
2103
2108
|
const DRAG_HOLD_DURATION = 100;
|
|
@@ -2300,8 +2305,8 @@ class RDPDevice {
|
|
|
2300
2305
|
await this.backend.mouseButton('left', 'down');
|
|
2301
2306
|
await (0, utils_namespaceObject.sleep)(DRAG_HOLD_DURATION);
|
|
2302
2307
|
await this.movePointer(Math.round(to.x), Math.round(to.y), {
|
|
2303
|
-
steps:
|
|
2304
|
-
stepDelayMs:
|
|
2308
|
+
steps: device_SMOOTH_MOVE_STEPS_DRAG,
|
|
2309
|
+
stepDelayMs: device_SMOOTH_MOVE_DELAY_DRAG
|
|
2305
2310
|
});
|
|
2306
2311
|
await (0, utils_namespaceObject.sleep)(DRAG_HOLD_DURATION);
|
|
2307
2312
|
await this.backend.mouseButton('left', 'up');
|
|
@@ -2628,7 +2633,7 @@ const tools = new ComputerMidsceneTools({
|
|
|
2628
2633
|
});
|
|
2629
2634
|
(0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-computer', {
|
|
2630
2635
|
stripPrefix: 'computer_',
|
|
2631
|
-
version: "1.12.
|
|
2636
|
+
version: "1.12.5-beta-20260907080441.0",
|
|
2632
2637
|
extraCommands: (0, core_namespaceObject.createReportCliCommands)()
|
|
2633
2638
|
}).catch((e)=>{
|
|
2634
2639
|
process.exit((0, cli_namespaceObject.reportCLIError)(e));
|
package/dist/lib/index.js
CHANGED
|
@@ -575,8 +575,10 @@ async function captureScreenshot(options) {
|
|
|
575
575
|
}
|
|
576
576
|
const SMOOTH_MOVE_STEPS_TAP = 8;
|
|
577
577
|
const SMOOTH_MOVE_STEPS_MOUSE_MOVE = 10;
|
|
578
|
+
const SMOOTH_MOVE_STEPS_DRAG = 20;
|
|
578
579
|
const SMOOTH_MOVE_DELAY_TAP = 8;
|
|
579
580
|
const SMOOTH_MOVE_DELAY_MOUSE_MOVE = 10;
|
|
581
|
+
const SMOOTH_MOVE_DELAY_DRAG = 10;
|
|
580
582
|
const MOUSE_MOVE_EFFECT_WAIT = 300;
|
|
581
583
|
const CLICK_SETTLE_DELAY = 50;
|
|
582
584
|
const CLICK_HOLD_DURATION = 100;
|
|
@@ -1203,7 +1205,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
1203
1205
|
}
|
|
1204
1206
|
async healthCheck(displays) {
|
|
1205
1207
|
console.log('[HealthCheck] Starting health check...');
|
|
1206
|
-
console.log("[HealthCheck] @midscene/computer v1.12.
|
|
1208
|
+
console.log("[HealthCheck] @midscene/computer v1.12.5-beta-20260907080441.0");
|
|
1207
1209
|
console.log('[HealthCheck] Taking screenshot...');
|
|
1208
1210
|
const screenshotTimeout = 15000;
|
|
1209
1211
|
let timeoutId;
|
|
@@ -1680,7 +1682,10 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1680
1682
|
await this.moveDisplayPointer(from, 'Mouse did not reach the drag start target');
|
|
1681
1683
|
await this.inputDriver.withMouseButton('left', async ()=>{
|
|
1682
1684
|
await this.inputDriver.delay(100);
|
|
1683
|
-
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target'
|
|
1685
|
+
await this.moveDisplayPointer(to, 'Mouse did not reach the drag end target', {
|
|
1686
|
+
smoothSteps: SMOOTH_MOVE_STEPS_DRAG,
|
|
1687
|
+
smoothDelay: SMOOTH_MOVE_DELAY_DRAG
|
|
1688
|
+
});
|
|
1684
1689
|
await this.inputDriver.delay(100);
|
|
1685
1690
|
});
|
|
1686
1691
|
}
|
|
@@ -2164,10 +2169,10 @@ function rdp_device_define_property(obj, key, value) {
|
|
|
2164
2169
|
const device_debug = (0, logger_namespaceObject.getDebug)('rdp:device');
|
|
2165
2170
|
const device_SMOOTH_MOVE_STEPS_TAP = 8;
|
|
2166
2171
|
const device_SMOOTH_MOVE_STEPS_MOUSE_MOVE = 10;
|
|
2167
|
-
const
|
|
2172
|
+
const device_SMOOTH_MOVE_STEPS_DRAG = 12;
|
|
2168
2173
|
const device_SMOOTH_MOVE_DELAY_TAP = 8;
|
|
2169
2174
|
const device_SMOOTH_MOVE_DELAY_MOUSE_MOVE = 10;
|
|
2170
|
-
const
|
|
2175
|
+
const device_SMOOTH_MOVE_DELAY_DRAG = 10;
|
|
2171
2176
|
const device_MOUSE_MOVE_EFFECT_WAIT = 300;
|
|
2172
2177
|
const device_CLICK_HOLD_DURATION = 50;
|
|
2173
2178
|
const DRAG_HOLD_DURATION = 100;
|
|
@@ -2370,8 +2375,8 @@ class RDPDevice {
|
|
|
2370
2375
|
await this.backend.mouseButton('left', 'down');
|
|
2371
2376
|
await (0, utils_namespaceObject.sleep)(DRAG_HOLD_DURATION);
|
|
2372
2377
|
await this.movePointer(Math.round(to.x), Math.round(to.y), {
|
|
2373
|
-
steps:
|
|
2374
|
-
stepDelayMs:
|
|
2378
|
+
steps: device_SMOOTH_MOVE_STEPS_DRAG,
|
|
2379
|
+
stepDelayMs: device_SMOOTH_MOVE_DELAY_DRAG
|
|
2375
2380
|
});
|
|
2376
2381
|
await (0, utils_namespaceObject.sleep)(DRAG_HOLD_DURATION);
|
|
2377
2382
|
await this.backend.mouseButton('left', 'up');
|
|
@@ -2698,7 +2703,7 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
|
|
|
2698
2703
|
}
|
|
2699
2704
|
const env_namespaceObject = require("@midscene/shared/env");
|
|
2700
2705
|
function version() {
|
|
2701
|
-
const currentVersion = "1.12.
|
|
2706
|
+
const currentVersion = "1.12.5-beta-20260907080441.0";
|
|
2702
2707
|
console.log(`@midscene/computer v${currentVersion}`);
|
|
2703
2708
|
return currentVersion;
|
|
2704
2709
|
}
|
|
@@ -92,6 +92,27 @@ if(BUILD_TESTING)
|
|
|
92
92
|
NAME rdp-session-policy-test
|
|
93
93
|
COMMAND rdp-session-policy-test
|
|
94
94
|
)
|
|
95
|
+
|
|
96
|
+
add_executable(
|
|
97
|
+
rdp-screenshot-test
|
|
98
|
+
tests/screenshot_test.cpp
|
|
99
|
+
src/session.cpp
|
|
100
|
+
)
|
|
101
|
+
target_include_directories(
|
|
102
|
+
rdp-screenshot-test PRIVATE
|
|
103
|
+
${CMAKE_CURRENT_SOURCE_DIR}/include
|
|
104
|
+
${FREERDP_INCLUDE_DIRS}
|
|
105
|
+
)
|
|
106
|
+
target_link_libraries(
|
|
107
|
+
rdp-screenshot-test PRIVATE
|
|
108
|
+
${FREERDP_LIBRARIES}
|
|
109
|
+
Threads::Threads
|
|
110
|
+
)
|
|
111
|
+
if(APPLE)
|
|
112
|
+
target_compile_definitions(rdp-screenshot-test PRIVATE _DARWIN_C_SOURCE)
|
|
113
|
+
endif()
|
|
114
|
+
add_test(NAME rdp-screenshot-test COMMAND rdp-screenshot-test)
|
|
115
|
+
set_tests_properties(rdp-screenshot-test PROPERTIES TIMEOUT 30)
|
|
95
116
|
endif()
|
|
96
117
|
|
|
97
118
|
if(APPLE)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <atomic>
|
|
4
|
+
#include <chrono>
|
|
4
5
|
#include <condition_variable>
|
|
5
6
|
#include <cstdint>
|
|
6
7
|
#include <mutex>
|
|
@@ -76,7 +77,7 @@ class FreeRdpSessionTransport final : public SessionTransport {
|
|
|
76
77
|
void HookEndPaint(rdpUpdate* update);
|
|
77
78
|
// Records that at least one paint has reached the local framebuffer and
|
|
78
79
|
// wakes Connect().
|
|
79
|
-
void MarkFramePainted(
|
|
80
|
+
void MarkFramePainted();
|
|
80
81
|
// Records a framebuffer update even when it is too blank or uniform to count
|
|
81
82
|
// as the first informative frame.
|
|
82
83
|
void MarkFramebufferUpdated();
|
|
@@ -90,6 +91,7 @@ class FreeRdpSessionTransport final : public SessionTransport {
|
|
|
90
91
|
|
|
91
92
|
private:
|
|
92
93
|
friend struct MidsceneRdpContext;
|
|
94
|
+
friend struct RdpScreenshotTestPeer;
|
|
93
95
|
|
|
94
96
|
freerdp* instance_ = nullptr;
|
|
95
97
|
std::thread event_thread_;
|
|
@@ -110,8 +112,8 @@ class FreeRdpSessionTransport final : public SessionTransport {
|
|
|
110
112
|
std::atomic<bool> session_active_{false};
|
|
111
113
|
std::condition_variable frame_cv_;
|
|
112
114
|
std::mutex frame_mutex_;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
// Protected by frame_mutex_; use a monotonic clock for screenshot settling.
|
|
116
|
+
std::chrono::steady_clock::time_point last_frame_update_{};
|
|
115
117
|
pEndPaint original_end_paint_ = nullptr;
|
|
116
118
|
uint16_t mouse_x_ = 0;
|
|
117
119
|
uint16_t mouse_y_ = 0;
|
|
@@ -68,6 +68,8 @@ using FdSetSocket = int;
|
|
|
68
68
|
// never paints within this window is treated as blank/locked and fails fast
|
|
69
69
|
// instead of feeding an all-black screenshot to the caller.
|
|
70
70
|
constexpr int kFirstFrameTimeoutMs = 20'000;
|
|
71
|
+
constexpr auto kScreenshotQuietPeriod = std::chrono::milliseconds(300);
|
|
72
|
+
constexpr auto kScreenshotTimeout = std::chrono::seconds(3);
|
|
71
73
|
// The first frame should prove that real desktop pixels reached the primary
|
|
72
74
|
// buffer without requiring a complex wallpaper or fully loaded app content.
|
|
73
75
|
constexpr size_t kMinInformativeColorCount = 128;
|
|
@@ -436,11 +438,11 @@ bool HasPendingFramebufferInvalidation(rdpContext* context) {
|
|
|
436
438
|
return hwnd->ninvalid > 0 && hwnd->invalid && !hwnd->invalid->null;
|
|
437
439
|
}
|
|
438
440
|
|
|
439
|
-
|
|
441
|
+
bool HasInformativeFramebuffer(rdpContext* context) {
|
|
440
442
|
if (!context || !context->gdi || !context->gdi->primary_buffer ||
|
|
441
443
|
context->gdi->width <= 0 || context->gdi->height <= 0 ||
|
|
442
444
|
context->gdi->stride == 0) {
|
|
443
|
-
return
|
|
445
|
+
return false;
|
|
444
446
|
}
|
|
445
447
|
|
|
446
448
|
rdpGdi* gdi = context->gdi;
|
|
@@ -448,7 +450,7 @@ std::optional<RawFrame> CaptureInformativeFramebuffer(rdpContext* context) {
|
|
|
448
450
|
const auto height = static_cast<size_t>(gdi->height);
|
|
449
451
|
const auto stride = static_cast<size_t>(gdi->stride);
|
|
450
452
|
if (stride < width * 4) {
|
|
451
|
-
return
|
|
453
|
+
return false;
|
|
452
454
|
}
|
|
453
455
|
|
|
454
456
|
const BYTE* buffer = gdi->primary_buffer;
|
|
@@ -489,16 +491,37 @@ std::optional<RawFrame> CaptureInformativeFramebuffer(rdpContext* context) {
|
|
|
489
491
|
|
|
490
492
|
if (colors.size() < kMinInformativeColorCount ||
|
|
491
493
|
non_black_pixels < min_non_black_pixels) {
|
|
492
|
-
return
|
|
494
|
+
return false;
|
|
493
495
|
}
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
494
498
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
499
|
+
// Called under the transport mutex so paints and resizes cannot race the copy.
|
|
500
|
+
// A black image is a retryable transition, not a successful screenshot.
|
|
501
|
+
std::optional<RawFrame> CaptureNonBlackFramebuffer(const rdpGdi& gdi) {
|
|
502
|
+
if (!gdi.primary_buffer || gdi.width <= 0 || gdi.height <= 0 ||
|
|
503
|
+
gdi.stride < static_cast<size_t>(gdi.width) * 4) {
|
|
504
|
+
throw std::runtime_error("Remote framebuffer is empty or invalid");
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Ignore alpha and row padding. Uniform non-black screens are valid; the
|
|
508
|
+
// first-paint color-diversity heuristic does not apply to later screenshots.
|
|
509
|
+
for (int y = 0; y < gdi.height; ++y) {
|
|
510
|
+
const BYTE* row = gdi.primary_buffer + static_cast<size_t>(y) * gdi.stride;
|
|
511
|
+
for (int x = 0; x < gdi.width; ++x) {
|
|
512
|
+
const BYTE* pixel = row + static_cast<size_t>(x) * 4;
|
|
513
|
+
if (pixel[0] || pixel[1] || pixel[2]) {
|
|
514
|
+
RawFrame frame;
|
|
515
|
+
frame.size.width = gdi.width;
|
|
516
|
+
frame.size.height = gdi.height;
|
|
517
|
+
frame.stride = static_cast<size_t>(gdi.stride);
|
|
518
|
+
const size_t buffer_size = frame.stride * static_cast<size_t>(gdi.height);
|
|
519
|
+
frame.bgra.assign(gdi.primary_buffer, gdi.primary_buffer + buffer_size);
|
|
520
|
+
return frame;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return std::nullopt;
|
|
502
525
|
}
|
|
503
526
|
|
|
504
527
|
// EndPaint hook chained onto FreeRDP's update pipeline. FreeRDP invokes this
|
|
@@ -516,9 +539,8 @@ BOOL MidsceneEndPaint(rdpContext* context) {
|
|
|
516
539
|
typed_context->owner->MarkFramebufferUpdated();
|
|
517
540
|
if (already_painted) {
|
|
518
541
|
typed_context->owner->MarkFramePainted();
|
|
519
|
-
} else if (
|
|
520
|
-
|
|
521
|
-
typed_context->owner->MarkFramePainted(std::move(first_frame));
|
|
542
|
+
} else if (HasInformativeFramebuffer(context)) {
|
|
543
|
+
typed_context->owner->MarkFramePainted();
|
|
522
544
|
}
|
|
523
545
|
}
|
|
524
546
|
}
|
|
@@ -983,8 +1005,7 @@ ConnectionInfo FreeRdpSessionTransport::Connect(const ConnectionConfig& config)
|
|
|
983
1005
|
framebuffer_updates_.store(0, std::memory_order_relaxed);
|
|
984
1006
|
{
|
|
985
1007
|
std::lock_guard<std::mutex> frame_lock(frame_mutex_);
|
|
986
|
-
|
|
987
|
-
first_frame_consumed_ = false;
|
|
1008
|
+
last_frame_update_ = {};
|
|
988
1009
|
}
|
|
989
1010
|
original_end_paint_ = nullptr;
|
|
990
1011
|
ClearSessionErrorLocked();
|
|
@@ -1083,36 +1104,46 @@ void FreeRdpSessionTransport::Disconnect() {
|
|
|
1083
1104
|
}
|
|
1084
1105
|
|
|
1085
1106
|
RawFrame FreeRdpSessionTransport::CaptureFrame() {
|
|
1086
|
-
std::
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1107
|
+
const auto started = std::chrono::steady_clock::now();
|
|
1108
|
+
const auto deadline = started + kScreenshotTimeout;
|
|
1109
|
+
for (;;) {
|
|
1110
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
1111
|
+
if (!connected_ || !instance_ || !instance_->context ||
|
|
1112
|
+
!instance_->context->gdi ||
|
|
1113
|
+
!session_active_.load(std::memory_order_relaxed)) {
|
|
1114
|
+
throw std::runtime_error(
|
|
1115
|
+
last_error_ ? "RDP screenshot failed: " + last_error_->message
|
|
1116
|
+
: "No remote framebuffer is available");
|
|
1117
|
+
}
|
|
1090
1118
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1119
|
+
if (frames_painted_.load(std::memory_order_relaxed) == 0) {
|
|
1120
|
+
throw std::runtime_error(
|
|
1121
|
+
"Remote framebuffer has not received its first paint yet");
|
|
1122
|
+
}
|
|
1095
1123
|
|
|
1096
|
-
|
|
1097
|
-
std::
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1124
|
+
std::unique_lock<std::mutex> frame_lock(frame_mutex_);
|
|
1125
|
+
const auto now = std::chrono::steady_clock::now();
|
|
1126
|
+
const auto quiet_until =
|
|
1127
|
+
std::max(started, last_frame_update_) + kScreenshotQuietPeriod;
|
|
1128
|
+
auto wake_at = std::min(quiet_until, deadline);
|
|
1129
|
+
if (now >= wake_at) {
|
|
1130
|
+
// Copy the live buffer even on the first screenshot; a cached first paint
|
|
1131
|
+
// can be partial or stale by the time the caller requests a screenshot.
|
|
1132
|
+
if (auto frame = CaptureNonBlackFramebuffer(*instance_->context->gdi)) {
|
|
1133
|
+
return std::move(*frame);
|
|
1134
|
+
}
|
|
1135
|
+
if (now >= deadline) {
|
|
1136
|
+
throw std::runtime_error(
|
|
1137
|
+
"RDP screenshot was still black after waiting for framebuffer updates");
|
|
1138
|
+
}
|
|
1139
|
+
wake_at = deadline;
|
|
1101
1140
|
}
|
|
1102
|
-
}
|
|
1103
1141
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1142
|
+
// Let the event loop process paints while waiting. Every screenshot gets a
|
|
1143
|
+
// short settling window, but animations cannot extend the total deadline.
|
|
1144
|
+
lock.unlock();
|
|
1145
|
+
frame_cv_.wait_until(frame_lock, wake_at);
|
|
1107
1146
|
}
|
|
1108
|
-
|
|
1109
|
-
RawFrame frame;
|
|
1110
|
-
frame.size.width = gdi->width;
|
|
1111
|
-
frame.size.height = gdi->height;
|
|
1112
|
-
frame.stride = static_cast<size_t>(gdi->stride);
|
|
1113
|
-
const size_t buffer_size = frame.stride * static_cast<size_t>(gdi->height);
|
|
1114
|
-
frame.bgra.assign(gdi->primary_buffer, gdi->primary_buffer + buffer_size);
|
|
1115
|
-
return frame;
|
|
1116
1147
|
}
|
|
1117
1148
|
|
|
1118
1149
|
Size FreeRdpSessionTransport::GetSize() {
|
|
@@ -1360,8 +1391,7 @@ void FreeRdpSessionTransport::ResetStateLocked() {
|
|
|
1360
1391
|
framebuffer_updates_.store(0, std::memory_order_relaxed);
|
|
1361
1392
|
{
|
|
1362
1393
|
std::lock_guard<std::mutex> frame_lock(frame_mutex_);
|
|
1363
|
-
|
|
1364
|
-
first_frame_consumed_ = false;
|
|
1394
|
+
last_frame_update_ = {};
|
|
1365
1395
|
}
|
|
1366
1396
|
original_end_paint_ = nullptr;
|
|
1367
1397
|
mouse_x_ = 0;
|
|
@@ -1388,23 +1418,21 @@ BOOL FreeRdpSessionTransport::CallOriginalEndPaint(rdpContext* context) {
|
|
|
1388
1418
|
return TRUE;
|
|
1389
1419
|
}
|
|
1390
1420
|
|
|
1391
|
-
void FreeRdpSessionTransport::MarkFramePainted(
|
|
1392
|
-
std::optional<RawFrame> first_frame) {
|
|
1421
|
+
void FreeRdpSessionTransport::MarkFramePainted() {
|
|
1393
1422
|
{
|
|
1394
1423
|
std::lock_guard<std::mutex> lock(frame_mutex_);
|
|
1395
|
-
if (first_frame.has_value() &&
|
|
1396
|
-
frames_painted_.load(std::memory_order_relaxed) == 0 &&
|
|
1397
|
-
!first_frame_.has_value()) {
|
|
1398
|
-
first_frame_ = std::move(*first_frame);
|
|
1399
|
-
first_frame_consumed_ = false;
|
|
1400
|
-
}
|
|
1401
1424
|
frames_painted_.fetch_add(1, std::memory_order_relaxed);
|
|
1402
1425
|
}
|
|
1403
1426
|
frame_cv_.notify_all();
|
|
1404
1427
|
}
|
|
1405
1428
|
|
|
1406
1429
|
void FreeRdpSessionTransport::MarkFramebufferUpdated() {
|
|
1407
|
-
|
|
1430
|
+
{
|
|
1431
|
+
std::lock_guard<std::mutex> lock(frame_mutex_);
|
|
1432
|
+
framebuffer_updates_.fetch_add(1, std::memory_order_relaxed);
|
|
1433
|
+
last_frame_update_ = std::chrono::steady_clock::now();
|
|
1434
|
+
}
|
|
1435
|
+
frame_cv_.notify_all();
|
|
1408
1436
|
}
|
|
1409
1437
|
|
|
1410
1438
|
bool FreeRdpSessionTransport::HasFramePainted() const {
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#include <algorithm>
|
|
2
|
+
#include <chrono>
|
|
3
|
+
#include <future>
|
|
4
|
+
#include <iostream>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <thread>
|
|
8
|
+
|
|
9
|
+
#include <freerdp/gdi/gdi.h>
|
|
10
|
+
|
|
11
|
+
#include "rdp_helper_session.hpp"
|
|
12
|
+
|
|
13
|
+
namespace midscene::rdp {
|
|
14
|
+
|
|
15
|
+
// Supply a framebuffer without a Windows server, but exercise the real
|
|
16
|
+
// CaptureFrame(), paint notifications, locks, and disconnect wakeups.
|
|
17
|
+
struct RdpScreenshotTestPeer {
|
|
18
|
+
FreeRdpSessionTransport transport;
|
|
19
|
+
freerdp instance{};
|
|
20
|
+
rdpContext context{};
|
|
21
|
+
rdpGdi gdi{};
|
|
22
|
+
std::vector<uint8_t> pixels;
|
|
23
|
+
|
|
24
|
+
RdpScreenshotTestPeer() : pixels(36 * 8, 255) {
|
|
25
|
+
gdi.width = 8;
|
|
26
|
+
gdi.height = 8;
|
|
27
|
+
gdi.stride = 36; // Non-black padding must not affect black detection.
|
|
28
|
+
gdi.primary_buffer = pixels.data();
|
|
29
|
+
context.gdi = &gdi;
|
|
30
|
+
instance.context = &context;
|
|
31
|
+
transport.instance_ = &instance;
|
|
32
|
+
transport.connected_ = true;
|
|
33
|
+
transport.running_ = true;
|
|
34
|
+
transport.session_active_.store(true);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
~RdpScreenshotTestPeer() {
|
|
38
|
+
// These FreeRDP structs belong to the fixture, not freerdp_new().
|
|
39
|
+
transport.instance_ = nullptr;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
void Paint(uint8_t color, int rows = 8) {
|
|
43
|
+
std::lock_guard<std::mutex> lock(transport.mutex_);
|
|
44
|
+
for (int y = 0; y < 8; ++y) {
|
|
45
|
+
for (int x = 0; x < 8; ++x) {
|
|
46
|
+
const size_t offset = y * gdi.stride + x * 4;
|
|
47
|
+
std::fill_n(pixels.data() + offset, 3, y < rows ? color : 0);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
transport.MarkFramebufferUpdated();
|
|
51
|
+
transport.MarkFramePainted();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void LoseSession() {
|
|
55
|
+
{
|
|
56
|
+
std::lock_guard<std::mutex> lock(transport.mutex_);
|
|
57
|
+
transport.connected_ = false;
|
|
58
|
+
transport.last_error_ = ErrorPayload{"session_lost", "test disconnect"};
|
|
59
|
+
}
|
|
60
|
+
transport.SignalSessionInactive();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
} // namespace midscene::rdp
|
|
65
|
+
|
|
66
|
+
namespace {
|
|
67
|
+
|
|
68
|
+
using namespace std::chrono_literals;
|
|
69
|
+
using midscene::rdp::RawFrame;
|
|
70
|
+
using midscene::rdp::RdpScreenshotTestPeer;
|
|
71
|
+
|
|
72
|
+
void Expect(bool condition, const char* message) {
|
|
73
|
+
if (!condition) {
|
|
74
|
+
throw std::runtime_error(message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
bool HasColor(const RawFrame& frame, uint8_t color) {
|
|
79
|
+
for (int y = 0; y < frame.size.height; ++y) {
|
|
80
|
+
for (int x = 0; x < frame.size.width; ++x) {
|
|
81
|
+
const size_t offset = y * frame.stride + x * 4;
|
|
82
|
+
for (size_t channel = 0; channel < 3; ++channel) {
|
|
83
|
+
if (frame.bgra[offset + channel] != color) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
void TestPartialFirstPaint() {
|
|
93
|
+
RdpScreenshotTestPeer peer;
|
|
94
|
+
peer.Paint(64, 2);
|
|
95
|
+
auto painting = std::async(std::launch::async, [&] {
|
|
96
|
+
// Keep painting beyond the initial quiet window: each update must extend
|
|
97
|
+
// settling, while still letting the event-loop mutex make progress.
|
|
98
|
+
for (int rows = 3; rows <= 6; ++rows) {
|
|
99
|
+
std::this_thread::sleep_for(100ms);
|
|
100
|
+
peer.Paint(128, rows);
|
|
101
|
+
}
|
|
102
|
+
std::this_thread::sleep_for(100ms);
|
|
103
|
+
peer.Paint(192);
|
|
104
|
+
});
|
|
105
|
+
const auto frame = peer.transport.CaptureFrame();
|
|
106
|
+
painting.get();
|
|
107
|
+
Expect(HasColor(frame, 192), "first screenshot returned a partial old paint");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
void TestLaterScreenshotAndBlackRecovery() {
|
|
111
|
+
RdpScreenshotTestPeer peer;
|
|
112
|
+
peer.Paint(64);
|
|
113
|
+
Expect(HasColor(peer.transport.CaptureFrame(), 64),
|
|
114
|
+
"uniform non-black screen should be accepted");
|
|
115
|
+
peer.Paint(0);
|
|
116
|
+
auto painting = std::async(std::launch::async, [&] {
|
|
117
|
+
// Stay black beyond the quiet period; the screenshot must await recovery.
|
|
118
|
+
std::this_thread::sleep_for(500ms);
|
|
119
|
+
peer.Paint(128);
|
|
120
|
+
});
|
|
121
|
+
const auto frame = peer.transport.CaptureFrame();
|
|
122
|
+
painting.get();
|
|
123
|
+
Expect(HasColor(frame, 128), "later screenshot returned a black or cached frame");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
void TestContinuousUpdatesHaveDeadline() {
|
|
127
|
+
RdpScreenshotTestPeer peer;
|
|
128
|
+
peer.Paint(64);
|
|
129
|
+
std::atomic<bool> stop{false};
|
|
130
|
+
auto painting = std::async(std::launch::async, [&] {
|
|
131
|
+
while (!stop.load()) {
|
|
132
|
+
peer.Paint(128);
|
|
133
|
+
std::this_thread::sleep_for(30ms);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
auto capture = std::async(std::launch::async,
|
|
137
|
+
[&] { return peer.transport.CaptureFrame(); });
|
|
138
|
+
const auto status = capture.wait_for(5s);
|
|
139
|
+
stop.store(true);
|
|
140
|
+
painting.get();
|
|
141
|
+
Expect(status == std::future_status::ready,
|
|
142
|
+
"continuous updates extended the screenshot deadline");
|
|
143
|
+
Expect(HasColor(capture.get(), 128), "deadline did not return the live frame");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
void TestBlackTimeout() {
|
|
147
|
+
RdpScreenshotTestPeer peer;
|
|
148
|
+
peer.Paint(0);
|
|
149
|
+
auto capture = std::async(std::launch::async, [&] {
|
|
150
|
+
try {
|
|
151
|
+
peer.transport.CaptureFrame();
|
|
152
|
+
return std::string();
|
|
153
|
+
} catch (const std::exception& error) {
|
|
154
|
+
return std::string(error.what());
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
Expect(capture.wait_for(500ms) == std::future_status::timeout,
|
|
158
|
+
"black framebuffer was not given time to recover");
|
|
159
|
+
const auto status = capture.wait_for(5s);
|
|
160
|
+
if (status != std::future_status::ready) {
|
|
161
|
+
peer.LoseSession();
|
|
162
|
+
}
|
|
163
|
+
Expect(status == std::future_status::ready, "black screenshot never timed out");
|
|
164
|
+
Expect(capture.get().find("still black") != std::string::npos,
|
|
165
|
+
"black framebuffer (with opaque alpha and padding) was not rejected");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
void TestDisconnectWakesScreenshot(uint8_t color) {
|
|
169
|
+
RdpScreenshotTestPeer peer;
|
|
170
|
+
peer.Paint(color);
|
|
171
|
+
auto capture = std::async(std::launch::async, [&] {
|
|
172
|
+
try {
|
|
173
|
+
peer.transport.CaptureFrame();
|
|
174
|
+
return std::string();
|
|
175
|
+
} catch (const std::exception& error) {
|
|
176
|
+
return std::string(error.what());
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
Expect(capture.wait_for(50ms) == std::future_status::timeout,
|
|
180
|
+
"screenshot did not wait for framebuffer updates");
|
|
181
|
+
peer.LoseSession();
|
|
182
|
+
Expect(capture.wait_for(1s) == std::future_status::ready,
|
|
183
|
+
"session loss did not wake the screenshot waiter");
|
|
184
|
+
Expect(capture.get().find("test disconnect") != std::string::npos,
|
|
185
|
+
"screenshot lost the session failure reason");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
void TestInvalidFramebuffer() {
|
|
189
|
+
for (int invalid_case = 0; invalid_case < 4; ++invalid_case) {
|
|
190
|
+
RdpScreenshotTestPeer peer;
|
|
191
|
+
peer.Paint(64);
|
|
192
|
+
switch (invalid_case) {
|
|
193
|
+
case 0: peer.gdi.primary_buffer = nullptr; break;
|
|
194
|
+
case 1: peer.gdi.width = 0; break;
|
|
195
|
+
case 2: peer.gdi.height = 0; break;
|
|
196
|
+
case 3: peer.gdi.stride = 4; break;
|
|
197
|
+
}
|
|
198
|
+
std::string error_message;
|
|
199
|
+
try {
|
|
200
|
+
peer.transport.CaptureFrame();
|
|
201
|
+
} catch (const std::exception& error) {
|
|
202
|
+
error_message = error.what();
|
|
203
|
+
}
|
|
204
|
+
Expect(error_message == "Remote framebuffer is empty or invalid",
|
|
205
|
+
"invalid framebuffer layout was not rejected");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
} // namespace
|
|
210
|
+
|
|
211
|
+
int main() {
|
|
212
|
+
try {
|
|
213
|
+
TestPartialFirstPaint();
|
|
214
|
+
TestLaterScreenshotAndBlackRecovery();
|
|
215
|
+
TestContinuousUpdatesHaveDeadline();
|
|
216
|
+
TestBlackTimeout();
|
|
217
|
+
TestDisconnectWakesScreenshot(0);
|
|
218
|
+
TestDisconnectWakesScreenshot(64);
|
|
219
|
+
TestInvalidFramebuffer();
|
|
220
|
+
return 0;
|
|
221
|
+
} catch (const std::exception& error) {
|
|
222
|
+
std::cerr << error.what() << '\n';
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midscene/computer",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.5-beta-20260907080441.0",
|
|
4
4
|
"description": "Midscene.js Computer Desktop Automation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"@computer-use/libnut": "^4.2.0",
|
|
34
34
|
"clipboardy": "^4.0.0",
|
|
35
35
|
"screenshot-desktop": "^1.15.3",
|
|
36
|
-
"@midscene/core": "1.12.
|
|
37
|
-
"@midscene/shared": "1.12.
|
|
36
|
+
"@midscene/core": "1.12.5-beta-20260907080441.0",
|
|
37
|
+
"@midscene/shared": "1.12.5-beta-20260907080441.0"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
40
|
"node-mac-permissions": "2.5.0"
|