diff --git a/discover_overlay/discover_overlay.py b/discover_overlay/discover_overlay.py index 4176bcb..75b2b92 100755 --- a/discover_overlay/discover_overlay.py +++ b/discover_overlay/discover_overlay.py @@ -286,6 +286,13 @@ class Discover: if title_font: self.voice_overlay.set_title_font(title_font) + self.voice_overlay.set_fade_out_inactive( + config.getboolean("main", "fade_out_inactive", fallback=False), + config.getint("main", "inactive_time", fallback=10), + config.getint("main", "inactive_fade_time", fallback=30), + config.getfloat("main", "fade_out_limit", fallback=0.3) + ) + # Set Text overlay options self.text_overlay.set_enabled(config.getboolean( "text", "enabled", fallback=False)) @@ -400,6 +407,7 @@ class Discover: self.text_overlay.set_hidden(hidden) self.notification_overlay.set_hidden(hidden) + def parse_guild_ids(self, guild_ids_str): """Parse the guild_ids from a str and return them in a list""" guild_ids = [] diff --git a/discover_overlay/glade/settings.glade b/discover_overlay/glade/settings.glade index 379235c..b721c42 100644 --- a/discover_overlay/glade/settings.glade +++ b/discover_overlay/glade/settings.glade @@ -3,7 +3,7 @@ - 0.5 + 0.10 1 0.5 0.01 @@ -155,6 +155,25 @@ 1 8 + + 1 + 0.3 + 0.01 + 0.10 + + + 1 + 100 + 1 + 1 + 10 + + + 1 + 100 + 1 + 10 + 10 32 @@ -720,7 +739,7 @@ - + voice_advanced_grid True @@ -1428,9 +1447,147 @@ 5 + 12 + + + + + voice_inactive_fade_label + True + False + Fade out when Inactive + 0 + + + 4 + 7 + + + + + voice_inactive_opacity_label + True + False + Inactive Opacity + 0 + + + 4 + 8 + + + + + voice_inactive_time_label + True + False + Time before Inactive + 0 + + + 4 9 + + + voice_inactive_fade_time_label + True + False + Time Fading out + 0 + + + 4 + 10 + + + + + voice_inactive_fade + True + True + False + True + + + + 5 + 7 + + + + + voice_inactive_opacity + True + True + voice_inactive_fade_opacity + 1 + + + + 5 + 8 + + + + + voice_inactive_time + True + True + 0 + voice_inactive_time_range + + + + 5 + 9 + + + + + voice_inactive_fade_time + True + True + 0 + voice_inactive_fade_time_range + + + + 5 + 10 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/discover_overlay/image_getter.py b/discover_overlay/image_getter.py index eb911d8..3c368ca 100644 --- a/discover_overlay/image_getter.py +++ b/discover_overlay/image_getter.py @@ -38,7 +38,7 @@ class SurfaceGetter(): self.url = url self.size = size - def get_url(self, alpha): + def get_url(self): """Downloads and decodes""" try: resp = requests.get( @@ -49,7 +49,7 @@ class SurfaceGetter(): ) raw = resp.raw image = Image.open(raw) - (surface, mask) = from_pil(image, alpha) + (surface, mask) = from_pil(image) self.func(self.identifier, surface, mask) except requests.HTTPError: @@ -67,7 +67,7 @@ class SurfaceGetter(): except PIL.UnidentifiedImageError: log.error("Unknown image type: %s", self.url) - def get_file(self, alpha): + def get_file(self): locations = [os.path.expanduser('~/.local/'), '/usr/', '/app'] for prefix in locations: mixpath = os.path.join(prefix, self.url) @@ -83,13 +83,13 @@ class SurfaceGetter(): except FileNotFoundError: log.error("File not found: %s", mixpath) if image: - (surface, mask) = from_pil(image, alpha) + (surface, mask) = from_pil(image) if surface: self.func(self.identifier, surface, mask) return -def from_pil(image, alpha): +def from_pil(image, alpha=1.0, format='BGRa'): """ :param im: Pillow Image :param alpha: 0..1 alpha to add to non-alpha images @@ -99,19 +99,21 @@ def from_pil(image, alpha): mask = bytearray() if 'A' not in image.getbands(): image.putalpha(int(alpha * 255.0)) - arr = bytearray(image.tobytes('raw', 'BGRa')) + arr = bytearray(image.tobytes('raw', format)) mask = arr else: - arr = bytearray(image.tobytes('raw', 'BGRa')) + arr = bytearray(image.tobytes('raw', format)) mask = copy.deepcopy((arr)) - idx = 3 + idx = 0 while idx < len(arr): if arr[idx] > 0: mask[idx] = 255 else: mask[idx] = 0 + # 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 += 4 + idx +=1 surface = cairo.ImageSurface.create_for_data( arr, cairo.FORMAT_ARGB32, image.width, image.height) mask = cairo.ImageSurface.create_for_data( @@ -119,14 +121,19 @@ def from_pil(image, alpha): return (surface, mask) -def get_surface(func, identifier, ava, size, alpha=1.0): +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) + +def get_surface(func, identifier, ava, size): """Download to cairo surface""" image_getter = SurfaceGetter(func, identifier, ava, size) if identifier.startswith('http'): - thread = threading.Thread(target=image_getter.get_url, args=[alpha]) + thread = threading.Thread(target=image_getter.get_url) thread.start() else: - thread = threading.Thread(target=image_getter.get_file, args=[alpha]) + thread = threading.Thread(target=image_getter.get_file) thread.start() @@ -157,12 +164,11 @@ 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, path=False, aspect=False, - anchor=0, hanchor=0): + anchor=0, hanchor=0, alpha=1.0): """Draw cairo surface onto context Path - only add the path do not fill : True/False @@ -181,7 +187,21 @@ 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()) - ctx.set_source_surface(img, 0, 0) + + if alpha != 1.0: + # Honestly, couldn't find a 'use-image-with-modifier' option + # Tried RasterSourcePattern but it appears... broken? in the python implementation + # Or just lacking documentation. + + # Pass raw data to PIL and then back with an alpha modifier + ctx.set_source_surface( + from_pil( + to_pil(img), + alpha + )[0], + 0,0) + else: + ctx.set_source_surface(img, 0, 0) ctx.rectangle(0, 0, img.get_width(), img.get_height()) if not path: @@ -189,7 +209,6 @@ 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/locales/base.pot b/discover_overlay/locales/base.pot index f68dd99..8708867 100644 --- a/discover_overlay/locales/base.pot +++ b/discover_overlay/locales/base.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-23 14:16+0100\n" +"POT-Creation-Date: 2024-03-22 15:37+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -298,6 +298,18 @@ msgstr "" msgid "Reset Voice Settings" msgstr "" +msgid "Fade out when Inactive" +msgstr "" + +msgid "Inactive Opacity" +msgstr "" + +msgid "Time before Inactive" +msgstr "" + +msgid "Time Fading out" +msgstr "" + msgid "Hide Overlay on Mouseover" msgstr "" diff --git a/discover_overlay/locales/cy/LC_MESSAGES/default.mo b/discover_overlay/locales/cy/LC_MESSAGES/default.mo index 3337022..ef2c5ac 100644 Binary files a/discover_overlay/locales/cy/LC_MESSAGES/default.mo and b/discover_overlay/locales/cy/LC_MESSAGES/default.mo differ diff --git a/discover_overlay/locales/cy/LC_MESSAGES/default.po b/discover_overlay/locales/cy/LC_MESSAGES/default.po index 07ca0ea..20a472d 100644 --- a/discover_overlay/locales/cy/LC_MESSAGES/default.po +++ b/discover_overlay/locales/cy/LC_MESSAGES/default.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-23 14:16+0100\n" +"POT-Creation-Date: 2024-03-22 15:37+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -307,6 +307,18 @@ msgstr "Padio rhwng defnyddwyr" msgid "Reset Voice Settings" msgstr "Ailosod Gosodiadau Llais" +msgid "Fade out when Inactive" +msgstr "Diffoddwch pan yn anweithgar" + +msgid "Inactive Opacity" +msgstr "Anweithgar Gormes" + +msgid "Time before Inactive" +msgstr "Amser cyn anweithgar" + +msgid "Time Fading out" +msgstr "Amser yn pylu" + msgid "Hide Overlay on Mouseover" msgstr "Cuddio troshaenu ar mouseover" diff --git a/discover_overlay/locales/de/LC_MESSAGES/default.po b/discover_overlay/locales/de/LC_MESSAGES/default.po index 8d5ae08..f6095d6 100644 --- a/discover_overlay/locales/de/LC_MESSAGES/default.po +++ b/discover_overlay/locales/de/LC_MESSAGES/default.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: unnamed project\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-23 14:16+0100\n" +"POT-Creation-Date: 2024-03-22 15:37+0000\n" "PO-Revision-Date: 2022-09-23 23:05+0200\n" "Last-Translator: Baumfinder \n" "Language-Team: German \n" @@ -314,6 +314,18 @@ msgstr "" msgid "Reset Voice Settings" msgstr "" +msgid "Fade out when Inactive" +msgstr "" + +msgid "Inactive Opacity" +msgstr "" + +msgid "Time before Inactive" +msgstr "" + +msgid "Time Fading out" +msgstr "" + msgid "Hide Overlay on Mouseover" msgstr "" diff --git a/discover_overlay/locales/en/LC_MESSAGES/default.po b/discover_overlay/locales/en/LC_MESSAGES/default.po index d9a21c5..1723602 100644 --- a/discover_overlay/locales/en/LC_MESSAGES/default.po +++ b/discover_overlay/locales/en/LC_MESSAGES/default.po @@ -279,6 +279,18 @@ msgstr "" msgid "Reset Voice Settings" msgstr "" +msgid "Fade out when Inactive" +msgstr "" + +msgid "Inactive Opacity" +msgstr "" + +msgid "Time before Inactive" +msgstr "" + +msgid "Time Fading out" +msgstr "" + msgid "Hide Overlay on Mouseover" msgstr "" diff --git a/discover_overlay/locales/fr/LC_MESSAGES/default.po b/discover_overlay/locales/fr/LC_MESSAGES/default.po index acba801..f5b9757 100644 --- a/discover_overlay/locales/fr/LC_MESSAGES/default.po +++ b/discover_overlay/locales/fr/LC_MESSAGES/default.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-23 14:16+0100\n" +"POT-Creation-Date: 2024-03-22 15:37+0000\n" "PO-Revision-Date: 2024-02-23 14:05+0100\n" "Last-Translator: Noé Lopez \n" "Language-Team: English \n" @@ -307,6 +307,18 @@ msgstr "Espace entre les utilisateurs" msgid "Reset Voice Settings" msgstr "Réinitialiser les paramètres de voix" +msgid "Fade out when Inactive" +msgstr "" + +msgid "Inactive Opacity" +msgstr "" + +msgid "Time before Inactive" +msgstr "" + +msgid "Time Fading out" +msgstr "" + msgid "Hide Overlay on Mouseover" msgstr "Cacher l'overlay au survolement de la souris" diff --git a/discover_overlay/locales/tr/LC_MESSAGES/default.po b/discover_overlay/locales/tr/LC_MESSAGES/default.po index f0b4c52..62661ab 100644 --- a/discover_overlay/locales/tr/LC_MESSAGES/default.po +++ b/discover_overlay/locales/tr/LC_MESSAGES/default.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: 1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-23 14:16+0100\n" +"POT-Creation-Date: 2024-03-22 15:37+0000\n" "PO-Revision-Date: 2023-04-17 15:44+0300\n" "Last-Translator: Ahmet Arda Kavakcı \n" "Language-Team: Turkish \n" @@ -306,6 +306,18 @@ msgstr "Kullanıcılar arası iç boşluk" msgid "Reset Voice Settings" msgstr "" +msgid "Fade out when Inactive" +msgstr "" + +msgid "Inactive Opacity" +msgstr "" + +msgid "Time before Inactive" +msgstr "" + +msgid "Time Fading out" +msgstr "" + msgid "Hide Overlay on Mouseover" msgstr "" diff --git a/discover_overlay/overlay.py b/discover_overlay/overlay.py index 6556a3e..2b203d3 100644 --- a/discover_overlay/overlay.py +++ b/discover_overlay/overlay.py @@ -115,7 +115,13 @@ class OverlayWindow(Gtk.Window): self.connect("enter-notify-event", self.mouseover) self.connect("leave-notify-event", self.mouseout) self.mouse_over_timer = None - + + # It shouldn't be possible, but let's not leave + # this process hanging if it happens + self.connect('destroy', self.window_exited) + + def window_exited(self, window=None): + sys.exit(1) def set_gamescope_xatom(self, enabled): if self.piggyback_parent: diff --git a/discover_overlay/settings_window.py b/discover_overlay/settings_window.py index 91bc5d3..f66975f 100644 --- a/discover_overlay/settings_window.py +++ b/discover_overlay/settings_window.py @@ -409,6 +409,19 @@ class MainSettingsWindow(): self.widget['voice_dummy_count'].set_value( config.getint("main", "dummy_count", fallback=50)) + self.widget['voice_inactive_fade'].set_active( + config.getboolean("main", "fade_out_inactive", fallback=False) + ) + self.widget['voice_inactive_opacity'].set_value( + config.getfloat("main", "fade_out_limit", fallback=0.3) + ) + self.widget['voice_inactive_time'].set_value( + config.getint("main", "inactive_time", fallback=10) + ) + self.widget['voice_inactive_fade_time'].set_value( + config.getint("main", "inactive_fade_time", fallback=30) + ) + # Read Text section self.text_floating_x = config.getint("text", "floating_x", fallback=0) @@ -1150,4 +1163,19 @@ class MainSettingsWindow(): def text_mouseover_timeout_changed(self, button): self.config_set("text", "autohide_timer", "%s" % - (int(button.get_value()))) \ No newline at end of file + (int(button.get_value()))) + + def inactive_fade_changed(self, button): + 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" % + (button.get_value())) + + def inactive_time_changed(self, button): + self.config_set("main", "inactive_time", "%s" % + (int(button.get_value()))) + + 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 diff --git a/discover_overlay/voice_overlay.py b/discover_overlay/voice_overlay.py index a1686e7..d8f461c 100644 --- a/discover_overlay/voice_overlay.py +++ b/discover_overlay/voice_overlay.py @@ -96,6 +96,16 @@ class VoiceOverlayWindow(OverlayWindow): self.icon_transparency = 0.0 self.fancy_border = False + self.fade_out_inactive = True + self.fade_out_limit = 0.1 + self.inactive_time = 10 # Seconds + self.inactive_fade_time = 20 # Seconds + self.fade_opacity = 1.0 + self.fade_start = 0 + + self.inactive_timeout = None + self.fadeout_timeout = None + self.round_avatar = True self.icon_only = True self.talk_col = [0.0, 0.6, 0.0, 0.1] @@ -115,23 +125,58 @@ class VoiceOverlayWindow(OverlayWindow): self.force_location() get_surface(self.recv_avatar, "share/icons/hicolor/256x256/apps/discover-overlay-default.png", - 'def', self.avatar_size, self.icon_transparency) + 'def', self.avatar_size) self.set_title("Discover Voice") self.redraw() + def reset_action_timer(self): + self.fade_opacity = 1.0 + if self.inactive_timeout: + GLib.source_remove(self.inactive_timeout) + if self.fadeout_timeout: + GLib.source_remove(self.fade_opacity) + + if self.fade_out_inactive: + GLib.timeout_add_seconds(self.inactive_time, self.overlay_inactive) + + def overlay_inactive(self): + self.fade_start= perf_counter() + GLib.timeout_add(self.inactive_fade_time/200 * 1000, self.overlay_fadeout) + return False + + def overlay_fadeout(self): + self.set_needs_redraw() + now = perf_counter() + time_percent = (now - self.fade_start) / self.inactive_fade_time + if time_percent>=1.0: + self.fade_opacity = self.fade_out_limit + return False + + 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): + """ + Convenience function to set the cairo context next colour. Altered to account for fade-out function + """ + 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) + 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.icon_transparency) + #get_surface(self.recv_avatar, + # "share/icons/hicolor/256x256/apps/discover-overlay-default.png", + # 'def', self.avatar_size) - self.avatars = {} - self.avatar_masks = {} + #self.avatars = {} + #self.avatar_masks = {} - self.channel_icon = None - self.channel_mask = None + #self.channel_icon = None + #self.channel_mask = None self.set_needs_redraw() @@ -143,6 +188,15 @@ class VoiceOverlayWindow(OverlayWindow): self.connection_status = "DISCONNECTED" 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() + def set_title_font(self, font): self.title_font = font self.set_needs_redraw() @@ -360,7 +414,7 @@ class VoiceOverlayWindow(OverlayWindow): """ Use window colour to draw """ - self.col(self.wind_col) + self.col(self.wind_col, None) def set_norm_col(self): """ @@ -374,11 +428,11 @@ class VoiceOverlayWindow(OverlayWindow): """ self.col(self.talk_col, alpha) - def set_mute_col(self, alpha=1.0): + def set_mute_col(self): """ Use mute colour to draw """ - self.col(self.mute_col, alpha) + self.col(self.mute_col) def set_channel_title(self, channel_title): """ @@ -395,7 +449,7 @@ class VoiceOverlayWindow(OverlayWindow): self.channel_icon_url = None else: get_surface(self.recv_avatar, url, "channel", - self.avatar_size, self.icon_transparency) + self.avatar_size) self.channel_icon_url = url def set_user_list(self, userlist, alt): @@ -410,6 +464,7 @@ class VoiceOverlayWindow(OverlayWindow): user["friendlyname"] = user["username"] self.sort_list(self.userlist) if alt: + self.reset_action_timer() self.set_needs_redraw() def set_connection_status(self, connection): @@ -447,6 +502,7 @@ class VoiceOverlayWindow(OverlayWindow): context.set_antialias(cairo.ANTIALIAS_GOOD) # Get size of window (width, height) = self.get_size() + # Make background transparent self.set_wind_col() # Don't layer drawing over each other, always replace @@ -708,10 +764,15 @@ class VoiceOverlayWindow(OverlayWindow): self.blank_avatar(context, pos_x, pos_y, avatar_size) if self.channel_icon_url: get_surface(self.recv_avatar, self.channel_icon_url, "channel", - self.avatar_size, self.icon_transparency) + self.avatar_size) return tw def unused_fn_needed_translations(self): + """ + These are here to force them to be picked up for translations + + They're fed right through from Discord client as string literals + """ _("DISCONNECTED") _("NO_ROUTE") _("VOICE_DISCONNECTED") @@ -752,7 +813,7 @@ class VoiceOverlayWindow(OverlayWindow): url = "https://cdn.discordapp.com/avatars/%s/%s.png" % ( user['id'], user['avatar']) get_surface(self.recv_avatar, url, user["id"], - self.avatar_size, self.icon_transparency) + self.avatar_size) # Set the key with no value to avoid spamming requests self.avatars[user["id"]] = None @@ -950,7 +1011,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) + 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): @@ -966,7 +1027,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) + self.col(bg_col, None) context.fill() context.set_operator(cairo.OPERATOR_OVER) @@ -1035,7 +1096,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) + self.col(bg_col,None) context.fill() context.set_operator(cairo.OPERATOR_OVER)