diff --git a/discover_overlay/audio_assist.py b/discover_overlay/audio_assist.py
new file mode 100644
index 0000000..f391446
--- /dev/null
+++ b/discover_overlay/audio_assist.py
@@ -0,0 +1,130 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+"""A class to assist with reading pulseaudio changes"""
+import os
+import logging
+import signal
+import pulsectl_asyncio
+from contextlib import suppress
+import asyncio
+from threading import Thread, Event
+
+log = logging.getLogger(__name__)
+
+class DiscoverAudioAssist:
+ def __init__(self, discover):
+
+ self.thread=None
+ self.enabled=False
+ self.source=None # String containing the name of the PA/PW microphone or other input
+ self.sink=None # String containing the name of the PA/PW output
+
+ self.discover = discover
+
+ # Keep last known state (or None) so that we don't repeatedly send messages for every little PA/PW signal
+ self.last_set_mute = None
+ self.last_set_deaf = None
+
+ def set_enabled(self, enabled):
+ self.enabled = enabled
+ if enabled:
+ self.start()
+
+ def set_devices(self, sink, source):
+ # Changed devices from client
+ self.source = source
+ self.sink = sink
+
+ def start(self):
+ if not self.enabled:
+ return
+ if not self.thread:
+ self.thread=Thread(target=self.thread_loop)
+ self.thread.start()
+
+ def thread_loop(self):
+ # Start an asyncio specific thread. Not the prettiest but I'm not rewriting from ground up for one feature
+ log.info("Staring Audio subsystem assistance")
+ loop = asyncio.new_event_loop()
+ loop.run_until_complete(self.pulse_loop())
+ log.info("Stopped Audio subsystem assistance")
+
+ async def listen(self):
+ # Async to connect to pulse and listen for events
+ async with pulsectl_asyncio.PulseAsync('Discover-Monitor') as pulse:
+ await self.get_device_details(pulse)
+ async for event in pulse.subscribe_events('all'):
+ await self.print_events(pulse, event)
+
+ async def pulse_loop(self):
+ # Prep before connecting to pulse
+ loop = asyncio.get_event_loop()
+ listen_task = asyncio.create_task(self.listen())
+ with suppress(asyncio.CancelledError):
+ await listen_task
+
+ async def get_device_details(self, pulse):
+ # Decant information about our chosen devices
+ # Feed this back to client to change deaf/mute state
+ mute = None
+ deaf = None
+ for sink in await pulse.sink_list():
+ if sink.description == self.sink:
+ if sink.mute == 1 or sink.volume.values[0]==0.0:
+ deaf = True
+ elif sink.mute == 0:
+ deaf = False
+
+ for source in await pulse.source_list():
+ if source.description == self.source:
+ if source.mute == 1 or source.volume.values[0]==0.0:
+ mute = True
+ elif sink.mute == 0:
+ mute = False
+
+ if mute != self.last_set_mute:
+ self.last_set_mute = mute
+ self.discover.set_mute_async(mute)
+
+ if deaf != self.last_set_deaf:
+ self.last_set_deaf = deaf
+ self.discover.set_deaf_async(deaf)
+
+ async def print_events(self,pulse, ev):
+ if not self.enabled:
+ return
+ # Sink and Source events are fired for changes to output and ints
+ # Server is fired when default sink or source changes.
+ match ev.facility:
+ case 'sink':
+ await self.get_device_details(pulse)
+
+ case 'source':
+ await self.get_device_details(pulse)
+
+ case 'server':
+ await self.get_device_details(pulse)
+
+ case 'source_output':
+ pass
+
+ case 'sink_input':
+ pass
+
+ case 'client':
+ pass
+
+ case _:
+ # If we need to find more events, this here will do it
+ #log.info('Pulse event: %s' % ev)
+ pass
\ No newline at end of file
diff --git a/discover_overlay/discord_connector.py b/discover_overlay/discord_connector.py
index 79da26b..a56f72f 100644
--- a/discover_overlay/discord_connector.py
+++ b/discover_overlay/discord_connector.py
@@ -347,6 +347,19 @@ class DiscordConnector:
self.req_channel_details(j["data"]["id"], 'new')
elif j["evt"] == "NOTIFICATION_CREATE":
self.discover.notification_overlay.add_notification_message(j)
+ elif j["evt"] == "VOICE_SETTINGS_UPDATE":
+ source = j['data']['input']['device_id']
+ sink = j['data']['output']['device_id']
+ if sink == 'default':
+ for available_sink in j['data']['output']['available_devices']:
+ if available_sink['id']=='default':
+ sink = available_sink['name'][9:]
+ if source == 'default':
+ for available_source in j['data']['input']['available_devices']:
+ if available_source['id']=='default':
+ source = available_source['name'][9:]
+ self.discover.audio_assist.set_devices(sink, source)
+
else:
log.warning(j)
return
@@ -589,6 +602,7 @@ class DiscordConnector:
or that reports the users current location
"""
self.sub_raw("VOICE_CHANNEL_SELECT", {}, "VOICE_CHANNEL_SELECT")
+ self.sub_raw("VOICE_SETTINGS_UPDATE", {}, "VOICE_SETTINGS_UPDATE")
self.sub_raw("VOICE_CONNECTION_STATUS", {}, "VOICE_CONNECTION_STATUS")
self.sub_raw("GUILD_CREATE", {}, "GUILD_CREATE")
self.sub_raw("CHANNEL_CREATE", {}, "CHANNEL_CREATE")
@@ -662,6 +676,7 @@ class DiscordConnector:
}
if self.websocket:
self.websocket.send(json.dumps(cmd))
+ return False
def set_deaf(self, deaf):
cmd = {
@@ -671,6 +686,7 @@ class DiscordConnector:
}
if self.websocket:
self.websocket.send(json.dumps(cmd))
+ return False
def change_voice_room(self, id):
"""
diff --git a/discover_overlay/discover_overlay.py b/discover_overlay/discover_overlay.py
index 75b2b92..03c8f32 100755
--- a/discover_overlay/discover_overlay.py
+++ b/discover_overlay/discover_overlay.py
@@ -20,6 +20,7 @@ import traceback
import logging
import pkg_resources
import json
+import signal
import gi
from configparser import ConfigParser
@@ -28,6 +29,7 @@ from .voice_overlay import VoiceOverlayWindow
from .text_overlay import TextOverlayWindow
from .notification_overlay import NotificationOverlayWindow
from .discord_connector import DiscordConnector
+from .audio_assist import DiscoverAudioAssist
gi.require_version("Gtk", "3.0")
# pylint: disable=wrong-import-position,wrong-import-order
@@ -78,6 +80,7 @@ class Discover:
self.connection = DiscordConnector(self)
self.connection.connect()
+ self.audio_assist = DiscoverAudioAssist(self)
rpc_file = Gio.File.new_for_path(rpc_file)
monitor = rpc_file.monitor_file(0, None)
@@ -407,6 +410,8 @@ class Discover:
self.text_overlay.set_hidden(hidden)
self.notification_overlay.set_hidden(hidden)
+ self.audio_assist.set_enabled(config.getboolean("general", "audio_assist", fallback = False))
+
def parse_guild_ids(self, guild_ids_str):
"""Parse the guild_ids from a str and return them in a list"""
@@ -468,6 +473,13 @@ class Discover:
if self.notification_overlay:
self.notification_overlay.set_task(visible)
+ def set_mute_async(self, mute):
+ if mute != None:
+ GLib.idle_add(self.connection.set_mute, mute)
+
+ def set_deaf_async(self, deaf):
+ if deaf != None:
+ GLib.idle_add(self.connection.set_deaf, deaf)
def entrypoint():
"""
@@ -482,6 +494,7 @@ def entrypoint():
otherwise start overlay
"""
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
# Find Config directory
config_dir = os.path.join(xdg_config_home, "discover_overlay")
os.makedirs(config_dir, exist_ok=True)
diff --git a/discover_overlay/glade/settings.glade b/discover_overlay/glade/settings.glade
index b721c42..558056e 100644
--- a/discover_overlay/glade/settings.glade
+++ b/discover_overlay/glade/settings.glade
@@ -2620,7 +2620,7 @@
-
+
1
@@ -2801,6 +2802,33 @@
True
+
+ 1
+ 7
+
+
+
+
+
+ 0
+ 6
+
+
+
+
16
diff --git a/discover_overlay/settings_window.py b/discover_overlay/settings_window.py
index f66975f..113add2 100644
--- a/discover_overlay/settings_window.py
+++ b/discover_overlay/settings_window.py
@@ -552,6 +552,7 @@ class MainSettingsWindow():
self.start_minimized = config.getboolean(
"general", "start_min", fallback=False)
+
self.widget['core_settings_min'].set_active(self.start_minimized)
self.widget['core_settings_min'].set_sensitive(self.show_sys_tray_icon)
@@ -559,6 +560,8 @@ class MainSettingsWindow():
if 'XDG_SESSION_DESKTOP' in os.environ and os.environ['XDG_SESSION_DESKTOP']=='cinnamon':
self.widget['voice_anchor_to_edge_button'].set_sensitive(False)
+ self.widget['core_audio_assist'].set_active(config.getboolean("general", "audio_assist", fallback=False))
+
self.loading_config = False
def make_colour(self, col):
@@ -1178,4 +1181,7 @@ class MainSettingsWindow():
def inactive_fade_time_changed(self,button):
self.config_set("main", "inactive_fade_time", "%s" %
- (int(button.get_value())))
\ No newline at end of file
+ (int(button.get_value())))
+
+ def core_audio_assist_changed(self, button):
+ self.config_set("general", "audio_assist", "%s" % (button.get_active()))
diff --git a/setup.py b/setup.py
index b6cbcc3..60696e9 100644
--- a/setup.py
+++ b/setup.py
@@ -32,7 +32,8 @@ setup(
'requests',
'pillow',
'python-xlib',
- 'setuptools'
+ 'setuptools',
+ 'pulsectl-asyncio'
],
entry_points={
'console_scripts': [