├── .gitignore ├── android-automate ├── AutoSunshine.flo └── AutoSunshine.png ├── .vscode └── launch.json ├── automation.config ├── config ├── sunshine_1.conf ├── sunshine_3.conf └── sunshine_2.conf ├── debug_log.txt ├── README.md ├── ApolloBulkAutomation.ahk └── lib └── VA.ahk /.gitignore: -------------------------------------------------------------------------------- 1 | debug_log.txt 2 | -------------------------------------------------------------------------------- /android-automate/AutoSunshine.flo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drajabr/My-Sunshine-setup/HEAD/android-automate/AutoSunshine.flo -------------------------------------------------------------------------------- /android-automate/AutoSunshine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drajabr/My-Sunshine-setup/HEAD/android-automate/AutoSunshine.png -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "ahk", 10 | "request": "launch", 11 | "name": "AutoHotkey v1 Debugger", 12 | "program": "${file}", 13 | "stopOnEntry": true 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /automation.config: -------------------------------------------------------------------------------- 1 | exeDirectory=C:\Program Files\Apollo 2 | confDirectory=C:\Program Files\Apollo\config 3 | platformToolsDirectory=C:\Tools\platform-tools 4 | 5 | confFiles=sunshine_1.conf,sunshine_2.conf,sunshine_3.conf 6 | 7 | logFiles=sunshine_1.log,sunshine_2.log,sunshine_3.log 8 | 9 | 10 | autoExitOnDisconnect=true 11 | autoSyncVolume=true 12 | 13 | autoCaptureAndroidMic=true 14 | androidMicDeviceID=PE3SIL21060300042 15 | 16 | autoStartAndroidCamera=true 17 | androidCamDeviceID=4200c2c9e4ef8449 18 | 19 | autoReverseTethering=true 20 | 21 | debugLevel=1 -------------------------------------------------------------------------------- /config/sunshine_1.conf: -------------------------------------------------------------------------------- 1 | av1_mode = 1 2 | controller = disabled 3 | external_ip = 0.0.0.0 4 | fec_percentage = 0 5 | file_state = sunshine_1.json 6 | fps = [60] 7 | headless_mode = enabled 8 | hevc_mode = 1 9 | install_steam_audio_drivers = disabled 10 | keep_sink_default = disabled 11 | log_path = sunshine_1.log 12 | min_fps_factor = 3 13 | min_threads = 1 14 | origin_web_ui_allowed = wan 15 | port = 17987 16 | qp = 0 17 | qsv_coder = cavlc 18 | qsv_preset = veryfast 19 | resolutions = [ 20 | 1920x1080, 21 | 1920x1200, 22 | 1280x800, 23 | 800x1280, 24 | 1440x900 25 | ] 26 | sunshine_name = Left 27 | -------------------------------------------------------------------------------- /config/sunshine_3.conf: -------------------------------------------------------------------------------- 1 | av1_mode = 1 2 | controller = disabled 3 | external_ip = 0.0.0.0 4 | fec_percentage = 0 5 | file_state = sunshine_3.json 6 | fps = [60] 7 | headless_mode = enabled 8 | hevc_mode = 1 9 | install_steam_audio_drivers = disabled 10 | keep_sink_default = disabled 11 | log_path = sunshine_3.log 12 | min_fps_factor = 3 13 | min_threads = 1 14 | origin_web_ui_allowed = pc 15 | port = 37987 16 | qp = 0 17 | qsv_coder = cavlc 18 | qsv_preset = veryfast 19 | resolutions = [ 20 | 1920x1080, 21 | 1920x1200, 22 | 1280x800, 23 | 800x1280, 24 | 1440x900, 25 | 1680x1050 26 | ] 27 | sunshine_name = Right 28 | -------------------------------------------------------------------------------- /config/sunshine_2.conf: -------------------------------------------------------------------------------- 1 | av1_mode = 1 2 | controller = disabled 3 | external_ip = 0.0.0.0 4 | fec_percentage = 0 5 | file_state = sunshine_2.json 6 | fps = [60] 7 | headless_mode = enabled 8 | hevc_mode = 1 9 | install_steam_audio_drivers = disabled 10 | keep_sink_default = disabled 11 | log_path = sunshine_2.log 12 | min_fps_factor = 3 13 | min_threads = 1 14 | port = 27987 15 | qp = 0 16 | qsv_coder = cavlc 17 | qsv_preset = veryfast 18 | resolutions = [ 19 | 1920x1080, 20 | 1920x1200, 21 | 1280x800, 22 | 800x1280, 23 | 1440x900, 24 | 1680x1050 25 | ] 26 | set_vdisplay_primary = disabled 27 | sunshine_name = Bottom 28 | -------------------------------------------------------------------------------- /debug_log.txt: -------------------------------------------------------------------------------- 1 | 20250424153352 - Cleared debug_logfile: C:\Users\Ahmed\Documents\git\My-Sunshine-setup\debug_log.txt before starting 2 | 20250424153352 - Failed to terminate residual process with PID: 44076 trying force kill 3 | 20250424153353 - Force killed residual apollo process with PID: 44076 4 | 20250424153353 - Failed to terminate residual process with PID: 26304 trying force kill 5 | 20250424153353 - Force killed residual apollo process with PID: 26304 6 | 20250424153353 - Failed to terminate residual process with PID: 37324 trying force kill 7 | 20250424153353 - Force killed residual apollo process with PID: 37324 8 | 20250424153353 - Failed to terminate residual process with PID: 9052 trying force kill 9 | 20250424153353 - Force killed residual apollo process with PID: 9052 10 | 20250424153356 - Starting Apollo instance: 1 process with param: C:\Program Files\Apollo\config\sunshine_1.conf 11 | 20250424153356 - Started Apollo instance: 1 process with PID: 29756 for param: C:\Program Files\Apollo\config\sunshine_1.conf 12 | 20250424153356 - Starting Apollo instance: 2 process with param: C:\Program Files\Apollo\config\sunshine_2.conf 13 | 20250424153357 - Started Apollo instance: 2 process with PID: 31568 for param: C:\Program Files\Apollo\config\sunshine_2.conf 14 | 20250424153357 - Starting Apollo instance: 3 process with param: C:\Program Files\Apollo\config\sunshine_3.conf 15 | 20250424153357 - Started Apollo instance: 3 process with PID: 28860 for param: C:\Program Files\Apollo\config\sunshine_3.conf 16 | 20250424153357 - Initializing Apollo has finished, procceding to auxilairy scripts. 17 | 20250424153357 - No more residual scrcpy process found 18 | 20250424153357 - Mic Device PE3SIL21060300042 status changed to: disconnected 19 | 20250424153357 - Failed to terminate existing gnirehtet process with PID: 40948 trying force kill 20 | 20250424153357 - Force killed existing gnirehtet process with PID: 40948 21 | 20250424153357 - No devices connected and gnirehtet relay process is not running 22 | 20250424153357 - Failed to terminate existing ADB process with PID: 25268 trying force kill 23 | 20250424153357 - Force killed existing ADB process with PID: 25268 24 | 20250424153357 - No more residual ADB process found 25 | 20250424153403 - New ADB device connected: 4200c2c9e4ef8449 26 | 20250424153403 - New ADB device connected: A80039832ETH024855 27 | 20250424153403 - Synced mute state: Unmuted for PIDs: 29756, 31568, 28860 28 | 20250424153403 - Starting gnirehtet relay process in autorun mode 29 | 20250424153403 - Started gnirehtet relay process with PID: 46424 for currently connected 2 devices 30 | 20250424153403 - Synced Volume: 35.000 for PIDs: 29756, 31568, 28860 31 | 20250424153404 - Apollo PID: 31568 instance: 2 terminated by script for disconnection 32 | 20250424153404 - Starting Apollo instance: 2 process with param: C:\Program Files\Apollo\config\sunshine_2.conf 33 | 20250424153404 - Restarted Apollo instance: 2 successfully with new PID: 22300 for param: C:\Program Files\Apollo\config\sunshine_2.conf 34 | 20250424153405 - Current PIDs: 29756, 28860, 47236, 22300 35 | 20250424153405 - Configured PIDs: 29756, 22300, 28860 36 | 20250424153405 - Orphan Apollo process PID: 47236 is not started by script, trying to terminate it.. 37 | 20250424153405 - Failed to terminate orphan Apollo process PID: 47236 trying force kill 38 | 20250424153405 - Force killed orphan Apollo process PID: 47236 39 | 20250424153411 - Found 'CLIENT CONNECTED' in log file: C:\Program Files\Apollo\config\sunshine_1.log Syncing volume level 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Check out the new GUI tool [ApollopFleet-Launcher](https://github.com/drajabr/Apollo-Fleet-Launcher) 2 | 3 | > [!CAUTION] 4 | > Script is not working anymore, and the [ApollopFleet-Launcher](https://github.com/drajabr/Apollo-Fleet-Launcher) renders it useless anyway, so won't try to fix it anyway. 5 | 6 | 7 | # My-Sunshine-setup 8 | Config and AutoHotKey script used in my multi monitor Apollo "fork of Sunshine" setup. 9 | 10 | If you're looking for easier tutorial you better see user @YouDoMeIDoYou compiled in his comment https://github.com/ClassicOldSong/Apollo/discussions/325#discussioncomment-12891543 11 | 12 | ## What it is 13 | Mainly the AutoHotKey script has 3 features: 14 | 1. Launch as many apollo instances as configured "has conf file inside apollo folder, and entry inside the script" 15 | 2. If client disconnect, kill the instance so windows deattaches the display, and restart it. 16 | 3. Sync master volume level (and mute status) from windows to all apollo instances currently running. 17 | 4. Start scrcpy to capture mic from specfied android device. 18 | 5. Start gnirehtet proccess so any adb connected android device can get connection vi reverse tethering. 19 | 20 | Final result? Fully plug and play android tablet(s) as external monitror using (mostly)foss solutions. 21 | 22 | ### Motivation 23 | Many thanks to [ClassicOldSong](https://github.com/ClassicOldSong) for the great work making this easily possible out of the box. 24 | 25 | Achieve multi-monitor streaming solution usable for office usage, using the most performant setup. 26 | 27 | ### Preview 28 | ![image](https://github.com/user-attachments/assets/c7448d48-f867-4103-9baa-a1fa30e5bf48) 29 | 30 | ### Alternatives 31 | Spacedesk and superDisplay are possible alternatives and maybe more suitable for your needs "and less headache to setup" 32 | 33 | ## Setup 34 | 1. Install Apollo 35 | Download and install sunshine's fork Apollo from https://github.com/ClassicOldSong/Apollo/releases. 36 | 37 | 2. install AutoHotKey 38 | Download and install v1, and v2 from https://www.autohotkey.com/ 39 | 40 | 3. Clone the repo to local folder, last update ditched powershell script in favour of AutoHotKey script doing everything. 41 | 42 | Now, inside config directory `C:\Program Files\Apollo\config`. 43 | 44 | 4. Add '.conf files', you can use mine from repo as starting point, just make sure you create the '.json' files for each instance. 45 | 5. Add json state files: create as many empty `.json` files to be used as state file, like `sunshine_1.josn`, `sunshine_3.josn`, and `sunshine_3.josn`. 46 | 47 | 6. Check inside the AHK script `ApolloBulkAutomation.ahk` for filenames and paths 48 | 7. Run script as adminstrator, check log file inside script folder for errors.. 49 | 8. If everything is fine, add the script as schduled task on startup 50 | 51 | > [!TIP] 52 | > You can setup the script to run automaticall on-login: 53 | > 1. Open task schduler: `Win+R` > `taskschd.msc` > `Create Task`. 54 | > 2. Name: `Apollo-bulk-start` (for example) > Enable `Run with highest privileges` in the bottom of `General` Tab 55 | > 3. Triggers: `New` > `At log on` 56 | > 4. Actions: `New` > `Start a program` > Select `ApolloBulkAutomation.ahk`. 57 | > 5. Test run the task: `Select task` > `Right-Mouse Button click` > `Run` 58 | 59 | > [!WARNING] 60 | > **You need to disable the default apollo service for this to work properly.** 61 | > 62 | > Inside Apollo installation path, there's a `scripts` folder i.e `C:\Program Files\Apollo\scripts` 63 | > Run `uninstall-service.bat` as adminstrator so apollo default service won't run at startup. 64 | 65 | ## Bonus 66 | ### Connect android devices via USB 67 | Using https://github.com/cotzhang/app.Cot-Hypernet2 to automatically enable "reverse" tethering for android devices via ADB, it actually runs proxy on laptop and use kinda VPN interface on Android to pass the connections over that proxy via ADB reverse, it works better than WiFi on old/cheapo android tablets, and I need to keep usb connected for charging anyway. 68 | 69 | > [!NOTE] 70 | > You can replace gnirehtet.apk with the https://github.com/Linus789/gnirehtetx as it has some quality of life enhancments, mainly for me its auto exit on tablet, which made it easier to use the big tablet as normal when I'm not working "just disconnect and it'll automaticall stop the gnirehtet vpn, so you don't have to ;) 71 | 72 | Anopther option is to use native hardware tethering as a workaround to have a network between PC and Android, I noticed slight better performance than the ADB method, so here it is: 73 | 1. Enable USB tethering from android settings 74 | 2. Check the IP your computer acquired via DHCP 75 | 3. Set it to static IP in the same range 76 | 4. Use that IP to connect from the android tablet to the PC 77 | I noticed my 2 tablets gave diffrent IP range for the laptop one gave 192.168.42.x, and the other gave 192.168.98.x range, I have no clue if it will work if both gave same range "as the android device itself will usually have the .1 IP in the range, so having 2 devices with same IP in same subnet doesn't sound good, but further testing needed. 78 | Advantages: No internet connection to Android devices, No need for ADB, More stable connection, and no need extra software. 79 | 80 | ### Auto launch and connect to hosts from android tablet clients 81 | This may not be easy to setup for first time Automate App users, but ther result deserve the setup pain 82 | 1. Download and install "Automate" App 83 | 2. Create launcher shortcut for the desktop from Moonlight app "or better, Artimis from ClassicOldSong" 84 | 3. Enable "App start inspect" flow in Automate app 85 | 4. Now go back to home screen, and connect to host from the shortcut, then long-click home button to get the prameters which the app launched with, we're looking for the Extras section for example: 86 | ```ini 87 | • Package: com.limelight.noir 88 | • Activity class: com.limelight.Game 89 | • Action: 90 | • Data URI: 91 | • MIME type: 92 | • Categories: 93 | • Extras: HttpsPort as Int: 17982, PcName as String: Left, UniqueId as String: b368efbc98069085, HDR as Boolean: 0, Host as String: 192.168.1.130, Port as Int: 17987, UUID as String: BBA3F7D5-7DA2-7B02-4C0E-681A7131C02B, AppId as Int: 881448767, VirtualDisplay as Boolean: 0, AppName as String: Desktop 94 | ``` 95 | 96 | 5. Edit "App start" block, in "Extras" field need to do little work, to match the parameters with Automate sytax, mainly the brackets and the quotation marks, something like this: 97 | ```ini 98 | { 99 | "HttpsPort": 17982, 100 | "PcName": "Left", 101 | "UniqueId": "b368efbc98069085", 102 | "HDR": false, 103 | "Host": "192.168.1.130", 104 | "Port": 17987, 105 | "UUID": "BBA3F7D5-7DA2-7B02-4C0E-681A7131C02B", 106 | "AppId": 881448767, 107 | "VirtualDisplay": false, 108 | "AppName": "Desktop" 109 | } 110 | ``` 111 | Doing that on touchscreen maybe annoying, so I created a simple webpage to extract the extras part we need: https://appstart-extrasparameters.pages.dev/ 112 | 6. Now, remaining the "HTTPS request" block, which is responsible to ping the url of sunshine WebUI to auto launch or close app, in my case it was `https://192.168.1.130:17988` edit according to each instance webUI url, hint: WebUI Port = sunshine port - 1. 113 | 114 | ### Seperate Audio output device for each display 115 | UPDATE: I'm not using this setup anymore, Its a PITA and not worth it. I now use FxSound to manually switch audio output, and still keep 1 dummy virtual audio device "not configured in apollo" but if I select it the host won't output any actual sound, but apollo can still capture it. So its my option when I want to mute the host while keeping clients audio "this is my daily usage now", also, AutoHotkey to keep the audio levels synced. 116 | 117 | 118 | To make use of the speaker in each of these devices, in such a way each display outputs audio of the thing beinng displayed on it. While audio multitasking may not always be practical, but most of the time I have background music playing even if I'm in a meeting 😆 even for watching multiple live streams. 119 | 120 | If you need Audio multitasking, and your brain can handle it "not easy", this section is for you. the idea is to create multiple virtual audio devices, and set each one as a sink for each instance, also, disable "Play audio on PC" on android clients using artemis or monlight and enable "Mute host PC speakers while streaming" on windows clients using moonlight-qt, this is important so each Apollo/sunshine instace doesn't playback its captured stream to the main playback device again, thus combining what we want to seperate :) . 121 | 122 | 1. Prepare virtual audio devices: In the host I used VAC "virtual audio cable" (paid) to create 3 virtual audio devices, you can also use ab-cable or any other virtual audio device app you like. 123 | 2. Rename each virtual device: from windows audio control panell, you can rename each device to your like, for me I set these 3 names "left" "bottom" "right" so I can put them in configuration using their name, also when switching apps I have conventional names for audio outputs. 124 | 3. Configure each instance: In **Virtual Sink** option for each instance add the corrosponding virtual device name. this is the device apollo/sunshine will capture and send to the client AND not play it back to default audio output. 125 | 126 | 127 | > [!NOTE] 128 | > Now, I no longer use multi audio device setup, and the AHK script has part to sync audio level for all instances with tha master volume level, that may interfere with your needs if multi audio output is critical for you 129 | 130 | 131 | Now, to route audio output from windows apps, use windows control panel, and to route audio output from diffrent browser tabs, I found extension "AudioPick" as I'm using soundcloud, youtube, and others as PWA, and I can set each one to output specfic device by default and it remebers them too. 132 | 133 | ### Control volume level from inside the host 134 | The issue is Apollo captures digital audio (raw), and the only way to control volume level is from the client, which is not suitable for my daily usage. 135 | 136 | Using windows volume mixer, I figured out we can control Apollo app volume level from there, and it actually worked! (no one knows how). anyway, in the repo the AHK V1 script (also called from the powershell script) that mainly keep volume level and mute status in sync between the master level and apollo app level inside the windows mixer. 137 | 138 | 139 | 140 | > [!NOTE] 141 | > This is guide is made using https://github.com/ClassicOldSong/Apollo repo. 142 | > Apollo is a fork that despite being backed by only one dev, it recently got more fixes/features implemented in a faster pace than mainstream sunshine, and probably the two won't be compatible in the near future. 143 | 144 | > [!CAUTION] 145 | > If you decided to use Apollo, please, don't ask for support on moonlight-stream discord server or open issues related to Apollo in sunshine mainstream repo. 146 | 147 | > [!TIP] 148 | > If you want support or have issues you're welcome to open discussion or create an issue in Apollo's repo, but again, don't mix between both. 149 | 150 | 151 | 152 | 153 | > [!CAUTION] 154 | > I'm not a programmer, most of the actual script was written with help from copilot and deepseek. 155 | -------------------------------------------------------------------------------- /ApolloBulkAutomation.ahk: -------------------------------------------------------------------------------- 1 | #Persistent 2 | #NoEnv 3 | #Requires AutoHotkey v1.1.33+ 4 | 5 | SetWorkingDir %A_ScriptDir% 6 | SetBatchLines, -1 7 | 8 | #Include %A_ScriptDir%\lib\VA.ahk ; Volume Automation library 9 | 10 | configFile := A_ScriptDir . "\automation.config" 11 | config := {} 12 | 13 | Loop, Read, %configFile% 14 | { 15 | StringSplit, keyValue, A_LoopReadLine, = 16 | if (keyValue0 = 2) 17 | { 18 | config[keyValue1] := keyValue2 19 | } 20 | } 21 | 22 | ; Access the configuration values 23 | autoExitOnDisconnect := config["autoExitOnDisconnect"] 24 | autoSyncVolume := config["autoSyncVolume"] 25 | autoCaptureAndroidMic := config["autoCaptureAndroidMic"] 26 | androidMicDeviceID := config["androidMicDeviceID"] 27 | autoStartAndroidCamera := config["autoStartAndroidCamera"] 28 | androidCamDeviceID := config["androidCamDeviceID"] 29 | autoReverseTethering := config["autoReverseTethering"] 30 | exeDirectory := config["exeDirectory"] 31 | confDirectory := config["confDirectory"] 32 | platformToolsDirectory := config["platformToolsDirectory"] 33 | debugLevel := config["debugLevel"] 34 | 35 | ; Parse confFiles and logFiles as arrays 36 | confFiles := ParseArray(config["confFiles"]) 37 | logFiles := ParseArray(config["logFiles"]) 38 | ; Default values if not overridden by config.txt 39 | if !confFiles 40 | confFiles := ["sunshine.conf"] 41 | if !logFiles 42 | logFiles := ["sunshine.log"] 43 | if !autoExitOnDisconnect 44 | autoExitOnDisconnect := true 45 | if !autoSyncVolume 46 | autoSyncVolume := true 47 | if !autoCaptureAndroidMic 48 | autoCaptureAndroidMic := false 49 | if !androidMicDeviceID 50 | androidMicDeviceID := "" 51 | if !autoStartAndroidCamera 52 | autoStartAndroidCamera := false 53 | if !androidCamDeviceID 54 | androidCamDeviceID := "" 55 | if !autoReverseTethering 56 | autoReverseTethering := false 57 | if !exeDirectory 58 | exeDirectory := "C:\Program Files\Apollo" 59 | if !confDirectory 60 | confDirectory := "C:\Program Files\Apollo\config" 61 | if !platformToolsDirectory 62 | platformToolsDirectory := "C:\platform-tools" 63 | if !debugLevel 64 | debugLevel := 1 65 | 66 | logFilePath := A_ScriptDir . "\debug_log.txt" 67 | apolloExePath := exeDirectory . "\sunshine.exe" 68 | adbExePath := platformToolsDirectory . "\adb.exe" 69 | scrCpyPath := platformToolsDirectory . "\scrcpy.exe" 70 | gnirehtetExecPath := platformToolsDirectory . "\gnirehtet.exe" 71 | 72 | 73 | LogMessage(level, message) { 74 | global logFilePath, debugLevel 75 | if (level <= debugLevel) { 76 | FileAppend, %A_Now% - %message%`n, %logFilePath% 77 | } 78 | } 79 | 80 | ParseArray(str) { 81 | array := [] 82 | Loop, Parse, str, `, 83 | { 84 | array.Push(A_LoopField) 85 | } 86 | return array 87 | } 88 | JoinArray(arr, delimiter) { 89 | result := "" 90 | Loop, % arr.MaxIndex() { 91 | result .= arr[A_Index] . (A_Index < arr.MaxIndex() ? delimiter : "") 92 | } 93 | return result 94 | } 95 | 96 | ; https://www.autohotkey.com/boards/viewtopic.php?style=19&t=84976 97 | CmdRetWithTimeout(sCmd, callBackFuncObj := "", encoding := ""){ 98 | static HANDLE_FLAG_INHERIT := 0x00000001, flags := HANDLE_FLAG_INHERIT 99 | , STARTF_USESTDHANDLES := 0x100, CREATE_NO_WINDOW := 0x08000000 100 | 101 | (encoding = "" && encoding := "cp" . DllCall("GetOEMCP", "UInt")) 102 | DllCall("CreatePipe", "PtrP", hPipeRead, "PtrP", hPipeWrite, "Ptr", 0, "UInt", 0) 103 | DllCall("SetHandleInformation", "Ptr", hPipeWrite, "UInt", flags, "UInt", HANDLE_FLAG_INHERIT) 104 | 105 | VarSetCapacity(STARTUPINFO , siSize := A_PtrSize*4 + 4*8 + A_PtrSize*5, 0) 106 | NumPut(siSize , STARTUPINFO) 107 | NumPut(STARTF_USESTDHANDLES, STARTUPINFO, A_PtrSize*4 + 4*7) 108 | NumPut(hPipeWrite , STARTUPINFO, A_PtrSize*4 + 4*8 + A_PtrSize*3) 109 | NumPut(hPipeWrite , STARTUPINFO, A_PtrSize*4 + 4*8 + A_PtrSize*4) 110 | 111 | VarSetCapacity(PROCESS_INFORMATION, A_PtrSize*2 + 4*2, 0) 112 | 113 | if !DllCall("CreateProcess", "Ptr", 0, "Str", sCmd, "Ptr", 0, "Ptr", 0, "UInt", true, "UInt", CREATE_NO_WINDOW 114 | , "Ptr", 0, "Ptr", 0, "Ptr", &STARTUPINFO, "Ptr", &PROCESS_INFORMATION) 115 | { 116 | DllCall("CloseHandle", "Ptr", hPipeRead) 117 | DllCall("CloseHandle", "Ptr", hPipeWrite) 118 | throw "CreateProcess is failed" 119 | } 120 | DllCall("CloseHandle", "Ptr", hPipeWrite) 121 | VarSetCapacity(sTemp, 4096), nSize := 0 122 | while DllCall("ReadFile", "Ptr", hPipeRead, "Ptr", &sTemp, "UInt", 4096, "UIntP", nSize, "UInt", 0) { 123 | sOutput .= stdOut := StrGet(&sTemp, nSize, encoding) 124 | ( callBackFuncObj && callBackFuncObj.Call(stdOut) ) 125 | } 126 | DllCall("CloseHandle", "Ptr", NumGet(PROCESS_INFORMATION)) 127 | DllCall("CloseHandle", "Ptr", NumGet(PROCESS_INFORMATION, A_PtrSize)) 128 | DllCall("CloseHandle", "Ptr", hPipeRead) 129 | Return sOutput 130 | } 131 | 132 | 133 | pidListFromName(name) { 134 | static wmi := ComObjGet("winmgmts:\\.\root\cimv2") 135 | 136 | if (name == "") 137 | return 138 | 139 | PIDs := [] 140 | for Process in wmi.ExecQuery("SELECT * FROM Win32_Process WHERE Name = '" name "'") 141 | PIDs.Push(Process.processId) 142 | 143 | return PIDs 144 | } 145 | 146 | ArrayHasValue(array, value) { 147 | for index, item in array { 148 | if (item == value) { 149 | return true 150 | } 151 | } 152 | return false 153 | } 154 | 155 | global ConfiguredApolloPIDs := [], firstRunApollo := true 156 | 157 | BulkStartApollo() { 158 | global apolloExePath, exeDirectory, confDirectory, logFiles, confFiles, debugLevel, logFilePath , ConfiguredApolloPIDs , firstRunApollo, processTerminated, processKilled, TerminatedPIDs 159 | static running := false 160 | if (running) 161 | return 162 | running := true 163 | LogMessage(3, "Starting BulkStartApollo()") 164 | ; Populate PIDs for every sunshine.exe process currently running 165 | CurrentPIDs := pidListFromName("sunshine.exe") 166 | if (firstRunApollo) { 167 | Terminated := false, Killed := false 168 | ; Clear the log file before restarting 169 | logFile := confDirectory . "\" . logFiles[TerminatedIndexes[A_Index]] 170 | FileDelete, %logFilePath% 171 | FileAppend,, %logFilePath% ; Create an empty log file 172 | LogMessage(1, "Cleared debug_logfile: " . logFilePath . " before starting") 173 | Sleep, 50 174 | Loop , % CurrentPIDs.MaxIndex() { 175 | pid := CurrentPIDs[A_Index] 176 | LogMessage(2, "Trying to terminate residual apollo process PID: " . pid) 177 | RunWait, %ComSpec% /c taskkill /PID %pid% /T /FI "SIGNAL=SIGINT",, Hide 178 | sleep, 50 179 | Process, Exist, %pid% 180 | if (ErrorLevel != 0) { 181 | LogMessage(1, "Failed to terminate residual process with PID: " . pid . " trying force kill") 182 | RunWait, %ComSpec% /c tskill %pid%,, Hide 183 | Sleep, 50 184 | Process, Exist, %pid% 185 | if (ErrorLevel != 0) { 186 | LogMessage(1, "Failed to force kill residual process with PID: " . pid) 187 | } else { 188 | LogMessage(1, "Force killed residual apollo process with PID: " . pid) 189 | Killed := true 190 | } 191 | } else { 192 | LogMessage(1, "Terminated residual process with PID: " . pid) 193 | Terminated := true 194 | } 195 | } 196 | if (Killed) 197 | Sleep, 3000 198 | else if (Terminated) 199 | Sleep, 500 200 | } else if (CurrentPIDs.MaxIndex() > configuredApolloPIDs.MaxIndex()) { 201 | LogMessage(1, "Current PIDs: " . JoinArray(CurrentPIDs, ", ")) 202 | LogMessage(1, "Configured PIDs: " . JoinArray(ConfiguredApolloPIDs, ", ")) 203 | Loop, % CurrentPIDs.MaxIndex() { 204 | pid := CurrentPIDs[A_Index] 205 | LogMessage(2, "Checking if PID: " . pid . " is orphaned") 206 | if !(ArrayHasValue(ConfiguredApolloPIDs, pid)) { 207 | LogMessage(1, "Orphan Apollo process PID: " . pid . " is not started by script, trying to terminate it..") 208 | RunWait, %ComSpec% /c taskkill /PID %pid% /T /FI "SIGNAL=SIGINT",, Hide 209 | Sleep, 50 210 | Process, Exist, %pid% 211 | if (ErrorLevel != 0) { 212 | LogMessage(1, "Failed to terminate orphan Apollo process PID: " . pid . " trying force kill") 213 | RunWait, %ComSpec% /c tskill %pid%,, Hide 214 | Sleep, 50 215 | Process, Exist, %pid% 216 | if (ErrorLevel != 0) { 217 | LogMessage(1, "Failed to force kill orphan Apollo process PID: " . pid) 218 | } else { 219 | LogMessage(1, "Force killed orphan Apollo process PID: " . pid) 220 | } 221 | } else { 222 | LogMessage(1, "Terminated orphan Apollo process PID: " . pid . " successfully") 223 | } 224 | } 225 | } 226 | } 227 | Loop, % confFiles.MaxIndex() { 228 | ;logFile := confDirectory . "\" . logFiles[TerminatedIndexes[A_Index]] 229 | ;FileDelete, %logFile% 230 | ;FileAppend,, %logFile% ; Create an empty log file 231 | ;LogMessage(1, "Cleared logfile: " . logFile . " before restarting") 232 | ;Sleep, 50 233 | param := confDirectory . "\" . confFiles[A_Index] 234 | if (firstRunApollo){ 235 | LogMessage(1, "Starting Apollo instance: " . A_Index . " process with param: " . param) 236 | Run, "%apolloExePath%" "%param%", %exeDirectory%, Hide, newPid 237 | Sleep , 100 238 | Process, Exist, %newPid% 239 | if (ErrorLevel = 0) { 240 | LogMessage(1, "Failed to start Apollo instance: " . A_Index . " process with param: " . param) 241 | } else { 242 | ConfiguredApolloPIDs[A_Index] := newPid 243 | LogMessage(1, "Started Apollo instance: " . A_Index . " process with PID: " . newPid . " for param: " . param) 244 | } 245 | } else { 246 | pid := ConfiguredApolloPIDs[A_Index] 247 | Process, Exist, %pid% 248 | if (ErrorLevel = 0 || pid = "") { 249 | if (pid in TerminatedPIDs) { 250 | LogMessage(1, "Apollo PID: " . pid . " instance: " . A_Index . " terminated by script for disconnection") 251 | if (processKilled) 252 | Sleep, 3000 253 | else 254 | Sleep, 50 255 | TerminatedPIDs.__Delete(pid) 256 | } else { 257 | LogMessage(1, "Apollo PID: " . pid . " instance: " . JoinArray(TerminatedPIDs, ", ") . " terminated externally") 258 | } 259 | LogMessage(1, "Starting Apollo instance: " . A_Index . " process with param: " . param) 260 | Run, "%apolloExePath%" "%param%", %exeDirectory%, Hide, newPid 261 | Sleep , 100 262 | Process, Exist, %newPid% 263 | if (ErrorLevel = 0) { 264 | LogMessage(1, "Failed to restart Apollo instance: " . A_Index . " process with param: " . param) 265 | } else { 266 | LogMessage(1, "Restarted Apollo instance: " . A_Index . " successfully with new PID: " . newPid . " for param: " . param) 267 | ConfiguredApolloPIDs[A_Index] := newPid 268 | } 269 | } 270 | else { 271 | LogMessage(2, "Checked Apollo instance: " . A_Index . " process with PID: " . pid . " is running okay") 272 | } 273 | } 274 | } 275 | if ((processTerminated || processKilled) && TerminatedPIDs.MaxIndex() = 0) { 276 | processKilled := false 277 | processTerminated := false 278 | } 279 | if (firstRunApollo) 280 | firstRunApollo := false 281 | running := false 282 | } 283 | 284 | global processTerminated := false, processKilled := false, TerminatedPIDs := [] 285 | WatchLogFiles() { 286 | global apolloExePath, logFiles, exeDirectory, confDirectory, confFiles, debugLevel, ConfiguredApolloPIDs, processKilled, processTerminated, TerminatedIndexes 287 | static running := False 288 | 289 | if (running) 290 | return 291 | running := True 292 | LogMessage(3, "Starting WatchLogFiles()") 293 | 294 | static lastReadPositions := {} 295 | Loop, % ConfiguredApolloPIDs.MaxIndex() { 296 | logFile := confDirectory . "\" . logFiles[A_Index] 297 | if (!lastReadPositions.HasKey(logFile)) { 298 | lastReadPositions[logFile] := 0 299 | } 300 | FileGetSize, fileSize, %logFile% 301 | if (fileSize > lastReadPositions[logFile]) { 302 | FileRead, logContent, %logFile% 303 | logContent := SubStr(logContent, lastReadPositions[logFile] + 1) 304 | lastReadPositions[logFile] := fileSize 305 | LogMessage(2, "Checking log file: " . logFile) 306 | if InStr(logContent, "CLIENT DISCONNECTED") { 307 | pid := ConfiguredApolloPIDs[A_Index] 308 | if (pid) { 309 | LogMessage(1, "Found 'CLIENT DISCONNECTED' in log file: " . logFile . " for Apollo instance: " . A_Index . " with PID: " . pid) 310 | Sleep, 50 311 | LogMessage(2, "Trying to Terminate Apollo instance: " . A_Index . " with PID: " . pid) 312 | RunWait, %ComSpec% /c taskkill /PID %pid% /T /FI "SIGNAL=SIGINT",, Hide 313 | Sleep, 50 314 | Process, Exist, %pid% 315 | if (ErrorLevel != 0) { 316 | LogMessage(1, "Failed to terminate Apollo instance: " . A_Index . " with PID: " . pid . " trying force kill") 317 | RunWait, %ComSpec% /c tskill %pid%,, Hide 318 | Sleep, 50 319 | Process, Exist, %pid% 320 | if (ErrorLevel != 0) { 321 | LogMessage(1, "Failed to force kill Apollo instance: " . A_Index . " with PID: " . pid) 322 | } else { 323 | LogMessage(1, "Force killed Apollo instance: " . A_Index . " with PID: " . pid) 324 | processKilled := true 325 | TerminatedPIDs.Push(pid) 326 | } 327 | } else { 328 | LogMessage(1, "Terminated Apollo Apollo instance: " . A_Index . " with PID: " . pid) 329 | processTerminated := true 330 | TerminatedPIDs.Push(pid) 331 | } 332 | } 333 | } 334 | } 335 | } 336 | LogMessage(3, "WatchLogFiles() completed") 337 | running := False 338 | } 339 | 340 | SyncVolume() { 341 | global logFiles, confDirectory, debugLevel, pids 342 | static lastVolume := -1 343 | static lastMute := -1 344 | 345 | static running := False 346 | if (running) 347 | return 348 | if (!ConfiguredApolloPIDs || ConfiguredApolloPIDs.MaxIndex() = 0) 349 | return 350 | LogMessage(3, "Starting SyncVolume()") 351 | running := True 352 | 353 | masterVolume := VA_GetMasterVolume() 354 | isMuted := VA_GetMasterMute() 355 | 356 | clientConnected := false 357 | 358 | static lastReadPositions := {} 359 | Loop, % ConfiguredApolloPIDs.MaxIndex() { 360 | logFile := confDirectory . "\" . logFiles[A_Index] 361 | if (!lastReadPositions.HasKey(logFile)) { 362 | lastReadPositions[logFile] := 0 363 | } 364 | FileGetSize, fileSize, %logFile% 365 | if (fileSize > lastReadPositions[logFile]) { 366 | FileRead, logContent, %logFile% 367 | logContent := SubStr(logContent, lastReadPositions[logFile] + 1) 368 | lastReadPositions[logFile] := fileSize 369 | LogMessage(2, "Checking log file: " . logFile) 370 | if InStr(logContent, "CLIENT CONNECTED") { 371 | LogMessage(1, "Found 'CLIENT CONNECTED' in log file: " . logFile . " Syncing volume level") 372 | clientConnected := true 373 | } 374 | } 375 | } 376 | 377 | updatedVolumePIDs := [] 378 | updatedMutePIDs := [] 379 | 380 | if (clientConnected) { 381 | Loop, 150 { 382 | masterVolume := VA_GetMasterVolume() 383 | isMuted := VA_GetMasterMute() 384 | for index, PID in ConfiguredApolloPIDs { 385 | VA_SetAppVolume(PID, masterVolume) 386 | if (isMuted != lastMute) 387 | VA_SetAppMute(PID, isMuted ? 1 : 0) 388 | LogMessage(3, "Retrying to set volume/mute for PID: " . PID . " (Current Volume: " . currentVolume . ", Expected: " . masterVolume . ", Current Mute: " . currentMute . ", Expected: " . isMuted . ")") 389 | } 390 | Sleep, 50 391 | } 392 | for index, PID in ConfiguredApolloPIDs { 393 | updatedVolumePIDs.Push(PID) 394 | updatedMutePIDs.Push(PID) 395 | } 396 | lastVolume := masterVolume 397 | lastMute := isMuted 398 | } 399 | else if ( isMuted != lastMute) { 400 | LogMessage(2, "Syncing mute settings") 401 | for index, PID in ConfiguredApolloPIDs { 402 | if (isMuted != lastMute) 403 | VA_SetAppMute(PID, isMuted ? 1 : 0) 404 | updatedMutePIDs.Push(PID) 405 | } 406 | lastMute := isMuted 407 | } 408 | else if ( masterVolume != lastVolume) { 409 | LogMessage(2, "Syncing volume settings") 410 | for index, PID in ConfiguredApolloPIDs { 411 | VA_SetAppVolume(PID, masterVolume) 412 | updatedVolumePIDs.Push(PID) 413 | } 414 | lastVolume := masterVolume 415 | } 416 | 417 | if (updatedMutePIDs.MaxIndex() > 0) 418 | LogMessage(1, "Synced mute state: " . (isMuted ? "Muted" : "Unmuted") . " for PIDs: " . JoinArray(updatedMutePIDs, ", ")) 419 | if (updatedVolumePIDs.MaxIndex() > 0) 420 | LogMessage(1, "Synced Volume: " . masterVolume . " for PIDs: " . JoinArray(updatedVolumePIDs, ", ")) 421 | 422 | LogMessage(3, "Volume and mute settings synced for all updated processes") 423 | running := False 424 | } 425 | 426 | global CurrentlyConnectedIDs 427 | 428 | watchAndroidADBDevices(){ 429 | global adbExePath, LogMessage, CmdRetWithTimeout, CurrentlyConnectedIDs 430 | static firstRun := true, running:= false 431 | if (firstRun) { 432 | firstRun := false 433 | Loop { 434 | Process, Exist, adb.exe 435 | if (ErrorLevel = 0){ 436 | LogMessage(1, "No more residual ADB process found") 437 | break 438 | } 439 | pid := ErrorLevel 440 | RunWait, %ComSpec% /c taskkill /PID %pid% /T /FI "SIGNAL=SIGINT",, Hide 441 | Sleep, 50 442 | Process, Exist, adb.exe 443 | if (ErrorLevel != 0) { 444 | LogMessage(1, "Failed to terminate existing ADB process with PID: " . pid . " trying force kill") 445 | RunWait, %ComSpec% /c tskill %pid%,, Hide 446 | Sleep, 50 447 | Process, Exist, adb.exe 448 | if (ErrorLevel != 0) { 449 | LogMessage(1, "Failed to force kill existing ADB process with PID: " . pid) 450 | } else { 451 | LogMessage(1, "Force killed existing ADB process with PID: " . pid) 452 | } 453 | } else { 454 | LogMessage(1, "Terminated existing ADB process with PID: " . pid) 455 | } 456 | } 457 | } 458 | else if (running) { 459 | sleep, 500 460 | LogMessage(2, "Already running ADB device watcher, skipping this run") 461 | return 462 | } 463 | command := adbExePath . " devices" 464 | running := true 465 | adbOutput := CmdRetWithTimeout(command, 5000) ; 5 seconds timeout 466 | running := false 467 | static lastConnectedIDs := {} 468 | CurrentlyConnectedIDs := {} 469 | 470 | Loop, Parse, adbOutput, `n, `r 471 | { 472 | if (RegExMatch(A_LoopField, "^\s*(\S+)\s+device", match)) { 473 | CurrentlyConnectedIDs[match1] := true 474 | } 475 | } 476 | 477 | ; Compare with the last connected IDs and log changes 478 | for deviceID in CurrentlyConnectedIDs { 479 | if (!lastConnectedIDs.HasKey(deviceID)) { 480 | LogMessage(1, "New ADB device connected: " . deviceID) 481 | } 482 | } 483 | 484 | for deviceID in lastConnectedIDs { 485 | if (!CurrentlyConnectedIDs.HasKey(deviceID)) { 486 | LogMessage(1, "ADB Device disconnected: " . deviceID) 487 | } 488 | } 489 | 490 | ; Update the last connected IDs 491 | lastConnectedIDs := CurrentlyConnectedIDs.Clone() 492 | } 493 | 494 | 495 | MaintainMicConnectivity() { 496 | global adbExePath, LogMessage, CurrentlyConnectedIDs, androidMicDeviceID, scrCpyPath 497 | static lastStatus := "", ShouldRunMic := true, scrcpyPID := "", consolePID := "", firstRun := true 498 | 499 | ; Kill any existing scrcpy.exe process on the first run 500 | if (firstRun) { 501 | firstRun := false 502 | Loop { 503 | Process, Exist, scrcpy.exe 504 | if (ErrorLevel = 0){ 505 | LogMessage(1, "No more residual scrcpy process found") 506 | break 507 | } 508 | pid := ErrorLevel 509 | RunWait, %ComSpec% /c taskkill /PID %pid% /T /FI "SIGNAL=SIGINT",, Hide 510 | Sleep, 50 511 | Process, Exist, scrcpy.exe 512 | if (ErrorLevel != 0) { 513 | LogMessage(1, "Failed to terminate existing scrcpy process with PID: " . pid . " trying force kill") 514 | RunWait, %ComSpec% /c tskill %pid%,, Hide 515 | Sleep, 50 516 | Process, Exist, scrcpy.exe 517 | if (ErrorLevel != 0) { 518 | LogMessage(1, "Failed to force kill existing scrcpy process with PID: " . pid) 519 | } else { 520 | LogMessage(1, "Force killed existing scrcpy process with PID: " . pid) 521 | break 522 | } 523 | } else { 524 | LogMessage(1, "Terminated existing scrcpy process with PID: " . pid) 525 | break 526 | } 527 | } 528 | } 529 | 530 | ; Determine device status 531 | deviceStatus := CurrentlyConnectedIDs.HasKey(androidMicDeviceID) ? "connected" : "disconnected" 532 | 533 | if (deviceStatus != lastStatus) { 534 | LogMessage(1, "Mic Device " androidMicDeviceID " status changed to: " deviceStatus) 535 | ShouldRunMic := true 536 | lastStatus := deviceStatus 537 | } 538 | 539 | if (deviceStatus = "connected") { 540 | if (ShouldRunMic) { 541 | Process, Exist, scrcpyPID 542 | if (ErrorLevel != 0) { 543 | LogMessage(1, "scrcpy process " . scrcpyPID . " is already running, skipping restart.") 544 | ShouldRunMic := false 545 | } else { 546 | Run, "%adbExePath%" -s %androidMicDeviceID% shell input keyevent KEYCODE_WAKEUP,, Hide 547 | Sleep, 50 548 | Run, "%scrCpyPath%" -s %androidMicDeviceID% --no-video --no-window --audio-source=mic --window-borderless,, Hide, consolePID 549 | Loop, 100 { 550 | Sleep, 50 551 | Process, Exist, scrcpy.exe 552 | if (ErrorLevel != 0) { 553 | scrcpyPID := ErrorLevel 554 | LogMessage(1, "Started scrcpy with PID: " scrcpyPID " for device: " androidMicDeviceID) 555 | ShouldRunMic := false 556 | break 557 | } 558 | } 559 | if (!scrcpyPID) { 560 | LogMessage(0, "Failed to start scrcpy for device: " androidMicDeviceID) 561 | RunWait, %ComSpec% /c taskkill /PID %consolePID% /T /FI "SIGNAL=SIGINT",, Hide 562 | } 563 | } 564 | } else { 565 | Process, Exist, %scrcpyPID% 566 | if (ErrorLevel = 0) { 567 | LogMessage(1, "scrcpy process " . scrcpyPID . " is not running, restarting.") 568 | ShouldRunMic := true 569 | scrcpyPID := "" 570 | } 571 | } 572 | } else if (scrcpyPID) { 573 | ; Terminate scrcpy process if the device is disconnected 574 | LogMessage(1, "Device " androidMicDeviceID " disconnected, terminating scrcpy process with PID: " scrcpyPID) 575 | RunWait, %ComSpec% /c taskkill /PID %scrcpyPID% /T /FI "SIGNAL=SIGINT",, Hide 576 | scrcpyPID := "" 577 | if (consolePID) { 578 | LogMessage(1, "Terminating residual console process with PID: " . consolePID) 579 | RunWait, %ComSpec% /c taskkill /PID %consolePID% /T /FI "SIGNAL=SIGINT",, Hide 580 | consolePID := "" 581 | } 582 | } 583 | } 584 | MaintainReverseTethering() { 585 | global gnirehtetExecPath, LogMessage, platformToolsDirectory, CurrentlyConnectedIDs 586 | static gnirehtetRelayPID := "", gnirehtetRunning, lastConnectedDevices := 0 , firstRun := true 587 | if (firstRun) { 588 | firstRun := false 589 | Loop { 590 | Process, Exist, gnirehtet.exe 591 | if (ErrorLevel = 0){ 592 | LogMessage(1, "No more residual gnirehtet process found") 593 | gnirehtetRunning := false 594 | break 595 | } 596 | pid := ErrorLevel 597 | RunWait, %ComSpec% /c taskkill /PID %consolePID% /T /FI "SIGNAL=SIGINT",, Hide 598 | Sleep, 50 599 | Process, Exist, gnirehtet.exe 600 | if (ErrorLevel != 0) { 601 | LogMessage(1, "Failed to terminate existing gnirehtet process with PID: " . pid . " trying force kill") 602 | RunWait, %ComSpec% /c tskill %pid%,, Hide 603 | Sleep, 50 604 | Process, Exist, gnirehtet.exe 605 | if (ErrorLevel != 0) { 606 | LogMessage(1, "Failed to force kill existing gnirehtet process with PID: " . pid) 607 | } else { 608 | LogMessage(1, "Force killed existing gnirehtet process with PID: " . pid) 609 | gnirehtetRunning := false 610 | break 611 | } 612 | } else { 613 | LogMessage(1, "Terminated existing gnirehtet process with PID: " . pid) 614 | gnirehtetRunning := false 615 | break 616 | } 617 | } 618 | } 619 | connectedDevices := CurrentlyConnectedIDs.Count() 620 | LogMessage(3, "Number of currently connected devices: " . connectedDevices) 621 | if (connectedDevices != lastConnectedDevices){ 622 | if ( connectedDevices > 0) { 623 | if (gnirehtetRunning) { 624 | LogMessage(1, "Gnirehtet already running with PID: " . gnirehtetRelayPID . " for currently connected " . connectedDevices . " device" . (connectedDevices > 1 ? "s" : "")) 625 | } else { 626 | LogMessage(1, "Starting gnirehtet relay process in autorun mode") 627 | Run, "%gnirehtetExecPath%" autorun, %platformToolsDirectory%, Hide, gnirehtetRelayPID 628 | Sleep, 500 629 | Process, Exist, gnirehtet.exe 630 | if (ErrorLevel != 0) { 631 | gnirehtetRelayPID := ErrorLevel 632 | LogMessage(1, "Started gnirehtet relay process with PID: " . gnirehtetRelayPID . " for currently connected " . connectedDevices . " device" . (connectedDevices > 1 ? "s" : "")) 633 | } else { 634 | LogMessage(0, "Failed to start gnirehtet relay process") 635 | } 636 | } 637 | } 638 | else { 639 | if (gnirehtetRunning) { 640 | LogMessage(1, "All ADB devices disconnected, terminating gnirehtet with PID: " . gnirehtetRelayPID) 641 | RunWait, %ComSpec% /c taskkill /PID %gnirehtetRelayPID% /T /FI "SIGNAL=SIGINT",, Hide 642 | gnirehtetRelayPID := "" 643 | } else 644 | LogMessage(1, "No devices connected and gnirehtet relay process is not running") 645 | } 646 | } 647 | lastConnectedDevices := connectedDevices 648 | 649 | Process, Exist, gnirehtet.exe 650 | gnirehtetRelayPID := ErrorLevel 651 | gnirehtetRunning := (ErrorLevel != 0) 652 | } 653 | MaintainCamConnectivity(){ 654 | 655 | } 656 | 657 | 658 | 659 | 660 | 661 | LogMessage(1, "Script started at " . A_Now) 662 | 663 | SetTimer, BulkStartApollo, 1000 664 | 665 | ; Wait for BulkStartApollo to finish 666 | Loop { 667 | Sleep, 50 668 | if (!firstrunApollo) { 669 | LogMessage(1, "Initializing Apollo has finished, procceding to auxilairy scripts.") 670 | break 671 | } 672 | } 673 | 674 | ;if (autoExitOnDisconnect || autoSyncVolume) 675 | ; SetTimer, watchApolloLogfiles, 100 676 | 677 | if (autoExitOnDisconnect) 678 | SetTimer, WatchLogFiles, 100 679 | 680 | if (autoSyncVolume) 681 | SetTimer, SyncVolume, 100 682 | 683 | if (autoReverseTethering || autoCaptureAndroidMic) 684 | SetTimer, watchAndroidADBDevices, 100 685 | 686 | if (autoCaptureAndroidMic) 687 | SetTimer, MaintainMicConnectivity, 100 688 | 689 | if (autoReverseTethering) 690 | SetTimer, MaintainReverseTethering, 100 691 | 692 | -------------------------------------------------------------------------------- /lib/VA.ahk: -------------------------------------------------------------------------------- 1 | ; VA v2.3 2 | 3 | ; 4 | ; MASTER CONTROLS 5 | ; https://autohotkey.com/board/topic/21984-vista-audio-control-functions/ 6 | 7 | VA_GetMasterVolume(channel="", device_desc="playback") 8 | { 9 | if ! aev := VA_GetAudioEndpointVolume(device_desc) 10 | return 11 | if channel = 12 | VA_IAudioEndpointVolume_GetMasterVolumeLevelScalar(aev, vol) 13 | else 14 | VA_IAudioEndpointVolume_GetChannelVolumeLevelScalar(aev, channel-1, vol) 15 | ObjRelease(aev) 16 | return Round(vol*100,3) 17 | } 18 | 19 | VA_SetMasterVolume(vol, channel="", device_desc="playback") 20 | { 21 | vol := vol>100 ? 100 : vol<0 ? 0 : vol 22 | if ! aev := VA_GetAudioEndpointVolume(device_desc) 23 | return 24 | if channel = 25 | VA_IAudioEndpointVolume_SetMasterVolumeLevelScalar(aev, vol/100) 26 | else 27 | VA_IAudioEndpointVolume_SetChannelVolumeLevelScalar(aev, channel-1, vol/100) 28 | ObjRelease(aev) 29 | } 30 | 31 | VA_GetMasterChannelCount(device_desc="playback") 32 | { 33 | if ! aev := VA_GetAudioEndpointVolume(device_desc) 34 | return 35 | VA_IAudioEndpointVolume_GetChannelCount(aev, count) 36 | ObjRelease(aev) 37 | return count 38 | } 39 | 40 | VA_SetMasterMute(mute, device_desc="playback") 41 | { 42 | if ! aev := VA_GetAudioEndpointVolume(device_desc) 43 | return 44 | VA_IAudioEndpointVolume_SetMute(aev, mute) 45 | ObjRelease(aev) 46 | } 47 | 48 | VA_GetMasterMute(device_desc="playback") 49 | { 50 | if ! aev := VA_GetAudioEndpointVolume(device_desc) 51 | return 52 | VA_IAudioEndpointVolume_GetMute(aev, mute) 53 | ObjRelease(aev) 54 | return mute 55 | } 56 | 57 | ; 58 | ; SUBUNIT CONTROLS 59 | ; 60 | 61 | VA_GetVolume(subunit_desc="1", channel="", device_desc="playback") 62 | { 63 | if ! avl := VA_GetDeviceSubunit(device_desc, subunit_desc, "{7FB7B48F-531D-44A2-BCB3-5AD5A134B3DC}") 64 | return 65 | VA_IPerChannelDbLevel_GetChannelCount(avl, channel_count) 66 | if channel = 67 | { 68 | vol = 0 69 | 70 | Loop, %channel_count% 71 | { 72 | VA_IPerChannelDbLevel_GetLevelRange(avl, A_Index-1, min_dB, max_dB, step_dB) 73 | VA_IPerChannelDbLevel_GetLevel(avl, A_Index-1, this_vol) 74 | this_vol := VA_dB2Scalar(this_vol, min_dB, max_dB) 75 | 76 | ; "Speakers Properties" reports the highest channel as the volume. 77 | if (this_vol > vol) 78 | vol := this_vol 79 | } 80 | } 81 | else if channel between 1 and channel_count 82 | { 83 | channel -= 1 84 | VA_IPerChannelDbLevel_GetLevelRange(avl, channel, min_dB, max_dB, step_dB) 85 | VA_IPerChannelDbLevel_GetLevel(avl, channel, vol) 86 | vol := VA_dB2Scalar(vol, min_dB, max_dB) 87 | } 88 | ObjRelease(avl) 89 | return vol 90 | } 91 | 92 | VA_SetVolume(vol, subunit_desc="1", channel="", device_desc="playback") 93 | { 94 | if ! avl := VA_GetDeviceSubunit(device_desc, subunit_desc, "{7FB7B48F-531D-44A2-BCB3-5AD5A134B3DC}") 95 | return 96 | 97 | vol := vol<0 ? 0 : vol>100 ? 100 : vol 98 | 99 | VA_IPerChannelDbLevel_GetChannelCount(avl, channel_count) 100 | 101 | if channel = 102 | { 103 | ; Simple method -- resets balance to "center": 104 | ;VA_IPerChannelDbLevel_SetLevelUniform(avl, vol) 105 | 106 | vol_max = 0 107 | 108 | Loop, %channel_count% 109 | { 110 | VA_IPerChannelDbLevel_GetLevelRange(avl, A_Index-1, min_dB, max_dB, step_dB) 111 | VA_IPerChannelDbLevel_GetLevel(avl, A_Index-1, this_vol) 112 | this_vol := VA_dB2Scalar(this_vol, min_dB, max_dB) 113 | 114 | channel%A_Index%vol := this_vol 115 | channel%A_Index%min := min_dB 116 | channel%A_Index%max := max_dB 117 | 118 | ; Scale all channels relative to the loudest channel. 119 | ; (This is how Vista's "Speakers Properties" dialog seems to work.) 120 | if (this_vol > vol_max) 121 | vol_max := this_vol 122 | } 123 | 124 | Loop, %channel_count% 125 | { 126 | this_vol := vol_max ? channel%A_Index%vol / vol_max * vol : vol 127 | this_vol := VA_Scalar2dB(this_vol/100, channel%A_Index%min, channel%A_Index%max) 128 | VA_IPerChannelDbLevel_SetLevel(avl, A_Index-1, this_vol) 129 | } 130 | } 131 | else if channel between 1 and %channel_count% 132 | { 133 | channel -= 1 134 | VA_IPerChannelDbLevel_GetLevelRange(avl, channel, min_dB, max_dB, step_dB) 135 | VA_IPerChannelDbLevel_SetLevel(avl, channel, VA_Scalar2dB(vol/100, min_dB, max_dB)) 136 | } 137 | ObjRelease(avl) 138 | } 139 | 140 | VA_GetChannelCount(subunit_desc="1", device_desc="playback") 141 | { 142 | if ! avl := VA_GetDeviceSubunit(device_desc, subunit_desc, "{7FB7B48F-531D-44A2-BCB3-5AD5A134B3DC}") 143 | return 144 | VA_IPerChannelDbLevel_GetChannelCount(avl, channel_count) 145 | ObjRelease(avl) 146 | return channel_count 147 | } 148 | 149 | VA_SetMute(mute, subunit_desc="1", device_desc="playback") 150 | { 151 | if ! amute := VA_GetDeviceSubunit(device_desc, subunit_desc, "{DF45AEEA-B74A-4B6B-AFAD-2366B6AA012E}") 152 | return 153 | VA_IAudioMute_SetMute(amute, mute) 154 | ObjRelease(amute) 155 | } 156 | 157 | VA_GetMute(subunit_desc="1", device_desc="playback") 158 | { 159 | if ! amute := VA_GetDeviceSubunit(device_desc, subunit_desc, "{DF45AEEA-B74A-4B6B-AFAD-2366B6AA012E}") 160 | return 161 | VA_IAudioMute_GetMute(amute, muted) 162 | ObjRelease(amute) 163 | return muted 164 | } 165 | 166 | ; 167 | ; AUDIO METERING 168 | ; 169 | 170 | VA_GetAudioMeter(device_desc="playback") 171 | { 172 | if ! device := VA_GetDevice(device_desc) 173 | return 0 174 | VA_IMMDevice_Activate(device, "{C02216F6-8C67-4B5B-9D00-D008E73E0064}", 7, 0, audioMeter) 175 | ObjRelease(device) 176 | return audioMeter 177 | } 178 | 179 | VA_GetDevicePeriod(device_desc, ByRef default_period, ByRef minimum_period="") 180 | { 181 | defaultPeriod := minimumPeriod := 0 182 | if ! device := VA_GetDevice(device_desc) 183 | return false 184 | VA_IMMDevice_Activate(device, "{1CB9AD4C-DBFA-4c32-B178-C2F568A703B2}", 7, 0, audioClient) 185 | ObjRelease(device) 186 | ; IAudioClient::GetDevicePeriod 187 | DllCall(NumGet(NumGet(audioClient+0)+9*A_PtrSize), "ptr",audioClient, "int64*",default_period, "int64*",minimum_period) 188 | ; Convert 100-nanosecond units to milliseconds. 189 | default_period /= 10000 190 | minimum_period /= 10000 191 | ObjRelease(audioClient) 192 | return true 193 | } 194 | 195 | VA_GetAudioEndpointVolume(device_desc="playback") 196 | { 197 | if ! device := VA_GetDevice(device_desc) 198 | return 0 199 | VA_IMMDevice_Activate(device, "{5CDF2C82-841E-4546-9722-0CF74078229A}", 7, 0, endpointVolume) 200 | ObjRelease(device) 201 | return endpointVolume 202 | } 203 | 204 | VA_GetDeviceSubunit(device_desc, subunit_desc, subunit_iid) 205 | { 206 | if ! device := VA_GetDevice(device_desc) 207 | return 0 208 | subunit := VA_FindSubunit(device, subunit_desc, subunit_iid) 209 | ObjRelease(device) 210 | return subunit 211 | } 212 | 213 | VA_FindSubunit(device, target_desc, target_iid) 214 | { 215 | if target_desc is integer 216 | target_index := target_desc 217 | else 218 | RegExMatch(target_desc, "(?<_name>.*?)(?::(?<_index>\d+))?$", target) 219 | ; v2.01: Since target_name is now a regular expression, default to case-insensitive mode if no options are specified. 220 | if !RegExMatch(target_name,"^[^\(]+\)") 221 | target_name := "i)" target_name 222 | r := VA_EnumSubunits(device, "VA_FindSubunitCallback", target_name, target_iid 223 | , Object(0, target_index ? target_index : 1, 1, 0)) 224 | return r 225 | } 226 | 227 | VA_FindSubunitCallback(part, interface, index) 228 | { 229 | index[1] := index[1] + 1 ; current += 1 230 | if (index[0] == index[1]) ; target == current ? 231 | { 232 | ObjAddRef(interface) 233 | return interface 234 | } 235 | } 236 | 237 | VA_EnumSubunits(device, callback, target_name="", target_iid="", callback_param="") 238 | { 239 | VA_IMMDevice_Activate(device, "{2A07407E-6497-4A18-9787-32F79BD0D98F}", 7, 0, deviceTopology) 240 | VA_IDeviceTopology_GetConnector(deviceTopology, 0, conn) 241 | ObjRelease(deviceTopology) 242 | VA_IConnector_GetConnectedTo(conn, conn_to) 243 | VA_IConnector_GetDataFlow(conn, data_flow) 244 | ObjRelease(conn) 245 | if !conn_to 246 | return ; blank to indicate error 247 | part := ComObjQuery(conn_to, "{AE2DE0E4-5BCA-4F2D-AA46-5D13F8FDB3A9}") ; IID_IPart 248 | ObjRelease(conn_to) 249 | if !part 250 | return 251 | r := VA_EnumSubunitsEx(part, data_flow, callback, target_name, target_iid, callback_param) 252 | ObjRelease(part) 253 | return r ; value returned by callback, or zero. 254 | } 255 | 256 | VA_EnumSubunitsEx(part, data_flow, callback, target_name="", target_iid="", callback_param="") 257 | { 258 | r := 0 259 | 260 | VA_IPart_GetPartType(part, type) 261 | 262 | if type = 1 ; Subunit 263 | { 264 | VA_IPart_GetName(part, name) 265 | 266 | ; v2.01: target_name is now a regular expression. 267 | if RegExMatch(name, target_name) 268 | { 269 | if target_iid = 270 | r := %callback%(part, 0, callback_param) 271 | else 272 | if VA_IPart_Activate(part, 7, target_iid, interface) = 0 273 | { 274 | r := %callback%(part, interface, callback_param) 275 | ; The callback is responsible for calling ObjAddRef() 276 | ; if it intends to keep the interface pointer. 277 | ObjRelease(interface) 278 | } 279 | 280 | if r 281 | return r ; early termination 282 | } 283 | } 284 | 285 | if data_flow = 0 286 | VA_IPart_EnumPartsIncoming(part, parts) 287 | else 288 | VA_IPart_EnumPartsOutgoing(part, parts) 289 | 290 | VA_IPartsList_GetCount(parts, count) 291 | Loop %count% 292 | { 293 | VA_IPartsList_GetPart(parts, A_Index-1, subpart) 294 | r := VA_EnumSubunitsEx(subpart, data_flow, callback, target_name, target_iid, callback_param) 295 | ObjRelease(subpart) 296 | if r 297 | break ; early termination 298 | } 299 | ObjRelease(parts) 300 | return r ; continue/finished enumeration 301 | } 302 | 303 | ; device_desc = device_id 304 | ; | ( friendly_name | 'playback' | 'capture' ) [ ':' index ] 305 | VA_GetDevice(device_desc="playback") 306 | { 307 | static CLSID_MMDeviceEnumerator := "{BCDE0395-E52F-467C-8E3D-C4579291692E}" 308 | , IID_IMMDeviceEnumerator := "{A95664D2-9614-4F35-A746-DE8DB63617E6}" 309 | if !(deviceEnumerator := ComObjCreate(CLSID_MMDeviceEnumerator, IID_IMMDeviceEnumerator)) 310 | return 0 311 | 312 | device := 0 313 | 314 | if VA_IMMDeviceEnumerator_GetDevice(deviceEnumerator, device_desc, device) = 0 315 | goto VA_GetDevice_Return 316 | 317 | if device_desc is integer 318 | { 319 | m2 := device_desc 320 | if m2 >= 4096 ; Probably a device pointer, passed here indirectly via VA_GetAudioMeter or such. 321 | { 322 | ObjAddRef(device := m2) 323 | goto VA_GetDevice_Return 324 | } 325 | } 326 | else 327 | RegExMatch(device_desc, "(.*?)\s*(?::(\d+))?$", m) 328 | 329 | if m1 in playback,p 330 | m1 := "", flow := 0 ; eRender 331 | else if m1 in capture,c 332 | m1 := "", flow := 1 ; eCapture 333 | else if (m1 . m2) = "" ; no name or number specified 334 | m1 := "", flow := 0 ; eRender (default) 335 | else 336 | flow := 2 ; eAll 337 | 338 | if (m1 . m2) = "" ; no name or number (maybe "playback" or "capture") 339 | { 340 | VA_IMMDeviceEnumerator_GetDefaultAudioEndpoint(deviceEnumerator, flow, 0, device) 341 | goto VA_GetDevice_Return 342 | } 343 | 344 | VA_IMMDeviceEnumerator_EnumAudioEndpoints(deviceEnumerator, flow, 1, devices) 345 | 346 | if m1 = 347 | { 348 | VA_IMMDeviceCollection_Item(devices, m2-1, device) 349 | goto VA_GetDevice_Return 350 | } 351 | 352 | VA_IMMDeviceCollection_GetCount(devices, count) 353 | index := 0 354 | Loop % count 355 | if VA_IMMDeviceCollection_Item(devices, A_Index-1, device) = 0 356 | if InStr(VA_GetDeviceName(device), m1) && (m2 = "" || ++index = m2) 357 | goto VA_GetDevice_Return 358 | else 359 | ObjRelease(device), device:=0 360 | 361 | VA_GetDevice_Return: 362 | ObjRelease(deviceEnumerator) 363 | if devices 364 | ObjRelease(devices) 365 | 366 | return device ; may be 0 367 | } 368 | 369 | VA_GetDeviceName(device) 370 | { 371 | static PKEY_Device_FriendlyName 372 | if !VarSetCapacity(PKEY_Device_FriendlyName) 373 | VarSetCapacity(PKEY_Device_FriendlyName, 20) 374 | ,VA_GUID(PKEY_Device_FriendlyName :="{A45C254E-DF1C-4EFD-8020-67D146A850E0}") 375 | ,NumPut(14, PKEY_Device_FriendlyName, 16) 376 | VarSetCapacity(prop, 16) 377 | VA_IMMDevice_OpenPropertyStore(device, 0, store) 378 | ; store->GetValue(.., [out] prop) 379 | DllCall(NumGet(NumGet(store+0)+5*A_PtrSize), "ptr", store, "ptr", &PKEY_Device_FriendlyName, "ptr", &prop) 380 | ObjRelease(store) 381 | VA_WStrOut(deviceName := NumGet(prop,8)) 382 | return deviceName 383 | } 384 | 385 | VA_SetDefaultEndpoint(device_desc, role) 386 | { 387 | /* Roles: 388 | eConsole = 0 ; Default Device 389 | eMultimedia = 1 390 | eCommunications = 2 ; Default Communications Device 391 | */ 392 | if ! device := VA_GetDevice(device_desc) 393 | return 0 394 | if VA_IMMDevice_GetId(device, id) = 0 395 | { 396 | cfg := ComObjCreate("{294935CE-F637-4E7C-A41B-AB255460B862}" 397 | , "{568b9108-44bf-40b4-9006-86afe5b5a620}") 398 | hr := VA_xIPolicyConfigVista_SetDefaultEndpoint(cfg, id, role) 399 | ObjRelease(cfg) 400 | } 401 | ObjRelease(device) 402 | return hr = 0 403 | } 404 | 405 | 406 | ; 407 | ; HELPERS 408 | ; 409 | 410 | ; Convert string to binary GUID structure. 411 | VA_GUID(ByRef guid_out, guid_in="%guid_out%") { 412 | if (guid_in == "%guid_out%") 413 | guid_in := guid_out 414 | if guid_in is integer 415 | return guid_in 416 | VarSetCapacity(guid_out, 16, 0) 417 | DllCall("ole32\CLSIDFromString", "wstr", guid_in, "ptr", &guid_out) 418 | return &guid_out 419 | } 420 | 421 | ; Convert binary GUID structure to string. 422 | VA_GUIDOut(ByRef guid) { 423 | VarSetCapacity(buf, 78) 424 | DllCall("ole32\StringFromGUID2", "ptr", &guid, "ptr", &buf, "int", 39) 425 | guid := StrGet(&buf, "UTF-16") 426 | } 427 | 428 | ; Convert COM-allocated wide char string pointer to usable string. 429 | VA_WStrOut(ByRef str) { 430 | str := StrGet(ptr := str, "UTF-16") 431 | DllCall("ole32\CoTaskMemFree", "ptr", ptr) ; FREES THE STRING. 432 | } 433 | 434 | VA_dB2Scalar(dB, min_dB, max_dB) { 435 | min_s := 10**(min_dB/20), max_s := 10**(max_dB/20) 436 | return ((10**(dB/20))-min_s)/(max_s-min_s)*100 437 | } 438 | 439 | VA_Scalar2dB(s, min_dB, max_dB) { 440 | min_s := 10**(min_dB/20), max_s := 10**(max_dB/20) 441 | return log((max_s-min_s)*s+min_s)*20 442 | } 443 | 444 | 445 | ; 446 | ; INTERFACE WRAPPERS 447 | ; Reference: Core Audio APIs in Windows Vista -- Programming Reference 448 | ; http://msdn2.microsoft.com/en-us/library/ms679156(VS.85).aspx 449 | ; 450 | 451 | ; 452 | ; IMMDevice : {D666063F-1587-4E43-81F1-B948E807363F} 453 | ; 454 | VA_IMMDevice_Activate(this, iid, ClsCtx, ActivationParams, ByRef Interface) { 455 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "ptr", VA_GUID(iid), "uint", ClsCtx, "uint", ActivationParams, "ptr*", Interface) 456 | } 457 | VA_IMMDevice_OpenPropertyStore(this, Access, ByRef Properties) { 458 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "uint", Access, "ptr*", Properties) 459 | } 460 | VA_IMMDevice_GetId(this, ByRef Id) { 461 | hr := DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "uint*", Id) 462 | VA_WStrOut(Id) 463 | return hr 464 | } 465 | VA_IMMDevice_GetState(this, ByRef State) { 466 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "uint*", State) 467 | } 468 | 469 | ; 470 | ; IDeviceTopology : {2A07407E-6497-4A18-9787-32F79BD0D98F} 471 | ; 472 | VA_IDeviceTopology_GetConnectorCount(this, ByRef Count) { 473 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "uint*", Count) 474 | } 475 | VA_IDeviceTopology_GetConnector(this, Index, ByRef Connector) { 476 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "uint", Index, "ptr*", Connector) 477 | } 478 | VA_IDeviceTopology_GetSubunitCount(this, ByRef Count) { 479 | return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "uint*", Count) 480 | } 481 | VA_IDeviceTopology_GetSubunit(this, Index, ByRef Subunit) { 482 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "uint", Index, "ptr*", Subunit) 483 | } 484 | VA_IDeviceTopology_GetPartById(this, Id, ByRef Part) { 485 | return DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "uint", Id, "ptr*", Part) 486 | } 487 | VA_IDeviceTopology_GetDeviceId(this, ByRef DeviceId) { 488 | hr := DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "uint*", DeviceId) 489 | VA_WStrOut(DeviceId) 490 | return hr 491 | } 492 | VA_IDeviceTopology_GetSignalPath(this, PartFrom, PartTo, RejectMixedPaths, ByRef Parts) { 493 | return DllCall(NumGet(NumGet(this+0)+9*A_PtrSize), "ptr", this, "ptr", PartFrom, "ptr", PartTo, "int", RejectMixedPaths, "ptr*", Parts) 494 | } 495 | 496 | ; 497 | ; IConnector : {9c2c4058-23f5-41de-877a-df3af236a09e} 498 | ; 499 | VA_IConnector_GetType(this, ByRef Type) { 500 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "int*", Type) 501 | } 502 | VA_IConnector_GetDataFlow(this, ByRef Flow) { 503 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "int*", Flow) 504 | } 505 | VA_IConnector_ConnectTo(this, ConnectTo) { 506 | return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "ptr", ConnectTo) 507 | } 508 | VA_IConnector_Disconnect(this) { 509 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this) 510 | } 511 | VA_IConnector_IsConnected(this, ByRef Connected) { 512 | return DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "int*", Connected) 513 | } 514 | VA_IConnector_GetConnectedTo(this, ByRef ConTo) { 515 | return DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "ptr*", ConTo) 516 | } 517 | VA_IConnector_GetConnectorIdConnectedTo(this, ByRef ConnectorId) { 518 | hr := DllCall(NumGet(NumGet(this+0)+9*A_PtrSize), "ptr", this, "ptr*", ConnectorId) 519 | VA_WStrOut(ConnectorId) 520 | return hr 521 | } 522 | VA_IConnector_GetDeviceIdConnectedTo(this, ByRef DeviceId) { 523 | hr := DllCall(NumGet(NumGet(this+0)+10*A_PtrSize), "ptr", this, "ptr*", DeviceId) 524 | VA_WStrOut(DeviceId) 525 | return hr 526 | } 527 | 528 | ; 529 | ; IPart : {AE2DE0E4-5BCA-4F2D-AA46-5D13F8FDB3A9} 530 | ; 531 | VA_IPart_GetName(this, ByRef Name) { 532 | hr := DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "ptr*", Name) 533 | VA_WStrOut(Name) 534 | return hr 535 | } 536 | VA_IPart_GetLocalId(this, ByRef Id) { 537 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "uint*", Id) 538 | } 539 | VA_IPart_GetGlobalId(this, ByRef GlobalId) { 540 | hr := DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "ptr*", GlobalId) 541 | VA_WStrOut(GlobalId) 542 | return hr 543 | } 544 | VA_IPart_GetPartType(this, ByRef PartType) { 545 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "int*", PartType) 546 | } 547 | VA_IPart_GetSubType(this, ByRef SubType) { 548 | VarSetCapacity(SubType,16,0) 549 | hr := DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "ptr", &SubType) 550 | VA_GUIDOut(SubType) 551 | return hr 552 | } 553 | VA_IPart_GetControlInterfaceCount(this, ByRef Count) { 554 | return DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "uint*", Count) 555 | } 556 | VA_IPart_GetControlInterface(this, Index, ByRef InterfaceDesc) { 557 | return DllCall(NumGet(NumGet(this+0)+9*A_PtrSize), "ptr", this, "uint", Index, "ptr*", InterfaceDesc) 558 | } 559 | VA_IPart_EnumPartsIncoming(this, ByRef Parts) { 560 | return DllCall(NumGet(NumGet(this+0)+10*A_PtrSize), "ptr", this, "ptr*", Parts) 561 | } 562 | VA_IPart_EnumPartsOutgoing(this, ByRef Parts) { 563 | return DllCall(NumGet(NumGet(this+0)+11*A_PtrSize), "ptr", this, "ptr*", Parts) 564 | } 565 | VA_IPart_GetTopologyObject(this, ByRef Topology) { 566 | return DllCall(NumGet(NumGet(this+0)+12*A_PtrSize), "ptr", this, "ptr*", Topology) 567 | } 568 | VA_IPart_Activate(this, ClsContext, iid, ByRef Object) { 569 | return DllCall(NumGet(NumGet(this+0)+13*A_PtrSize), "ptr", this, "uint", ClsContext, "ptr", VA_GUID(iid), "ptr*", Object) 570 | } 571 | VA_IPart_RegisterControlChangeCallback(this, iid, Notify) { 572 | return DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this, "ptr", VA_GUID(iid), "ptr", Notify) 573 | } 574 | VA_IPart_UnregisterControlChangeCallback(this, Notify) { 575 | return DllCall(NumGet(NumGet(this+0)+15*A_PtrSize), "ptr", this, "ptr", Notify) 576 | } 577 | 578 | ; 579 | ; IPartsList : {6DAA848C-5EB0-45CC-AEA5-998A2CDA1FFB} 580 | ; 581 | VA_IPartsList_GetCount(this, ByRef Count) { 582 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "uint*", Count) 583 | } 584 | VA_IPartsList_GetPart(this, INdex, ByRef Part) { 585 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "uint", Index, "ptr*", Part) 586 | } 587 | 588 | ; 589 | ; IAudioEndpointVolume : {5CDF2C82-841E-4546-9722-0CF74078229A} 590 | ; 591 | VA_IAudioEndpointVolume_RegisterControlChangeNotify(this, Notify) { 592 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "ptr", Notify) 593 | } 594 | VA_IAudioEndpointVolume_UnregisterControlChangeNotify(this, Notify) { 595 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "ptr", Notify) 596 | } 597 | VA_IAudioEndpointVolume_GetChannelCount(this, ByRef ChannelCount) { 598 | return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "uint*", ChannelCount) 599 | } 600 | VA_IAudioEndpointVolume_SetMasterVolumeLevel(this, LevelDB, GuidEventContext="") { 601 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "float", LevelDB, "ptr", VA_GUID(GuidEventContext)) 602 | } 603 | VA_IAudioEndpointVolume_SetMasterVolumeLevelScalar(this, Level, GuidEventContext="") { 604 | return DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "float", Level, "ptr", VA_GUID(GuidEventContext)) 605 | } 606 | VA_IAudioEndpointVolume_GetMasterVolumeLevel(this, ByRef LevelDB) { 607 | return DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "float*", LevelDB) 608 | } 609 | VA_IAudioEndpointVolume_GetMasterVolumeLevelScalar(this, ByRef Level) { 610 | return DllCall(NumGet(NumGet(this+0)+9*A_PtrSize), "ptr", this, "float*", Level) 611 | } 612 | VA_IAudioEndpointVolume_SetChannelVolumeLevel(this, Channel, LevelDB, GuidEventContext="") { 613 | return DllCall(NumGet(NumGet(this+0)+10*A_PtrSize), "ptr", this, "uint", Channel, "float", LevelDB, "ptr", VA_GUID(GuidEventContext)) 614 | } 615 | VA_IAudioEndpointVolume_SetChannelVolumeLevelScalar(this, Channel, Level, GuidEventContext="") { 616 | return DllCall(NumGet(NumGet(this+0)+11*A_PtrSize), "ptr", this, "uint", Channel, "float", Level, "ptr", VA_GUID(GuidEventContext)) 617 | } 618 | VA_IAudioEndpointVolume_GetChannelVolumeLevel(this, Channel, ByRef LevelDB) { 619 | return DllCall(NumGet(NumGet(this+0)+12*A_PtrSize), "ptr", this, "uint", Channel, "float*", LevelDB) 620 | } 621 | VA_IAudioEndpointVolume_GetChannelVolumeLevelScalar(this, Channel, ByRef Level) { 622 | return DllCall(NumGet(NumGet(this+0)+13*A_PtrSize), "ptr", this, "uint", Channel, "float*", Level) 623 | } 624 | VA_IAudioEndpointVolume_SetMute(this, Mute, GuidEventContext="") { 625 | return DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this, "int", Mute, "ptr", VA_GUID(GuidEventContext)) 626 | } 627 | VA_IAudioEndpointVolume_GetMute(this, ByRef Mute) { 628 | return DllCall(NumGet(NumGet(this+0)+15*A_PtrSize), "ptr", this, "int*", Mute) 629 | } 630 | VA_IAudioEndpointVolume_GetVolumeStepInfo(this, ByRef Step, ByRef StepCount) { 631 | return DllCall(NumGet(NumGet(this+0)+16*A_PtrSize), "ptr", this, "uint*", Step, "uint*", StepCount) 632 | } 633 | VA_IAudioEndpointVolume_VolumeStepUp(this, GuidEventContext="") { 634 | return DllCall(NumGet(NumGet(this+0)+17*A_PtrSize), "ptr", this, "ptr", VA_GUID(GuidEventContext)) 635 | } 636 | VA_IAudioEndpointVolume_VolumeStepDown(this, GuidEventContext="") { 637 | return DllCall(NumGet(NumGet(this+0)+18*A_PtrSize), "ptr", this, "ptr", VA_GUID(GuidEventContext)) 638 | } 639 | VA_IAudioEndpointVolume_QueryHardwareSupport(this, ByRef HardwareSupportMask) { 640 | return DllCall(NumGet(NumGet(this+0)+19*A_PtrSize), "ptr", this, "uint*", HardwareSupportMask) 641 | } 642 | VA_IAudioEndpointVolume_GetVolumeRange(this, ByRef MinDB, ByRef MaxDB, ByRef IncrementDB) { 643 | return DllCall(NumGet(NumGet(this+0)+20*A_PtrSize), "ptr", this, "float*", MinDB, "float*", MaxDB, "float*", IncrementDB) 644 | } 645 | 646 | ; 647 | ; IPerChannelDbLevel : {C2F8E001-F205-4BC9-99BC-C13B1E048CCB} 648 | ; IAudioVolumeLevel : {7FB7B48F-531D-44A2-BCB3-5AD5A134B3DC} 649 | ; IAudioBass : {A2B1A1D9-4DB3-425D-A2B2-BD335CB3E2E5} 650 | ; IAudioMidrange : {5E54B6D7-B44B-40D9-9A9E-E691D9CE6EDF} 651 | ; IAudioTreble : {0A717812-694E-4907-B74B-BAFA5CFDCA7B} 652 | ; 653 | VA_IPerChannelDbLevel_GetChannelCount(this, ByRef Channels) { 654 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "uint*", Channels) 655 | } 656 | VA_IPerChannelDbLevel_GetLevelRange(this, Channel, ByRef MinLevelDB, ByRef MaxLevelDB, ByRef Stepping) { 657 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "uint", Channel, "float*", MinLevelDB, "float*", MaxLevelDB, "float*", Stepping) 658 | } 659 | VA_IPerChannelDbLevel_GetLevel(this, Channel, ByRef LevelDB) { 660 | return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "uint", Channel, "float*", LevelDB) 661 | } 662 | VA_IPerChannelDbLevel_SetLevel(this, Channel, LevelDB, GuidEventContext="") { 663 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "uint", Channel, "float", LevelDB, "ptr", VA_GUID(GuidEventContext)) 664 | } 665 | VA_IPerChannelDbLevel_SetLevelUniform(this, LevelDB, GuidEventContext="") { 666 | return DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "float", LevelDB, "ptr", VA_GUID(GuidEventContext)) 667 | } 668 | VA_IPerChannelDbLevel_SetLevelAllChannels(this, LevelsDB, ChannelCount, GuidEventContext="") { 669 | return DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "uint", LevelsDB, "uint", ChannelCount, "ptr", VA_GUID(GuidEventContext)) 670 | } 671 | 672 | ; 673 | ; IAudioMute : {DF45AEEA-B74A-4B6B-AFAD-2366B6AA012E} 674 | ; 675 | VA_IAudioMute_SetMute(this, Muted, GuidEventContext="") { 676 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "int", Muted, "ptr", VA_GUID(GuidEventContext)) 677 | } 678 | VA_IAudioMute_GetMute(this, ByRef Muted) { 679 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "int*", Muted) 680 | } 681 | 682 | ; 683 | ; IAudioAutoGainControl : {85401FD4-6DE4-4b9d-9869-2D6753A82F3C} 684 | ; 685 | VA_IAudioAutoGainControl_GetEnabled(this, ByRef Enabled) { 686 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "int*", Enabled) 687 | } 688 | VA_IAudioAutoGainControl_SetEnabled(this, Enable, GuidEventContext="") { 689 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "int", Enable, "ptr", VA_GUID(GuidEventContext)) 690 | } 691 | 692 | ; 693 | ; IAudioMeterInformation : {C02216F6-8C67-4B5B-9D00-D008E73E0064} 694 | ; 695 | VA_IAudioMeterInformation_GetPeakValue(this, ByRef Peak) { 696 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "float*", Peak) 697 | } 698 | VA_IAudioMeterInformation_GetMeteringChannelCount(this, ByRef ChannelCount) { 699 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "uint*", ChannelCount) 700 | } 701 | VA_IAudioMeterInformation_GetChannelsPeakValues(this, ChannelCount, PeakValues) { 702 | return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "uint", ChannelCount, "ptr", PeakValues) 703 | } 704 | VA_IAudioMeterInformation_QueryHardwareSupport(this, ByRef HardwareSupportMask) { 705 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "uint*", HardwareSupportMask) 706 | } 707 | 708 | ; 709 | ; IAudioClient : {1CB9AD4C-DBFA-4c32-B178-C2F568A703B2} 710 | ; 711 | VA_IAudioClient_Initialize(this, ShareMode, StreamFlags, BufferDuration, Periodicity, Format, AudioSessionGuid) { 712 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "int", ShareMode, "uint", StreamFlags, "int64", BufferDuration, "int64", Periodicity, "ptr", Format, "ptr", VA_GUID(AudioSessionGuid)) 713 | } 714 | VA_IAudioClient_GetBufferSize(this, ByRef NumBufferFrames) { 715 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "uint*", NumBufferFrames) 716 | } 717 | VA_IAudioClient_GetStreamLatency(this, ByRef Latency) { 718 | return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "int64*", Latency) 719 | } 720 | VA_IAudioClient_GetCurrentPadding(this, ByRef NumPaddingFrames) { 721 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "uint*", NumPaddingFrames) 722 | } 723 | VA_IAudioClient_IsFormatSupported(this, ShareMode, Format, ByRef ClosestMatch) { 724 | return DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "int", ShareMode, "ptr", Format, "ptr*", ClosestMatch) 725 | } 726 | VA_IAudioClient_GetMixFormat(this, ByRef Format) { 727 | return DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "uint*", Format) 728 | } 729 | VA_IAudioClient_GetDevicePeriod(this, ByRef DefaultDevicePeriod, ByRef MinimumDevicePeriod) { 730 | return DllCall(NumGet(NumGet(this+0)+9*A_PtrSize), "ptr", this, "int64*", DefaultDevicePeriod, "int64*", MinimumDevicePeriod) 731 | } 732 | VA_IAudioClient_Start(this) { 733 | return DllCall(NumGet(NumGet(this+0)+10*A_PtrSize), "ptr", this) 734 | } 735 | VA_IAudioClient_Stop(this) { 736 | return DllCall(NumGet(NumGet(this+0)+11*A_PtrSize), "ptr", this) 737 | } 738 | VA_IAudioClient_Reset(this) { 739 | return DllCall(NumGet(NumGet(this+0)+12*A_PtrSize), "ptr", this) 740 | } 741 | VA_IAudioClient_SetEventHandle(this, eventHandle) { 742 | return DllCall(NumGet(NumGet(this+0)+13*A_PtrSize), "ptr", this, "ptr", eventHandle) 743 | } 744 | VA_IAudioClient_GetService(this, iid, ByRef Service) { 745 | return DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this, "ptr", VA_GUID(iid), "ptr*", Service) 746 | } 747 | 748 | ; 749 | ; IAudioSessionControl : {F4B1A599-7266-4319-A8CA-E70ACB11E8CD} 750 | ; 751 | /* 752 | AudioSessionStateInactive = 0 753 | AudioSessionStateActive = 1 754 | AudioSessionStateExpired = 2 755 | */ 756 | VA_IAudioSessionControl_GetState(this, ByRef State) { 757 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "int*", State) 758 | } 759 | VA_IAudioSessionControl_GetDisplayName(this, ByRef DisplayName) { 760 | hr := DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "ptr*", DisplayName) 761 | VA_WStrOut(DisplayName) 762 | return hr 763 | } 764 | VA_IAudioSessionControl_SetDisplayName(this, DisplayName, EventContext) { 765 | return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "wstr", DisplayName, "ptr", VA_GUID(EventContext)) 766 | } 767 | VA_IAudioSessionControl_GetIconPath(this, ByRef IconPath) { 768 | hr := DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "ptr*", IconPath) 769 | VA_WStrOut(IconPath) 770 | return hr 771 | } 772 | VA_IAudioSessionControl_SetIconPath(this, IconPath) { 773 | return DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "wstr", IconPath) 774 | } 775 | VA_IAudioSessionControl_GetGroupingParam(this, ByRef Param) { 776 | VarSetCapacity(Param,16,0) 777 | hr := DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "ptr", &Param) 778 | VA_GUIDOut(Param) 779 | return hr 780 | } 781 | VA_IAudioSessionControl_SetGroupingParam(this, Param, EventContext) { 782 | return DllCall(NumGet(NumGet(this+0)+9*A_PtrSize), "ptr", this, "ptr", VA_GUID(Param), "ptr", VA_GUID(EventContext)) 783 | } 784 | VA_IAudioSessionControl_RegisterAudioSessionNotification(this, NewNotifications) { 785 | return DllCall(NumGet(NumGet(this+0)+10*A_PtrSize), "ptr", this, "ptr", NewNotifications) 786 | } 787 | VA_IAudioSessionControl_UnregisterAudioSessionNotification(this, NewNotifications) { 788 | return DllCall(NumGet(NumGet(this+0)+11*A_PtrSize), "ptr", this, "ptr", NewNotifications) 789 | } 790 | 791 | ; 792 | ; IAudioSessionManager : {BFA971F1-4D5E-40BB-935E-967039BFBEE4} 793 | ; 794 | VA_IAudioSessionManager_GetAudioSessionControl(this, AudioSessionGuid) { 795 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "ptr", VA_GUID(AudioSessionGuid)) 796 | } 797 | VA_IAudioSessionManager_GetSimpleAudioVolume(this, AudioSessionGuid, StreamFlags, ByRef AudioVolume) { 798 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "ptr", VA_GUID(AudioSessionGuid), "uint", StreamFlags, "uint*", AudioVolume) 799 | } 800 | 801 | ; 802 | ; IMMDeviceEnumerator 803 | ; 804 | VA_IMMDeviceEnumerator_EnumAudioEndpoints(this, DataFlow, StateMask, ByRef Devices) { 805 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "int", DataFlow, "uint", StateMask, "ptr*", Devices) 806 | } 807 | VA_IMMDeviceEnumerator_GetDefaultAudioEndpoint(this, DataFlow, Role, ByRef Endpoint) { 808 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "int", DataFlow, "int", Role, "ptr*", Endpoint) 809 | } 810 | VA_IMMDeviceEnumerator_GetDevice(this, id, ByRef Device) { 811 | return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "wstr", id, "ptr*", Device) 812 | } 813 | VA_IMMDeviceEnumerator_RegisterEndpointNotificationCallback(this, Client) { 814 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "ptr", Client) 815 | } 816 | VA_IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(this, Client) { 817 | return DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "ptr", Client) 818 | } 819 | 820 | ; 821 | ; IMMDeviceCollection 822 | ; 823 | VA_IMMDeviceCollection_GetCount(this, ByRef Count) { 824 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "uint*", Count) 825 | } 826 | VA_IMMDeviceCollection_Item(this, Index, ByRef Device) { 827 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "uint", Index, "ptr*", Device) 828 | } 829 | 830 | ; 831 | ; IControlInterface 832 | ; 833 | VA_IControlInterface_GetName(this, ByRef Name) { 834 | hr := DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "ptr*", Name) 835 | VA_WStrOut(Name) 836 | return hr 837 | } 838 | VA_IControlInterface_GetIID(this, ByRef IID) { 839 | VarSetCapacity(IID,16,0) 840 | hr := DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "ptr", &IID) 841 | VA_GUIDOut(IID) 842 | return hr 843 | } 844 | 845 | 846 | /* 847 | INTERFACES REQUIRING WINDOWS 7 / SERVER 2008 R2 848 | */ 849 | 850 | ; 851 | ; IAudioSessionControl2 : {bfb7ff88-7239-4fc9-8fa2-07c950be9c6d} 852 | ; extends IAudioSessionControl 853 | ; 854 | VA_IAudioSessionControl2_GetSessionIdentifier(this, ByRef id) { 855 | hr := DllCall(NumGet(NumGet(this+0)+12*A_PtrSize), "ptr", this, "ptr*", id) 856 | VA_WStrOut(id) 857 | return hr 858 | } 859 | VA_IAudioSessionControl2_GetSessionInstanceIdentifier(this, ByRef id) { 860 | hr := DllCall(NumGet(NumGet(this+0)+13*A_PtrSize), "ptr", this, "ptr*", id) 861 | VA_WStrOut(id) 862 | return hr 863 | } 864 | VA_IAudioSessionControl2_GetProcessId(this, ByRef pid) { 865 | return DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this, "uint*", pid) 866 | } 867 | VA_IAudioSessionControl2_IsSystemSoundsSession(this) { 868 | return DllCall(NumGet(NumGet(this+0)+15*A_PtrSize), "ptr", this) 869 | } 870 | VA_IAudioSessionControl2_SetDuckingPreference(this, OptOut) { 871 | return DllCall(NumGet(NumGet(this+0)+16*A_PtrSize), "ptr", this, "int", OptOut) 872 | } 873 | 874 | ; 875 | ; IAudioSessionManager2 : {77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F} 876 | ; extends IAudioSessionManager 877 | ; 878 | VA_IAudioSessionManager2_GetSessionEnumerator(this, ByRef SessionEnum) { 879 | return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "ptr*", SessionEnum) 880 | } 881 | VA_IAudioSessionManager2_RegisterSessionNotification(this, SessionNotification) { 882 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "ptr", SessionNotification) 883 | } 884 | VA_IAudioSessionManager2_UnregisterSessionNotification(this, SessionNotification) { 885 | return DllCall(NumGet(NumGet(this+0)+7*A_PtrSize), "ptr", this, "ptr", SessionNotification) 886 | } 887 | VA_IAudioSessionManager2_RegisterDuckNotification(this, SessionNotification) { 888 | return DllCall(NumGet(NumGet(this+0)+8*A_PtrSize), "ptr", this, "ptr", SessionNotification) 889 | } 890 | VA_IAudioSessionManager2_UnregisterDuckNotification(this, SessionNotification) { 891 | return DllCall(NumGet(NumGet(this+0)+9*A_PtrSize), "ptr", this, "ptr", SessionNotification) 892 | } 893 | 894 | ; 895 | ; IAudioSessionEnumerator : {E2F5BB11-0570-40CA-ACDD-3AA01277DEE8} 896 | ; 897 | VA_IAudioSessionEnumerator_GetCount(this, ByRef SessionCount) { 898 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "int*", SessionCount) 899 | } 900 | VA_IAudioSessionEnumerator_GetSession(this, SessionCount, ByRef Session) { 901 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "int", SessionCount, "ptr*", Session) 902 | } 903 | 904 | 905 | /* 906 | UNDOCUMENTED INTERFACES 907 | */ 908 | 909 | ; Thanks to Dave Amenta for publishing this interface - http://goo.gl/6L93L 910 | ; IID := "{568b9108-44bf-40b4-9006-86afe5b5a620}" 911 | ; CLSID := "{294935CE-F637-4E7C-A41B-AB255460B862}" 912 | VA_xIPolicyConfigVista_SetDefaultEndpoint(this, DeviceId, Role) { 913 | return DllCall(NumGet(NumGet(this+0)+12*A_PtrSize), "ptr", this, "wstr", DeviceId, "int", Role) 914 | } 915 | 916 | ; 当前窗口静音添加下面所有行 917 | VA_GetISimpleAudioVolume(Param) 918 | { 919 | static IID_IASM2 := "{77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F}" 920 | , IID_IASC2 := "{bfb7ff88-7239-4fc9-8fa2-07c950be9c6d}" 921 | , IID_ISAV := "{87CE5498-68D6-44E5-9215-6DA47EF883D8}" 922 | 923 | ; Turn empty into integer 924 | if !Param 925 | Param := 0 926 | 927 | ; Get PID from process name 928 | if Param is not Integer 929 | { 930 | Process, Exist, %Param% 931 | Param := ErrorLevel 932 | } 933 | 934 | ; GetDefaultAudioEndpoint 935 | DAE := VA_GetDevice() 936 | 937 | ; activate the session manager 938 | VA_IMMDevice_Activate(DAE, IID_IASM2, 0, 0, IASM2) 939 | 940 | ; enumerate sessions for on this device 941 | VA_IAudioSessionManager2_GetSessionEnumerator(IASM2, IASE) 942 | VA_IAudioSessionEnumerator_GetCount(IASE, Count) 943 | 944 | ; search for an audio session with the required name 945 | Loop, % Count 946 | { 947 | ; Get the IAudioSessionControl object 948 | VA_IAudioSessionEnumerator_GetSession(IASE, A_Index-1, IASC) 949 | 950 | ; Query the IAudioSessionControl for an IAudioSessionControl2 object 951 | IASC2 := ComObjQuery(IASC, IID_IASC2) 952 | ObjRelease(IASC) 953 | 954 | ; Get the session's process ID 955 | VA_IAudioSessionControl2_GetProcessID(IASC2, SPID) 956 | 957 | ; If the process name is the one we are looking for 958 | if (SPID == Param) 959 | { 960 | ; Query for the ISimpleAudioVolume 961 | ISAV := ComObjQuery(IASC2, IID_ISAV) 962 | 963 | ObjRelease(IASC2) 964 | break 965 | } 966 | ObjRelease(IASC2) 967 | } 968 | ObjRelease(IASE) 969 | ObjRelease(IASM2) 970 | ObjRelease(DAE) 971 | return ISAV 972 | } 973 | 974 | ; 975 | ; ISimpleAudioVolume : {87CE5498-68D6-44E5-9215-6DA47EF883D8} 976 | ; 977 | VA_ISimpleAudioVolume_GetMasterVolume(this, ByRef fLevel) { 978 | return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "float*", fLevel) 979 | } 980 | VA_ISimpleAudioVolume_SetMasterVolume(this, ByRef fLevel, GuidEventContext="") { 981 | return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "float", fLevel, "ptr", VA_GUID(GuidEventContext)) 982 | } 983 | VA_ISimpleAudioVolume_GetMute(this, ByRef Muted) { 984 | return DllCall(NumGet(NumGet(this+0)+6*A_PtrSize), "ptr", this, "int*", Muted) 985 | } 986 | VA_ISimpleAudioVolume_SetMute(this, ByRef Muted, GuidEventContext="") { 987 | return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "int", Muted, "ptr", VA_GUID(GuidEventContext)) 988 | } 989 | 990 | VA_GetAppVolume(App) 991 | { 992 | ISAV := VA_GetISimpleAudioVolume(App) 993 | VA_ISimpleAudioVolume_GetMasterVolume(ISAV, fLevel) 994 | ObjRelease(ISAV) 995 | return fLevel * 100 996 | } 997 | 998 | VA_SetAppVolume(App, fLevel) 999 | { 1000 | ISAV := VA_GetISimpleAudioVolume(App) 1001 | fLevel := ((fLevel>100)?100:((fLevel < 0)?0:fLevel))/100 1002 | VA_ISimpleAudioVolume_SetMasterVolume(ISAV, fLevel) 1003 | ObjRelease(ISAV) 1004 | } 1005 | 1006 | VA_GetAppMute(App) 1007 | { 1008 | ISAV := VA_GetISimpleAudioVolume(App) 1009 | VA_ISimpleAudioVolume_GetMute(ISAV, Muted) 1010 | ObjRelease(ISAV) 1011 | return Muted 1012 | } 1013 | 1014 | VA_SetAppMute(App, Muted) 1015 | { 1016 | ISAV := VA_GetISimpleAudioVolume(App) 1017 | VA_ISimpleAudioVolume_SetMute(ISAV, Muted) 1018 | ObjRelease(ISAV) 1019 | } 1020 | --------------------------------------------------------------------------------