From 29f8c7476c68788ebbb28d8e5576bef3c4f5ef4e Mon Sep 17 00:00:00 2001 From: trigg Date: Mon, 25 Mar 2024 17:37:51 +0000 Subject: [PATCH] - Catch errors when audio assist can't reach PA/PW - Attempt to use GTK icon theme for local images, fallback to image search - Error message & fallback icon when settings icons not found - Fix notification overlay - All overlays check if config changed before scheduling a redraw - - Lowers flicker rate of overlay when editing config - ran formatter - probable fix for #288 - probable fix for #287 --- .gitignore | 1 + discover_overlay/audio_assist.py | 47 +++-- discover_overlay/autostart.py | 6 +- discover_overlay/discord_connector.py | 4 +- discover_overlay/discover_overlay.py | 7 +- discover_overlay/image_getter.py | 51 +++-- discover_overlay/notification_overlay.py | 77 ++++---- discover_overlay/overlay.py | 114 ++++++------ discover_overlay/settings_window.py | 44 +++-- discover_overlay/text_overlay.py | 41 ++-- discover_overlay/voice_overlay.py | 228 +++++++++++++---------- 11 files changed, 369 insertions(+), 251 deletions(-) diff --git a/.gitignore b/.gitignore index 6db20c2..40e43c2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ __pycache__ venv .idea discover_overlay/glade/settings.glade~ +discover_overlay/glade/#settings.glade# diff --git a/discover_overlay/audio_assist.py b/discover_overlay/audio_assist.py index f391446..ec13293 100644 --- a/discover_overlay/audio_assist.py +++ b/discover_overlay/audio_assist.py @@ -15,19 +15,21 @@ import os import logging import signal import pulsectl_asyncio +import pulsectl 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.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 @@ -49,7 +51,7 @@ class DiscoverAudioAssist: if not self.enabled: return if not self.thread: - self.thread=Thread(target=self.thread_loop) + self.thread = Thread(target=self.thread_loop) self.thread.start() def thread_loop(self): @@ -61,10 +63,15 @@ class DiscoverAudioAssist: 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) + try: + 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) + except (pulsectl.pulsectl.PulseDisconnected): + log.info("Pulse has gone away") + except (pulsectl.pulsectl.PulseError): + log.info("Pulse error") async def pulse_loop(self): # Prep before connecting to pulse @@ -74,20 +81,20 @@ class DiscoverAudioAssist: await listen_task async def get_device_details(self, pulse): - # Decant information about our chosen devices + # 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: + 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: + if source.mute == 1 or source.volume.values[0] == 0.0: mute = True elif sink.mute == 0: mute = False @@ -100,7 +107,7 @@ class DiscoverAudioAssist: self.last_set_deaf = deaf self.discover.set_deaf_async(deaf) - async def print_events(self,pulse, ev): + async def print_events(self, pulse, ev): if not self.enabled: return # Sink and Source events are fired for changes to output and ints @@ -108,7 +115,7 @@ class DiscoverAudioAssist: match ev.facility: case 'sink': await self.get_device_details(pulse) - + case 'source': await self.get_device_details(pulse) @@ -116,15 +123,15 @@ class DiscoverAudioAssist: await self.get_device_details(pulse) case 'source_output': - pass + pass case 'sink_input': - pass + pass case 'client': pass - case _: + 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 + # log.info('Pulse event: %s' % ev) + pass diff --git a/discover_overlay/autostart.py b/discover_overlay/autostart.py index 2119a75..7e4f9c1 100644 --- a/discover_overlay/autostart.py +++ b/discover_overlay/autostart.py @@ -97,14 +97,14 @@ class BazziteAutostart: root = '' if shutil.which('pkexec'): root = 'pkexec' - else: + else: log.error("No ability to request root privs. Cancel") return - command = " sed -i 's/AUTO_LAUNCH_DISCOVER_OVERLAY=./AUTO_LAUNCH_DISCOVER_OVERLAY=%s/g' /etc/default/discover-overlay" % (value) + command = " sed -i 's/AUTO_LAUNCH_DISCOVER_OVERLAY=./AUTO_LAUNCH_DISCOVER_OVERLAY=%s/g' /etc/default/discover-overlay" % ( + value) command_with_permissions = root + command os.system(command_with_permissions) - def is_auto(self): """Check if it's already set to auto-start""" return self.auto diff --git a/discover_overlay/discord_connector.py b/discover_overlay/discord_connector.py index a56f72f..501024f 100644 --- a/discover_overlay/discord_connector.py +++ b/discover_overlay/discord_connector.py @@ -352,11 +352,11 @@ class DiscordConnector: sink = j['data']['output']['device_id'] if sink == 'default': for available_sink in j['data']['output']['available_devices']: - if available_sink['id']=='default': + 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': + if available_source['id'] == 'default': source = available_source['name'][9:] self.discover.audio_assist.set_devices(sink, source) diff --git a/discover_overlay/discover_overlay.py b/discover_overlay/discover_overlay.py index 03c8f32..d34e0d3 100755 --- a/discover_overlay/discover_overlay.py +++ b/discover_overlay/discover_overlay.py @@ -251,7 +251,7 @@ class Discover: config.getboolean("main", "autohide", fallback=False)) self.voice_overlay.set_mouseover_timer( config.getint("main", "autohide_timer", fallback=1)) - + self.voice_overlay.set_horizontal(config.getboolean( "main", "horizontal", fallback=False)) self.voice_overlay.set_guild_ids(self.parse_guild_ids( @@ -410,8 +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)) - + 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""" @@ -481,6 +481,7 @@ class Discover: if deaf != None: GLib.idle_add(self.connection.set_deaf, deaf) + def entrypoint(): """ Entry Point. diff --git a/discover_overlay/image_getter.py b/discover_overlay/image_getter.py index 3c368ca..57e90f3 100644 --- a/discover_overlay/image_getter.py +++ b/discover_overlay/image_getter.py @@ -23,8 +23,9 @@ import os import io import copy gi.require_version('GdkPixbuf', '2.0') +gi.require_version("Gtk", "3.0") # pylint: disable=wrong-import-position -from gi.repository import Gio, GdkPixbuf # nopep8 +from gi.repository import Gio, GdkPixbuf, Gtk # nopep8 log = logging.getLogger(__name__) @@ -68,25 +69,50 @@ class SurfaceGetter(): log.error("Unknown image type: %s", self.url) def get_file(self): + errors = [] + # Grab icon from icon theme + icon_theme = Gtk.IconTheme.get_default() + icon = icon_theme.choose_icon( + [self.url, None], -1, Gtk.IconLookupFlags.NO_SVG) + + if icon: + try: + image = Image.open(icon.get_filename()) + (surface, mask) = from_pil(image) + if surface: + self.func(self.identifier, surface, mask) + return + except ValueError: + errors.append("Value Error - Unable to read %s" % (mixpath)) + except TypeError: + errors.append("Type Error - Unable to read %s" % (mixpath)) + except PIL.UnidentifiedImageError: + errors.append("Unknown image type: %s" % (mixpath)) + except FileNotFoundError: + errors.append("File not found: %s" % (mixpath)) + # Not found in theme, try some common locations locations = [os.path.expanduser('~/.local/'), '/usr/', '/app'] for prefix in locations: - mixpath = os.path.join(prefix, self.url) + mixpath = os.path.join(os.path.join( + prefix, 'share/icons/hicolor/256x256/apps/'), self.url + ".png") image = None try: image = Image.open(mixpath) except ValueError: - log.error("Value Erorr - Unable to read %s", mixpath) + errors.append("Value Error - Unable to read %s" % (mixpath)) except TypeError: - log.error("Type Error - Unable to read %s", mixpath) + errors.append("Type Error - Unable to read %s" % (mixpath)) except PIL.UnidentifiedImageError: - log.error("Unknown image type: %s", mixpath) + errors.append("Unknown image type: %s" % (mixpath)) except FileNotFoundError: - log.error("File not found: %s", mixpath) + errors.append("File not found: %s" % (mixpath)) if image: (surface, mask) = from_pil(image) if surface: self.func(self.identifier, surface, mask) return + for error in errors: + log.error(error) def from_pil(image, alpha=1.0, format='BGRa'): @@ -113,7 +139,7 @@ def from_pil(image, alpha=1.0, format='BGRa'): # Cairo expects the raw data to be pre-multiplied alpha # This means when we change the alpha level we need to change the RGB channels equally arr[idx] = int(arr[idx] * alpha) - idx +=1 + idx += 1 surface = cairo.ImageSurface.create_for_data( arr, cairo.FORMAT_ARGB32, image.width, image.height) mask = cairo.ImageSurface.create_for_data( @@ -123,8 +149,9 @@ def from_pil(image, alpha=1.0, format='BGRa'): def to_pil(surface): if surface.get_format() == cairo.Format.ARGB32: - return Image.frombuffer('RGBA', (surface.get_width(), surface.get_height()), surface.get_data(),'raw',"BGRA",surface.get_stride()) - return Image.frombuffer("RGB", (surface.get_width(), surface.get_height()), surface.get_data(),'raw', "BGRX", stride) + return Image.frombuffer('RGBA', (surface.get_width(), surface.get_height()), surface.get_data(), 'raw', "BGRA", surface.get_stride()) + return Image.frombuffer("RGB", (surface.get_width(), surface.get_height()), surface.get_data(), 'raw', "BGRX", stride) + def get_surface(func, identifier, ava, size): """Download to cairo surface""" @@ -164,6 +191,7 @@ def get_aspected_size(img, width, height, anchor=0, hanchor=0): offset_x = offset_x + ((old_width - width) / 2) return (offset_x, offset_y, width, height) + def draw_img_to_rect(img, ctx, pos_x, pos_y, width, height, @@ -187,7 +215,7 @@ def draw_img_to_rect(img, ctx, ctx.translate(pos_x + offset_x, pos_y + offset_y) ctx.scale(width, height) ctx.scale(1 / img.get_width(), 1 / img.get_height()) - + if alpha != 1.0: # Honestly, couldn't find a 'use-image-with-modifier' option # Tried RasterSourcePattern but it appears... broken? in the python implementation @@ -199,7 +227,7 @@ def draw_img_to_rect(img, ctx, to_pil(img), alpha )[0], - 0,0) + 0, 0) else: ctx.set_source_surface(img, 0, 0) @@ -209,6 +237,7 @@ def draw_img_to_rect(img, ctx, ctx.restore() return (width, height) + def draw_img_to_mask(img, ctx, pos_x, pos_y, width, height, diff --git a/discover_overlay/notification_overlay.py b/discover_overlay/notification_overlay.py index 64327e9..a7d0f8e 100644 --- a/discover_overlay/notification_overlay.py +++ b/discover_overlay/notification_overlay.py @@ -82,7 +82,7 @@ class NotificationOverlayWindow(OverlayWindow): self.content = newlist # If the list is different than before if oldsize != len(newlist): - self.needsredraw = True + self.set_needs_redraw() def add_notification_message(self, data): noti = None @@ -104,41 +104,46 @@ class NotificationOverlayWindow(OverlayWindow): if noti: self.content.append(noti) - self.needsredraw = True + self.set_needs_redraw() self.get_all_images() def set_padding(self, padding): """ Set the padding between notifications """ - self.padding = padding - self.needsredraw = True + if self.padding != padding: + self.padding = padding + self.set_needs_redraw() def set_border_radius(self, radius): """ Set the radius of the border """ - self.border_radius = radius - self.needsredraw = True + if self.border_radius != radius: + self.border_radius = radius + self.set_needs_redraw() def set_icon_size(self, size): """ Set Icon size """ - self.icon_size = size - self.image_list = {} - self.get_all_images() + if self.icon_size != size: + self.icon_size = size + self.image_list = {} + self.get_all_images() def set_icon_pad(self, pad): """ Set padding between icon and message """ - self.icon_pad = pad - self.needsredraw = True + if self.icon_pad != pad: + self.icon_pad = pad + self.set_needs_redraw() def set_icon_left(self, left): - self.icon_left = left - self.needsredraw = True + if self.icon_left != left: + self.icon_left = left + self.set_needs_redraw() def set_text_time(self, timer): """ @@ -151,8 +156,9 @@ class NotificationOverlayWindow(OverlayWindow): """ Set the word wrap limit in pixels """ - self.limit_width = limit - self.needsredraw = True + if self.limit_width != limit: + self.limit_width = limit + self.set_needs_redraw() def get_all_images(self): the_list = self.content @@ -170,52 +176,57 @@ class NotificationOverlayWindow(OverlayWindow): Called when image_getter has downloaded an image """ self.image_list[identifier] = pix - self.needsredraw = True + self.set_needs_redraw() def set_fg(self, fg_col): """ Set default text colour """ - self.fg_col = fg_col - self.needsredraw = True + if self.fg_col != fg_col: + self.fg_col = fg_col + self.set_needs_redraw() def set_bg(self, bg_col): """ Set background colour """ - self.bg_col = bg_col - self.needsredraw = True + if self.bg_col != bg_col: + self.bg_col = bg_col + self.set_needs_redraw() def set_show_icon(self, icon): """ Set if icons should be shown inline """ - self.show_icon = icon - self.needsredraw = True - self.get_all_images() + if self.show_icon != icon: + self.show_icon = icon + self.set_needs_redraw() + self.get_all_images() def set_reverse_order(self, rev): - self.reverse_order = rev - self.needsredraw = True + if self.reverse_order != rev: + self.reverse_order = rev + self.set_needs_redraw() def set_font(self, font): """ Set font used to render text """ - self.text_font = font + if self.text_font != font: + self.text_font = font - self.pango_rect = Pango.Rectangle() - font = Pango.FontDescription(self.text_font) - self.pango_rect.width = font.get_size() * Pango.SCALE - self.pango_rect.height = font.get_size() * Pango.SCALE - self.needsredraw = True + self.pango_rect = Pango.Rectangle() + font = Pango.FontDescription(self.text_font) + self.pango_rect.width = font.get_size() * Pango.SCALE + self.pango_rect.height = font.get_size() * Pango.SCALE + self.set_needs_redraw() def recv_attach(self, identifier, pix): """ Called when an image has been downloaded by image_getter """ self.icons[identifier] = pix - self.needsredraw = True + self.set_needs_redraw() def calc_all_height(self): h = 0 @@ -516,5 +527,5 @@ class NotificationOverlayWindow(OverlayWindow): def set_testing(self, testing): self.testing = testing - self.needsredraw = True + self.set_needs_redraw() self.get_all_images() diff --git a/discover_overlay/overlay.py b/discover_overlay/overlay.py index 9c81305..7503f8b 100644 --- a/discover_overlay/overlay.py +++ b/discover_overlay/overlay.py @@ -55,7 +55,7 @@ class OverlayWindow(Gtk.Window): def __init__(self, discover, piggyback=None): Gtk.Window.__init__(self, type=self.detect_type()) - self.is_xatom_set=False + self.is_xatom_set = False self.discover = discover screen = self.get_screen() @@ -114,7 +114,8 @@ class OverlayWindow(Gtk.Window): self.get_screen().connect("monitors-changed", self.screen_changed) self.get_screen().connect("size-changed", self.screen_changed) if self.get_window(): - self.get_window().set_events(self.get_window().get_events() | Gdk.EventMask.ENTER_NOTIFY_MASK) + self.get_window().set_events(self.get_window().get_events() + | Gdk.EventMask.ENTER_NOTIFY_MASK) self.connect("enter-notify-event", self.mouseover) self.connect("leave-notify-event", self.mouseout) self.mouse_over_timer = None @@ -202,8 +203,6 @@ class OverlayWindow(Gtk.Window): reg = Gdk.cairo_region_create_from_surface(surface) self.input_shape_combine_region(reg) - - self.overlay_draw(_w, context, data) def overlay_draw(self, _w, context, data=None): @@ -215,23 +214,24 @@ class OverlayWindow(Gtk.Window): """ Set the font used by the overlay """ - self.text_font = font - self.set_needs_redraw() + if self.text_font != font: + self.text_font = font + self.set_needs_redraw() def set_floating(self, floating, pos_x, pos_y, width, height): """ Set if the window is floating and what dimensions to use """ - - # Special case for Cinnamon desktop : see https://github.com/trigg/Discover/issues/322 - if 'XDG_SESSION_DESKTOP' in os.environ and os.environ['XDG_SESSION_DESKTOP']=='cinnamon': - floating = True - self.floating = floating - self.pos_x = pos_x - self.pos_y = pos_y - self.width = width - self.height = height - self.force_location() + if self.floating != floating or self.pos_x != pos_x or self.pos_y != pos_y or self.width != width or self.height != height: + # Special case for Cinnamon desktop : see https://github.com/trigg/Discover/issues/322 + if 'XDG_SESSION_DESKTOP' in os.environ and os.environ['XDG_SESSION_DESKTOP'] == 'cinnamon': + floating = True + self.floating = floating + self.pos_x = pos_x + self.pos_y = pos_y + self.width = width + self.height = height + self.force_location() def set_untouchable(self): """ @@ -248,11 +248,12 @@ class OverlayWindow(Gtk.Window): self.input_shape_combine_region(reg) def set_hide_on_mouseover(self, hide): - self.hide_on_mouseover = hide - if self.hide_on_mouseover: - self.set_needs_redraw() - else: - self.set_untouchable() + if self.hide_on_mouseover != hide: + self.hide_on_mouseover = hide + if self.hide_on_mouseover: + self.set_needs_redraw() + else: + self.set_untouchable() def set_mouseover_timer(self, time): self.timeout_mouse_over = time @@ -367,38 +368,41 @@ class OverlayWindow(Gtk.Window): """ if type(idx) is str: idx = 0 - self.monitor = idx - if self.is_wayland: - display = Gdk.Display.get_default() - if "get_monitor" in dir(display): - monitor = display.get_monitor(self.monitor) - if monitor: - GtkLayerShell.set_monitor(self, monitor) + if self.monitor != idx: + self.monitor = idx + if self.is_wayland: + display = Gdk.Display.get_default() + if "get_monitor" in dir(display): + monitor = display.get_monitor(self.monitor) + if monitor: + GtkLayerShell.set_monitor(self, monitor) + else: + self.hide() + self.set_wayland_state() + self.show() else: - self.hide() - self.set_wayland_state() - self.show() - else: - log.error("No get_monitor in display") - self.set_untouchable() - self.force_location() - self.set_needs_redraw() + log.error("No get_monitor in display") + self.set_untouchable() + self.force_location() + self.set_needs_redraw() def set_align_x(self, align_right): """ Set the alignment (True for right, False for left) """ - self.align_right = align_right - self.force_location() - self.set_needs_redraw() + if self.align_right != align_right: + self.align_right = align_right + self.force_location() + self.set_needs_redraw() def set_align_y(self, align_vert): """ Set the veritcal alignment """ - self.align_vert = align_vert - self.force_location() - self.set_needs_redraw() + if self.align_vert != align_vert: + self.align_vert = align_vert + self.force_location() + self.set_needs_redraw() def col(self, col, alpha=1.0): """ @@ -420,22 +424,24 @@ class OverlayWindow(Gtk.Window): """ Set if this overlay should be visible """ - self.enabled = enabled - if enabled and not self.hidden and not self.piggyback_parent: - self.show_all() - self.set_untouchable() - if self.discover.steamos: - self.set_gamescope_xatom(1) - else: - if self.discover.steamos: - self.set_gamescope_xatom(0) - self.hide() + if self.enabled != enabled: + self.enabled = enabled + if enabled and not self.hidden and not self.piggyback_parent: + self.show_all() + self.set_untouchable() + if self.discover.steamos: + self.set_gamescope_xatom(1) + else: + if self.discover.steamos: + self.set_gamescope_xatom(0) + self.hide() def set_task(self, visible): self.set_skip_pager_hint(not visible) self.set_skip_taskbar_hint(not visible) def check_composite(self, _a=None, _b=None): + # Called when an X11 session switched compositing on or off self.redraw() def screen_changed(self, screen=None): @@ -448,9 +454,9 @@ class OverlayWindow(Gtk.Window): def mouseout(self, a=None, b=None): GLib.timeout_add_seconds(self.timeout_mouse_over, self.mouseout_timed) - return True def mouseout_timed(self, a=None, b=None): self.draw_blank = False - self.set_needs_redraw() \ No newline at end of file + self.set_needs_redraw() + return False diff --git a/discover_overlay/settings_window.py b/discover_overlay/settings_window.py index 113add2..bba0d1f 100644 --- a/discover_overlay/settings_window.py +++ b/discover_overlay/settings_window.py @@ -43,6 +43,18 @@ class MainSettingsWindow(): "/etc/default/discover-overlay") # Detect flatpak en self.disable_autostart = 'container' in os.environ + self.icon_name = "discover-overlay" + self.tray_icon_name = "discover-overlay-tray" + + icon_theme = Gtk.IconTheme.get_default() + icon_theme.add_resource_path(os.path.expanduser( + '~/.local/share/pipx/venvs/discover-overlay/share/icons')) + if not icon_theme.has_icon("discover-overlay"): + log.error("No icon found in theme") + self.icon_name = 'user-info' + if not icon_theme.has_icon(self.tray_icon_name): + log.error("No tray icon found in theme") + self.tray_icon_name = 'user-info' self.steamos = False self.voice_placement_window = None self.text_placement_window = None @@ -162,6 +174,11 @@ class MainSettingsWindow(): if not self.start_minimized or not self.show_sys_tray_icon: window.show() + if self.icon_name != 'discover-overlay': + self.widget['overview_image'].set_from_icon_name( + self.icon_name, Gtk.IconSize.DIALOG) + self.widget['window'].set_default_icon_name(self.icon_name) + def request_channels_from_guild(self, guild_id): with open(self.rpc_file, 'w') as f: f.write('--rpc --guild-request=%s' % (guild_id)) @@ -556,11 +573,12 @@ class MainSettingsWindow(): self.widget['core_settings_min'].set_active(self.start_minimized) self.widget['core_settings_min'].set_sensitive(self.show_sys_tray_icon) - - if 'XDG_SESSION_DESKTOP' in os.environ and os.environ['XDG_SESSION_DESKTOP']=='cinnamon': + + 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.widget['core_audio_assist'].set_active( + config.getboolean("general", "audio_assist", fallback=False)) self.loading_config = False @@ -595,7 +613,7 @@ class MainSettingsWindow(): from gi.repository import AppIndicator3 self.ind = AppIndicator3.Indicator.new( "discover_overlay", - "discover-overlay-tray", + self.tray_icon_name, AppIndicator3.IndicatorCategory.APPLICATION_STATUS) # Hide for now since we don't know if it should be shown yet self.ind.set_status(AppIndicator3.IndicatorStatus.PASSIVE) @@ -604,7 +622,7 @@ class MainSettingsWindow(): # Create System Tray log.info("Falling back to Systray : %s", exception) self.tray = Gtk.StatusIcon.new_from_icon_name( - "discover-overlay-tray") + self.tray_icon_name) self.tray.connect('popup-menu', self.show_menu) # Hide for now since we don't know if it should be shown yet self.tray.set_visible(False) @@ -1157,7 +1175,7 @@ class MainSettingsWindow(): def voice_hide_mouseover_changed(self, button): self.config_set("main", "autohide", "%s" % (button.get_active())) - def text_hide_mouseover_changed(self,button): + def text_hide_mouseover_changed(self, button): self.config_set("text", "autohide", "%s" % (button.get_active())) def voice_mouseover_timeout_changed(self, button): @@ -1169,7 +1187,8 @@ class MainSettingsWindow(): (int(button.get_value()))) def inactive_fade_changed(self, button): - self.config_set("main", "fade_out_inactive", "%s" % (button.get_active())) + self.config_set("main", "fade_out_inactive", "%s" % + (button.get_active())) def inactive_fade_opacity_changed(self, button): self.config_set("main", "fade_out_limit", "%.2f" % @@ -1177,11 +1196,12 @@ class MainSettingsWindow(): def inactive_time_changed(self, button): self.config_set("main", "inactive_time", "%s" % - (int(button.get_value()))) + (int(button.get_value()))) - def inactive_fade_time_changed(self,button): + def inactive_fade_time_changed(self, button): self.config_set("main", "inactive_fade_time", "%s" % - (int(button.get_value()))) - + (int(button.get_value()))) + def core_audio_assist_changed(self, button): - self.config_set("general", "audio_assist", "%s" % (button.get_active())) + self.config_set("general", "audio_assist", "%s" % + (button.get_active())) diff --git a/discover_overlay/text_overlay.py b/discover_overlay/text_overlay.py index fffec6d..33e0e0a 100644 --- a/discover_overlay/text_overlay.py +++ b/discover_overlay/text_overlay.py @@ -75,9 +75,10 @@ class TextOverlayWindow(OverlayWindow): """ Set the duration that a message will be visible for. """ - self.text_time = timer - self.timer_after_draw = timer - self.set_needs_redraw() + if self.text_time != timer or self.timer_after_draw != timer: + self.text_time = timer + self.timer_after_draw = timer + self.set_needs_redraw() def set_text_list(self, tlist, altered): """ @@ -91,46 +92,52 @@ class TextOverlayWindow(OverlayWindow): """ Set default text colour """ - self.fg_col = fg_col - self.set_needs_redraw() + if self.fg_col != fg_col: + self.fg_col = fg_col + self.set_needs_redraw() def set_bg(self, bg_col): """ Set background colour """ - self.bg_col = bg_col - self.set_needs_redraw() + if self.bg_col != bg_col: + self.bg_col = bg_col + self.set_needs_redraw() def set_show_attach(self, attachment): """ Set if attachments should be shown inline """ - self.show_attach = attachment - self.set_needs_redraw() + if self.attachment != attachment: + self.show_attach = attachment + self.set_needs_redraw() def set_popup_style(self, boolean): """ Set if message disappear after a certain duration """ - self.popup_style = boolean + if self.popup_style != boolean: + self.popup_style = boolean def set_font(self, font): """ Set font used to render text """ - self.text_font = font + if self.text_font != font: + self.text_font = font - self.pango_rect = Pango.Rectangle() - font = Pango.FontDescription(self.text_font) - self.pango_rect.width = font.get_size() * Pango.SCALE - self.pango_rect.height = font.get_size() * Pango.SCALE - self.set_needs_redraw() + self.pango_rect = Pango.Rectangle() + font = Pango.FontDescription(self.text_font) + self.pango_rect.width = font.get_size() * Pango.SCALE + self.pango_rect.height = font.get_size() * Pango.SCALE + self.set_needs_redraw() def set_line_limit(self, limit): """ Change maximum number of lines in overlay """ - self.line_limit = limit + if self.line_limit != limit: + self.line_limit = limit def make_line(self, message): """ diff --git a/discover_overlay/voice_overlay.py b/discover_overlay/voice_overlay.py index 815d703..991143a 100644 --- a/discover_overlay/voice_overlay.py +++ b/discover_overlay/voice_overlay.py @@ -98,8 +98,8 @@ class VoiceOverlayWindow(OverlayWindow): self.fade_out_inactive = True self.fade_out_limit = 0.1 - self.inactive_time = 10 # Seconds - self.inactive_fade_time = 20 # Seconds + self.inactive_time = 10 # Seconds + self.inactive_fade_time = 20 # Seconds self.fade_opacity = 1.0 self.fade_start = 0 @@ -124,13 +124,16 @@ class VoiceOverlayWindow(OverlayWindow): self.guild_ids = tuple() self.force_location() get_surface(self.recv_avatar, - "share/icons/hicolor/256x256/apps/discover-overlay-default.png", + "discover-overlay-default", 'def', self.avatar_size) self.set_title("Discover Voice") self.redraw() def reset_action_timer(self): + # Reset time since last voice activity self.fade_opacity = 1.0 + + # Remove both fading-out effect and timer set last time this happened if self.inactive_timeout: GLib.source_remove(self.inactive_timeout) self.inactive_timeout = None @@ -138,25 +141,33 @@ class VoiceOverlayWindow(OverlayWindow): GLib.source_remove(self.fadeout_timeout) self.fadeout_timeout = None + # If we're using this feature, schedule a new iactivity timer if self.fade_out_inactive: - self.inactive_timeout = GLib.timeout_add_seconds(self.inactive_time, self.overlay_inactive) + self.inactive_timeout = GLib.timeout_add_seconds( + self.inactive_time, self.overlay_inactive) def overlay_inactive(self): - self.fade_start= perf_counter() - self.fadeout_timeout = GLib.timeout_add(self.inactive_fade_time/200 * 1000, self.overlay_fadeout) + # Inactivity has hit the first threshold, start fading out + self.fade_start = perf_counter() + # Fade out in 200 steps over X seconds. + self.fadeout_timeout = GLib.timeout_add( + self.inactive_fade_time/200 * 1000, self.overlay_fadeout) self.inactive_timeout = None return False def overlay_fadeout(self): self.set_needs_redraw() + # There's no guarantee over the granularity of the callback here, so use our time-since to work out how faded out we should be + # Might look choppy on systems under high cpu usage but that's just how it's going to be now = perf_counter() time_percent = (now - self.fade_start) / self.inactive_fade_time - if time_percent>=1.0: + if time_percent >= 1.0: self.fade_opacity = self.fade_out_limit self.fadeout_timeout = None return False - self.fade_opacity = self.fade_out_limit + ((1.0 - self.fade_out_limit) * (1.0 - time_percent)) + self.fade_opacity = self.fade_out_limit + \ + ((1.0 - self.fade_out_limit) * (1.0 - time_percent)) return True def col(self, col, alpha=1.0): @@ -166,23 +177,13 @@ class VoiceOverlayWindow(OverlayWindow): if alpha == None: self.context.set_source_rgba(col[0], col[1], col[2], col[3]) else: - self.context.set_source_rgba(col[0], col[1], col[2], col[3] * alpha * self.fade_opacity) + self.context.set_source_rgba( + col[0], col[1], col[2], col[3] * alpha * self.fade_opacity) def set_icon_transparency(self, trans): - if self.icon_transparency == trans: - return - self.icon_transparency = trans - #get_surface(self.recv_avatar, - # "share/icons/hicolor/256x256/apps/discover-overlay-default.png", - # 'def', self.avatar_size) - - #self.avatars = {} - #self.avatar_masks = {} - - #self.channel_icon = None - #self.channel_mask = None - - self.set_needs_redraw() + if self.icon_transparency != trans: + self.icon_transparency = trans + self.set_needs_redraw() def set_blank(self): self.userlist = [] @@ -193,183 +194,210 @@ class VoiceOverlayWindow(OverlayWindow): self.set_needs_redraw() def set_fade_out_inactive(self, enabled, fade_time, fade_duration, fade_to): - if self.fade_out_inactive == enabled and self.inactive_time == fade_time and self.inactive_fade_time == fade_duration and self.fade_out_limit == fade_to: - return - self.fade_out_inactive = enabled - self.inactive_time = fade_time - self.inactive_fade_time = fade_duration - self.fade_out_limit = fade_to - self.reset_action_timer() + if self.fade_out_inactive != enabled or self.inactive_time != fade_time or self.inactive_fade_time != fade_duration or self.fade_out_limit != fade_to: + self.fade_out_inactive = enabled + self.inactive_time = fade_time + self.inactive_fade_time = fade_duration + self.fade_out_limit = fade_to + self.reset_action_timer() def set_title_font(self, font): - self.title_font = font - self.set_needs_redraw() + if self.title_font != font: + self.title_font = font + self.set_needs_redraw() def set_show_connection(self, show_connection): - self.show_connection = show_connection - self.set_needs_redraw() + if self.show_connection != show_connection: + self.show_connection = show_connection + self.set_needs_redraw() def set_show_avatar(self, show_avatar): - self.show_avatar = show_avatar - self.set_needs_redraw() + if self.show_avatar != show_avatar: + self.show_avatar = show_avatar + self.set_needs_redraw() def set_show_title(self, show_title): - self.show_title = show_title - self.set_needs_redraw() + if self.show_title != show_title: + self.show_title = show_title + self.set_needs_redraw() def set_show_disconnected(self, show_disconnected): - self.show_disconnected = show_disconnected - self.set_needs_redraw() + if self.show_disconnected != show_disconnected: + self.show_disconnected = show_disconnected + self.set_needs_redraw() def set_show_dummy(self, show_dummy): """ Toggle use of dummy userdata to help choose settings """ - self.use_dummy = show_dummy - self.set_needs_redraw() + if self.use_dummy != show_dummy: + self.use_dummy = show_dummy + self.set_needs_redraw() def set_dummy_count(self, dummy_count): - self.dummy_count = dummy_count - self.set_needs_redraw() + if self.dummy_count != dummy_count: + self.dummy_count = dummy_count + self.set_needs_redraw() def set_overflow(self, overflow): """ How should excessive numbers of users be dealt with? """ - self.overflow = overflow - self.set_needs_redraw() + if self.overflow != overflow: + self.overflow = overflow + self.set_needs_redraw() def set_bg(self, background_colour): """ Set the background colour """ - self.norm_col = background_colour - self.set_needs_redraw() + if self.norm_col != background_colour: + self.norm_col = background_colour + self.set_needs_redraw() def set_fg(self, foreground_colour): """ Set the text colour """ - self.text_col = foreground_colour - self.set_needs_redraw() + if self.text_col != foreground_colour: + self.text_col = foreground_colour + self.set_needs_redraw() def set_tk(self, talking_colour): """ Set the border colour for users who are talking """ - self.talk_col = talking_colour - self.set_needs_redraw() + if self.talk_col != talking_colour: + self.talk_col = talking_colour + self.set_needs_redraw() def set_mt(self, mute_colour): """ Set the colour of mute and deafen logos """ - self.mute_col = mute_colour - self.set_needs_redraw() + if self.mute_col != mute_colour: + self.mute_col = mute_colour + self.set_needs_redraw() def set_mute_bg(self, mute_bg_col): """ Set the background colour for mute/deafen icon """ - self.mute_bg_col = mute_bg_col - self.set_needs_redraw() + if self.mute_bg_col != mute_bg_col: + self.mute_bg_col = mute_bg_col + self.set_needs_redraw() def set_avatar_bg_col(self, avatar_bg_col): """ Set Avatar background colour """ - self.avatar_bg_col = avatar_bg_col - self.set_needs_redraw() + if self.avatar_bg_col != avatar_bg_col: + self.avatar_bg_col = avatar_bg_col + self.set_needs_redraw() def set_hi(self, highlight_colour): """ Set the colour of background for speaking users """ - self.hili_col = highlight_colour - self.set_needs_redraw() + if self.hili_col != highlight_colour: + self.hili_col = highlight_colour + self.set_needs_redraw() def set_fg_hi(self, highlight_colour): """ Set the colour of background for speaking users """ - self.text_hili_col = highlight_colour - self.set_needs_redraw() + if self.text_hili_col != highlight_colour: + self.text_hili_col = highlight_colour + self.set_needs_redraw() def set_bo(self, border_colour): """ Set the colour for idle border """ - self.border_col = border_colour - self.set_needs_redraw() + if self.border_col != border_colour: + self.border_col = border_colour + self.set_needs_redraw() def set_avatar_size(self, size): """ Set the size of the avatar icons """ - self.avatar_size = size - self.set_needs_redraw() + if self.avatar_size != size: + self.avatar_size = size + self.set_needs_redraw() def set_nick_length(self, size): """ Set the length of nickname """ - self.nick_length = size - self.set_needs_redraw() + if self.nick_length != size: + self.nick_length = size + self.set_needs_redraw() def set_icon_spacing(self, i): """ Set the spacing between avatar icons """ - self.icon_spacing = i - self.set_needs_redraw() + if self.icon_spacing != i: + self.icon_spacing = i + self.set_needs_redraw() def set_text_padding(self, i): """ Set padding between text and border """ - self.text_pad = i - self.set_needs_redraw() + if self.text_pad != i: + self.text_pad = i + self.set_needs_redraw() def set_text_baseline_adj(self, i): """ Set padding between text and border """ - self.text_baseline_adj = i - self.set_needs_redraw() + if self.text_baseline_adj != i: + self.text_baseline_adj = i + self.set_needs_redraw() def set_vert_edge_padding(self, i): """ Set padding between top/bottom of screen and overlay contents """ - self.vert_edge_padding = i - self.set_needs_redraw() + if self.vert_edge_padding != i: + self.vert_edge_padding = i + self.set_needs_redraw() def set_horz_edge_padding(self, i): """ Set padding between left/right of screen and overlay contents """ - self.horz_edge_padding = i - self.set_needs_redraw() + if self.horz_edge_padding != i: + self.horz_edge_padding = i + self.set_needs_redraw() def set_square_avatar(self, i): """ Set if the overlay should crop avatars to a circle or show full square image """ - self.round_avatar = not i - self.set_needs_redraw() + if self.round_avatar == i: + self.round_avatar = not i + self.set_needs_redraw() def set_fancy_border(self, border): """ Sets if border should wrap around non-square avatar images """ - self.fancy_border = border - self.set_needs_redraw() + if self.fancy_border != border: + self.fancy_border = border + self.set_needs_redraw() def set_only_speaking(self, only_speaking): """ Set if overlay should only show people who are talking """ - self.only_speaking = only_speaking + if self.only_speaking != only_speaking: + self.only_speaking = only_speaking + self.set_needs_redraw() def set_only_speaking_grace_period(self, grace_period): """ @@ -382,30 +410,36 @@ class VoiceOverlayWindow(OverlayWindow): """ Set if the overlay should highlight the user """ - self.highlight_self = highlight_self + if self.highlight_self != highlight_self: + self.highlight_self = highlight_self + self.set_needs_redraw() def set_order(self, i): """ Set the method used to order avatar icons & names """ - self.order = i - self.sort_list(self.userlist) - self.set_needs_redraw() + if self.order != i: + self.order = i + self.sort_list(self.userlist) + self.set_needs_redraw() def set_icon_only(self, i): """ Set if the overlay should draw only the icon """ - self.icon_only = i - self.set_needs_redraw() + if self.icon_only != i: + self.icon_only = i + self.set_needs_redraw() def set_border_width(self, width): - self.border_width = width - self.set_needs_redraw() + if self.border_width != width: + self.border_width = width + self.set_needs_redraw() def set_horizontal(self, horizontal=False): - self.horizontal = horizontal - self.set_needs_redraw() + if self.horizontal != horizontal: + self.horizontal = horizontal + self.set_needs_redraw() def set_guild_ids(self, guild_ids=tuple()): if self.discover.connection: @@ -442,7 +476,9 @@ class VoiceOverlayWindow(OverlayWindow): """ Set title above voice list """ - self.channel_title = channel_title + if self.channel_title != channel_title: + self.channel_title = channel_title + self.set_needs_redraw() def set_channel_icon(self, url): """ @@ -1015,7 +1051,7 @@ class VoiceOverlayWindow(OverlayWindow): context.clip() context.set_operator(cairo.OPERATOR_OVER) draw_img_to_rect(pixbuf, context, pos_x, pos_y, - avatar_size, avatar_size, False, False, 0,0,self.fade_opacity * self.icon_transparency) + avatar_size, avatar_size, False, False, 0, 0, self.fade_opacity * self.icon_transparency) context.restore() def draw_mute(self, context, pos_x, pos_y, bg_col, avatar_size): @@ -1100,7 +1136,7 @@ class VoiceOverlayWindow(OverlayWindow): # Add a dark background context.set_operator(cairo.OPERATOR_ATOP) context.rectangle(0.0, 0.0, 1.0, 1.0) - self.col(bg_col,None) + self.col(bg_col, None) context.fill() context.set_operator(cairo.OPERATOR_OVER)