From db794f229c8cf9e0062e089206861313e9ef2fe8 Mon Sep 17 00:00:00 2001 From: rewalo <170283204+rewalo@users.noreply.github.com> Date: Wed, 18 Mar 2026 05:07:58 -0700 Subject: [PATCH 1/3] Fork Improvements --- discover_overlay/discord_connector.py | 3 +- discover_overlay/discover_overlay.py | 18 +- discover_overlay/glade/settings.glade | 56 +++- discover_overlay/settings_window.py | 37 ++- discover_overlay/voice_overlay.py | 419 ++++++++++++++++++-------- 5 files changed, 383 insertions(+), 150 deletions(-) diff --git a/discover_overlay/discord_connector.py b/discover_overlay/discord_connector.py index c13bc81..1ac9c4c 100644 --- a/discover_overlay/discord_connector.py +++ b/discover_overlay/discord_connector.py @@ -370,6 +370,7 @@ def on_message(self, message): elif j["cmd"] == "AUTHENTICATE": if j["evt"] == "ERROR": self.access_token = None + self.discover.config_set("cache", "access_token", "") self.get_access_token_stage1() return else: @@ -793,7 +794,7 @@ def schedule_reconnect(self): """Set a timer to attempt reconnection""" if self.reconnect_cb is None: log.info("Scheduled a reconnect") - self.reconnect_cb = GLib.timeout_add_seconds(60, self.connect) + self.reconnect_cb = GLib.timeout_add_seconds(3, self.connect) else: log.error("Reconnect already scheduled") diff --git a/discover_overlay/discover_overlay.py b/discover_overlay/discover_overlay.py index 4e75283..16ab4ff 100755 --- a/discover_overlay/discover_overlay.py +++ b/discover_overlay/discover_overlay.py @@ -178,21 +178,21 @@ def config_changed(self, _a=None, _b=None, _c=None, _d=None): self.voice_overlay.set_align_y( config.getint("main", "topalign", fallback=1)) self.voice_overlay.set_bg(json.loads(config.get( - "main", "bg_col", fallback="[0.0,0.0,0.0,0.5]"))) + "main", "bg_col", fallback="[0.153, 0.153, 0.165, 1.0]"))) self.voice_overlay.set_fg(json.loads(config.get( - "main", "fg_col", fallback="[1.0,1.0,1.0,1.0]"))) + "main", "fg_col", fallback="[0.63, 0.63, 0.67, 1.0]"))) self.voice_overlay.set_fg_hi(json.loads(config.get( "main", "fg_hi_col", fallback="[1.0,1.0,1.0,1.0]"))) self.voice_overlay.set_tk(json.loads(config.get( - "main", "tk_col", fallback="[0.0,0.7,0.0,1.0]"))) + "main", "tk_col", fallback="[0.13, 0.77, 0.37, 1.0]"))) self.voice_overlay.set_mt(json.loads(config.get( "main", "mt_col", fallback="[0.6,0.0,0.0,1.0]"))) self.voice_overlay.set_mute_bg(json.loads(config.get( - "main", "mt_bg_col", fallback="[0.0,0.0,0.0,0.5]"))) + "main", "mt_bg_col", fallback="[0.0, 0.0, 0.0, 0.8]"))) self.voice_overlay.set_hi(json.loads(config.get( - "main", "hi_col", fallback="[0.0,0.0,0.0,0.5]"))) + "main", "hi_col", fallback="[0.153, 0.153, 0.165, 1.0]"))) self.voice_overlay.set_bo(json.loads(config.get( - "main", "bo_col", fallback="[0.0,0.0,0.0,0.0]"))) + "main", "bo_col", fallback="[0.63, 0.63, 0.67, 1.0]"))) self.voice_overlay.set_avatar_bg_col(json.loads(config.get( "main", "avatar_bg_col", fallback="[0.0,0.0,0.0,0.0]"))) self.voice_overlay.set_avatar_size( @@ -208,7 +208,11 @@ def config_changed(self, _a=None, _b=None, _c=None, _d=None): font = config.get("main", "font", fallback=None) title_font = config.get("main", "title_font", fallback=None) self.voice_overlay.set_square_avatar(config.getboolean( - "main", "square_avatar", fallback=True)) + "main", "square_avatar", fallback=False)) + self.voice_overlay.set_rounded_names(config.getboolean( + "main", "rounded_names", fallback=True)) + self.voice_overlay.set_separate_names(config.getboolean( + "main", "separate_names", fallback=False)) self.voice_overlay.set_only_speaking(config.getboolean( "main", "only_speaking", fallback=False)) self.voice_overlay.set_only_speaking_grace_period(config.getint( diff --git a/discover_overlay/glade/settings.glade b/discover_overlay/glade/settings.glade index c6d671c..0acb46c 100644 --- a/discover_overlay/glade/settings.glade +++ b/discover_overlay/glade/settings.glade @@ -1039,6 +1039,60 @@ 5 + + + voice_rounded_names_label + True + False + Rounded Names + 0 + + + 0 + 6 + + + + + voice_rounded_names + True + True + False + True + + + + 1 + 6 + + + + + voice_separate_names_label + True + False + Seperate Names + 0 + + + 0 + 10 + + + + + voice_separate_names + True + True + False + True + + + + 1 + 10 + + voice_show_test_content_label @@ -1276,7 +1330,7 @@ voice_avatar_opacity_label True False - Avatar Opacity + Opacity 0 diff --git a/discover_overlay/settings_window.py b/discover_overlay/settings_window.py index 640b7a6..87b8b76 100644 --- a/discover_overlay/settings_window.py +++ b/discover_overlay/settings_window.py @@ -507,25 +507,25 @@ def read_config(self): "main", "fg_hi_col", fallback="[1.0,1.0,1.0,1.0]"))) self.widget['voice_talking_background'].set_rgba(self.make_colour(config.get( - "main", "hi_col", fallback="[0.0,0.0,0.0,0.5]"))) + "main", "hi_col", fallback="[0.153,0.153,0.165,1.0]"))) self.widget['voice_talking_border'].set_rgba(self.make_colour(config.get( - "main", "tk_col", fallback="[0.0,0.7,0.0,1.0]"))) + "main", "tk_col", fallback="[0.13, 0.77, 0.37, 1.0]"))) self.widget['voice_idle_foreground'].set_rgba(self.make_colour(config.get( - "main", "fg_col", fallback="[1.0,1.0,1.0,1.0]"))) + "main", "fg_col", fallback="[0.63,0.63,0.67,1.0]"))) self.widget['voice_idle_background'].set_rgba(self.make_colour(config.get( - "main", "bg_col", fallback="[0.0,0.0,0.0,0.5]"))) + "main", "bg_col", fallback="[0.153,0.153,0.165,1.0]"))) self.widget['voice_idle_border'].set_rgba(self.make_colour(config.get( - "main", "bo_col", fallback="[0.0,0.0,0.0,0.0]"))) + "main", "bo_col", fallback="[0.63,0.63,0.67,1.0]"))) self.widget['voice_mute_foreground'].set_rgba(self.make_colour(config.get( "main", "mt_col", fallback="[0.6,0.0,0.0,1.0]"))) self.widget['voice_mute_background'].set_rgba(self.make_colour(config.get( - "main", "mt_bg_col", fallback="[0.0,0.0,0.0,0.5]"))) + "main", "mt_bg_col", fallback="[0.0,0.0,0.0,0.8]"))) self.widget['voice_avatar_background'].set_rgba(self.make_colour(config.get( "main", "avatar_bg_col", fallback="[0.0,0.0,0.0,0.0]"))) @@ -544,7 +544,12 @@ def read_config(self): self.voice_show_name_hide_others(show_name) self.widget['voice_square_avatar'].set_active(config.getboolean( - "main", "square_avatar", fallback=True)) + "main", "square_avatar", fallback=False)) + + self.widget['voice_rounded_names'].set_active(config.getboolean( + "main", "rounded_names", fallback=True)) + self.widget['voice_separate_names'].set_active(config.getboolean( + "main", "separate_names", fallback=False)) self.widget['voice_fancy_avatar_shapes'].set_active( config.getboolean("main", "fancy_border", fallback=True)) @@ -1126,7 +1131,19 @@ def voice_display_icon_only_changed(self, button): self.voice_show_name_hide_others(button.get_active()) def voice_square_avatar_changed(self, button): - self.config_set("main", "square_avatar", f"{button.get_active()}") + """Update active setting for config option""" + update = button.get_active() + self.config_set("main", "square_avatar", f"{update}") + + def voice_rounded_names_changed(self, button): + """Update active setting for config option""" + update = button.get_active() + self.config_set("main", "rounded_names", f"{update}") + + def voice_separate_names_changed(self, button): + """Update active setting for config option""" + update = button.get_active() + self.config_set("main", "separate_names", f"{update}") def voice_fancy_avatar_shapes_changed(self, button): self.config_set("main", "fancy_border", f"{button.get_active()}") @@ -1163,12 +1180,16 @@ def voice_show_name_hide_others(self, val): self.widget['voice_text_padding'].set_sensitive(True) self.widget['voice_text_vertical_offset'].set_sensitive(True) self.widget['voice_nick_length'].set_sensitive(True) + self.widget['voice_rounded_names'].set_sensitive(True) + self.widget['voice_separate_names'].set_sensitive(True) else: # Hide name options self.widget['voice_font'].set_sensitive(False) self.widget['voice_text_padding'].set_sensitive(False) self.widget['voice_text_vertical_offset'].set_sensitive(False) self.widget['voice_nick_length'].set_sensitive(False) + self.widget['voice_rounded_names'].set_sensitive(False) + self.widget['voice_separate_names'].set_sensitive(False) def voice_show_avatar_hide_others(self, val): if val: diff --git a/discover_overlay/voice_overlay.py b/discover_overlay/voice_overlay.py index b84e517..4c28d80 100644 --- a/discover_overlay/voice_overlay.py +++ b/discover_overlay/voice_overlay.py @@ -104,6 +104,8 @@ def __init__(self, discover, piggyback=None): self.fadeout_timeout = None self.round_avatar = True + self.rounded_names = True + self.separate_names = False self.icon_only = True self.talk_col = [0.0, 0.6, 0.0, 0.1] self.text_col = [1.0, 1.0, 1.0, 1.0] @@ -176,10 +178,11 @@ def col(self, col, alpha=1.0): 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) + col[0], col[1], col[2], col[3] * alpha * + self.fade_opacity * self.icon_transparency) def set_icon_transparency(self, trans): - """Config option: icon transparency""" + """Config option: overall voice overlay opacity""" if self.icon_transparency != trans: self.icon_transparency = trans self.set_needs_redraw() @@ -233,6 +236,57 @@ def set_show_disconnected(self, show_disconnected): self.show_disconnected = show_disconnected self.set_needs_redraw() + def set_rounded_names(self, rnames): + """Config option: Draw rounded text backgrounds""" + if self.rounded_names != rnames: + self.rounded_names = rnames + self.set_needs_redraw() + + def set_separate_names(self, separate): + """Config option: separate name backgrounds from avatar body""" + if self.separate_names != separate: + self.separate_names = separate + self.set_needs_redraw() + + def draw_rounded_rect(self, context, x, y, width, height, radius=6): + """Helper to draw rounded square/squircle-ish rectangle""" + radius = max(0, min(radius, width / 2.0, height / 2.0)) + if radius == 0: + context.new_path() + context.rectangle(x, y, width, height) + return + + smoothness = 0.24 + control = radius * smoothness + + context.new_path() + context.move_to(x + radius, y) + context.line_to(x + width - radius, y) + context.curve_to( + x + width - radius + control, y, + x + width, y + radius - control, + x + width, y + radius + ) + context.line_to(x + width, y + height - radius) + context.curve_to( + x + width, y + height - radius + control, + x + width - radius + control, y + height, + x + width - radius, y + height + ) + context.line_to(x + radius, y + height) + context.curve_to( + x + radius - control, y + height, + x, y + height - radius + control, + x, y + height - radius + ) + context.line_to(x, y + radius) + context.curve_to( + x, y + radius - control, + x + radius - control, y, + x + radius, y + ) + context.close_path() + def set_show_dummy(self, show_dummy): """Config option: Show placeholder information""" if self.use_dummy != show_dummy: @@ -499,16 +553,22 @@ def overlay_draw(self, w, context, data=None): (floating_x, floating_y, floating_width, floating_height) = self.get_floating_coords() if self.is_wayland or self.piggyback_parent or self.discover.steamos: - # Special case! - # The window is full-screen regardless of what the user has selected. - # We need to set a clip and a transform to imitate original behaviour - # Used in wlroots & gamescope - + # Special case! Full-screen window; we clip to floating rect when floating. if self.floating: context.new_path() context.translate(floating_x, floating_y) context.rectangle(0, 0, floating_width, floating_height) context.clip() + layout_width = floating_width + layout_height = floating_height + else: + edge_margin = 4 + context.translate(edge_margin, edge_margin) + layout_width = max(1, width - 2 * edge_margin) + layout_height = max(1, height - 2 * edge_margin) + else: + layout_width = width + layout_height = height context.set_operator(cairo.OPERATOR_OVER) if (not self.show_disconnected and self.connection_status == "DISCONNECTED" @@ -569,7 +629,8 @@ def overlay_draw(self, w, context, data=None): users_to_draw.insert(0, self_user) avatar_size = self.avatar_size if self.show_avatar else 0 - line_height = self.avatar_size + slot_size = avatar_size + line_height = slot_size avatars_per_row = sys.maxsize # Calculate height needed to show overlay @@ -586,21 +647,25 @@ def overlay_draw(self, w, context, data=None): needed_width = (len(users_to_draw) * line_height) + \ (len(users_to_draw) + 1) * self.icon_spacing - if needed_width > width: + if needed_width > layout_width: if self.overflow == 1: # Wrap avatars_per_row = int( - width / (avatar_size+self.icon_spacing)) + layout_width / (slot_size + self.icon_spacing)) elif self.overflow == 2: # Shrink - available_size = width / len(users_to_draw) - avatar_size = available_size - self.icon_spacing + available_size = layout_width / len(users_to_draw) + # Correct math: available_size is the total space including the gap + slot_size = available_size - self.icon_spacing + avatar_size = slot_size if avatar_size < 8: avatar_size = 8 + slot_size = avatar_size + line_height = slot_size current_y = 0 + self.vert_edge_padding - offset_y = avatar_size + self.icon_spacing + offset_y = slot_size + self.icon_spacing if self.align_right: # A lie. Align bottom - current_y = (height - avatar_size) - self.vert_edge_padding - offset_y = -(avatar_size + self.icon_spacing) + current_y = (layout_height - slot_size) - self.vert_edge_padding + offset_y = -(slot_size + self.icon_spacing) rows_to_draw = [] while len(users_to_draw) > 0: row = [] @@ -611,9 +676,9 @@ def overlay_draw(self, w, context, data=None): needed_width = (len(row) * (line_height + self.icon_spacing)) current_x = 0 + self.horz_edge_padding if self.align_vert == 1: - current_x = (width / 2) - (needed_width) / 2 + current_x = (layout_width / 2) - (needed_width) / 2 elif self.align_vert == 2: - current_x = width - needed_width - self.horz_edge_padding + current_x = layout_width - needed_width - self.horz_edge_padding for user in row: if not user: @@ -628,35 +693,39 @@ def overlay_draw(self, w, context, data=None): else: self.draw_avatar(context, user, current_x, current_y, avatar_size, line_height) - current_x += avatar_size + self.icon_spacing + current_x += slot_size + self.icon_spacing current_y += offset_y else: needed_height = ((len(users_to_draw)+0) * line_height) + \ (len(users_to_draw) + 1) * self.icon_spacing - if needed_height > height: + if needed_height > layout_height: if self.overflow == 1: # Wrap avatars_per_row = int( - height / (avatar_size + self.icon_spacing)) + layout_height / (slot_size + self.icon_spacing)) elif self.overflow == 2: # Shrink - available_size = height / len(users_to_draw) - avatar_size = available_size - self.icon_spacing + available_size = layout_height / len(users_to_draw) + # Correct math: available_size is the total space including the gap + slot_size = available_size - self.icon_spacing + avatar_size = slot_size if avatar_size < 8: avatar_size = 8 + slot_size = avatar_size + line_height = slot_size current_x = 0 + self.horz_edge_padding offset_x_mult = 1 - offset_x = avatar_size + self.horz_edge_padding + offset_x = slot_size + self.horz_edge_padding if self.align_right: offset_x_mult = -1 - current_x = floating_width - avatar_size - self.horz_edge_padding + current_x = layout_width - slot_size - self.horz_edge_padding # Choose where to start drawing current_y = 0 + self.vert_edge_padding if self.align_vert == 1: - current_y = (height / 2) - (needed_height / 2) + current_y = (layout_height / 2) - (needed_height / 2) elif self.align_vert == 2: - current_y = height - needed_height - self.vert_edge_padding + current_y = layout_height - needed_height - self.vert_edge_padding cols_to_draw = [] while len(users_to_draw) > 0: @@ -668,9 +737,9 @@ def overlay_draw(self, w, context, data=None): needed_height = (len(col) * (line_height + self.icon_spacing)) current_y = 0 + self.vert_edge_padding if self.align_vert == 1: - current_y = (height/2) - (needed_height / 2) + current_y = (layout_height / 2) - (needed_height / 2) elif self.align_vert == 2: - current_y = height - needed_height - self.vert_edge_padding + current_y = layout_height - needed_height - self.vert_edge_padding largest_text_width = 0 for user in col: if not user: @@ -726,6 +795,8 @@ def delete_avatar(self, identifier): def draw_title(self, context, pos_x, pos_y, avatar_size, line_height): """Draw title at given Y position. Includes both text and image based on settings""" + av_x = pos_x + (line_height - avatar_size) / 2.0 + av_y = pos_y + (line_height - avatar_size) / 2.0 tw = 0 if not self.horizontal and not self.icon_only: title = self.channel_title @@ -733,8 +804,8 @@ def draw_title(self, context, pos_x, pos_y, avatar_size, line_height): title = "Dummy Title" tw = self.draw_text( context, title, - pos_x, - pos_y, + av_x, + av_y, self.text_col, self.norm_col, avatar_size, @@ -743,9 +814,9 @@ def draw_title(self, context, pos_x, pos_y, avatar_size, line_height): ) if self.channel_icon: self.draw_avatar_pix(context, self.channel_icon, self.channel_mask, - pos_x, pos_y, None, avatar_size) + av_x, av_y, None, avatar_size) else: - self.blank_avatar(context, pos_x, pos_y, avatar_size) + self.blank_avatar(context, av_x, av_y, avatar_size) if self.channel_icon_url: get_surface(self.recv_avatar, self.channel_icon_url, "channel", self.avatar_size) @@ -770,24 +841,28 @@ def unused_fn_needed_translations(self): def draw_connection(self, context, pos_x, pos_y, avatar_size, line_height): """Draw title at given Y position. Includes both text and image based on settings""" + av_x = pos_x + (line_height - avatar_size) / 2.0 + av_y = pos_y + (line_height - avatar_size) / 2.0 tw = 0 if not self.horizontal and not self.icon_only: tw = self.draw_text( context, _(self.connection_status), - pos_x, - pos_y, + av_x, + av_y, self.text_col, self.norm_col, avatar_size, line_height, self.text_font ) - self.blank_avatar(context, pos_x, pos_y, avatar_size) - self.draw_connection_icon(context, pos_x, pos_y, avatar_size) + self.blank_avatar(context, av_x, av_y, avatar_size) + self.draw_connection_icon(context, av_x, av_y, avatar_size) return tw def draw_avatar(self, context, user, pos_x, pos_y, avatar_size, line_height): """Draw avatar at given Y position. Includes both text and image based on settings""" + av_x = pos_x + (line_height - avatar_size) / 2.0 + av_y = pos_y + (line_height - avatar_size) / 2.0 # Ensure pixbuf for avatar if user["id"] not in self.avatars and user["avatar"] and avatar_size > 0: url = f"https://cdn.discordapp.com/avatars/{user['id']}/{user['avatar']}.png" @@ -827,21 +902,21 @@ def draw_avatar(self, context, user, pos_x, pos_y, avatar_size, line_height): if not self.icon_only: tw = self.draw_text( context, user["friendlyname"], - pos_x, - pos_y, + av_x, + av_y, fg_col, bg_col, avatar_size, line_height, self.text_font ) - self.draw_avatar_pix(context, pix, mask, pos_x, - pos_y, colour, avatar_size) + self.draw_avatar_pix(context, pix, mask, av_x, + av_y, colour, avatar_size) if deaf: - self.draw_deaf(context, pos_x, pos_y, + self.draw_deaf(context, av_x, av_y, self.mute_bg_col, avatar_size) elif mute: - self.draw_mute(context, pos_x, pos_y, + self.draw_mute(context, av_x, av_y, self.mute_bg_col, avatar_size) return tw @@ -865,56 +940,139 @@ def draw_text(self, context, string, pos_x, pos_y, (ink_rect, logical_rect) = layout.get_pixel_extents() text_height = logical_rect.height text_width = logical_rect.width + layout.set_width(Pango.SCALE * text_width) self.col(tx_col) height_offset = (line_height / 2) - (text_height / 2) text_y_offset = height_offset + self.text_baseline_adj + reported_width = text_width + separate_pill = self.show_avatar and self.separate_names + name_gap = 0 + if separate_pill: + name_gap = max(8, int(round(self.text_pad))) + pill_pad_x = 8 + pill_pad_y = 3 + bg_height = text_height + (pill_pad_y * 2) + bg_width = text_width + (pill_pad_x * 2) + rounded_radius = min(6, max(3, bg_height * 0.25)) + reported_width = bg_width + name_gap + else: + bg_height = text_height + 12 + bg_width = text_width + (self.text_pad * 4) + rounded_radius = min(7, max(3, bg_height * 0.22)) + rounded_radius = min(rounded_radius, bg_width * 0.14) + if self.align_right: context.move_to(0, 0) self.col(bg_col) - context.rectangle( - pos_x - text_width - (self.text_pad * 2), - pos_y + height_offset - self.text_pad, - text_width + (self.text_pad * 4), - text_height + (self.text_pad * 2) - ) - context.fill() + if separate_pill: + bg_x = pos_x - name_gap - bg_width + else: + bg_x = pos_x - text_width - (self.text_pad * 2) + bg_y = pos_y + (line_height / 2) - (bg_height / 2) + if self.is_wayland: + context.save() + context.set_antialias(cairo.ANTIALIAS_NONE) + if self.rounded_names: + if self.is_wayland: + # Avoid corner fringe leaking desktop through anti-aliased edges. + bg_x -= 1.0 + bg_y -= 1.0 + bg_width += 2.0 + bg_height += 2.0 + self.draw_rounded_rect(context, + bg_x, + bg_y, + bg_width, + bg_height, + radius=rounded_radius) + context.fill() + else: + context.rectangle( + bg_x, + bg_y, + bg_width, + bg_height + ) + context.fill() + if self.is_wayland: + context.restore() self.col(tx_col) - context.move_to( - pos_x - text_width - self.text_pad - ink_rect.x, - pos_y + text_y_offset - ) + if separate_pill: + text_x = bg_x + pill_pad_x + text_y = pos_y + (line_height / 2) - (text_height / 2) + self.text_baseline_adj + context.move_to(text_x - ink_rect.x, text_y) + else: + context.move_to( + pos_x - text_width - self.text_pad - ink_rect.x, + pos_y + text_y_offset) layout.set_alignment(Pango.Alignment.RIGHT) - PangoCairo.show_layout(self.context, layout) + PangoCairo.show_layout(context, layout) else: context.move_to(0, 0) self.col(bg_col) - context.rectangle( - pos_x - (self.text_pad * 2) + avatar_size, - pos_y + height_offset - self.text_pad, - text_width + (self.text_pad * 4), - text_height + (self.text_pad * 2) - ) - context.fill() + if separate_pill: + bg_x = pos_x + avatar_size + name_gap + else: + bg_x = pos_x - (self.text_pad * 2) + avatar_size + bg_y = pos_y + (line_height / 2) - (bg_height / 2) + if self.is_wayland: + context.save() + context.set_antialias(cairo.ANTIALIAS_NONE) + if self.rounded_names: + if self.is_wayland: + # Avoid corner fringe leaking desktop through anti-aliased edges. + bg_x -= 1.0 + bg_y -= 1.0 + bg_width += 2.0 + bg_height += 2.0 + self.draw_rounded_rect(context, + bg_x, + bg_y, + bg_width, + bg_height, + radius=rounded_radius) + context.fill() + else: + context.rectangle( + bg_x, + bg_y, + bg_width, + bg_height + ) + context.fill() + if self.is_wayland: + context.restore() self.col(tx_col) - context.move_to( - pos_x + self.text_pad + avatar_size- ink_rect.x, - pos_y + text_y_offset - ) + if separate_pill: + text_x = bg_x + pill_pad_x + text_y = pos_y + (line_height / 2) - (text_height / 2) + self.text_baseline_adj + context.move_to(text_x - ink_rect.x, text_y) + else: + context.move_to( + pos_x + self.text_pad + avatar_size - ink_rect.x, + pos_y + text_y_offset) layout.set_alignment(Pango.Alignment.LEFT) - PangoCairo.show_layout(self.context, layout) + PangoCairo.show_layout(context, layout) context.restore() - return text_width + return reported_width + + def _circle_path(self, context, cx, cy, radius, overshoot=0): + """Draw a full circle path, optionally overshooting for anti-aliased edge coverage.""" + r = max(0.001, radius + overshoot) + context.arc(cx, cy, r, 0, 2 * math.pi) def blank_avatar(self, context, pos_x, pos_y, avatar_size): """Draw a cut-out of the previous shape with a forcible transparent hole""" context.save() if self.round_avatar: - context.arc(pos_x + (avatar_size / 2), pos_y + - (avatar_size / 2), avatar_size / 2, 0, 2 * math.pi) + context.new_path() + cx = pos_x + (avatar_size / 2) + cy = pos_y + (avatar_size / 2) + self._circle_path(context, cx, cy, avatar_size / 2) context.clip() self.col(self.avatar_bg_col) context.set_operator(cairo.OPERATOR_SOURCE) @@ -939,58 +1097,13 @@ def draw_avatar_pix(self, context, pixbuf, mask, pos_x, pos_y, border_colour, av if not mask: return - # Draw the "border" by doing a scaled-up copy in a flat colour - if border_colour: - self.col(border_colour) - if self.fancy_border: - context.set_operator(cairo.OPERATOR_SOURCE) - for off_x in range(-self.border_width, self.border_width+1): - for off_y in range(-self.border_width, self.border_width+1): - context.save() - if self.round_avatar: - context.new_path() - context.arc(pos_x + off_x + (avatar_size / 2), pos_y + off_y + - (avatar_size / 2), avatar_size / 2, 0, 2 * math.pi) - context.clip() - draw_img_to_mask(mask, context, pos_x + off_x, pos_y + off_y, - avatar_size, avatar_size) - context.restore() - else: - if self.round_avatar: - context.new_path() - context.arc(pos_x + (avatar_size / 2), pos_y + - (avatar_size / 2), avatar_size / 2 + - (self.border_width/2.0), 0, 2 * math.pi) - context.set_line_width(self.border_width) - context.stroke() - else: - context.new_path() - context.rectangle(pos_x - (self.border_width/2), - pos_y - (self.border_width/2), - avatar_size + self.border_width, - avatar_size + self.border_width) - context.set_line_width(self.border_width) - - context.stroke() - - # Cut the image back out - context.save() - if self.round_avatar: - context.new_path() - context.arc(pos_x + (avatar_size / 2), pos_y + - (avatar_size / 2), avatar_size / 2, 0, 2 * math.pi) - context.clip() - self.col([0.0, 0.0, 0.0, 0.0]) - context.set_operator(cairo.OPERATOR_SOURCE) - draw_img_to_mask(mask, context, pos_x, pos_y, - avatar_size, avatar_size) - context.restore() # Draw the image context.save() if self.round_avatar: context.new_path() - context.arc(pos_x + (avatar_size / 2), pos_y + - (avatar_size / 2), avatar_size / 2, 0, 2 * math.pi) + cx = pos_x + (avatar_size / 2) + cy = pos_y + (avatar_size / 2) + self._circle_path(context, cx, cy, avatar_size / 2) context.clip() context.set_operator(cairo.OPERATOR_OVER) draw_img_to_rect(pixbuf, context, pos_x, pos_y, @@ -998,22 +1111,53 @@ def draw_avatar_pix(self, context, pixbuf, mask, pos_x, pos_y, border_colour, av self.fade_opacity * self.icon_transparency) context.restore() + # Draw the "border" on top + if border_colour: + self.col(border_colour) + if self.round_avatar: + context.new_path() + cx = pos_x + (avatar_size / 2) + cy = pos_y + (avatar_size / 2) + # Radius is inset by half the border width so the stroke is entirely inside the avatar + self._circle_path(context, cx, cy, + (avatar_size / 2.0) - (self.border_width / 2.0), + overshoot=0) + context.set_line_width(self.border_width) + context.stroke() + else: + context.new_path() + context.rectangle(pos_x + (self.border_width / 2.0), + pos_y + (self.border_width / 2.0), + avatar_size - self.border_width, + avatar_size - self.border_width) + context.set_line_width(self.border_width) + context.stroke() + def draw_mute(self, context, pos_x, pos_y, bg_col, avatar_size): """Draw Mute logo""" if avatar_size <= 0: return context.save() - context.translate(pos_x, pos_y) - context.scale(avatar_size, avatar_size) + + icon_size = max(avatar_size * 0.5, 12) + offset_x = pos_x + avatar_size - (icon_size * 0.9) + offset_y = pos_y + avatar_size - (icon_size * 0.9) - # 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) - context.fill() + context.translate(offset_x, offset_y) + context.scale(icon_size, icon_size) + + # Add a dark bubble background + context.save() context.set_operator(cairo.OPERATOR_OVER) + context.arc(0.5, 0.5, 0.5, 0, 2 * math.pi) + context.clip() + self.col([0.0, 0.0, 0.0, 0.8]) + context.rectangle(0, 0, 1, 1) + context.fill() + context.restore() - self.set_mute_col() + # Red tint overlay icon color + self.col([1.0, 0.3, 0.3, 1.0]) context.save() # Clip Strike-through @@ -1070,17 +1214,26 @@ def draw_deaf(self, context, pos_x, pos_y, bg_col, avatar_size): if avatar_size <= 0: return context.save() - context.translate(pos_x, pos_y) - context.scale(avatar_size, avatar_size) - # 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) - context.fill() + icon_size = max(avatar_size * 0.5, 12) + offset_x = pos_x + avatar_size - (icon_size * 0.9) + offset_y = pos_y + avatar_size - (icon_size * 0.9) + + context.translate(offset_x, offset_y) + context.scale(icon_size, icon_size) + + # Add a dark bubble background + context.save() context.set_operator(cairo.OPERATOR_OVER) + context.arc(0.5, 0.5, 0.5, 0, 2 * math.pi) + context.clip() + self.col([0.0, 0.0, 0.0, 0.8]) + context.rectangle(0, 0, 1, 1) + context.fill() + context.restore() - self.set_mute_col() + # Red tint overlay icon color + self.col([1.0, 0.3, 0.3, 1.0]) context.save() # Clip Strike-through From 05641fc44a698483b31d47e1d2357cb48cf9ddc6 Mon Sep 17 00:00:00 2001 From: rewalo <170283204+rewalo@users.noreply.github.com> Date: Wed, 18 Mar 2026 05:11:43 -0700 Subject: [PATCH 2/3] Fix typo --- discover_overlay/glade/settings.glade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discover_overlay/glade/settings.glade b/discover_overlay/glade/settings.glade index 0acb46c..8d2353d 100644 --- a/discover_overlay/glade/settings.glade +++ b/discover_overlay/glade/settings.glade @@ -1071,7 +1071,7 @@ voice_separate_names_label True False - Seperate Names + Separate Names 0 From 61e67ffde4f06e5c9bf8a6b099c8123bd99d0217 Mon Sep 17 00:00:00 2001 From: rewalo <170283204+rewalo@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:22:03 -0700 Subject: [PATCH 3/3] Add PKGBUILD --- PKGBUILD | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 PKGBUILD diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..189cd77 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,14 @@ +pkgname=discover-overlay-git +pkgver=0.7.9 +pkgrel=1 +pkgdesc="Voice chat overlay" +arch=('any') +url="https://github.com/trigg/Discover" +license=('GPL3') +depends=('python' 'python-gobject' 'python-requests' 'python-pillow' 'python-xlib') +makedepends=('python-setuptools') + +package() { + cd "$startdir" + python setup.py install --root="$pkgdir" --optimize=1 +}