Skip to content

Instantly share code, notes, and snippets.

@jpsutton
Last active July 10, 2025 15:34
Show Gist options
  • Save jpsutton/d5e742756c4b3077bce59d5b3ec7dd34 to your computer and use it in GitHub Desktop.
Save jpsutton/d5e742756c4b3077bce59d5b3ec7dd34 to your computer and use it in GitHub Desktop.
Toggle all microphones mute status with on-screen indicator
#!/usr/bin/env python3
import pulsectl
import dbus
import time
def show_notification(title, message, icon_name):
"""
Show a KDE desktop notification using D-Bus directly.
Args:
title: The notification title
message: The notification message body
icon_name: Name of the icon to display
"""
print(f"{title}: {message}")
try:
# Connect to the D-Bus session bus
bus = dbus.SessionBus()
# Get the notifications service
notify_service = bus.get_object(
'org.freedesktop.Notifications',
'/org/freedesktop/Notifications'
)
# Get the notifications interface
notify_interface = dbus.Interface(
notify_service,
'org.freedesktop.Notifications'
)
# Send the notification
notify_interface.Notify(
"Mic Toggle", # App name
0, # Replace id (0 = new notification)
icon_name, # Icon name
title, # Title
message, # Body
[], # Actions
{}, # Hints
2000 # Timeout in ms (2 seconds)
)
except Exception as e:
# Fall back to print if D-Bus fails
print(f"Notification failed: {e}")
print(f"{title}: {message}")
def toggle_all_mics():
"""
Toggle mute status of all input devices (microphones) in the PulseAudio system.
Shows an on-screen notification of the action.
Returns True if mics are now muted, False if unmuted.
"""
with pulsectl.Pulse('mic-toggle') as pulse:
# Get all source (input) devices that are actually microphones
all_sources = pulse.source_list()
# Filter for actual microphone devices - exclude monitors and non-mic devices
microphones = list(filter(lambda x: "input" in x.name.split(".")[0].lower(), all_sources))
# If no microphones found, show notification and exit early
if not microphones:
show_notification(
"Mic Toggle",
"No microphones found in the system",
"microphone-sensitivity-muted"
)
return False
# Check if any mics are already unmuted to determine current state
any_unmuted = any(mic.mute == 0 for mic in microphones)
# If any mic is unmuted, we'll mute all
# If all mics are muted, we'll unmute all
new_mute = 1 if any_unmuted else 0
# Apply mute/unmute only to microphone sources
for mic in microphones:
print(f"{mic.description}\n\tMuted: {mic.mute}\n\tDesired state: {new_mute}")
if mic.mute == new_mute:
continue
pulse.source_mute(mic.index, new_mute)
print(("\tMuting " if new_mute else "\tUnmuting ") + str(mic.index))
# Fast iteration through mics via the Pulse interface seems to be time-sensitive, so delay a bit to get consistent results
time.sleep(0.15)
# Show notification based on action
icon = 'microphone-sensitivity-muted' if new_mute else 'microphone-sensitivity-high'
message = 'All microphones muted' if new_mute else 'All microphones unmuted'
show_notification("Mic Toggle", message, icon)
return new_mute == 1 # Return True if we muted, False if we unmuted
if __name__ == "__main__":
status = toggle_all_mics()
@jpsutton
Copy link
Author

Some system update after originally publishing this introduced a race condition when toggling, so I added a small delay when iterating through mics. My own testing showed 1/10 of a second was enough to produce consistent results, so I added 50% and called it a day :)

Also added a check of current mic state to avoid trying to set a mute/unmute flag unncessarily, and added some additional terminal logging to help with debugging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment