All four packs are pure Lua (sandbox=full) and call out to PowerShell helpers
where COM / native APIs are needed. No new Rust code, no rebuild required:
post_build.py --sync copies them under target/{debug,release}/resources/.
volume/
- volume_up / volume_down: 5x VK_VOLUME_UP|DOWN via keybd_event
- volume_mute: VK_VOLUME_MUTE
- volume_max: 50x VK_VOLUME_UP (system clamps)
Helper _volume_helper.ps1 P/Invokes user32!keybd_event.
apps/
- open_browser : start "" https://www.google.com (uses system default browser)
- open_notepad : notepad.exe
- open_calculator: calc.exe
- open_explorer : explorer.exe
- open_terminal : wt.exe, falls back to powershell.exe
- open_settings : ms-settings: protocol
- open_task_manager: taskmgr.exe
- lock_screen : rundll32 user32.dll,LockWorkStation
- screenshot : System.Drawing capture to ~/Pictures/jarvis_screenshot_*.png
file_search/
- find file <query> : recursive Get-ChildItem in Desktop/Documents/Downloads
(depth 3, top 5 matches). Strips Russian/English/Ukrainian trigger
prefixes from the recognized phrase, then opens explorer /select on the
first hit and notifies with the total count.
output_device/
- list_output_devices : enumerates active render endpoints via
IMMDeviceEnumerator and notifies friendly names with indices
- next_output_device : cycles the system default render device via the
undocumented IPolicyConfig::SetDefaultEndpoint (eConsole/Multimedia/
Communications). Tested locally — listed 7 devices, current correctly
identified at index 5.
195 lines
6.5 KiB
PowerShell
195 lines
6.5 KiB
PowerShell
param(
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateSet("list","set","next","current")]
|
|
[string]$Action,
|
|
|
|
[int]$Index = -1
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
Add-Type -TypeDefinition @'
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace JarvisAudio
|
|
{
|
|
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IMMDeviceEnumerator
|
|
{
|
|
[PreserveSig] int EnumAudioEndpoints(int dataFlow, int stateMask, out IMMDeviceCollection devices);
|
|
[PreserveSig] int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice device);
|
|
[PreserveSig] int GetDevice([MarshalAs(UnmanagedType.LPWStr)] string id, out IMMDevice device);
|
|
}
|
|
|
|
[Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IMMDeviceCollection
|
|
{
|
|
[PreserveSig] int GetCount(out int count);
|
|
[PreserveSig] int Item(int idx, out IMMDevice device);
|
|
}
|
|
|
|
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IMMDevice
|
|
{
|
|
[PreserveSig] int Activate(ref Guid iid, int clsCtx, IntPtr activationParams, [MarshalAs(UnmanagedType.IUnknown)] out object iface);
|
|
[PreserveSig] int OpenPropertyStore(int access, out IPropertyStore store);
|
|
[PreserveSig] int GetId([MarshalAs(UnmanagedType.LPWStr)] out string id);
|
|
[PreserveSig] int GetState(out int state);
|
|
}
|
|
|
|
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IPropertyStore
|
|
{
|
|
[PreserveSig] int GetCount(out int count);
|
|
[PreserveSig] int GetAt(int idx, out PROPERTYKEY key);
|
|
[PreserveSig] int GetValue(ref PROPERTYKEY key, out PROPVARIANT value);
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct PROPERTYKEY
|
|
{
|
|
public Guid formatId;
|
|
public int propertyId;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct PROPVARIANT
|
|
{
|
|
public ushort vt;
|
|
public ushort r1;
|
|
public ushort r2;
|
|
public ushort r3;
|
|
public IntPtr p;
|
|
public IntPtr p2;
|
|
}
|
|
|
|
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
|
public class MMDeviceEnumerator {}
|
|
|
|
[Guid("F8679F50-850A-41CF-9C72-430F290290C8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IPolicyConfig
|
|
{
|
|
[PreserveSig] int GetMixFormat();
|
|
[PreserveSig] int GetDeviceFormat();
|
|
[PreserveSig] int ResetDeviceFormat();
|
|
[PreserveSig] int SetDeviceFormat();
|
|
[PreserveSig] int GetProcessingPeriod();
|
|
[PreserveSig] int SetProcessingPeriod();
|
|
[PreserveSig] int GetShareMode();
|
|
[PreserveSig] int SetShareMode();
|
|
[PreserveSig] int GetPropertyValue();
|
|
[PreserveSig] int SetPropertyValue();
|
|
[PreserveSig] int SetDefaultEndpoint([MarshalAs(UnmanagedType.LPWStr)] string deviceId, uint role);
|
|
[PreserveSig] int SetEndpointVisibility();
|
|
}
|
|
|
|
[ComImport, Guid("870AF99C-171D-4F9E-AF0D-E63DF40C2BC9")]
|
|
public class PolicyConfigClient {}
|
|
|
|
public class Device
|
|
{
|
|
public string Id { get; set; }
|
|
public string Name { get; set; }
|
|
}
|
|
|
|
public static class Manager
|
|
{
|
|
const int eRender = 0;
|
|
const int DEVICE_STATE_ACTIVE = 1;
|
|
|
|
static PROPERTYKEY PKEY_Device_FriendlyName = new PROPERTYKEY {
|
|
formatId = new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"),
|
|
propertyId = 14
|
|
};
|
|
|
|
public static List<Device> ListRender()
|
|
{
|
|
var enumr = (IMMDeviceEnumerator)new MMDeviceEnumerator();
|
|
IMMDeviceCollection coll;
|
|
int hr = enumr.EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, out coll);
|
|
if (hr != 0) throw new Exception("EnumAudioEndpoints failed: 0x" + hr.ToString("X"));
|
|
|
|
int count;
|
|
coll.GetCount(out count);
|
|
|
|
var result = new List<Device>();
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
IMMDevice dev;
|
|
coll.Item(i, out dev);
|
|
string id;
|
|
dev.GetId(out id);
|
|
|
|
IPropertyStore store;
|
|
dev.OpenPropertyStore(0, out store);
|
|
|
|
PROPVARIANT v;
|
|
store.GetValue(ref PKEY_Device_FriendlyName, out v);
|
|
string name = v.p != IntPtr.Zero ? Marshal.PtrToStringUni(v.p) : "(unknown)";
|
|
|
|
result.Add(new Device { Id = id, Name = name });
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static string GetDefaultRenderId()
|
|
{
|
|
var enumr = (IMMDeviceEnumerator)new MMDeviceEnumerator();
|
|
IMMDevice dev;
|
|
int hr = enumr.GetDefaultAudioEndpoint(eRender, 0, out dev); // eConsole = 0
|
|
if (hr != 0) return null;
|
|
string id;
|
|
dev.GetId(out id);
|
|
return id;
|
|
}
|
|
|
|
public static void SetDefault(string deviceId)
|
|
{
|
|
var cfg = (IPolicyConfig)new PolicyConfigClient();
|
|
cfg.SetDefaultEndpoint(deviceId, 0); // eConsole
|
|
cfg.SetDefaultEndpoint(deviceId, 1); // eMultimedia
|
|
cfg.SetDefaultEndpoint(deviceId, 2); // eCommunications
|
|
}
|
|
}
|
|
}
|
|
'@ -Language CSharp
|
|
|
|
$devs = [JarvisAudio.Manager]::ListRender()
|
|
|
|
switch ($Action) {
|
|
"list" {
|
|
for ($i = 0; $i -lt $devs.Count; $i++) {
|
|
Write-Output ("{0}`t{1}" -f $i, $devs[$i].Name)
|
|
}
|
|
}
|
|
"current" {
|
|
$cur = [JarvisAudio.Manager]::GetDefaultRenderId()
|
|
for ($i = 0; $i -lt $devs.Count; $i++) {
|
|
if ($devs[$i].Id -eq $cur) {
|
|
Write-Output ("{0}`t{1}" -f $i, $devs[$i].Name)
|
|
return
|
|
}
|
|
}
|
|
Write-Output "-1`t(unknown)"
|
|
}
|
|
"set" {
|
|
if ($Index -lt 0 -or $Index -ge $devs.Count) {
|
|
Write-Error "Index out of range: $Index (have $($devs.Count) devices)"
|
|
exit 2
|
|
}
|
|
[JarvisAudio.Manager]::SetDefault($devs[$Index].Id)
|
|
Write-Output ("OK`t{0}" -f $devs[$Index].Name)
|
|
}
|
|
"next" {
|
|
$cur = [JarvisAudio.Manager]::GetDefaultRenderId()
|
|
$curIdx = -1
|
|
for ($i = 0; $i -lt $devs.Count; $i++) {
|
|
if ($devs[$i].Id -eq $cur) { $curIdx = $i; break }
|
|
}
|
|
$nextIdx = ($curIdx + 1) % $devs.Count
|
|
[JarvisAudio.Manager]::SetDefault($devs[$nextIdx].Id)
|
|
Write-Output ("OK`t{0}" -f $devs[$nextIdx].Name)
|
|
}
|
|
}
|