Last active
May 3, 2024 16:36
-
-
Save stduhpf/d12d0eb321005467d1a7f36d32554042 to your computer and use it in GitHub Desktop.
Force mute out of focus Windows app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This code is licensed under the terms of the MIT license | |
# This script is made to force some Windows app to stop outputting sound when not focused. | |
# Requirements: - Windows operating system (tested on Windows 10 22H2) | |
# - python 3 | |
# - pycaw (Python Core Audio Windows Library): `pip install pycaw` | |
# Acknowledgments: | |
# - Most of the credit goes to superuser.com user steve0: https://superuser.com/a/1796210 | |
# - Refactoring and improved UX by stduhpf, with the help of a local llama-3 8B (4bit) instance | |
from pycaw.pycaw import AudioUtilities | |
import win32gui | |
import win32process | |
import time | |
from sys import argv | |
def autoSelectTargetWindow(): | |
# Find second to last focused window (to use as default) | |
selfHwnd = 0 | |
if len(argv)<=1 or argv[1]=="--": | |
def callback(hwnd:int,_): | |
nonlocal selfHwnd | |
if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowText(hwnd).strip(): | |
status = selfHwnd | |
selfHwnd = hwnd | |
return status==0 | |
return True | |
try: | |
win32gui.EnumWindows(callback,None) | |
except: | |
pass | |
return selfHwnd | |
######################################### | |
def find_process_id(hwnd_target): | |
if hwnd_target != 0: | |
_, process_id = win32process.GetWindowThreadProcessId(hwnd_target) | |
return process_id | |
return None | |
def app_is_in_foreground(hwnd_target): | |
try: | |
fg_hwnd = win32gui.GetForegroundWindow() | |
if hwnd_target == fg_hwnd: | |
return True | |
except Exception as e: | |
print(f"Error: {e}") | |
return False | |
def change_app_mute(process_id, mute=1): | |
sessions = AudioUtilities.GetAllSessions() | |
for session in sessions: | |
if session.Process and session.Process.pid == process_id: | |
session.SimpleAudioVolume.SetMute(mute, None) | |
######################################### | |
def worker(hwnd_target,interval,dbg): | |
mute_state = -1 | |
prev_mute_state = mute_state | |
while True: | |
process_id = find_process_id(hwnd_target) | |
if process_id and win32gui.IsWindow(hwnd_target): | |
mute_state = 0 if app_is_in_foreground(hwnd_target) else 1 | |
if mute_state != prev_mute_state: | |
change_app_mute(process_id, mute_state) | |
if dbg: | |
print(f"Toggled mute {'ON' if mute_state else 'OFF'}") | |
prev_mute_state = mute_state | |
else: | |
print(f"Target process terminated! {'(App will remain muted after next launch)' if mute_state else ''}") | |
break | |
time.sleep(interval) | |
######################################### | |
def printUsage(): | |
print(f"USAGE: {argv[0]} TARGETED_WINDOW_NAME Debug=[True|False] update_interval") | |
print(f"\t- TARGETED_WINDOW_NAME: Name of the target window (defaults to the previous focused window)") | |
print(f"\t- Debug: \t\tSet to False to suppress output (defaults to True)") | |
print(f"\t- update_interval: \tDelay in-between consecutive checks of the state of the target window (in s) (defaults to 1)") | |
print(f"To use default values, you can ignore an argument by setting it to '--'") | |
print(f"\t(example: `{argv[0]} -- -- 0.1` to only affect update_interval)\n") | |
def printWindowError(): | |
print("ERROR: Couldn't find valid hwnd for target window") | |
if (len(argv) > 1 and argv[1]!="--"): | |
def dumpAvailableWindowTexts(): | |
def callback(hwnd:int,_): | |
text = win32gui.GetWindowText(hwnd).strip() | |
if win32gui.IsWindowVisible(hwnd) and text: | |
print(f"\t{text}") | |
return True | |
try: | |
win32gui.EnumWindows(callback,None) | |
except: | |
pass | |
print(f"No window named \"{argv[1]}\" found\nAvailable windows:") | |
dumpAvailableWindowTexts() | |
exit(-1) | |
if __name__ == "__main__": | |
try: | |
dbg = bool(argv[2]) if len(argv) > 2 and argv[2]!="--" else True | |
interval = float(argv[3]) if len(argv) > 3 and argv[3]!="--" else 1 | |
except ValueError: | |
printUsage() | |
exit(-1) | |
hwnd_target = win32gui.FindWindow(None, argv[1]) if len(argv) > 1 and argv[1]!="--" else autoSelectTargetWindow() | |
if hwnd_target==0: | |
printUsage() | |
printWindowError() | |
if dbg: | |
print(f"Targeted window: {win32gui.GetWindowText(hwnd_target)}") | |
try: | |
worker(hwnd_target, interval, dbg) | |
except KeyboardInterrupt: | |
print("Un-muting target app...") | |
try: | |
process_id = find_process_id(hwnd_target) | |
change_app_mute(process_id, 0) | |
except: | |
print("Error: un-muting might have failed") | |
print("Exiting...") | |
exit(0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment