- 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
This commit is contained in:
trigg 2024-03-25 17:37:51 +00:00
parent 6d92d0f79f
commit 29f8c7476c
11 changed files with 369 additions and 251 deletions

1
.gitignore vendored
View file

@ -8,3 +8,4 @@ __pycache__
venv venv
.idea .idea
discover_overlay/glade/settings.glade~ discover_overlay/glade/settings.glade~
discover_overlay/glade/#settings.glade#

View file

@ -15,19 +15,21 @@ import os
import logging import logging
import signal import signal
import pulsectl_asyncio import pulsectl_asyncio
import pulsectl
from contextlib import suppress from contextlib import suppress
import asyncio import asyncio
from threading import Thread, Event from threading import Thread, Event
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class DiscoverAudioAssist: class DiscoverAudioAssist:
def __init__(self, discover): def __init__(self, discover):
self.thread=None self.thread = None
self.enabled=False self.enabled = False
self.source=None # String containing the name of the PA/PW microphone or other input 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.sink = None # String containing the name of the PA/PW output
self.discover = discover self.discover = discover
@ -49,7 +51,7 @@ class DiscoverAudioAssist:
if not self.enabled: if not self.enabled:
return return
if not self.thread: if not self.thread:
self.thread=Thread(target=self.thread_loop) self.thread = Thread(target=self.thread_loop)
self.thread.start() self.thread.start()
def thread_loop(self): def thread_loop(self):
@ -61,10 +63,15 @@ class DiscoverAudioAssist:
async def listen(self): async def listen(self):
# Async to connect to pulse and listen for events # Async to connect to pulse and listen for events
try:
async with pulsectl_asyncio.PulseAsync('Discover-Monitor') as pulse: async with pulsectl_asyncio.PulseAsync('Discover-Monitor') as pulse:
await self.get_device_details(pulse) await self.get_device_details(pulse)
async for event in pulse.subscribe_events('all'): async for event in pulse.subscribe_events('all'):
await self.print_events(pulse, event) 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): async def pulse_loop(self):
# Prep before connecting to pulse # Prep before connecting to pulse
@ -80,14 +87,14 @@ class DiscoverAudioAssist:
deaf = None deaf = None
for sink in await pulse.sink_list(): for sink in await pulse.sink_list():
if sink.description == self.sink: 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 deaf = True
elif sink.mute == 0: elif sink.mute == 0:
deaf = False deaf = False
for source in await pulse.source_list(): for source in await pulse.source_list():
if source.description == self.source: 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 mute = True
elif sink.mute == 0: elif sink.mute == 0:
mute = False mute = False
@ -100,7 +107,7 @@ class DiscoverAudioAssist:
self.last_set_deaf = deaf self.last_set_deaf = deaf
self.discover.set_deaf_async(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: if not self.enabled:
return return
# Sink and Source events are fired for changes to output and ints # Sink and Source events are fired for changes to output and ints
@ -126,5 +133,5 @@ class DiscoverAudioAssist:
case _: case _:
# If we need to find more events, this here will do it # If we need to find more events, this here will do it
#log.info('Pulse event: %s' % ev) # log.info('Pulse event: %s' % ev)
pass pass

View file

@ -100,11 +100,11 @@ class BazziteAutostart:
else: else:
log.error("No ability to request root privs. Cancel") log.error("No ability to request root privs. Cancel")
return 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 command_with_permissions = root + command
os.system(command_with_permissions) os.system(command_with_permissions)
def is_auto(self): def is_auto(self):
"""Check if it's already set to auto-start""" """Check if it's already set to auto-start"""
return self.auto return self.auto

View file

@ -352,11 +352,11 @@ class DiscordConnector:
sink = j['data']['output']['device_id'] sink = j['data']['output']['device_id']
if sink == 'default': if sink == 'default':
for available_sink in j['data']['output']['available_devices']: for available_sink in j['data']['output']['available_devices']:
if available_sink['id']=='default': if available_sink['id'] == 'default':
sink = available_sink['name'][9:] sink = available_sink['name'][9:]
if source == 'default': if source == 'default':
for available_source in j['data']['input']['available_devices']: for available_source in j['data']['input']['available_devices']:
if available_source['id']=='default': if available_source['id'] == 'default':
source = available_source['name'][9:] source = available_source['name'][9:]
self.discover.audio_assist.set_devices(sink, source) self.discover.audio_assist.set_devices(sink, source)

View file

@ -410,8 +410,8 @@ class Discover:
self.text_overlay.set_hidden(hidden) self.text_overlay.set_hidden(hidden)
self.notification_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): def parse_guild_ids(self, guild_ids_str):
"""Parse the guild_ids from a str and return them in a list""" """Parse the guild_ids from a str and return them in a list"""
@ -481,6 +481,7 @@ class Discover:
if deaf != None: if deaf != None:
GLib.idle_add(self.connection.set_deaf, deaf) GLib.idle_add(self.connection.set_deaf, deaf)
def entrypoint(): def entrypoint():
""" """
Entry Point. Entry Point.

View file

@ -23,8 +23,9 @@ import os
import io import io
import copy import copy
gi.require_version('GdkPixbuf', '2.0') gi.require_version('GdkPixbuf', '2.0')
gi.require_version("Gtk", "3.0")
# pylint: disable=wrong-import-position # pylint: disable=wrong-import-position
from gi.repository import Gio, GdkPixbuf # nopep8 from gi.repository import Gio, GdkPixbuf, Gtk # nopep8
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -68,25 +69,50 @@ class SurfaceGetter():
log.error("Unknown image type: %s", self.url) log.error("Unknown image type: %s", self.url)
def get_file(self): 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'] locations = [os.path.expanduser('~/.local/'), '/usr/', '/app']
for prefix in locations: 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 image = None
try: try:
image = Image.open(mixpath) image = Image.open(mixpath)
except ValueError: except ValueError:
log.error("Value Erorr - Unable to read %s", mixpath) errors.append("Value Error - Unable to read %s" % (mixpath))
except TypeError: except TypeError:
log.error("Type Error - Unable to read %s", mixpath) errors.append("Type Error - Unable to read %s" % (mixpath))
except PIL.UnidentifiedImageError: except PIL.UnidentifiedImageError:
log.error("Unknown image type: %s", mixpath) errors.append("Unknown image type: %s" % (mixpath))
except FileNotFoundError: except FileNotFoundError:
log.error("File not found: %s", mixpath) errors.append("File not found: %s" % (mixpath))
if image: if image:
(surface, mask) = from_pil(image) (surface, mask) = from_pil(image)
if surface: if surface:
self.func(self.identifier, surface, mask) self.func(self.identifier, surface, mask)
return return
for error in errors:
log.error(error)
def from_pil(image, alpha=1.0, format='BGRa'): 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 # 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 # This means when we change the alpha level we need to change the RGB channels equally
arr[idx] = int(arr[idx] * alpha) arr[idx] = int(arr[idx] * alpha)
idx +=1 idx += 1
surface = cairo.ImageSurface.create_for_data( surface = cairo.ImageSurface.create_for_data(
arr, cairo.FORMAT_ARGB32, image.width, image.height) arr, cairo.FORMAT_ARGB32, image.width, image.height)
mask = cairo.ImageSurface.create_for_data( mask = cairo.ImageSurface.create_for_data(
@ -123,8 +149,9 @@ def from_pil(image, alpha=1.0, format='BGRa'):
def to_pil(surface): def to_pil(surface):
if surface.get_format() == cairo.Format.ARGB32: 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('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("RGB", (surface.get_width(), surface.get_height()), surface.get_data(), 'raw', "BGRX", stride)
def get_surface(func, identifier, ava, size): def get_surface(func, identifier, ava, size):
"""Download to cairo surface""" """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) offset_x = offset_x + ((old_width - width) / 2)
return (offset_x, offset_y, width, height) return (offset_x, offset_y, width, height)
def draw_img_to_rect(img, ctx, def draw_img_to_rect(img, ctx,
pos_x, pos_y, pos_x, pos_y,
width, height, width, height,
@ -199,7 +227,7 @@ def draw_img_to_rect(img, ctx,
to_pil(img), to_pil(img),
alpha alpha
)[0], )[0],
0,0) 0, 0)
else: else:
ctx.set_source_surface(img, 0, 0) ctx.set_source_surface(img, 0, 0)
@ -209,6 +237,7 @@ def draw_img_to_rect(img, ctx,
ctx.restore() ctx.restore()
return (width, height) return (width, height)
def draw_img_to_mask(img, ctx, def draw_img_to_mask(img, ctx,
pos_x, pos_y, pos_x, pos_y,
width, height, width, height,

View file

@ -82,7 +82,7 @@ class NotificationOverlayWindow(OverlayWindow):
self.content = newlist self.content = newlist
# If the list is different than before # If the list is different than before
if oldsize != len(newlist): if oldsize != len(newlist):
self.needsredraw = True self.set_needs_redraw()
def add_notification_message(self, data): def add_notification_message(self, data):
noti = None noti = None
@ -104,27 +104,30 @@ class NotificationOverlayWindow(OverlayWindow):
if noti: if noti:
self.content.append(noti) self.content.append(noti)
self.needsredraw = True self.set_needs_redraw()
self.get_all_images() self.get_all_images()
def set_padding(self, padding): def set_padding(self, padding):
""" """
Set the padding between notifications Set the padding between notifications
""" """
if self.padding != padding:
self.padding = padding self.padding = padding
self.needsredraw = True self.set_needs_redraw()
def set_border_radius(self, radius): def set_border_radius(self, radius):
""" """
Set the radius of the border Set the radius of the border
""" """
if self.border_radius != radius:
self.border_radius = radius self.border_radius = radius
self.needsredraw = True self.set_needs_redraw()
def set_icon_size(self, size): def set_icon_size(self, size):
""" """
Set Icon size Set Icon size
""" """
if self.icon_size != size:
self.icon_size = size self.icon_size = size
self.image_list = {} self.image_list = {}
self.get_all_images() self.get_all_images()
@ -133,12 +136,14 @@ class NotificationOverlayWindow(OverlayWindow):
""" """
Set padding between icon and message Set padding between icon and message
""" """
if self.icon_pad != pad:
self.icon_pad = pad self.icon_pad = pad
self.needsredraw = True self.set_needs_redraw()
def set_icon_left(self, left): def set_icon_left(self, left):
if self.icon_left != left:
self.icon_left = left self.icon_left = left
self.needsredraw = True self.set_needs_redraw()
def set_text_time(self, timer): def set_text_time(self, timer):
""" """
@ -151,8 +156,9 @@ class NotificationOverlayWindow(OverlayWindow):
""" """
Set the word wrap limit in pixels Set the word wrap limit in pixels
""" """
if self.limit_width != limit:
self.limit_width = limit self.limit_width = limit
self.needsredraw = True self.set_needs_redraw()
def get_all_images(self): def get_all_images(self):
the_list = self.content the_list = self.content
@ -170,52 +176,57 @@ class NotificationOverlayWindow(OverlayWindow):
Called when image_getter has downloaded an image Called when image_getter has downloaded an image
""" """
self.image_list[identifier] = pix self.image_list[identifier] = pix
self.needsredraw = True self.set_needs_redraw()
def set_fg(self, fg_col): def set_fg(self, fg_col):
""" """
Set default text colour Set default text colour
""" """
if self.fg_col != fg_col:
self.fg_col = fg_col self.fg_col = fg_col
self.needsredraw = True self.set_needs_redraw()
def set_bg(self, bg_col): def set_bg(self, bg_col):
""" """
Set background colour Set background colour
""" """
if self.bg_col != bg_col:
self.bg_col = bg_col self.bg_col = bg_col
self.needsredraw = True self.set_needs_redraw()
def set_show_icon(self, icon): def set_show_icon(self, icon):
""" """
Set if icons should be shown inline Set if icons should be shown inline
""" """
if self.show_icon != icon:
self.show_icon = icon self.show_icon = icon
self.needsredraw = True self.set_needs_redraw()
self.get_all_images() self.get_all_images()
def set_reverse_order(self, rev): def set_reverse_order(self, rev):
if self.reverse_order != rev:
self.reverse_order = rev self.reverse_order = rev
self.needsredraw = True self.set_needs_redraw()
def set_font(self, font): def set_font(self, font):
""" """
Set font used to render text Set font used to render text
""" """
if self.text_font != font:
self.text_font = font self.text_font = font
self.pango_rect = Pango.Rectangle() self.pango_rect = Pango.Rectangle()
font = Pango.FontDescription(self.text_font) font = Pango.FontDescription(self.text_font)
self.pango_rect.width = font.get_size() * Pango.SCALE self.pango_rect.width = font.get_size() * Pango.SCALE
self.pango_rect.height = font.get_size() * Pango.SCALE self.pango_rect.height = font.get_size() * Pango.SCALE
self.needsredraw = True self.set_needs_redraw()
def recv_attach(self, identifier, pix): def recv_attach(self, identifier, pix):
""" """
Called when an image has been downloaded by image_getter Called when an image has been downloaded by image_getter
""" """
self.icons[identifier] = pix self.icons[identifier] = pix
self.needsredraw = True self.set_needs_redraw()
def calc_all_height(self): def calc_all_height(self):
h = 0 h = 0
@ -516,5 +527,5 @@ class NotificationOverlayWindow(OverlayWindow):
def set_testing(self, testing): def set_testing(self, testing):
self.testing = testing self.testing = testing
self.needsredraw = True self.set_needs_redraw()
self.get_all_images() self.get_all_images()

View file

@ -55,7 +55,7 @@ class OverlayWindow(Gtk.Window):
def __init__(self, discover, piggyback=None): def __init__(self, discover, piggyback=None):
Gtk.Window.__init__(self, type=self.detect_type()) Gtk.Window.__init__(self, type=self.detect_type())
self.is_xatom_set=False self.is_xatom_set = False
self.discover = discover self.discover = discover
screen = self.get_screen() 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("monitors-changed", self.screen_changed)
self.get_screen().connect("size-changed", self.screen_changed) self.get_screen().connect("size-changed", self.screen_changed)
if self.get_window(): 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("enter-notify-event", self.mouseover)
self.connect("leave-notify-event", self.mouseout) self.connect("leave-notify-event", self.mouseout)
self.mouse_over_timer = None self.mouse_over_timer = None
@ -202,8 +203,6 @@ class OverlayWindow(Gtk.Window):
reg = Gdk.cairo_region_create_from_surface(surface) reg = Gdk.cairo_region_create_from_surface(surface)
self.input_shape_combine_region(reg) self.input_shape_combine_region(reg)
self.overlay_draw(_w, context, data) self.overlay_draw(_w, context, data)
def overlay_draw(self, _w, context, data=None): def overlay_draw(self, _w, context, data=None):
@ -215,6 +214,7 @@ class OverlayWindow(Gtk.Window):
""" """
Set the font used by the overlay Set the font used by the overlay
""" """
if self.text_font != font:
self.text_font = font self.text_font = font
self.set_needs_redraw() self.set_needs_redraw()
@ -222,9 +222,9 @@ class OverlayWindow(Gtk.Window):
""" """
Set if the window is floating and what dimensions to use Set if the window is floating and what dimensions to use
""" """
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 # 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': if 'XDG_SESSION_DESKTOP' in os.environ and os.environ['XDG_SESSION_DESKTOP'] == 'cinnamon':
floating = True floating = True
self.floating = floating self.floating = floating
self.pos_x = pos_x self.pos_x = pos_x
@ -248,6 +248,7 @@ class OverlayWindow(Gtk.Window):
self.input_shape_combine_region(reg) self.input_shape_combine_region(reg)
def set_hide_on_mouseover(self, hide): def set_hide_on_mouseover(self, hide):
if self.hide_on_mouseover != hide:
self.hide_on_mouseover = hide self.hide_on_mouseover = hide
if self.hide_on_mouseover: if self.hide_on_mouseover:
self.set_needs_redraw() self.set_needs_redraw()
@ -367,6 +368,7 @@ class OverlayWindow(Gtk.Window):
""" """
if type(idx) is str: if type(idx) is str:
idx = 0 idx = 0
if self.monitor != idx:
self.monitor = idx self.monitor = idx
if self.is_wayland: if self.is_wayland:
display = Gdk.Display.get_default() display = Gdk.Display.get_default()
@ -388,6 +390,7 @@ class OverlayWindow(Gtk.Window):
""" """
Set the alignment (True for right, False for left) Set the alignment (True for right, False for left)
""" """
if self.align_right != align_right:
self.align_right = align_right self.align_right = align_right
self.force_location() self.force_location()
self.set_needs_redraw() self.set_needs_redraw()
@ -396,6 +399,7 @@ class OverlayWindow(Gtk.Window):
""" """
Set the veritcal alignment Set the veritcal alignment
""" """
if self.align_vert != align_vert:
self.align_vert = align_vert self.align_vert = align_vert
self.force_location() self.force_location()
self.set_needs_redraw() self.set_needs_redraw()
@ -420,6 +424,7 @@ class OverlayWindow(Gtk.Window):
""" """
Set if this overlay should be visible Set if this overlay should be visible
""" """
if self.enabled != enabled:
self.enabled = enabled self.enabled = enabled
if enabled and not self.hidden and not self.piggyback_parent: if enabled and not self.hidden and not self.piggyback_parent:
self.show_all() self.show_all()
@ -436,6 +441,7 @@ class OverlayWindow(Gtk.Window):
self.set_skip_taskbar_hint(not visible) self.set_skip_taskbar_hint(not visible)
def check_composite(self, _a=None, _b=None): def check_composite(self, _a=None, _b=None):
# Called when an X11 session switched compositing on or off
self.redraw() self.redraw()
def screen_changed(self, screen=None): def screen_changed(self, screen=None):
@ -448,9 +454,9 @@ class OverlayWindow(Gtk.Window):
def mouseout(self, a=None, b=None): def mouseout(self, a=None, b=None):
GLib.timeout_add_seconds(self.timeout_mouse_over, self.mouseout_timed) GLib.timeout_add_seconds(self.timeout_mouse_over, self.mouseout_timed)
return True return True
def mouseout_timed(self, a=None, b=None): def mouseout_timed(self, a=None, b=None):
self.draw_blank = False self.draw_blank = False
self.set_needs_redraw() self.set_needs_redraw()
return False

View file

@ -43,6 +43,18 @@ class MainSettingsWindow():
"/etc/default/discover-overlay") "/etc/default/discover-overlay")
# Detect flatpak en # Detect flatpak en
self.disable_autostart = 'container' in os.environ 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.steamos = False
self.voice_placement_window = None self.voice_placement_window = None
self.text_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: if not self.start_minimized or not self.show_sys_tray_icon:
window.show() 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): def request_channels_from_guild(self, guild_id):
with open(self.rpc_file, 'w') as f: with open(self.rpc_file, 'w') as f:
f.write('--rpc --guild-request=%s' % (guild_id)) f.write('--rpc --guild-request=%s' % (guild_id))
@ -557,10 +574,11 @@ class MainSettingsWindow():
self.widget['core_settings_min'].set_sensitive(self.show_sys_tray_icon) 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['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 self.loading_config = False
@ -595,7 +613,7 @@ class MainSettingsWindow():
from gi.repository import AppIndicator3 from gi.repository import AppIndicator3
self.ind = AppIndicator3.Indicator.new( self.ind = AppIndicator3.Indicator.new(
"discover_overlay", "discover_overlay",
"discover-overlay-tray", self.tray_icon_name,
AppIndicator3.IndicatorCategory.APPLICATION_STATUS) AppIndicator3.IndicatorCategory.APPLICATION_STATUS)
# Hide for now since we don't know if it should be shown yet # Hide for now since we don't know if it should be shown yet
self.ind.set_status(AppIndicator3.IndicatorStatus.PASSIVE) self.ind.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
@ -604,7 +622,7 @@ class MainSettingsWindow():
# Create System Tray # Create System Tray
log.info("Falling back to Systray : %s", exception) log.info("Falling back to Systray : %s", exception)
self.tray = Gtk.StatusIcon.new_from_icon_name( self.tray = Gtk.StatusIcon.new_from_icon_name(
"discover-overlay-tray") self.tray_icon_name)
self.tray.connect('popup-menu', self.show_menu) self.tray.connect('popup-menu', self.show_menu)
# Hide for now since we don't know if it should be shown yet # Hide for now since we don't know if it should be shown yet
self.tray.set_visible(False) self.tray.set_visible(False)
@ -1157,7 +1175,7 @@ class MainSettingsWindow():
def voice_hide_mouseover_changed(self, button): def voice_hide_mouseover_changed(self, button):
self.config_set("main", "autohide", "%s" % (button.get_active())) 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())) self.config_set("text", "autohide", "%s" % (button.get_active()))
def voice_mouseover_timeout_changed(self, button): def voice_mouseover_timeout_changed(self, button):
@ -1169,7 +1187,8 @@ class MainSettingsWindow():
(int(button.get_value()))) (int(button.get_value())))
def inactive_fade_changed(self, button): 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): def inactive_fade_opacity_changed(self, button):
self.config_set("main", "fade_out_limit", "%.2f" % self.config_set("main", "fade_out_limit", "%.2f" %
@ -1179,9 +1198,10 @@ class MainSettingsWindow():
self.config_set("main", "inactive_time", "%s" % 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" % self.config_set("main", "inactive_fade_time", "%s" %
(int(button.get_value()))) (int(button.get_value())))
def core_audio_assist_changed(self, button): 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()))

View file

@ -75,6 +75,7 @@ class TextOverlayWindow(OverlayWindow):
""" """
Set the duration that a message will be visible for. Set the duration that a message will be visible for.
""" """
if self.text_time != timer or self.timer_after_draw != timer:
self.text_time = timer self.text_time = timer
self.timer_after_draw = timer self.timer_after_draw = timer
self.set_needs_redraw() self.set_needs_redraw()
@ -91,6 +92,7 @@ class TextOverlayWindow(OverlayWindow):
""" """
Set default text colour Set default text colour
""" """
if self.fg_col != fg_col:
self.fg_col = fg_col self.fg_col = fg_col
self.set_needs_redraw() self.set_needs_redraw()
@ -98,6 +100,7 @@ class TextOverlayWindow(OverlayWindow):
""" """
Set background colour Set background colour
""" """
if self.bg_col != bg_col:
self.bg_col = bg_col self.bg_col = bg_col
self.set_needs_redraw() self.set_needs_redraw()
@ -105,6 +108,7 @@ class TextOverlayWindow(OverlayWindow):
""" """
Set if attachments should be shown inline Set if attachments should be shown inline
""" """
if self.attachment != attachment:
self.show_attach = attachment self.show_attach = attachment
self.set_needs_redraw() self.set_needs_redraw()
@ -112,12 +116,14 @@ class TextOverlayWindow(OverlayWindow):
""" """
Set if message disappear after a certain duration Set if message disappear after a certain duration
""" """
if self.popup_style != boolean:
self.popup_style = boolean self.popup_style = boolean
def set_font(self, font): def set_font(self, font):
""" """
Set font used to render text Set font used to render text
""" """
if self.text_font != font:
self.text_font = font self.text_font = font
self.pango_rect = Pango.Rectangle() self.pango_rect = Pango.Rectangle()
@ -130,6 +136,7 @@ class TextOverlayWindow(OverlayWindow):
""" """
Change maximum number of lines in overlay Change maximum number of lines in overlay
""" """
if self.line_limit != limit:
self.line_limit = limit self.line_limit = limit
def make_line(self, message): def make_line(self, message):

View file

@ -124,13 +124,16 @@ class VoiceOverlayWindow(OverlayWindow):
self.guild_ids = tuple() self.guild_ids = tuple()
self.force_location() self.force_location()
get_surface(self.recv_avatar, get_surface(self.recv_avatar,
"share/icons/hicolor/256x256/apps/discover-overlay-default.png", "discover-overlay-default",
'def', self.avatar_size) 'def', self.avatar_size)
self.set_title("Discover Voice") self.set_title("Discover Voice")
self.redraw() self.redraw()
def reset_action_timer(self): def reset_action_timer(self):
# Reset time since last voice activity
self.fade_opacity = 1.0 self.fade_opacity = 1.0
# Remove both fading-out effect and timer set last time this happened
if self.inactive_timeout: if self.inactive_timeout:
GLib.source_remove(self.inactive_timeout) GLib.source_remove(self.inactive_timeout)
self.inactive_timeout = None self.inactive_timeout = None
@ -138,25 +141,33 @@ class VoiceOverlayWindow(OverlayWindow):
GLib.source_remove(self.fadeout_timeout) GLib.source_remove(self.fadeout_timeout)
self.fadeout_timeout = None self.fadeout_timeout = None
# If we're using this feature, schedule a new iactivity timer
if self.fade_out_inactive: 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): def overlay_inactive(self):
self.fade_start= perf_counter() # Inactivity has hit the first threshold, start fading out
self.fadeout_timeout = GLib.timeout_add(self.inactive_fade_time/200 * 1000, self.overlay_fadeout) 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 self.inactive_timeout = None
return False return False
def overlay_fadeout(self): def overlay_fadeout(self):
self.set_needs_redraw() 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() now = perf_counter()
time_percent = (now - self.fade_start) / self.inactive_fade_time 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.fade_opacity = self.fade_out_limit
self.fadeout_timeout = None self.fadeout_timeout = None
return False 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 return True
def col(self, col, alpha=1.0): def col(self, col, alpha=1.0):
@ -166,22 +177,12 @@ class VoiceOverlayWindow(OverlayWindow):
if alpha == None: if alpha == None:
self.context.set_source_rgba(col[0], col[1], col[2], col[3]) self.context.set_source_rgba(col[0], col[1], col[2], col[3])
else: 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): def set_icon_transparency(self, trans):
if self.icon_transparency == trans: if self.icon_transparency != trans:
return
self.icon_transparency = trans 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() self.set_needs_redraw()
def set_blank(self): def set_blank(self):
@ -193,8 +194,7 @@ class VoiceOverlayWindow(OverlayWindow):
self.set_needs_redraw() self.set_needs_redraw()
def set_fade_out_inactive(self, enabled, fade_time, fade_duration, fade_to): 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: 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:
return
self.fade_out_inactive = enabled self.fade_out_inactive = enabled
self.inactive_time = fade_time self.inactive_time = fade_time
self.inactive_fade_time = fade_duration self.inactive_fade_time = fade_duration
@ -202,22 +202,27 @@ class VoiceOverlayWindow(OverlayWindow):
self.reset_action_timer() self.reset_action_timer()
def set_title_font(self, font): def set_title_font(self, font):
if self.title_font != font:
self.title_font = font self.title_font = font
self.set_needs_redraw() self.set_needs_redraw()
def set_show_connection(self, show_connection): def set_show_connection(self, show_connection):
if self.show_connection != show_connection:
self.show_connection = show_connection self.show_connection = show_connection
self.set_needs_redraw() self.set_needs_redraw()
def set_show_avatar(self, show_avatar): def set_show_avatar(self, show_avatar):
if self.show_avatar != show_avatar:
self.show_avatar = show_avatar self.show_avatar = show_avatar
self.set_needs_redraw() self.set_needs_redraw()
def set_show_title(self, show_title): def set_show_title(self, show_title):
if self.show_title != show_title:
self.show_title = show_title self.show_title = show_title
self.set_needs_redraw() self.set_needs_redraw()
def set_show_disconnected(self, show_disconnected): def set_show_disconnected(self, show_disconnected):
if self.show_disconnected != show_disconnected:
self.show_disconnected = show_disconnected self.show_disconnected = show_disconnected
self.set_needs_redraw() self.set_needs_redraw()
@ -225,10 +230,12 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Toggle use of dummy userdata to help choose settings Toggle use of dummy userdata to help choose settings
""" """
if self.use_dummy != show_dummy:
self.use_dummy = show_dummy self.use_dummy = show_dummy
self.set_needs_redraw() self.set_needs_redraw()
def set_dummy_count(self, dummy_count): def set_dummy_count(self, dummy_count):
if self.dummy_count != dummy_count:
self.dummy_count = dummy_count self.dummy_count = dummy_count
self.set_needs_redraw() self.set_needs_redraw()
@ -236,6 +243,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
How should excessive numbers of users be dealt with? How should excessive numbers of users be dealt with?
""" """
if self.overflow != overflow:
self.overflow = overflow self.overflow = overflow
self.set_needs_redraw() self.set_needs_redraw()
@ -243,6 +251,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the background colour Set the background colour
""" """
if self.norm_col != background_colour:
self.norm_col = background_colour self.norm_col = background_colour
self.set_needs_redraw() self.set_needs_redraw()
@ -250,6 +259,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the text colour Set the text colour
""" """
if self.text_col != foreground_colour:
self.text_col = foreground_colour self.text_col = foreground_colour
self.set_needs_redraw() self.set_needs_redraw()
@ -257,6 +267,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the border colour for users who are talking Set the border colour for users who are talking
""" """
if self.talk_col != talking_colour:
self.talk_col = talking_colour self.talk_col = talking_colour
self.set_needs_redraw() self.set_needs_redraw()
@ -264,6 +275,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the colour of mute and deafen logos Set the colour of mute and deafen logos
""" """
if self.mute_col != mute_colour:
self.mute_col = mute_colour self.mute_col = mute_colour
self.set_needs_redraw() self.set_needs_redraw()
@ -271,6 +283,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the background colour for mute/deafen icon Set the background colour for mute/deafen icon
""" """
if self.mute_bg_col != mute_bg_col:
self.mute_bg_col = mute_bg_col self.mute_bg_col = mute_bg_col
self.set_needs_redraw() self.set_needs_redraw()
@ -278,6 +291,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set Avatar background colour Set Avatar background colour
""" """
if self.avatar_bg_col != avatar_bg_col:
self.avatar_bg_col = avatar_bg_col self.avatar_bg_col = avatar_bg_col
self.set_needs_redraw() self.set_needs_redraw()
@ -285,6 +299,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the colour of background for speaking users Set the colour of background for speaking users
""" """
if self.hili_col != highlight_colour:
self.hili_col = highlight_colour self.hili_col = highlight_colour
self.set_needs_redraw() self.set_needs_redraw()
@ -292,6 +307,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the colour of background for speaking users Set the colour of background for speaking users
""" """
if self.text_hili_col != highlight_colour:
self.text_hili_col = highlight_colour self.text_hili_col = highlight_colour
self.set_needs_redraw() self.set_needs_redraw()
@ -299,6 +315,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the colour for idle border Set the colour for idle border
""" """
if self.border_col != border_colour:
self.border_col = border_colour self.border_col = border_colour
self.set_needs_redraw() self.set_needs_redraw()
@ -306,6 +323,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the size of the avatar icons Set the size of the avatar icons
""" """
if self.avatar_size != size:
self.avatar_size = size self.avatar_size = size
self.set_needs_redraw() self.set_needs_redraw()
@ -313,6 +331,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the length of nickname Set the length of nickname
""" """
if self.nick_length != size:
self.nick_length = size self.nick_length = size
self.set_needs_redraw() self.set_needs_redraw()
@ -320,6 +339,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set the spacing between avatar icons Set the spacing between avatar icons
""" """
if self.icon_spacing != i:
self.icon_spacing = i self.icon_spacing = i
self.set_needs_redraw() self.set_needs_redraw()
@ -327,6 +347,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set padding between text and border Set padding between text and border
""" """
if self.text_pad != i:
self.text_pad = i self.text_pad = i
self.set_needs_redraw() self.set_needs_redraw()
@ -334,6 +355,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set padding between text and border Set padding between text and border
""" """
if self.text_baseline_adj != i:
self.text_baseline_adj = i self.text_baseline_adj = i
self.set_needs_redraw() self.set_needs_redraw()
@ -341,6 +363,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set padding between top/bottom of screen and overlay contents Set padding between top/bottom of screen and overlay contents
""" """
if self.vert_edge_padding != i:
self.vert_edge_padding = i self.vert_edge_padding = i
self.set_needs_redraw() self.set_needs_redraw()
@ -348,6 +371,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set padding between left/right of screen and overlay contents Set padding between left/right of screen and overlay contents
""" """
if self.horz_edge_padding != i:
self.horz_edge_padding = i self.horz_edge_padding = i
self.set_needs_redraw() self.set_needs_redraw()
@ -355,6 +379,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set if the overlay should crop avatars to a circle or show full square image Set if the overlay should crop avatars to a circle or show full square image
""" """
if self.round_avatar == i:
self.round_avatar = not i self.round_avatar = not i
self.set_needs_redraw() self.set_needs_redraw()
@ -362,6 +387,7 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Sets if border should wrap around non-square avatar images Sets if border should wrap around non-square avatar images
""" """
if self.fancy_border != border:
self.fancy_border = border self.fancy_border = border
self.set_needs_redraw() self.set_needs_redraw()
@ -369,7 +395,9 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set if overlay should only show people who are talking Set if overlay should only show people who are talking
""" """
if self.only_speaking != only_speaking:
self.only_speaking = only_speaking self.only_speaking = only_speaking
self.set_needs_redraw()
def set_only_speaking_grace_period(self, grace_period): def set_only_speaking_grace_period(self, grace_period):
""" """
@ -382,12 +410,15 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set if the overlay should highlight the user Set if the overlay should highlight the user
""" """
if self.highlight_self != highlight_self:
self.highlight_self = highlight_self self.highlight_self = highlight_self
self.set_needs_redraw()
def set_order(self, i): def set_order(self, i):
""" """
Set the method used to order avatar icons & names Set the method used to order avatar icons & names
""" """
if self.order != i:
self.order = i self.order = i
self.sort_list(self.userlist) self.sort_list(self.userlist)
self.set_needs_redraw() self.set_needs_redraw()
@ -396,14 +427,17 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set if the overlay should draw only the icon Set if the overlay should draw only the icon
""" """
if self.icon_only != i:
self.icon_only = i self.icon_only = i
self.set_needs_redraw() self.set_needs_redraw()
def set_border_width(self, width): def set_border_width(self, width):
if self.border_width != width:
self.border_width = width self.border_width = width
self.set_needs_redraw() self.set_needs_redraw()
def set_horizontal(self, horizontal=False): def set_horizontal(self, horizontal=False):
if self.horizontal != horizontal:
self.horizontal = horizontal self.horizontal = horizontal
self.set_needs_redraw() self.set_needs_redraw()
@ -442,7 +476,9 @@ class VoiceOverlayWindow(OverlayWindow):
""" """
Set title above voice list Set title above voice list
""" """
if self.channel_title != channel_title:
self.channel_title = channel_title self.channel_title = channel_title
self.set_needs_redraw()
def set_channel_icon(self, url): def set_channel_icon(self, url):
""" """
@ -1015,7 +1051,7 @@ class VoiceOverlayWindow(OverlayWindow):
context.clip() context.clip()
context.set_operator(cairo.OPERATOR_OVER) context.set_operator(cairo.OPERATOR_OVER)
draw_img_to_rect(pixbuf, context, pos_x, pos_y, 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() context.restore()
def draw_mute(self, context, pos_x, pos_y, bg_col, avatar_size): def draw_mute(self, context, pos_x, pos_y, bg_col, avatar_size):
@ -1100,7 +1136,7 @@ class VoiceOverlayWindow(OverlayWindow):
# Add a dark background # Add a dark background
context.set_operator(cairo.OPERATOR_ATOP) context.set_operator(cairo.OPERATOR_ATOP)
context.rectangle(0.0, 0.0, 1.0, 1.0) context.rectangle(0.0, 0.0, 1.0, 1.0)
self.col(bg_col,None) self.col(bg_col, None)
context.fill() context.fill()
context.set_operator(cairo.OPERATOR_OVER) context.set_operator(cairo.OPERATOR_OVER)