simen-keyboard-listener 1.1.11 → 1.1.13
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/dist/index.d.mts +371 -41
- package/dist/index.d.ts +371 -41
- package/dist/index.js +886 -6
- package/dist/index.mjs +888 -4
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
1
2
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
3
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
4
|
}) : x)(function(x) {
|
|
4
5
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
6
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
7
|
});
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
7
12
|
|
|
8
13
|
// src/index.ts
|
|
9
14
|
import * as path from "path";
|
|
@@ -1347,9 +1352,887 @@ async function getSystemContext(options) {
|
|
|
1347
1352
|
};
|
|
1348
1353
|
}
|
|
1349
1354
|
|
|
1355
|
+
// src/powershell/index.ts
|
|
1356
|
+
var powershell_exports = {};
|
|
1357
|
+
__export(powershell_exports, {
|
|
1358
|
+
escapeForPowerShell: () => escapeForPowerShell,
|
|
1359
|
+
executeAndParse: () => executeAndParse2,
|
|
1360
|
+
executeMultilineAndParse: () => executeMultilineAndParse2,
|
|
1361
|
+
executePowerShell: () => executePowerShell,
|
|
1362
|
+
executePowerShellLines: () => executePowerShellLines,
|
|
1363
|
+
getAgentContext: () => getAgentContext2,
|
|
1364
|
+
getClipboardContent: () => getClipboardContent2,
|
|
1365
|
+
getClipboardText: () => getClipboardText2,
|
|
1366
|
+
getExplorerCurrentFolder: () => getExplorerCurrentFolder,
|
|
1367
|
+
getExplorerSelection: () => getExplorerSelection,
|
|
1368
|
+
getExplorerWindows: () => getExplorerWindows,
|
|
1369
|
+
getFrontmostApp: () => getFrontmostApp2,
|
|
1370
|
+
getSystemContext: () => getSystemContext2,
|
|
1371
|
+
isPowerShellAvailable: () => isPowerShellAvailable
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
// src/powershell/executor.ts
|
|
1375
|
+
import { execFile as execFile2 } from "child_process";
|
|
1376
|
+
import { promisify as promisify2 } from "util";
|
|
1377
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
1378
|
+
var IS_WINDOWS = process.platform === "win32";
|
|
1379
|
+
var POWERSHELL_PATHS = [
|
|
1380
|
+
"pwsh.exe",
|
|
1381
|
+
// PowerShell Core (faster, recommended)
|
|
1382
|
+
"powershell.exe"
|
|
1383
|
+
// Windows PowerShell (legacy)
|
|
1384
|
+
];
|
|
1385
|
+
var DEFAULT_TIMEOUT2 = 3e4;
|
|
1386
|
+
var cachedPowerShellPath = null;
|
|
1387
|
+
async function findPowerShell() {
|
|
1388
|
+
if (cachedPowerShellPath) {
|
|
1389
|
+
return cachedPowerShellPath;
|
|
1390
|
+
}
|
|
1391
|
+
for (const psPath of POWERSHELL_PATHS) {
|
|
1392
|
+
try {
|
|
1393
|
+
await execFileAsync2(psPath, ["-NoProfile", "-Command", "exit 0"], { timeout: 5e3 });
|
|
1394
|
+
cachedPowerShellPath = psPath;
|
|
1395
|
+
return psPath;
|
|
1396
|
+
} catch {
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
function isPowerShellAvailable() {
|
|
1402
|
+
return IS_WINDOWS;
|
|
1403
|
+
}
|
|
1404
|
+
async function executePowerShell(script, options) {
|
|
1405
|
+
if (!IS_WINDOWS) {
|
|
1406
|
+
return {
|
|
1407
|
+
success: false,
|
|
1408
|
+
error: "PowerShell is only available on Windows"
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
const psPath = await findPowerShell();
|
|
1412
|
+
if (!psPath) {
|
|
1413
|
+
return {
|
|
1414
|
+
success: false,
|
|
1415
|
+
error: "PowerShell not found. Please install PowerShell Core or Windows PowerShell."
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
const timeout = options?.timeout ?? DEFAULT_TIMEOUT2;
|
|
1419
|
+
const startTime = Date.now();
|
|
1420
|
+
try {
|
|
1421
|
+
const { stdout, stderr } = await execFileAsync2(
|
|
1422
|
+
psPath,
|
|
1423
|
+
[
|
|
1424
|
+
"-NoProfile",
|
|
1425
|
+
// Don't load user profile (faster)
|
|
1426
|
+
"-NonInteractive",
|
|
1427
|
+
// Non-interactive mode
|
|
1428
|
+
"-ExecutionPolicy",
|
|
1429
|
+
"Bypass",
|
|
1430
|
+
// Bypass execution policy
|
|
1431
|
+
"-Command",
|
|
1432
|
+
script
|
|
1433
|
+
],
|
|
1434
|
+
{
|
|
1435
|
+
timeout,
|
|
1436
|
+
// Set output encoding to UTF-8 to handle Chinese characters
|
|
1437
|
+
encoding: "utf8"
|
|
1438
|
+
}
|
|
1439
|
+
);
|
|
1440
|
+
const executionTime = Date.now() - startTime;
|
|
1441
|
+
if (stderr && stderr.trim() && !stdout) {
|
|
1442
|
+
return {
|
|
1443
|
+
success: false,
|
|
1444
|
+
error: stderr.trim(),
|
|
1445
|
+
executionTime
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
return {
|
|
1449
|
+
success: true,
|
|
1450
|
+
data: stdout.trim(),
|
|
1451
|
+
executionTime
|
|
1452
|
+
};
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
const executionTime = Date.now() - startTime;
|
|
1455
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1456
|
+
return {
|
|
1457
|
+
success: false,
|
|
1458
|
+
error: errorMessage,
|
|
1459
|
+
executionTime
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
async function executePowerShellLines(lines, options) {
|
|
1464
|
+
const script = lines.join("; ");
|
|
1465
|
+
return executePowerShell(script, options);
|
|
1466
|
+
}
|
|
1467
|
+
async function executeAndParse2(script, parser, options) {
|
|
1468
|
+
const result = await executePowerShell(script, options);
|
|
1469
|
+
if (!result.success) {
|
|
1470
|
+
return {
|
|
1471
|
+
success: false,
|
|
1472
|
+
error: result.error,
|
|
1473
|
+
executionTime: result.executionTime
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
try {
|
|
1477
|
+
const parsed = parser(result.data ?? "");
|
|
1478
|
+
return {
|
|
1479
|
+
success: true,
|
|
1480
|
+
data: parsed,
|
|
1481
|
+
executionTime: result.executionTime
|
|
1482
|
+
};
|
|
1483
|
+
} catch (parseError) {
|
|
1484
|
+
const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
|
|
1485
|
+
return {
|
|
1486
|
+
success: false,
|
|
1487
|
+
error: `Failed to parse output: ${errorMessage}`,
|
|
1488
|
+
executionTime: result.executionTime
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
async function executeMultilineAndParse2(lines, parser, options) {
|
|
1493
|
+
const result = await executePowerShellLines(lines, options);
|
|
1494
|
+
if (!result.success) {
|
|
1495
|
+
return {
|
|
1496
|
+
success: false,
|
|
1497
|
+
error: result.error,
|
|
1498
|
+
executionTime: result.executionTime
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
try {
|
|
1502
|
+
const parsed = parser(result.data ?? "");
|
|
1503
|
+
return {
|
|
1504
|
+
success: true,
|
|
1505
|
+
data: parsed,
|
|
1506
|
+
executionTime: result.executionTime
|
|
1507
|
+
};
|
|
1508
|
+
} catch (parseError) {
|
|
1509
|
+
const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
|
|
1510
|
+
return {
|
|
1511
|
+
success: false,
|
|
1512
|
+
error: `Failed to parse output: ${errorMessage}`,
|
|
1513
|
+
executionTime: result.executionTime
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
function escapeForPowerShell(str) {
|
|
1518
|
+
return str.replace(/`/g, "``").replace(/"/g, '`"').replace(/\$/g, "`$").replace(/\n/g, "`n").replace(/\r/g, "`r").replace(/\t/g, "`t");
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// src/powershell/scripts/batchContext.ts
|
|
1522
|
+
async function getAgentContext2(options) {
|
|
1523
|
+
const includeClipboard = options?.includeClipboard ?? true;
|
|
1524
|
+
const includeExplorerWindows = options?.includeExplorerWindows ?? true;
|
|
1525
|
+
const maxSelectedItems = options?.maxSelectedItems ?? 50;
|
|
1526
|
+
const script = `
|
|
1527
|
+
# Add required types
|
|
1528
|
+
try {
|
|
1529
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
1530
|
+
} catch {
|
|
1531
|
+
# Assembly may already be loaded, ignore error
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
try {
|
|
1535
|
+
Add-Type @"
|
|
1536
|
+
using System;
|
|
1537
|
+
using System.Runtime.InteropServices;
|
|
1538
|
+
using System.Text;
|
|
1539
|
+
|
|
1540
|
+
public class Win32 {
|
|
1541
|
+
[DllImport("user32.dll")]
|
|
1542
|
+
public static extern IntPtr GetForegroundWindow();
|
|
1543
|
+
|
|
1544
|
+
[DllImport("user32.dll")]
|
|
1545
|
+
public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
|
|
1546
|
+
|
|
1547
|
+
[DllImport("user32.dll")]
|
|
1548
|
+
public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int processId);
|
|
1549
|
+
}
|
|
1550
|
+
"@
|
|
1551
|
+
} catch {
|
|
1552
|
+
# Type may already be loaded, ignore error
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
# Initialize result object
|
|
1556
|
+
$result = @{
|
|
1557
|
+
frontmostApp = $null
|
|
1558
|
+
explorerSelection = $null
|
|
1559
|
+
explorerCurrentFolder = $null
|
|
1560
|
+
explorerWindows = @()
|
|
1561
|
+
desktopPath = ""
|
|
1562
|
+
clipboard = @{
|
|
1563
|
+
text = $null
|
|
1564
|
+
hasFiles = $false
|
|
1565
|
+
filePaths = @()
|
|
1566
|
+
hasImage = $false
|
|
1567
|
+
}
|
|
1568
|
+
timestamp = (Get-Date -Format "o")
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
# ============================================================================
|
|
1572
|
+
# 1. Get Frontmost Application
|
|
1573
|
+
# ============================================================================
|
|
1574
|
+
try {
|
|
1575
|
+
$hwnd = [Win32]::GetForegroundWindow()
|
|
1576
|
+
if ($hwnd -ne [IntPtr]::Zero) {
|
|
1577
|
+
$processId = 0
|
|
1578
|
+
[Win32]::GetWindowThreadProcessId($hwnd, [ref]$processId) | Out-Null
|
|
1579
|
+
|
|
1580
|
+
$process = Get-Process -Id $processId -ErrorAction SilentlyContinue
|
|
1581
|
+
if ($process) {
|
|
1582
|
+
$windowTitle = New-Object System.Text.StringBuilder 256
|
|
1583
|
+
[Win32]::GetWindowText($hwnd, $windowTitle, 256) | Out-Null
|
|
1584
|
+
|
|
1585
|
+
$processName = $process.ProcessName
|
|
1586
|
+
$path = ""
|
|
1587
|
+
try {
|
|
1588
|
+
$path = $process.Path
|
|
1589
|
+
if (-not $path) {
|
|
1590
|
+
$path = $process.MainModule.FileName
|
|
1591
|
+
}
|
|
1592
|
+
} catch {
|
|
1593
|
+
$path = ""
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
$name = $process.MainWindowTitle
|
|
1597
|
+
if (-not $name -or $name -eq "") {
|
|
1598
|
+
$name = $processName
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
$isExplorer = $processName -eq "explorer"
|
|
1602
|
+
|
|
1603
|
+
$result.frontmostApp = @{
|
|
1604
|
+
name = $name
|
|
1605
|
+
processName = $processName
|
|
1606
|
+
path = $path
|
|
1607
|
+
processId = $processId
|
|
1608
|
+
windowTitle = $windowTitle.ToString()
|
|
1609
|
+
isExplorer = $isExplorer
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
} catch {
|
|
1614
|
+
# Frontmost app detection failed
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
# ============================================================================
|
|
1618
|
+
# 2. Get Explorer Context (if frontmost app is Explorer)
|
|
1619
|
+
# ============================================================================
|
|
1620
|
+
if ($result.frontmostApp -and $result.frontmostApp.isExplorer) {
|
|
1621
|
+
try {
|
|
1622
|
+
$shell = New-Object -ComObject Shell.Application
|
|
1623
|
+
$windows = $shell.Windows()
|
|
1624
|
+
|
|
1625
|
+
$activeHwnd = [Win32]::GetForegroundWindow()
|
|
1626
|
+
$selectedItems = @()
|
|
1627
|
+
$currentFolder = $null
|
|
1628
|
+
$explorerWindows = @()
|
|
1629
|
+
$foundActiveWindow = $false
|
|
1630
|
+
|
|
1631
|
+
foreach ($window in $windows) {
|
|
1632
|
+
if ($window.Name -eq "File Explorer" -or $window.FullName -like "*explorer.exe") {
|
|
1633
|
+
try {
|
|
1634
|
+
$path = $window.Document.Folder.Self.Path
|
|
1635
|
+
if ($path) {
|
|
1636
|
+
# Check if this is the active window
|
|
1637
|
+
$isActive = $false
|
|
1638
|
+
try {
|
|
1639
|
+
$hwnd = $window.HWND
|
|
1640
|
+
if ($hwnd -eq $activeHwnd.ToInt64()) {
|
|
1641
|
+
$isActive = $true
|
|
1642
|
+
|
|
1643
|
+
# Get current folder for active window
|
|
1644
|
+
$currentFolder = $path
|
|
1645
|
+
|
|
1646
|
+
# Get selected items for active window
|
|
1647
|
+
$items = $window.Document.SelectedItems()
|
|
1648
|
+
if ($items -and $items.Count -gt 0) {
|
|
1649
|
+
foreach ($item in $items) {
|
|
1650
|
+
if ($selectedItems.Count -ge ${maxSelectedItems}) {
|
|
1651
|
+
break
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
$isFolder = $false
|
|
1655
|
+
try {
|
|
1656
|
+
$isFolder = $item.IsFolder
|
|
1657
|
+
} catch {
|
|
1658
|
+
try {
|
|
1659
|
+
if (Test-Path $item.Path -PathType Container) {
|
|
1660
|
+
$isFolder = $true
|
|
1661
|
+
}
|
|
1662
|
+
} catch {
|
|
1663
|
+
$isFolder = $false
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
$selectedItems += @{
|
|
1668
|
+
path = if ($item.Path) { $item.Path } else { "" }
|
|
1669
|
+
name = if ($item.Name) { $item.Name } else { "" }
|
|
1670
|
+
isFolder = $isFolder
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
} catch {
|
|
1676
|
+
# HWND may not be accessible
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
# Add to explorer windows list (if enabled)
|
|
1680
|
+
if (${includeExplorerWindows}) {
|
|
1681
|
+
$title = $window.LocationName
|
|
1682
|
+
if (-not $title) {
|
|
1683
|
+
$title = Split-Path $path -Leaf
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
$explorerWindows += @{
|
|
1687
|
+
title = if ($title) { $title } else { "" }
|
|
1688
|
+
path = if ($path) { $path } else { "" }
|
|
1689
|
+
isActive = $isActive
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
} catch {
|
|
1694
|
+
# Window may not have a valid path
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
# Set explorer selection
|
|
1700
|
+
if ($selectedItems.Count -gt 0) {
|
|
1701
|
+
$result.explorerSelection = @{
|
|
1702
|
+
items = $selectedItems
|
|
1703
|
+
count = $selectedItems.Count
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
# Set current folder
|
|
1708
|
+
$result.explorerCurrentFolder = $currentFolder
|
|
1709
|
+
|
|
1710
|
+
# Set explorer windows
|
|
1711
|
+
$result.explorerWindows = $explorerWindows
|
|
1712
|
+
|
|
1713
|
+
} catch {
|
|
1714
|
+
# Explorer context detection failed
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
# ============================================================================
|
|
1719
|
+
# 3. Get Desktop Path
|
|
1720
|
+
# ============================================================================
|
|
1721
|
+
try {
|
|
1722
|
+
$result.desktopPath = [Environment]::GetFolderPath("Desktop")
|
|
1723
|
+
} catch {
|
|
1724
|
+
$result.desktopPath = ""
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
# ============================================================================
|
|
1728
|
+
# 4. Get Clipboard Content (if enabled)
|
|
1729
|
+
# ============================================================================
|
|
1730
|
+
if (${includeClipboard}) {
|
|
1731
|
+
try {
|
|
1732
|
+
$clipboardData = [System.Windows.Forms.Clipboard]::GetDataObject()
|
|
1733
|
+
if ($clipboardData) {
|
|
1734
|
+
# Check for text
|
|
1735
|
+
if ($clipboardData.GetDataPresent([System.Windows.Forms.DataFormats]::Text)) {
|
|
1736
|
+
$result.clipboard.text = $clipboardData.GetData([System.Windows.Forms.DataFormats]::Text)
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
# Check for files
|
|
1740
|
+
if ($clipboardData.GetDataPresent([System.Windows.Forms.DataFormats]::FileDrop)) {
|
|
1741
|
+
$result.clipboard.hasFiles = $true
|
|
1742
|
+
$files = $clipboardData.GetData([System.Windows.Forms.DataFormats]::FileDrop)
|
|
1743
|
+
if ($files) {
|
|
1744
|
+
$result.clipboard.filePaths = @($files)
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
# Check for image
|
|
1749
|
+
if ($clipboardData.GetDataPresent([System.Windows.Forms.DataFormats]::Bitmap)) {
|
|
1750
|
+
$result.clipboard.hasImage = $true
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
} catch {
|
|
1754
|
+
# Clipboard access failed
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
# ============================================================================
|
|
1759
|
+
# Output JSON
|
|
1760
|
+
# ============================================================================
|
|
1761
|
+
$result | ConvertTo-Json -Compress -Depth 4
|
|
1762
|
+
`;
|
|
1763
|
+
const result = await executeAndParse2(
|
|
1764
|
+
script,
|
|
1765
|
+
(output) => {
|
|
1766
|
+
if (!output) {
|
|
1767
|
+
throw new Error("No output from PowerShell script");
|
|
1768
|
+
}
|
|
1769
|
+
const parsed = JSON.parse(output);
|
|
1770
|
+
return {
|
|
1771
|
+
frontmostApp: parsed.frontmostApp || {
|
|
1772
|
+
name: "",
|
|
1773
|
+
processName: "",
|
|
1774
|
+
path: "",
|
|
1775
|
+
processId: 0,
|
|
1776
|
+
windowTitle: "",
|
|
1777
|
+
isExplorer: false
|
|
1778
|
+
},
|
|
1779
|
+
explorerSelection: parsed.explorerSelection || null,
|
|
1780
|
+
explorerCurrentFolder: parsed.explorerCurrentFolder || null,
|
|
1781
|
+
explorerWindows: parsed.explorerWindows || [],
|
|
1782
|
+
desktopPath: parsed.desktopPath || "",
|
|
1783
|
+
clipboard: {
|
|
1784
|
+
text: parsed.clipboard?.text || null,
|
|
1785
|
+
hasFiles: parsed.clipboard?.hasFiles || false,
|
|
1786
|
+
filePaths: parsed.clipboard?.filePaths || [],
|
|
1787
|
+
hasImage: parsed.clipboard?.hasImage || false
|
|
1788
|
+
},
|
|
1789
|
+
timestamp: parsed.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
1790
|
+
};
|
|
1791
|
+
},
|
|
1792
|
+
options
|
|
1793
|
+
);
|
|
1794
|
+
if (!result.success) {
|
|
1795
|
+
return null;
|
|
1796
|
+
}
|
|
1797
|
+
return result.data ?? null;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// src/powershell/scripts/frontmostApp.ts
|
|
1801
|
+
async function getFrontmostApp2(options) {
|
|
1802
|
+
const script = `
|
|
1803
|
+
try {
|
|
1804
|
+
Add-Type @"
|
|
1805
|
+
using System;
|
|
1806
|
+
using System.Runtime.InteropServices;
|
|
1807
|
+
using System.Text;
|
|
1808
|
+
|
|
1809
|
+
public class Win32 {
|
|
1810
|
+
[DllImport("user32.dll")]
|
|
1811
|
+
public static extern IntPtr GetForegroundWindow();
|
|
1812
|
+
|
|
1813
|
+
[DllImport("user32.dll")]
|
|
1814
|
+
public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
|
|
1815
|
+
|
|
1816
|
+
[DllImport("user32.dll")]
|
|
1817
|
+
public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int processId);
|
|
1818
|
+
}
|
|
1819
|
+
"@
|
|
1820
|
+
} catch {
|
|
1821
|
+
# Type may already be loaded, ignore error
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
$hwnd = [Win32]::GetForegroundWindow()
|
|
1825
|
+
if ($hwnd -eq [IntPtr]::Zero) {
|
|
1826
|
+
Write-Output "{}"
|
|
1827
|
+
exit
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
$processId = 0
|
|
1831
|
+
[Win32]::GetWindowThreadProcessId($hwnd, [ref]$processId) | Out-Null
|
|
1832
|
+
|
|
1833
|
+
$process = Get-Process -Id $processId -ErrorAction SilentlyContinue
|
|
1834
|
+
if (-not $process) {
|
|
1835
|
+
Write-Output "{}"
|
|
1836
|
+
exit
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
$windowTitle = New-Object System.Text.StringBuilder 256
|
|
1840
|
+
[Win32]::GetWindowText($hwnd, $windowTitle, 256) | Out-Null
|
|
1841
|
+
|
|
1842
|
+
$processName = $process.ProcessName
|
|
1843
|
+
$path = ""
|
|
1844
|
+
try {
|
|
1845
|
+
$path = $process.Path
|
|
1846
|
+
if (-not $path) {
|
|
1847
|
+
$path = $process.MainModule.FileName
|
|
1848
|
+
}
|
|
1849
|
+
} catch {
|
|
1850
|
+
$path = ""
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
$name = $process.MainWindowTitle
|
|
1854
|
+
if (-not $name -or $name -eq "") {
|
|
1855
|
+
$name = $processName
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
$isExplorer = ($processName -eq "explorer")
|
|
1859
|
+
|
|
1860
|
+
$result = @{
|
|
1861
|
+
name = if ($name) { $name } else { "" }
|
|
1862
|
+
processName = if ($processName) { $processName } else { "" }
|
|
1863
|
+
path = if ($path) { $path } else { "" }
|
|
1864
|
+
processId = $processId
|
|
1865
|
+
windowTitle = $windowTitle.ToString()
|
|
1866
|
+
isExplorer = $isExplorer
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
$result | ConvertTo-Json -Compress
|
|
1870
|
+
`;
|
|
1871
|
+
const result = await executeAndParse2(
|
|
1872
|
+
script,
|
|
1873
|
+
(output) => {
|
|
1874
|
+
if (!output || output === "{}") {
|
|
1875
|
+
throw new Error("No frontmost window found");
|
|
1876
|
+
}
|
|
1877
|
+
return JSON.parse(output);
|
|
1878
|
+
},
|
|
1879
|
+
options
|
|
1880
|
+
);
|
|
1881
|
+
if (!result.success) {
|
|
1882
|
+
return null;
|
|
1883
|
+
}
|
|
1884
|
+
return result.data ?? null;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// src/powershell/scripts/explorerSelection.ts
|
|
1888
|
+
async function getExplorerSelection(options) {
|
|
1889
|
+
const maxItems = 50;
|
|
1890
|
+
const script = `
|
|
1891
|
+
try {
|
|
1892
|
+
$shell = New-Object -ComObject Shell.Application
|
|
1893
|
+
$windows = $shell.Windows()
|
|
1894
|
+
|
|
1895
|
+
$selectedItems = @()
|
|
1896
|
+
$foundSelection = $false
|
|
1897
|
+
|
|
1898
|
+
foreach ($window in $windows) {
|
|
1899
|
+
if ($foundSelection) { break }
|
|
1900
|
+
|
|
1901
|
+
# Check if this is an Explorer window
|
|
1902
|
+
if ($window.Name -eq "File Explorer" -or $window.FullName -like "*explorer.exe") {
|
|
1903
|
+
try {
|
|
1904
|
+
$items = $window.Document.SelectedItems()
|
|
1905
|
+
if ($items -and $items.Count -gt 0) {
|
|
1906
|
+
foreach ($item in $items) {
|
|
1907
|
+
if ($selectedItems.Count -ge ${maxItems}) {
|
|
1908
|
+
break
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
$isFolder = $false
|
|
1912
|
+
try {
|
|
1913
|
+
$isFolder = $item.IsFolder
|
|
1914
|
+
} catch {
|
|
1915
|
+
# If IsFolder fails, check if it's a directory
|
|
1916
|
+
try {
|
|
1917
|
+
if (Test-Path $item.Path -PathType Container) {
|
|
1918
|
+
$isFolder = $true
|
|
1919
|
+
}
|
|
1920
|
+
} catch {
|
|
1921
|
+
$isFolder = $false
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
$selectedItems += @{
|
|
1926
|
+
path = if ($item.Path) { $item.Path } else { "" }
|
|
1927
|
+
name = if ($item.Name) { $item.Name } else { "" }
|
|
1928
|
+
isFolder = $isFolder
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
$foundSelection = $true
|
|
1932
|
+
}
|
|
1933
|
+
} catch {
|
|
1934
|
+
# Window may not support selection, continue to next window
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
$result = @{
|
|
1940
|
+
items = $selectedItems
|
|
1941
|
+
count = $selectedItems.Count
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
$result | ConvertTo-Json -Compress -Depth 3
|
|
1945
|
+
} catch {
|
|
1946
|
+
# COM object creation failed or other error
|
|
1947
|
+
Write-Output '{"items":[],"count":0}'
|
|
1948
|
+
}
|
|
1949
|
+
`;
|
|
1950
|
+
const result = await executeAndParse2(
|
|
1951
|
+
script,
|
|
1952
|
+
(output) => {
|
|
1953
|
+
if (!output) {
|
|
1954
|
+
return { items: [], count: 0 };
|
|
1955
|
+
}
|
|
1956
|
+
const parsed = JSON.parse(output);
|
|
1957
|
+
return {
|
|
1958
|
+
items: parsed.items || [],
|
|
1959
|
+
count: parsed.count || 0
|
|
1960
|
+
};
|
|
1961
|
+
},
|
|
1962
|
+
options
|
|
1963
|
+
);
|
|
1964
|
+
if (!result.success) {
|
|
1965
|
+
return null;
|
|
1966
|
+
}
|
|
1967
|
+
const data = result.data;
|
|
1968
|
+
if (!data || data.count === 0) {
|
|
1969
|
+
return null;
|
|
1970
|
+
}
|
|
1971
|
+
return data;
|
|
1972
|
+
}
|
|
1973
|
+
async function getExplorerCurrentFolder(options) {
|
|
1974
|
+
const script = `
|
|
1975
|
+
$shell = New-Object -ComObject Shell.Application
|
|
1976
|
+
$windows = $shell.Windows()
|
|
1977
|
+
|
|
1978
|
+
foreach ($window in $windows) {
|
|
1979
|
+
# Check if this is an Explorer window
|
|
1980
|
+
if ($window.Name -eq "File Explorer" -or $window.FullName -like "*explorer.exe") {
|
|
1981
|
+
try {
|
|
1982
|
+
$path = $window.Document.Folder.Self.Path
|
|
1983
|
+
if ($path) {
|
|
1984
|
+
Write-Output $path
|
|
1985
|
+
exit
|
|
1986
|
+
}
|
|
1987
|
+
} catch {
|
|
1988
|
+
# Window may not have a valid path
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
`;
|
|
1993
|
+
const result = await executeAndParse2(
|
|
1994
|
+
script,
|
|
1995
|
+
(output) => output || null,
|
|
1996
|
+
options
|
|
1997
|
+
);
|
|
1998
|
+
if (!result.success) {
|
|
1999
|
+
return null;
|
|
2000
|
+
}
|
|
2001
|
+
return result.data ?? null;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
// src/powershell/scripts/explorerWindows.ts
|
|
2005
|
+
async function getExplorerWindows(options) {
|
|
2006
|
+
const script = `
|
|
2007
|
+
try {
|
|
2008
|
+
Add-Type @"
|
|
2009
|
+
using System;
|
|
2010
|
+
using System.Runtime.InteropServices;
|
|
2011
|
+
|
|
2012
|
+
public class Win32 {
|
|
2013
|
+
[DllImport("user32.dll")]
|
|
2014
|
+
public static extern IntPtr GetForegroundWindow();
|
|
2015
|
+
}
|
|
2016
|
+
"@
|
|
2017
|
+
} catch {
|
|
2018
|
+
# Type may already be loaded, ignore error
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
try {
|
|
2022
|
+
$shell = New-Object -ComObject Shell.Application
|
|
2023
|
+
$windows = $shell.Windows()
|
|
2024
|
+
|
|
2025
|
+
$activeHwnd = [Win32]::GetForegroundWindow()
|
|
2026
|
+
|
|
2027
|
+
$explorerWindows = @()
|
|
2028
|
+
|
|
2029
|
+
foreach ($window in $windows) {
|
|
2030
|
+
# Check if this is an Explorer window
|
|
2031
|
+
if ($window.Name -eq "File Explorer" -or $window.FullName -like "*explorer.exe") {
|
|
2032
|
+
try {
|
|
2033
|
+
$path = $window.Document.Folder.Self.Path
|
|
2034
|
+
if ($path) {
|
|
2035
|
+
$title = $window.LocationName
|
|
2036
|
+
if (-not $title) {
|
|
2037
|
+
$title = Split-Path $path -Leaf
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
# Check if this is the active window
|
|
2041
|
+
$isActive = $false
|
|
2042
|
+
try {
|
|
2043
|
+
$hwnd = $window.HWND
|
|
2044
|
+
if ($hwnd -eq $activeHwnd.ToInt64()) {
|
|
2045
|
+
$isActive = $true
|
|
2046
|
+
}
|
|
2047
|
+
} catch {
|
|
2048
|
+
# HWND may not be accessible
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
$explorerWindows += @{
|
|
2052
|
+
title = if ($title) { $title } else { "" }
|
|
2053
|
+
path = if ($path) { $path } else { "" }
|
|
2054
|
+
isActive = $isActive
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
} catch {
|
|
2058
|
+
# Window may not have a valid path, continue to next window
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
$result = @{
|
|
2064
|
+
windows = $explorerWindows
|
|
2065
|
+
count = $explorerWindows.Count
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
$result | ConvertTo-Json -Compress -Depth 3
|
|
2069
|
+
} catch {
|
|
2070
|
+
# COM object creation failed or other error
|
|
2071
|
+
Write-Output '{"windows":[],"count":0}'
|
|
2072
|
+
}
|
|
2073
|
+
`;
|
|
2074
|
+
const result = await executeAndParse2(
|
|
2075
|
+
script,
|
|
2076
|
+
(output) => {
|
|
2077
|
+
if (!output) {
|
|
2078
|
+
return { windows: [], count: 0 };
|
|
2079
|
+
}
|
|
2080
|
+
const parsed = JSON.parse(output);
|
|
2081
|
+
return {
|
|
2082
|
+
windows: parsed.windows || [],
|
|
2083
|
+
count: parsed.count || 0
|
|
2084
|
+
};
|
|
2085
|
+
},
|
|
2086
|
+
options
|
|
2087
|
+
);
|
|
2088
|
+
if (!result.success) {
|
|
2089
|
+
return null;
|
|
2090
|
+
}
|
|
2091
|
+
return result.data ?? null;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
// src/powershell/scripts/clipboard.ts
|
|
2095
|
+
async function getClipboardText2(options) {
|
|
2096
|
+
const script = `
|
|
2097
|
+
try {
|
|
2098
|
+
$text = Get-Clipboard -Format Text -ErrorAction SilentlyContinue
|
|
2099
|
+
if ($text) {
|
|
2100
|
+
Write-Output $text
|
|
2101
|
+
}
|
|
2102
|
+
} catch {
|
|
2103
|
+
# Clipboard may be empty or contain non-text data
|
|
2104
|
+
}
|
|
2105
|
+
`;
|
|
2106
|
+
const result = await executeAndParse2(
|
|
2107
|
+
script,
|
|
2108
|
+
(output) => output || null,
|
|
2109
|
+
options
|
|
2110
|
+
);
|
|
2111
|
+
if (!result.success) {
|
|
2112
|
+
return null;
|
|
2113
|
+
}
|
|
2114
|
+
return result.data ?? null;
|
|
2115
|
+
}
|
|
2116
|
+
async function getClipboardContent2(options) {
|
|
2117
|
+
const script = `
|
|
2118
|
+
try {
|
|
2119
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
2120
|
+
} catch {
|
|
2121
|
+
# Assembly may already be loaded, ignore error
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
try {
|
|
2125
|
+
$clipboard = [System.Windows.Forms.Clipboard]::GetDataObject()
|
|
2126
|
+
if (-not $clipboard) {
|
|
2127
|
+
Write-Output '{"text":null,"hasFiles":false,"filePaths":[],"hasImage":false}'
|
|
2128
|
+
exit
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
$text = $null
|
|
2132
|
+
$hasFiles = $false
|
|
2133
|
+
$filePaths = @()
|
|
2134
|
+
$hasImage = $false
|
|
2135
|
+
|
|
2136
|
+
# Check for text
|
|
2137
|
+
try {
|
|
2138
|
+
if ($clipboard.GetDataPresent([System.Windows.Forms.DataFormats]::Text)) {
|
|
2139
|
+
$text = $clipboard.GetData([System.Windows.Forms.DataFormats]::Text)
|
|
2140
|
+
}
|
|
2141
|
+
} catch {
|
|
2142
|
+
# Text access failed, continue
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
# Check for files
|
|
2146
|
+
try {
|
|
2147
|
+
if ($clipboard.GetDataPresent([System.Windows.Forms.DataFormats]::FileDrop)) {
|
|
2148
|
+
$hasFiles = $true
|
|
2149
|
+
$files = $clipboard.GetData([System.Windows.Forms.DataFormats]::FileDrop)
|
|
2150
|
+
if ($files) {
|
|
2151
|
+
$filePaths = @($files)
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
} catch {
|
|
2155
|
+
# File access failed, continue
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
# Check for image
|
|
2159
|
+
try {
|
|
2160
|
+
if ($clipboard.GetDataPresent([System.Windows.Forms.DataFormats]::Bitmap)) {
|
|
2161
|
+
$hasImage = $true
|
|
2162
|
+
}
|
|
2163
|
+
} catch {
|
|
2164
|
+
# Image check failed, continue
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
$result = @{
|
|
2168
|
+
text = $text
|
|
2169
|
+
hasFiles = $hasFiles
|
|
2170
|
+
filePaths = $filePaths
|
|
2171
|
+
hasImage = $hasImage
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
$result | ConvertTo-Json -Compress
|
|
2175
|
+
} catch {
|
|
2176
|
+
# Clipboard access completely failed
|
|
2177
|
+
Write-Output '{"text":null,"hasFiles":false,"filePaths":[],"hasImage":false}'
|
|
2178
|
+
}
|
|
2179
|
+
`;
|
|
2180
|
+
const result = await executeAndParse2(
|
|
2181
|
+
script,
|
|
2182
|
+
(output) => {
|
|
2183
|
+
if (!output || output === "{}") {
|
|
2184
|
+
return {
|
|
2185
|
+
text: null,
|
|
2186
|
+
hasFiles: false,
|
|
2187
|
+
filePaths: [],
|
|
2188
|
+
hasImage: false
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
const parsed = JSON.parse(output);
|
|
2192
|
+
return {
|
|
2193
|
+
text: parsed.text || null,
|
|
2194
|
+
hasFiles: parsed.hasFiles || false,
|
|
2195
|
+
filePaths: parsed.filePaths || [],
|
|
2196
|
+
hasImage: parsed.hasImage || false
|
|
2197
|
+
};
|
|
2198
|
+
},
|
|
2199
|
+
options
|
|
2200
|
+
);
|
|
2201
|
+
if (!result.success) {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
return result.data ?? null;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// src/powershell/index.ts
|
|
2208
|
+
async function getSystemContext2(options) {
|
|
2209
|
+
if (!isPowerShellAvailable()) {
|
|
2210
|
+
return null;
|
|
2211
|
+
}
|
|
2212
|
+
const frontmostApp = await getFrontmostApp2(options);
|
|
2213
|
+
if (!frontmostApp) {
|
|
2214
|
+
return null;
|
|
2215
|
+
}
|
|
2216
|
+
let explorerSelection = null;
|
|
2217
|
+
let explorerCurrentFolder = null;
|
|
2218
|
+
if (frontmostApp.isExplorer) {
|
|
2219
|
+
[explorerSelection, explorerCurrentFolder] = await Promise.all([
|
|
2220
|
+
getExplorerSelection(options),
|
|
2221
|
+
getExplorerCurrentFolder(options)
|
|
2222
|
+
]);
|
|
2223
|
+
}
|
|
2224
|
+
const clipboard = await getClipboardContent2(options);
|
|
2225
|
+
return {
|
|
2226
|
+
frontmostApp,
|
|
2227
|
+
explorerSelection,
|
|
2228
|
+
explorerCurrentFolder,
|
|
2229
|
+
clipboard: clipboard ?? { text: null, hasFiles: false, filePaths: [], hasImage: false }
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
|
|
1350
2233
|
// src/index.ts
|
|
1351
2234
|
var IS_MACOS2 = process.platform === "darwin";
|
|
1352
|
-
var
|
|
2235
|
+
var IS_WINDOWS2 = process.platform === "win32";
|
|
1353
2236
|
var PLATFORM_PACKAGES = {
|
|
1354
2237
|
"darwin-arm64": "@simen-keyboard-listener/darwin-arm64",
|
|
1355
2238
|
"win32-x64": "@simen-keyboard-listener/win32-x64"
|
|
@@ -1502,7 +2385,7 @@ var NativeKeyboardListener = class _NativeKeyboardListener {
|
|
|
1502
2385
|
}
|
|
1503
2386
|
};
|
|
1504
2387
|
function getGlobalKeyboardListener() {
|
|
1505
|
-
if (!IS_MACOS2 && !
|
|
2388
|
+
if (!IS_MACOS2 && !IS_WINDOWS2) {
|
|
1506
2389
|
throw new Error(`Unsupported platform for global keyboard listener: ${process.platform}`);
|
|
1507
2390
|
}
|
|
1508
2391
|
return NativeKeyboardListener.getInstance();
|
|
@@ -1511,7 +2394,7 @@ function createGlobalKeyboardListener() {
|
|
|
1511
2394
|
return getGlobalKeyboardListener();
|
|
1512
2395
|
}
|
|
1513
2396
|
function checkKeyboardPermission() {
|
|
1514
|
-
if (!IS_MACOS2 && !
|
|
2397
|
+
if (!IS_MACOS2 && !IS_WINDOWS2) {
|
|
1515
2398
|
return false;
|
|
1516
2399
|
}
|
|
1517
2400
|
try {
|
|
@@ -1597,7 +2480,7 @@ function getSelectedTextSmart() {
|
|
|
1597
2480
|
}
|
|
1598
2481
|
}
|
|
1599
2482
|
function setBlockSystemHotkeys(block) {
|
|
1600
|
-
if (!
|
|
2483
|
+
if (!IS_WINDOWS2) {
|
|
1601
2484
|
return;
|
|
1602
2485
|
}
|
|
1603
2486
|
try {
|
|
@@ -1638,6 +2521,7 @@ export {
|
|
|
1638
2521
|
getSystemContext,
|
|
1639
2522
|
isAppRunning,
|
|
1640
2523
|
isOsascriptAvailable,
|
|
2524
|
+
powershell_exports as powershell,
|
|
1641
2525
|
readFileContent,
|
|
1642
2526
|
readMultipleFiles,
|
|
1643
2527
|
setBlockSystemHotkeys
|