From bc0c73f3cf41f57e97162809f4d2048999f57b51 Mon Sep 17 00:00:00 2001 From: Saif Alaqqad Date: Sun, 11 Jun 2023 19:59:15 +0300 Subject: [PATCH] Add Linked App Foreground-Only option + Refactor code --- src/AuraService.ahk | 2 +- src/Lib/WinUtils.ahk | 59 ++++++++++++++- src/MicMute.ahk | 42 +++++++---- src/UI/config/UI.ahk | 128 ++++++++++++--------------------- src/UI/config/UITemplates.ahk | 38 +++++++++- src/UI/config/html/UI.html | 14 +++- src/actions/AuraSyncAction.ahk | 2 +- src/config/ProfileTemplate.ahk | 1 + 8 files changed, 187 insertions(+), 99 deletions(-) diff --git a/src/AuraService.ahk b/src/AuraService.ahk index e04bc5f..7d7972a 100644 --- a/src/AuraService.ahk +++ b/src/AuraService.ahk @@ -20,7 +20,7 @@ global parentPID := A_Args[1] , lastTask , A_IsDebug := A_Args[2] = "/debug" -global parentHwnd := util_getMainWindowHwnd(parentPID) +global parentHwnd := util_getAhkMainWindowHwnd(parentPID) if (!parentPID || parentPID == servicePID) ExitService(-1) diff --git a/src/Lib/WinUtils.ahk b/src/Lib/WinUtils.ahk index d2e682e..4922a83 100644 --- a/src/Lib/WinUtils.ahk +++ b/src/Lib/WinUtils.ahk @@ -74,7 +74,57 @@ util_isProcessElevated(vPID){ return vRet ? vIsElevated : -1 } -util_getMainWindowHwnd(p_pid){ +util_getRunningProcesses(includeHidden:=0) { + static _sysProcesses := new StackSet("svchost.exe", "explorer.exe", "Taskmgr.exe", "SystemSettings.exe") + + _hiddenValue:= A_DetectHiddenWindows + if (includeHidden) + DetectHiddenWindows, On + + pSet:= {} + WinGet, pList, List + loop %pList% + { + pHwnd:= pList%A_Index% + + WinGet, pPath, ProcessPath, ahk_id %pHwnd% + if (!pPath) + continue + pSplitPath := util_splitPath(pPath) + + if(pSplitPath.fileExt != "exe" || pSet.HasKey(pSplitPath.fileName) || _sysProcesses.exists(pSplitPath.fileName)) + continue + + WinGetTitle, pTitle, ahk_id %pHwnd% + pInfo:= util_getFileInfo(pPath) + + pSet[pSplitPath.fileName] := { hwnd: pHwnd + , path: pPath + , name: pSplitPath.fileName + , title: pTitle + , description: pInfo.FileDescription } + } + DetectHiddenWindows, % _hiddenValue + + return pSet +} + +util_getFileInfo(lptstrFilename) { + List := "Comments InternalName ProductName CompanyName LegalCopyright ProductVersion" + . " FileDescription LegalTrademarks PrivateBuild FileVersion OriginalFilename SpecialBuild" + dwLen := DllCall("Version.dll\GetFileVersionInfoSize", "Str", lptstrFilename, "Ptr", 0) + dwLen := VarSetCapacity( lpData, dwLen + A_PtrSize) + DllCall("Version.dll\GetFileVersionInfo", "Str", lptstrFilename, "UInt", 0, "UInt", dwLen, "Ptr", &lpData) + DllCall("Version.dll\VerQueryValue", "Ptr", &lpData, "Str", "\VarFileInfo\Translation", "PtrP", lplpBuffer, "PtrP", puLen ) + sLangCP := Format("{:04X}{:04X}", NumGet(lplpBuffer+0, "UShort"), NumGet(lplpBuffer+2, "UShort")) + i := {} + Loop, Parse, % List, %A_Space% + DllCall("Version.dll\VerQueryValue", "Ptr", &lpData, "Str", "\StringFileInfo\" sLangCp "\" A_LoopField, "PtrP", lplpBuffer, "PtrP", puLen ) + ? i[A_LoopField] := StrGet(lplpBuffer, puLen) : "" + return i +} + +util_getAhkMainWindowHwnd(p_pid){ WinWait, % "ahk_pid " p_pid, , 1 ; list all windows @@ -196,6 +246,13 @@ util_toString(obj){ return output_str } +util_firstNonEmpty(params*) { + for _, param in params + if (param && Trim(param)) + return param + return "" +} + ; VerCmp() for Windows by SKAN on D35T/D37L @ tiny.cc/vercmp util_VerCmp(V1, V2) { return ( ( V1 := Format("{:04X}{:04X}{:04X}{:04X}", StrSplit(V1 . "...", ".",, 5)*) ) diff --git a/src/MicMute.ahk b/src/MicMute.ahk index 805b415..7cba779 100644 --- a/src/MicMute.ahk +++ b/src/MicMute.ahk @@ -187,8 +187,6 @@ initilizeMicMute(default_profile:="", exportConfig:=1){ setTimer, checkConfigDiff, 3000 ;update theme variables updateSysTheme() - ;initilize tray - tray_init() if(config_obj.AllowUpdateChecker==-1){ MsgBox, 36, MicMute, Allow MicMute to connect to the internet and check for updates on startup? IfMsgBox, Yes @@ -421,24 +419,44 @@ checkConfigDiff(){ checkLinkedApps(){ if(watched_profile){ - WinGet, minState, MinMax, % "ahk_exe " . watched_profile.LinkedApp ; -1 -> minimized - if(!WinExist("ahk_exe " . watched_profile.LinkedApp) || minState == -1){ + if(!isAppActive(watched_profile.LinkedApp)){ util_log("[Main] Linked app closed: " . watched_profile.LinkedApp) - switchProfile(config_obj.DefaultProfile) watched_profile:="" + switchProfile(config_obj.DefaultProfile) } return } - for _i, prof in watched_profiles { - WinGet, minState, MinMax, % "ahk_exe " . prof.LinkedApp - if(WinExist("ahk_exe " . prof.LinkedApp) && (minState!="" && minState!=-1)){ - util_log("[Main] Detected linked app: " . prof.LinkedApp) - watched_profile:= prof - switchProfile(prof.ProfileName) + + for _i, p in watched_profiles { + if(isAppActive(p.LinkedApp)){ + util_log("[Main] Detected linked app: " . p.LinkedApp) + watched_profile:= p + switchProfile(p.ProfileName) + break } } } +isAppActive(appFile){ + if (current_profile.ForegroundAppsOnly) { + windowExists := WinExist("ahk_exe " . appFile) + WinGet, minState, MinMax, ahk_exe %appFile% + + ; An app is active in the foreground if it has a window that's not hidden + ; Minimized windows are considered hidden + return windowExists && minState !== "" && minState !== -1 + } else { + _hiddenValue := A_DetectHiddenWindows + DetectHiddenWindows, On + + ; Try to get the PID + WinGet, appPid, PID, ahk_exe %appFile% + + DetectHiddenWindows, %_hiddenValue% + return appPid !== "" + } +} + onUpdateState(microphone){ if (mic_controllers.Length() == 1) { overlay_wnd.setState(microphone.state) @@ -482,7 +500,7 @@ reloadMicMute(p_profile:=""){ } parseArgs(){ - arg_regex:= "i)\/([\w]+)(=(.+))?" + arg_regex:= "i)[\/\-]?([\w]+)(=(.+))?" for _i, arg in A_Args { match:= RegExMatch(arg, arg_regex, val) if(!match) diff --git a/src/UI/config/UI.ahk b/src/UI/config/UI.ahk index 0f1ff5b..1fa4a84 100644 --- a/src/UI/config/UI.ahk +++ b/src/UI/config/UI.ahk @@ -1,46 +1,12 @@ global ui_obj, about_obj, current_profile, hotkey_panels, current_hp, action_editor -, onExitCallback, UI_scale:= A_ScreenDPI/96, UI_profileIsDirty:= 0 -, input_hook, input_hook_timer, key_set, modifier_set, is_multiple_mics:=0 -, UI_tooltips:= [ { selector: ".passthrough-label" - , string: "The hotkey's keystrokes won't be hidden from the OS"} - ,{ selector: ".wildcard-label" - , string: "Fire the hotkey even if extra modifiers are held down"} - ,{ selector: ".nt-label" - , string: "Use neutral modifiers (i.e. Alt instead of Left Alt / Right Alt)"} - ,{ selector: ".ptt-delay-label" - , string: "Delay between releasing the key and the audio cutting off"} - ,{ selector: ".afk-label" - , string: "Mute the microphone when idling for a length of time"} - ,{ selector: ".ExcludeFullscreen-label" - , string: "Don't show the OSD if the active app/game is fullscreen"} - ,{ selector: ".SwitchProfileOSD-label" - , string: "Show an OSD when switching between profiles"} - ,{ selector: ".SoundFeedback-label" - , string: "Play a sound when muting or unmuting the microphone"} - ,{ selector: ".OnscreenFeedback-label" - , string: "Show an OSD when muting or unmuting the microphone"} - ,{ selector: ".OnscreenOverlay-label" - , string: "Show the microphone's state in an always-on-top overlay"} - ,{ selector: ".multiple-mics-label" - , string: "Right click to view instructions"} - ,{ selector: ".ForceMicrophoneState-label" - , string: "Prevent other apps from changing the mic's state"} - ,{ selector: ".UseCustomSounds-label" - , string: "Right click to view instructions"} - ,{ selector: ".OverlayUseCustomIcons-label" - , string: "Right click to view instructions"} - ,{ selector: ".hybrid_ptt-label" - , string: "Short press will toggle the microphone"} - ,{ selector: ".mic-actions-label" - , string: "Run programs/scripts when muting/unmuting the microphone"} - ,{ selector: ".volume-lock-label" - , string: "Lock the microphone's volume to a specific value"}] + , onExitCallback, UI_scale:= A_ScreenDPI/96, UI_profileIsDirty:= 0 + , input_hook, input_hook_timer, key_set, modifier_set, is_multiple_mics:=0 UI_create(p_onExitCallback){ util_log("[UI] Creating 'config' window") features:= {"FEATURE_GPU_RENDERING": 0x1 - ,"FEATURE_BROWSER_EMULATION": 0x2AF8 - ,"FEATURE_96DPI_PIXEL": 0x1} + ,"FEATURE_BROWSER_EMULATION": 0x2AF8 + ,"FEATURE_96DPI_PIXEL": 0x1} UI_enableIeFeatures(features) ui_obj:= new NeutronWindow() ui_obj.load(resources_obj.htmlFile.UI) @@ -76,7 +42,7 @@ UI_HotReload(){ UI_enableIeFeatures(f_obj, delete:=0){ static reg_dir:= "SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\" - , executable:= A_IsCompiled? A_ScriptName : util_splitPath(A_AhkPath).fileName + , executable:= A_IsCompiled? A_ScriptName : util_splitPath(A_AhkPath).fileName for feature, value in f_obj if(!delete) RegWrite, REG_DWORD, % "HKCU\" reg_dir feature, % executable, % value @@ -129,6 +95,7 @@ UI_setProfile(_neutron, p_profile){ ui_obj.doc.getElementById("ExcludeFullscreen").checked:= current_profile.ExcludeFullscreen ui_obj.doc.getElementById("OSDPos_x").value:= current_profile.OSDPos.x==-1? "" : current_profile.OSDPos.x ui_obj.doc.getElementById("OSDPos_y").value:= current_profile.OSDPos.y==-1? "" : current_profile.OSDPos.y + ui_obj.doc.getElementById("ForegroundAppsOnly").checked := current_profile.ForegroundAppsOnly UI_onRefreshAppsList("") ui_obj.doc.getElementById("afkTimeout").value:= !current_profile.afkTimeout? "" : current_profile.afkTimeout ui_obj.doc.getElementById("PTTDelay").value:= current_profile.PTTDelay @@ -194,7 +161,7 @@ UI_resetMicSelect(){ }else{ select.insertAdjacentHTML("beforeend", Format(template_mic, device, "", device)) } - } + } select.value:= "Default" } @@ -247,6 +214,7 @@ UI_onSaveProfile(neutron, noReset:=0){ current_profile.OverlaySize:= ui_obj.doc.getElementById("OverlaySize").value current_profile.afkTimeout:= (val:= ui_obj.doc.getElementById("afkTimeout").value)? val+0 : 0 current_profile.LinkedApp:= ui_obj.doc.getElementById("LinkedApp").value + current_profile.ForegroundAppsOnly:= ui_obj.doc.getElementById("ForegroundAppsOnly").checked? 1 : 0 current_profile.PTTDelay:= ui_obj.doc.getElementById("PTTDelay").value+0 current_profile.OSDPos.x:= (val:= ui_obj.doc.getElementById("OSDPos_x").value)? val : -1 current_profile.OSDPos.y:= (val:= ui_obj.doc.getElementById("OSDPos_y").value)? val : -1 @@ -260,21 +228,21 @@ UI_onSaveProfile(neutron, noReset:=0){ if(!hp.isTypeValid()) Continue current_profile.Microphone.Push(new MicrophoneTemplate(mic - , hp.mute.hotkey - , hp.unmute.hotkey - , (hp.hotkeyType >= 2? 1 : 0) - , hp.hybrid_ptt - , (hp.hotkeyType == 3? 1 : 0))) + , hp.mute.hotkey + , hp.unmute.hotkey + , (hp.hotkeyType >= 2? 1 : 0) + , hp.hybrid_ptt + , (hp.hotkeyType == 3? 1 : 0))) } if(!current_profile.Microphone.Length()){ - current_profile.Microphone.Push(new MicrophoneTemplate("Default", "", "")) + current_profile.Microphone.Push(new MicrophoneTemplate("Default", "", "")) } config_obj.exportConfig() if(!noReset){ UI_reset() UI_setProfile(neutron, current_profile.ProfileName) } - UI_notify("Profile saved") + UI_notify("Profile saved") } UI_onDeleteProfile(neutron){ @@ -326,7 +294,7 @@ UI_onRecord(type){ inputElem:= ui_obj.doc.getElementByID(type . "_input") inputElem.value:="" inputElem.placeholder:="Recording" - ;setup a new input hook + ;setup a new input hook input_hook:= InputHook("L0 T3","{Enter}{Escape}") input_hook.KeyOpt("{ALL}", "NI") input_hook.VisibleText:= false @@ -343,8 +311,8 @@ UI_onRecord(type){ UI_addKey(type, _InputHook, VK, SC){ key_name:= GetKeyName(Format("vk{:x}sc{:x}", VK, SC)) - , inputElem:= ui_obj.doc.getElementByID(type "_input") - , unq:="" + , inputElem:= ui_obj.doc.getElementByID(type "_input") + , unq:="" if(StrLen(key_name) == 1) key_name:= Format("{:U}", key_name) key_name:= current_hp[type].nt? current_hp.modifierToNeutral(key_name) : key_name @@ -531,11 +499,11 @@ UI_asyncOnSetMicrophone(mic_name){ hotkey_panels[mic_name]:= new HotkeyPanel("","",1) } if(is_multiple_mics){ - UI_setHotkeyPanel(hotkey_panels[mic_name]) + UI_setHotkeyPanel(hotkey_panels[mic_name]) }else{ hotkey_panels:= {} hotkey_panels[mic_name]:= current_hp - UI_setHotkeyPanel(hotkey_panels[mic_name]) + UI_setHotkeyPanel(hotkey_panels[mic_name]) } ui_obj.doc.getElementById("hotkeys_panel").classList.remove("hidden") } @@ -625,19 +593,31 @@ UI_updateStopTimer(id){ } +UI_onToggleForegroundApps(){ + current_profile.ForegroundAppsOnly:= ui_obj.doc.getElementById("ForegroundAppsOnly").checked? 1 : 0 + UI_onRefreshAppsList("") +} + UI_onRefreshAppsList(neutron){ - list:= UI_getProcessList() + processes := util_getRunningProcesses(!current_profile.ForegroundAppsOnly) + + ; Add existing linked app to list if it's not running + if(current_profile.LinkedApp && !processes.HasKey(current_profile.LinkedApp)){ + processes[current_profile.LinkedApp] := { name: current_profile.LinkedApp } + } + select:= ui_obj.doc.getElementById("LinkedApp") - if(current_profile.LinkedApp) - list.push(current_profile.LinkedApp) select.innerHTML:="" select.insertAdjacentHTML("beforeend", Format(template_app, "", "Select an app", "selected")) - for _i, process in list.data { - WinGetTitle, winTitle, ahk_exe %process% - winTitle:= winTitle? winTitle " (" process ")" : process - select.insertAdjacentHTML("beforeend" - , Format(template_app, process, winTitle, process = current_profile.LinkedApp? "selected" : "")) + + for _i, proc in processes { + isSelected:= proc.name = current_profile.LinkedApp + titleValue:= util_firstNonEmpty(proc.description, proc.title) + + label:= titleValue ? Format("{} ({})", titleValue, proc.name) : proc.name + select.insertAdjacentHTML("beforeend", Format(template_app, proc.name, label, isSelected? "selected" : "")) } + if(neutron) UI_notify("Refreshed running apps") } @@ -698,8 +678,8 @@ UI_switchToTab(neutron, rootSelector, tabID){ wantedTabContent:= ui_obj.doc.getElementById(tabID "_content") if(neutron && activeTab.id == wantedTab.id) - return - + return + activeTab.classList.remove("is-active") wantedTab.classList.add("is-active") activeTabContent.classList.add("hidden") @@ -730,8 +710,7 @@ UI_onCreateMicAction(neutron:="", actionType:=""){ actionIndex:= current_profile.MicrophoneActions.Length() + 1 exitCallback:= Func("UI_onSaveMicAction").Bind(actionIndex) - switch actionType - { + switch actionType { case PowershellAction.TypeName: actionConfig:= PowershellAction.getConfig() action_editor:= new PowershellActionEditor(actionConfig, exitCallback) @@ -768,14 +747,13 @@ UI_onEditMicAction(neutron:="", actionIndex:=""){ action:= current_profile.MicrophoneActions[actionIndex] if(!action) return - + exitCallback:= Func("UI_onSaveMicAction").Bind(actionIndex) - switch action.Type - { + switch action.Type { case PowershellAction.TypeName: action_editor:= new PowershellActionEditor(action.Clone(), exitCallback) - + case ProgramAction.TypeName: action_editor:= new ProgramActionEditor(action.Clone(), exitCallback) @@ -794,7 +772,7 @@ UI_onSaveMicAction(actionIndex, actionConfig){ if(!actionConfig) return - + if(actionConfig == -1){ current_profile.MicrophoneActions.RemoveAt(actionIndex) }else{ @@ -976,16 +954,4 @@ UI_launchURL(_neutron:="", url:="", isFullUrl:=0){ UI_launchReleasePage(_neutron:="", version:=""){ url:= "https://github.com/SaifAqqad/AHK_MicMute/releases/tag/" . (version? version : A_Version) Run, %url%, %A_Desktop% -} - -UI_getProcessList(){ - pSet:= new StackSet() - WinGet, pList, List - loop %pList% - { - pHwnd:= pList%A_Index% - WinGet, pName, ProcessName, ahk_id %pHwnd% - pSet.push(pName) - } - return pSet } \ No newline at end of file diff --git a/src/UI/config/UITemplates.ahk b/src/UI/config/UITemplates.ahk index c856071..4f6cdc2 100644 --- a/src/UI/config/UITemplates.ahk +++ b/src/UI/config/UITemplates.ahk @@ -109,4 +109,40 @@ global template_link:= ""
  • Select another microphone from the list
  • Setup the hotkeys
  • - )"} \ No newline at end of file + )"} +, UI_tooltips:= [ { selector: ".passthrough-label" + , string: "The hotkey's keystrokes won't be hidden from the OS"} + ,{ selector: ".wildcard-label" + , string: "Fire the hotkey even if extra modifiers are held down"} + ,{ selector: ".nt-label" + , string: "Use neutral modifiers (i.e. Alt instead of Left Alt / Right Alt)"} + ,{ selector: ".ptt-delay-label" + , string: "Delay between releasing the key and the audio cutting off"} + ,{ selector: ".afk-label" + , string: "Mute the microphone when idling for a length of time"} + ,{ selector: ".ExcludeFullscreen-label" + , string: "Don't show the OSD if the active app/game is fullscreen"} + ,{ selector: ".SwitchProfileOSD-label" + , string: "Show an OSD when switching between profiles"} + ,{ selector: ".SoundFeedback-label" + , string: "Play a sound when muting or unmuting the microphone"} + ,{ selector: ".OnscreenFeedback-label" + , string: "Show an OSD when muting or unmuting the microphone"} + ,{ selector: ".OnscreenOverlay-label" + , string: "Show the microphone's state in an always-on-top overlay"} + ,{ selector: ".multiple-mics-label" + , string: "Right click to view instructions"} + ,{ selector: ".ForceMicrophoneState-label" + , string: "Prevent other apps from changing the mic's state"} + ,{ selector: ".UseCustomSounds-label" + , string: "Right click to view instructions"} + ,{ selector: ".OverlayUseCustomIcons-label" + , string: "Right click to view instructions"} + ,{ selector: ".hybrid_ptt-label" + , string: "Short press will toggle the microphone"} + ,{ selector: ".mic-actions-label" + , string: "Run programs/scripts when muting/unmuting the microphone"} + ,{ selector: ".volume-lock-label" + , string: "Lock the microphone's volume to a specific value"} + ,{ selector: ".ForegroundAppsOnly-label" + , string: "Require apps to be in the foreground to trigger a profile change"}] \ No newline at end of file diff --git a/src/UI/config/html/UI.html b/src/UI/config/html/UI.html index ed34906..1cadbf6 100644 --- a/src/UI/config/html/UI.html +++ b/src/UI/config/html/UI.html @@ -492,7 +492,17 @@ - Linked application + Linked application + + + +
    @@ -623,7 +633,7 @@ Volume Lock
    -
    +
    diff --git a/src/actions/AuraSyncAction.ahk b/src/actions/AuraSyncAction.ahk index 9a25b85..e5712b9 100644 --- a/src/actions/AuraSyncAction.ahk +++ b/src/actions/AuraSyncAction.ahk @@ -52,7 +52,7 @@ class AuraSyncAction extends MicrophoneAction { Run, % this.AuraServiceCommand, A_ScriptDir, Hide, childPID AuraSyncAction.AuraServicePID := childPID - AuraSyncAction.AuraServiceHwnd := util_getMainWindowHwnd(AuraSyncAction.AuraServicePID) + AuraSyncAction.AuraServiceHwnd := util_getAhkMainWindowHwnd(AuraSyncAction.AuraServicePID) util_log("[AuraSyncAction] Started AuraService with PID: " AuraSyncAction.AuraServicePID " and HWND: " AuraSyncAction.AuraServiceHwnd) OnExit(ObjBindMethod(AuraSyncAction, "stopAuraService")) diff --git a/src/config/ProfileTemplate.ahk b/src/config/ProfileTemplate.ahk index cd314c7..57e141c 100644 --- a/src/config/ProfileTemplate.ahk +++ b/src/config/ProfileTemplate.ahk @@ -18,6 +18,7 @@ class ProfileTemplate { this.PTTDelay := 100 this.LinkedApp := "" + this.ForegroundAppsOnly := 1 this.OverlayPos := { x: -1, y: -1 } this.OverlayShow := 2