@remcp/runtime 0.2.140 → 0.2.141
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/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/extended/catalog.mjs +3 -2
- package/src/extended/desktop-linux.mjs +154 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.141 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- Linux `display_inventory` capability discovery now uses the same real backend resolver as execution instead of treating every Wayland session as supported.
|
|
6
|
+
- wlroots compositors gain a native `wlr-randr` parser for connector metadata, logical position/size, scale, transform, pixel mode and refresh rate; the fallback chain is Mutter → wlr-randr → xrandr.
|
|
7
|
+
- Unsupported Wayland sessions without one of those working backends stop advertising `display_inventory`, and dynamic capability polling can add/remove the tool through `tools/list_changed`.
|
|
8
|
+
- Live GNOME Wayland verification still returns the ASUS eDP-1 through Mutter with the 83-tool desktop surface unchanged.
|
|
9
|
+
|
|
3
10
|
## 0.2.140 — 2026-10-02
|
|
4
11
|
|
|
5
12
|
- Windows `create_archive` / `extract_archive` are enabled when the native inbox `tar.exe` (libarchive/bsdtar) backend is available.
|
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ Desktop capabilities additionally require an active graphical session (`DISPLAY`
|
|
|
84
84
|
headless hosts do not advertise window/UI/input/clipboard/display/screenshot/launch/open/reveal/notification
|
|
85
85
|
operations merely because desktop helper binaries happen to be installed. The universal `computer_snapshot`
|
|
86
86
|
remains available on those hosts but skips unavailable desktop probes instead of reporting synthetic backend
|
|
87
|
-
errors. On Linux, `screenshot_region` is advertised only when the current session has a real matching capture
|
|
87
|
+
errors. Linux `display_inventory` follows the real monitor backend chain (GNOME Mutter DisplayConfig → `wlr-randr` → `xrandr`) and is hidden on Wayland when none of those backends can return a display. On Linux, `screenshot_region` is advertised only when the current session has a real matching capture
|
|
88
88
|
backend (Wayland grim/GNOME/portal path or X11 ImageMagick), not merely because `ffmpeg` is installed.
|
|
89
89
|
|
|
90
90
|
The generated hosted reference covers the current **10 hosted production definitions**, all model-visible
|
package/package.json
CHANGED
package/src/extended/catalog.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { isWaylandSession } from '../screenshot-portal.mjs';
|
|
|
9
9
|
import { waylandPortalCandidate } from '../wayland-remote-desktop.mjs';
|
|
10
10
|
import { commandExists, desktopSessionAvailable, runFile } from './common.mjs';
|
|
11
11
|
import { desktopHandlers } from './desktop.mjs';
|
|
12
|
-
import { regionScreenshotBackends } from './desktop-linux.mjs';
|
|
12
|
+
import { displayInventoryAvailable, regionScreenshotBackends } from './desktop-linux.mjs';
|
|
13
13
|
import { diagnosticHandlers, gnomeScreencastSupported, recordScreenAvailable } from './diagnostics.mjs';
|
|
14
14
|
import { documentHandlers } from './documents.mjs';
|
|
15
15
|
|
|
@@ -760,6 +760,7 @@ export async function capabilitySnapshot() {
|
|
|
760
760
|
const portalCandidate = wayland && waylandPortalCandidate();
|
|
761
761
|
const portalInput = portalCandidate;
|
|
762
762
|
const x11Input = linux && desktopSession && !wayland && (commandExists('wdotool') || commandExists('xdotool'));
|
|
763
|
+
const displayInventoryReady = linux && desktopSession ? await displayInventoryAvailable() : false;
|
|
763
764
|
const browserCdp = browserRemoteEnabled() && await browserControlAvailable(undefined, 500);
|
|
764
765
|
const documentCapabilities = documentCapabilitySnapshot();
|
|
765
766
|
return {
|
|
@@ -770,7 +771,7 @@ export async function capabilitySnapshot() {
|
|
|
770
771
|
pointer: win || mac || (desktopSession && (portalInput || x11Input)),
|
|
771
772
|
drag: win || mac || (desktopSession && (portalInput || x11Input)),
|
|
772
773
|
clipboard: win || mac || (desktopSession && (commandExists('wl-paste') || commandExists('xclip') || commandExists('xsel'))),
|
|
773
|
-
displays: win || mac ||
|
|
774
|
+
displays: win || mac || displayInventoryReady,
|
|
774
775
|
screen_region: win || mac || (desktopSession && regionScreenshotBackends({ wayland, portalCandidate }).length > 0),
|
|
775
776
|
notifications: win || mac || (desktopSession && commandExists('notify-send')),
|
|
776
777
|
browser_cdp: browserCdp,
|
|
@@ -1445,24 +1445,165 @@ async function mutterDisplayInventory() {
|
|
|
1445
1445
|
}
|
|
1446
1446
|
}
|
|
1447
1447
|
|
|
1448
|
-
export
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1448
|
+
export function parseWlrRandrOutput(stdout) {
|
|
1449
|
+
const rows = [];
|
|
1450
|
+
let current = null;
|
|
1451
|
+
|
|
1452
|
+
const finish = () => {
|
|
1453
|
+
if (!current || current.enabled === false) return;
|
|
1454
|
+
const mode = current.currentMode || current.firstMode;
|
|
1455
|
+
if (!mode) return;
|
|
1456
|
+
const scale = Number.isFinite(current.scale) && current.scale > 0 ? current.scale : 1;
|
|
1457
|
+
const rotated = /(?:^|\D)(?:90|270)(?:\D|$)/.test(current.transform || '');
|
|
1458
|
+
const pixelWidth = mode.width;
|
|
1459
|
+
const pixelHeight = mode.height;
|
|
1460
|
+
rows.push({
|
|
1461
|
+
name:current.name,
|
|
1462
|
+
display_name:current.description || current.model || current.name,
|
|
1463
|
+
primary:false,
|
|
1464
|
+
x:Number.isFinite(current.x) ? current.x : null,
|
|
1465
|
+
y:Number.isFinite(current.y) ? current.y : null,
|
|
1466
|
+
width:Math.round((rotated ? pixelHeight : pixelWidth) / scale),
|
|
1467
|
+
height:Math.round((rotated ? pixelWidth : pixelHeight) / scale),
|
|
1468
|
+
pixel_width:pixelWidth,
|
|
1469
|
+
pixel_height:pixelHeight,
|
|
1470
|
+
scale,
|
|
1471
|
+
refresh_hz:mode.refresh_hz,
|
|
1472
|
+
transform:current.transform || 'normal',
|
|
1473
|
+
builtin:/^(?:eDP|LVDS|DSI)-/i.test(current.name),
|
|
1474
|
+
vendor:current.make || null,
|
|
1475
|
+
product:current.model || null,
|
|
1476
|
+
serial:current.serial || null,
|
|
1477
|
+
backend:'wlr-randr',
|
|
1478
|
+
});
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
for (const rawLine of String(stdout || '').split(/\r?\n/)) {
|
|
1482
|
+
if (rawLine && !/^\s/.test(rawLine)) {
|
|
1483
|
+
finish();
|
|
1484
|
+
const header = rawLine.match(/^(\S+)(?:\s+"([^"]*)")?\s*$/);
|
|
1485
|
+
current = header ? {
|
|
1486
|
+
name:header[1],
|
|
1487
|
+
description:header[2] || '',
|
|
1488
|
+
enabled:true,
|
|
1489
|
+
scale:1,
|
|
1490
|
+
transform:'normal',
|
|
1491
|
+
currentMode:null,
|
|
1492
|
+
firstMode:null,
|
|
1493
|
+
} : null;
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
if (!current) continue;
|
|
1497
|
+
const line = rawLine.trim();
|
|
1498
|
+
let match;
|
|
1499
|
+
if ((match = line.match(/^Enabled:\s*(yes|no)$/i))) current.enabled = match[1].toLowerCase() === 'yes';
|
|
1500
|
+
else if ((match = line.match(/^Position:\s*(-?\d+)\s*,\s*(-?\d+)$/i))) {
|
|
1501
|
+
current.x = Number(match[1]);
|
|
1502
|
+
current.y = Number(match[2]);
|
|
1503
|
+
} else if ((match = line.match(/^Scale:\s*(\d+(?:\.\d+)?)$/i))) current.scale = Number(match[1]);
|
|
1504
|
+
else if ((match = line.match(/^Transform:\s*(.+)$/i))) current.transform = match[1].trim();
|
|
1505
|
+
else if ((match = line.match(/^Make:\s*(.*)$/i))) current.make = match[1].trim();
|
|
1506
|
+
else if ((match = line.match(/^Model:\s*(.*)$/i))) current.model = match[1].trim();
|
|
1507
|
+
else if ((match = line.match(/^Serial:\s*(.*)$/i))) current.serial = match[1].trim();
|
|
1508
|
+
else if ((match = line.match(/^(\d+)x(\d+)\s+px,\s*([\d.]+)\s+Hz(?:\s+\(([^)]*)\))?/i))) {
|
|
1509
|
+
const mode = { width:Number(match[1]), height:Number(match[2]), refresh_hz:Number(match[3]) };
|
|
1510
|
+
current.firstMode ||= mode;
|
|
1511
|
+
if (/\bcurrent\b/i.test(match[4] || '')) current.currentMode = mode;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
finish();
|
|
1515
|
+
return rows;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
export function parseXrandrDisplayOutput(stdout, scale = 1) {
|
|
1519
|
+
const rows = [];
|
|
1520
|
+
for (const line of String(stdout || '').split(/\r?\n/)) {
|
|
1521
|
+
const match = line.match(/^(\S+) connected( primary)?(?: (\d+)x(\d+)\+(-?\d+)\+(-?\d+))?/);
|
|
1522
|
+
if (match) rows.push({
|
|
1523
|
+
name:match[1],
|
|
1524
|
+
display_name:match[1],
|
|
1525
|
+
primary:Boolean(match[2]),
|
|
1526
|
+
width:match[3]?Number(match[3]):null,
|
|
1527
|
+
height:match[4]?Number(match[4]):null,
|
|
1528
|
+
pixel_width:match[3]?Number(match[3]):null,
|
|
1529
|
+
pixel_height:match[4]?Number(match[4]):null,
|
|
1530
|
+
x:match[5]?Number(match[5]):null,
|
|
1531
|
+
y:match[6]?Number(match[6]):null,
|
|
1532
|
+
scale,
|
|
1533
|
+
backend:'xrandr',
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
return rows;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
async function xrandrDisplayScale({ commandExistsFn = commandExists, runFileFn = runFile, env = process.env } = {}) {
|
|
1540
|
+
let scale = Number(env.GDK_SCALE || env.QT_SCALE_FACTOR || 0);
|
|
1454
1541
|
if (!Number.isFinite(scale) || scale <= 0) scale = 0;
|
|
1455
|
-
if (!scale &&
|
|
1456
|
-
const probe = await
|
|
1542
|
+
if (!scale && commandExistsFn('gsettings')) {
|
|
1543
|
+
const probe = await runFileFn('gsettings', ['get','org.gnome.desktop.interface','scaling-factor'], { label:'display scale', allowFailure:true, timeout:1500 });
|
|
1457
1544
|
const parsed = Number(String(probe.stdout || '').match(/(\d+(?:\.\d+)?)/)?.[1]);
|
|
1458
1545
|
if (Number.isFinite(parsed) && parsed > 0) scale = parsed;
|
|
1459
1546
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1547
|
+
return scale || 1;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
export async function resolveLinuxDisplayInventory({
|
|
1551
|
+
wayland = isWaylandSession(),
|
|
1552
|
+
mutterProbe = mutterDisplayInventory,
|
|
1553
|
+
commandExistsFn = commandExists,
|
|
1554
|
+
runFileFn = runFile,
|
|
1555
|
+
env = process.env,
|
|
1556
|
+
} = {}) {
|
|
1557
|
+
if (wayland) {
|
|
1558
|
+
const mutter = await mutterProbe().catch(() => null);
|
|
1559
|
+
if (Array.isArray(mutter) && mutter.length) return mutter;
|
|
1560
|
+
|
|
1561
|
+
if (commandExistsFn('wlr-randr')) {
|
|
1562
|
+
const result = await runFileFn('wlr-randr', [], { label:'display inventory', allowFailure:true, timeout:2500, maxBuffer:4*1024*1024 });
|
|
1563
|
+
if (result.code === 0) {
|
|
1564
|
+
const rows = parseWlrRandrOutput(result.stdout);
|
|
1565
|
+
if (rows.length) return rows;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1465
1568
|
}
|
|
1569
|
+
|
|
1570
|
+
if (commandExistsFn('xrandr')) {
|
|
1571
|
+
const result = await runFileFn('xrandr', ['--query'], { label:'display inventory', allowFailure:true, timeout:2500, maxBuffer:4*1024*1024 });
|
|
1572
|
+
if (result.code === 0) {
|
|
1573
|
+
const scale = await xrandrDisplayScale({ commandExistsFn, runFileFn, env });
|
|
1574
|
+
const rows = parseXrandrDisplayOutput(result.stdout, scale);
|
|
1575
|
+
if (rows.length) return rows;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return null;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
let displayCapabilityCache = { value:false, expiresAt:0, pending:null };
|
|
1582
|
+
|
|
1583
|
+
export async function displayInventoryAvailable(options) {
|
|
1584
|
+
if (options && Object.keys(options).length) {
|
|
1585
|
+
return Boolean((await resolveLinuxDisplayInventory(options))?.length);
|
|
1586
|
+
}
|
|
1587
|
+
const now = Date.now();
|
|
1588
|
+
if (displayCapabilityCache.expiresAt > now) return displayCapabilityCache.value;
|
|
1589
|
+
if (displayCapabilityCache.pending) return displayCapabilityCache.pending;
|
|
1590
|
+
const pending = resolveLinuxDisplayInventory()
|
|
1591
|
+
.then(rows => {
|
|
1592
|
+
const value = Boolean(rows?.length);
|
|
1593
|
+
displayCapabilityCache = { value, expiresAt:Date.now() + 5000, pending:null };
|
|
1594
|
+
return value;
|
|
1595
|
+
})
|
|
1596
|
+
.catch(() => {
|
|
1597
|
+
displayCapabilityCache = { value:false, expiresAt:Date.now() + 5000, pending:null };
|
|
1598
|
+
return false;
|
|
1599
|
+
});
|
|
1600
|
+
displayCapabilityCache.pending = pending;
|
|
1601
|
+
return pending;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
export async function displayInventory() {
|
|
1605
|
+
const rows = await resolveLinuxDisplayInventory();
|
|
1606
|
+
if (!rows?.length) unavailable('Display inventory', 'GNOME Mutter DisplayConfig, wlr-randr or xrandr is required');
|
|
1466
1607
|
return jsonResult(rows);
|
|
1467
1608
|
}
|
|
1468
1609
|
|