- 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
.idea
discover_overlay/glade/settings.glade~
discover_overlay/glade/#settings.glade#

View file

@ -15,19 +15,21 @@ import os
import logging
import signal
import pulsectl_asyncio
import pulsectl
from contextlib import suppress
import asyncio
from threading import Thread, Event
log = logging.getLogger(__name__)
class DiscoverAudioAssist:
def __init__(self, discover):
self.thread=None
self.enabled=False
self.source=None # String containing the name of the PA/PW microphone or other input
self.sink=None # String containing the name of the PA/PW output
self.thread = None
self.enabled = False
self.source = None # String containing the name of the PA/PW microphone or other input
self.sink = None # String containing the name of the PA/PW output
self.discover = discover
@ -49,7 +51,7 @@ class DiscoverAudioAssist:
if not self.enabled:
return
if not self.thread:
self.thread=Thread(target=self.thread_loop)
self.thread = Thread(target=self.thread_loop)
self.thread.start()
def thread_loop(self):
@ -61,10 +63,15 @@ class DiscoverAudioAssist:
async def listen(self):
# Async to connect to pulse and listen for events
async with pulsectl_asyncio.PulseAsync('Discover-Monitor') as pulse:
await self.get_device_details(pulse)
async for event in pulse.subscribe_events('all'):
await self.print_events(pulse, event)
try:
async with pulsectl_asyncio.PulseAsync('Discover-Monitor') as pulse:
await self.get_device_details(pulse)
async for event in pulse.subscribe_events('all'):
await self.print_events(pulse, event)
except (pulsectl.pulsectl.PulseDisconnected):
log.info("Pulse has gone away")
except (pulsectl.pulsectl.PulseError):
log.info("Pulse error")
async def pulse_loop(self):
# Prep before connecting to pulse
@ -74,20 +81,20 @@ class DiscoverAudioAssist:
await listen_task
async def get_device_details(self, pulse):
# Decant information about our chosen devices
# Decant information about our chosen devices
# Feed this back to client to change deaf/mute state
mute = None
deaf = None
for sink in await pulse.sink_list():
if sink.description == self.sink:
if sink.mute == 1 or sink.volume.values[0]==0.0:
if sink.mute == 1 or sink.volume.values[0] == 0.0:
deaf = True
elif sink.mute == 0:
deaf = False
for source in await pulse.source_list():
if source.description == self.source:
if source.mute == 1 or source.volume.values[0]==0.0:
if source.mute == 1 or source.volume.values[0] == 0.0:
mute = True
elif sink.mute == 0:
mute = False
@ -100,7 +107,7 @@ class DiscoverAudioAssist:
self.last_set_deaf = deaf
self.discover.set_deaf_async(deaf)
async def print_events(self,pulse, ev):
async def print_events(self, pulse, ev):
if not self.enabled:
return
# Sink and Source events are fired for changes to output and ints
@ -108,7 +115,7 @@ class DiscoverAudioAssist:
match ev.facility:
case 'sink':
await self.get_device_details(pulse)
case 'source':
await self.get_device_details(pulse)
@ -116,15 +123,15 @@ class DiscoverAudioAssist:
await self.get_device_details(pulse)
case 'source_output':
pass
pass
case 'sink_input':
pass
pass
case 'client':
pass
case _:
case _:
# If we need to find more events, this here will do it
#log.info('Pulse event: %s' % ev)
pass
# log.info('Pulse event: %s' % ev)
pass

View file

@ -97,14 +97,14 @@ class BazziteAutostart:
root = ''
if shutil.which('pkexec'):
root = 'pkexec'
else:
else:
log.error("No ability to request root privs. Cancel")
return
command = " sed -i 's/AUTO_LAUNCH_DISCOVER_OVERLAY=./AUTO_LAUNCH_DISCOVER_OVERLAY=%s/g' /etc/default/discover-overlay" % (value)
command = " sed -i 's/AUTO_LAUNCH_DISCOVER_OVERLAY=./AUTO_LAUNCH_DISCOVER_OVERLAY=%s/g' /etc/default/discover-overlay" % (
value)
command_with_permissions = root + command
os.system(command_with_permissions)
def is_auto(self):
"""Check if it's already set to auto-start"""
return self.auto

View file

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

View file

@ -251,7 +251,7 @@ class Discover:
config.getboolean("main", "autohide", fallback=False))
self.voice_overlay.set_mouseover_timer(
config.getint("main", "autohide_timer", fallback=1))
self.voice_overlay.set_horizontal(config.getboolean(
"main", "horizontal", fallback=False))
self.voice_overlay.set_guild_ids(self.parse_guild_ids(
@ -410,8 +410,8 @@ class Discover:
self.text_overlay.set_hidden(hidden)
self.notification_overlay.set_hidden(hidden)
self.audio_assist.set_enabled(config.getboolean("general", "audio_assist", fallback = False))
self.audio_assist.set_enabled(config.getboolean(
"general", "audio_assist", fallback=False))
def parse_guild_ids(self, guild_ids_str):
"""Parse the guild_ids from a str and return them in a list"""
@ -481,6 +481,7 @@ class Discover:
if deaf != None:
GLib.idle_add(self.connection.set_deaf, deaf)
def entrypoint():
"""
Entry Point.

View file

@ -23,8 +23,9 @@ import os
import io
import copy
gi.require_version('GdkPixbuf', '2.0')
gi.require_version("Gtk", "3.0")
# pylint: disable=wrong-import-position
from gi.repository import Gio, GdkPixbuf # nopep8
from gi.repository import Gio, GdkPixbuf, Gtk # nopep8
log = logging.getLogger(__name__)
@ -68,25 +69,50 @@ class SurfaceGetter():
log.error("Unknown image type: %s", self.url)
def get_file(self):
errors = []
# Grab icon from icon theme
icon_theme = Gtk.IconTheme.get_default()
icon = icon_theme.choose_icon(
[self.url, None], -1, Gtk.IconLookupFlags.NO_SVG)
if icon:
try:
image = Image.open(icon.get_filename())
(surface, mask) = from_pil(image)
if surface:
self.func(self.identifier, surface, mask)
return
except ValueError:
errors.append("Value Error - Unable to read %s" % (mixpath))
except TypeError:
errors.append("Type Error - Unable to read %s" % (mixpath))
except PIL.UnidentifiedImageError:
errors.append("Unknown image type: %s" % (mixpath))
except FileNotFoundError:
errors.append("File not found: %s" % (mixpath))
# Not found in theme, try some common locations
locations = [os.path.expanduser('~/.local/'), '/usr/', '/app']
for prefix in locations:
mixpath = os.path.join(prefix, self.url)
mixpath = os.path.join(os.path.join(
prefix, 'share/icons/hicolor/256x256/apps/'), self.url + ".png")
image = None
try:
image = Image.open(mixpath)
except ValueError:
log.error("Value Erorr - Unable to read %s", mixpath)
errors.append("Value Error - Unable to read %s" % (mixpath))
except TypeError:
log.error("Type Error - Unable to read %s", mixpath)
errors.append("Type Error - Unable to read %s" % (mixpath))
except PIL.UnidentifiedImageError:
log.error("Unknown image type: %s", mixpath)
errors.append("Unknown image type: %s" % (mixpath))
except FileNotFoundError:
log.error("File not found: %s", mixpath)
errors.append("File not found: %s" % (mixpath))
if image:
(surface, mask) = from_pil(image)
if surface:
self.func(self.identifier, surface, mask)
return
for error in errors:
log.error(error)
def from_pil(image, alpha=1.0, format='BGRa'):
@ -113,7 +139,7 @@ def from_pil(image, alpha=1.0, format='BGRa'):
# Cairo expects the raw data to be pre-multiplied alpha
# This means when we change the alpha level we need to change the RGB channels equally
arr[idx] = int(arr[idx] * alpha)
idx +=1
idx += 1
surface = cairo.ImageSurface.create_for_data(
arr, cairo.FORMAT_ARGB32, image.width, image.height)
mask = cairo.ImageSurface.create_for_data(
@ -123,8 +149,9 @@ def from_pil(image, alpha=1.0, format='BGRa'):
def to_pil(surface):
if surface.get_format() == cairo.Format.ARGB32:
return Image.frombuffer('RGBA', (surface.get_width(), surface.get_height()), surface.get_data(),'raw',"BGRA",surface.get_stride())
return Image.frombuffer("RGB", (surface.get_width(), surface.get_height()), surface.get_data(),'raw', "BGRX", stride)
return Image.frombuffer('RGBA', (surface.get_width(), surface.get_height()), surface.get_data(), 'raw', "BGRA", surface.get_stride())
return Image.frombuffer("RGB", (surface.get_width(), surface.get_height()), surface.get_data(), 'raw', "BGRX", stride)
def get_surface(func, identifier, ava, size):
"""Download to cairo surface"""
@ -164,6 +191,7 @@ def get_aspected_size(img, width, height, anchor=0, hanchor=0):
offset_x = offset_x + ((old_width - width) / 2)
return (offset_x, offset_y, width, height)
def draw_img_to_rect(img, ctx,
pos_x, pos_y,
width, height,
@ -187,7 +215,7 @@ def draw_img_to_rect(img, ctx,
ctx.translate(pos_x + offset_x, pos_y + offset_y)
ctx.scale(width, height)
ctx.scale(1 / img.get_width(), 1 / img.get_height())
if alpha != 1.0:
# Honestly, couldn't find a 'use-image-with-modifier' option
# Tried RasterSourcePattern but it appears... broken? in the python implementation
@ -199,7 +227,7 @@ def draw_img_to_rect(img, ctx,
to_pil(img),
alpha
)[0],
0,0)
0, 0)
else:
ctx.set_source_surface(img, 0, 0)
@ -209,6 +237,7 @@ def draw_img_to_rect(img, ctx,
ctx.restore()
return (width, height)
def draw_img_to_mask(img, ctx,
pos_x, pos_y,
width, height,

View file

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

View file

@ -55,7 +55,7 @@ class OverlayWindow(Gtk.Window):
def __init__(self, discover, piggyback=None):
Gtk.Window.__init__(self, type=self.detect_type())
self.is_xatom_set=False
self.is_xatom_set = False
self.discover = discover
screen = self.get_screen()
@ -114,7 +114,8 @@ class OverlayWindow(Gtk.Window):
self.get_screen().connect("monitors-changed", self.screen_changed)
self.get_screen().connect("size-changed", self.screen_changed)
if self.get_window():
self.get_window().set_events(self.get_window().get_events() | Gdk.EventMask.ENTER_NOTIFY_MASK)
self.get_window().set_events(self.get_window().get_events()
| Gdk.EventMask.ENTER_NOTIFY_MASK)
self.connect("enter-notify-event", self.mouseover)
self.connect("leave-notify-event", self.mouseout)
self.mouse_over_timer = None
@ -202,8 +203,6 @@ class OverlayWindow(Gtk.Window):
reg = Gdk.cairo_region_create_from_surface(surface)
self.input_shape_combine_region(reg)
self.overlay_draw(_w, context, data)
def overlay_draw(self, _w, context, data=None):
@ -215,23 +214,24 @@ class OverlayWindow(Gtk.Window):
"""
Set the font used by the overlay
"""
self.text_font = font
self.set_needs_redraw()
if self.text_font != font:
self.text_font = font
self.set_needs_redraw()
def set_floating(self, floating, pos_x, pos_y, width, height):
"""
Set if the window is floating and what dimensions to use
"""
# Special case for Cinnamon desktop : see https://github.com/trigg/Discover/issues/322
if 'XDG_SESSION_DESKTOP' in os.environ and os.environ['XDG_SESSION_DESKTOP']=='cinnamon':
floating = True
self.floating = floating
self.pos_x = pos_x
self.pos_y = pos_y
self.width = width
self.height = height
self.force_location()
if self.floating != floating or self.pos_x != pos_x or self.pos_y != pos_y or self.width != width or self.height != height:
# Special case for Cinnamon desktop : see https://github.com/trigg/Discover/issues/322
if 'XDG_SESSION_DESKTOP' in os.environ and os.environ['XDG_SESSION_DESKTOP'] == 'cinnamon':
floating = True
self.floating = floating
self.pos_x = pos_x
self.pos_y = pos_y
self.width = width
self.height = height
self.force_location()
def set_untouchable(self):
"""
@ -248,11 +248,12 @@ class OverlayWindow(Gtk.Window):
self.input_shape_combine_region(reg)
def set_hide_on_mouseover(self, hide):
self.hide_on_mouseover = hide
if self.hide_on_mouseover:
self.set_needs_redraw()
else:
self.set_untouchable()
if self.hide_on_mouseover != hide:
self.hide_on_mouseover = hide
if self.hide_on_mouseover:
self.set_needs_redraw()
else:
self.set_untouchable()
def set_mouseover_timer(self, time):
self.timeout_mouse_over = time
@ -367,38 +368,41 @@ class OverlayWindow(Gtk.Window):
"""
if type(idx) is str:
idx = 0
self.monitor = idx
if self.is_wayland:
display = Gdk.Display.get_default()
if "get_monitor" in dir(display):
monitor = display.get_monitor(self.monitor)
if monitor:
GtkLayerShell.set_monitor(self, monitor)
if self.monitor != idx:
self.monitor = idx
if self.is_wayland:
display = Gdk.Display.get_default()
if "get_monitor" in dir(display):
monitor = display.get_monitor(self.monitor)
if monitor:
GtkLayerShell.set_monitor(self, monitor)
else:
self.hide()
self.set_wayland_state()
self.show()
else:
self.hide()
self.set_wayland_state()
self.show()
else:
log.error("No get_monitor in display")
self.set_untouchable()
self.force_location()
self.set_needs_redraw()
log.error("No get_monitor in display")
self.set_untouchable()
self.force_location()
self.set_needs_redraw()
def set_align_x(self, align_right):
"""
Set the alignment (True for right, False for left)
"""
self.align_right = align_right
self.force_location()
self.set_needs_redraw()
if self.align_right != align_right:
self.align_right = align_right
self.force_location()
self.set_needs_redraw()
def set_align_y(self, align_vert):
"""
Set the veritcal alignment
"""
self.align_vert = align_vert
self.force_location()
self.set_needs_redraw()
if self.align_vert != align_vert:
self.align_vert = align_vert
self.force_location()
self.set_needs_redraw()
def col(self, col, alpha=1.0):
"""
@ -420,22 +424,24 @@ class OverlayWindow(Gtk.Window):
"""
Set if this overlay should be visible
"""
self.enabled = enabled
if enabled and not self.hidden and not self.piggyback_parent:
self.show_all()
self.set_untouchable()
if self.discover.steamos:
self.set_gamescope_xatom(1)
else:
if self.discover.steamos:
self.set_gamescope_xatom(0)
self.hide()
if self.enabled != enabled:
self.enabled = enabled
if enabled and not self.hidden and not self.piggyback_parent:
self.show_all()
self.set_untouchable()
if self.discover.steamos:
self.set_gamescope_xatom(1)
else:
if self.discover.steamos:
self.set_gamescope_xatom(0)
self.hide()
def set_task(self, visible):
self.set_skip_pager_hint(not visible)
self.set_skip_taskbar_hint(not visible)
def check_composite(self, _a=None, _b=None):
# Called when an X11 session switched compositing on or off
self.redraw()
def screen_changed(self, screen=None):
@ -448,9 +454,9 @@ class OverlayWindow(Gtk.Window):
def mouseout(self, a=None, b=None):
GLib.timeout_add_seconds(self.timeout_mouse_over, self.mouseout_timed)
return True
def mouseout_timed(self, a=None, b=None):
self.draw_blank = False
self.set_needs_redraw()
self.set_needs_redraw()
return False

View file

@ -43,6 +43,18 @@ class MainSettingsWindow():
"/etc/default/discover-overlay")
# Detect flatpak en
self.disable_autostart = 'container' in os.environ
self.icon_name = "discover-overlay"
self.tray_icon_name = "discover-overlay-tray"
icon_theme = Gtk.IconTheme.get_default()
icon_theme.add_resource_path(os.path.expanduser(
'~/.local/share/pipx/venvs/discover-overlay/share/icons'))
if not icon_theme.has_icon("discover-overlay"):
log.error("No icon found in theme")
self.icon_name = 'user-info'
if not icon_theme.has_icon(self.tray_icon_name):
log.error("No tray icon found in theme")
self.tray_icon_name = 'user-info'
self.steamos = False
self.voice_placement_window = None
self.text_placement_window = None
@ -162,6 +174,11 @@ class MainSettingsWindow():
if not self.start_minimized or not self.show_sys_tray_icon:
window.show()
if self.icon_name != 'discover-overlay':
self.widget['overview_image'].set_from_icon_name(
self.icon_name, Gtk.IconSize.DIALOG)
self.widget['window'].set_default_icon_name(self.icon_name)
def request_channels_from_guild(self, guild_id):
with open(self.rpc_file, 'w') as f:
f.write('--rpc --guild-request=%s' % (guild_id))
@ -556,11 +573,12 @@ class MainSettingsWindow():
self.widget['core_settings_min'].set_active(self.start_minimized)
self.widget['core_settings_min'].set_sensitive(self.show_sys_tray_icon)
if 'XDG_SESSION_DESKTOP' in os.environ and os.environ['XDG_SESSION_DESKTOP']=='cinnamon':
if 'XDG_SESSION_DESKTOP' in os.environ and os.environ['XDG_SESSION_DESKTOP'] == 'cinnamon':
self.widget['voice_anchor_to_edge_button'].set_sensitive(False)
self.widget['core_audio_assist'].set_active(config.getboolean("general", "audio_assist", fallback=False))
self.widget['core_audio_assist'].set_active(
config.getboolean("general", "audio_assist", fallback=False))
self.loading_config = False
@ -595,7 +613,7 @@ class MainSettingsWindow():
from gi.repository import AppIndicator3
self.ind = AppIndicator3.Indicator.new(
"discover_overlay",
"discover-overlay-tray",
self.tray_icon_name,
AppIndicator3.IndicatorCategory.APPLICATION_STATUS)
# Hide for now since we don't know if it should be shown yet
self.ind.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
@ -604,7 +622,7 @@ class MainSettingsWindow():
# Create System Tray
log.info("Falling back to Systray : %s", exception)
self.tray = Gtk.StatusIcon.new_from_icon_name(
"discover-overlay-tray")
self.tray_icon_name)
self.tray.connect('popup-menu', self.show_menu)
# Hide for now since we don't know if it should be shown yet
self.tray.set_visible(False)
@ -1157,7 +1175,7 @@ class MainSettingsWindow():
def voice_hide_mouseover_changed(self, button):
self.config_set("main", "autohide", "%s" % (button.get_active()))
def text_hide_mouseover_changed(self,button):
def text_hide_mouseover_changed(self, button):
self.config_set("text", "autohide", "%s" % (button.get_active()))
def voice_mouseover_timeout_changed(self, button):
@ -1169,7 +1187,8 @@ class MainSettingsWindow():
(int(button.get_value())))
def inactive_fade_changed(self, button):
self.config_set("main", "fade_out_inactive", "%s" % (button.get_active()))
self.config_set("main", "fade_out_inactive", "%s" %
(button.get_active()))
def inactive_fade_opacity_changed(self, button):
self.config_set("main", "fade_out_limit", "%.2f" %
@ -1177,11 +1196,12 @@ class MainSettingsWindow():
def inactive_time_changed(self, button):
self.config_set("main", "inactive_time", "%s" %
(int(button.get_value())))
(int(button.get_value())))
def inactive_fade_time_changed(self,button):
def inactive_fade_time_changed(self, button):
self.config_set("main", "inactive_fade_time", "%s" %
(int(button.get_value())))
(int(button.get_value())))
def core_audio_assist_changed(self, button):
self.config_set("general", "audio_assist", "%s" % (button.get_active()))
self.config_set("general", "audio_assist", "%s" %
(button.get_active()))

View file

@ -75,9 +75,10 @@ class TextOverlayWindow(OverlayWindow):
"""
Set the duration that a message will be visible for.
"""
self.text_time = timer
self.timer_after_draw = timer
self.set_needs_redraw()
if self.text_time != timer or self.timer_after_draw != timer:
self.text_time = timer
self.timer_after_draw = timer
self.set_needs_redraw()
def set_text_list(self, tlist, altered):
"""
@ -91,46 +92,52 @@ class TextOverlayWindow(OverlayWindow):
"""
Set default text colour
"""
self.fg_col = fg_col
self.set_needs_redraw()
if self.fg_col != fg_col:
self.fg_col = fg_col
self.set_needs_redraw()
def set_bg(self, bg_col):
"""
Set background colour
"""
self.bg_col = bg_col
self.set_needs_redraw()
if self.bg_col != bg_col:
self.bg_col = bg_col
self.set_needs_redraw()
def set_show_attach(self, attachment):
"""
Set if attachments should be shown inline
"""
self.show_attach = attachment
self.set_needs_redraw()
if self.attachment != attachment:
self.show_attach = attachment
self.set_needs_redraw()
def set_popup_style(self, boolean):
"""
Set if message disappear after a certain duration
"""
self.popup_style = boolean
if self.popup_style != boolean:
self.popup_style = boolean
def set_font(self, font):
"""
Set font used to render text
"""
self.text_font = font
if self.text_font != font:
self.text_font = font
self.pango_rect = Pango.Rectangle()
font = Pango.FontDescription(self.text_font)
self.pango_rect.width = font.get_size() * Pango.SCALE
self.pango_rect.height = font.get_size() * Pango.SCALE
self.set_needs_redraw()
self.pango_rect = Pango.Rectangle()
font = Pango.FontDescription(self.text_font)
self.pango_rect.width = font.get_size() * Pango.SCALE
self.pango_rect.height = font.get_size() * Pango.SCALE
self.set_needs_redraw()
def set_line_limit(self, limit):
"""
Change maximum number of lines in overlay
"""
self.line_limit = limit
if self.line_limit != limit:
self.line_limit = limit
def make_line(self, message):
"""

View file

@ -98,8 +98,8 @@ class VoiceOverlayWindow(OverlayWindow):
self.fade_out_inactive = True
self.fade_out_limit = 0.1
self.inactive_time = 10 # Seconds
self.inactive_fade_time = 20 # Seconds
self.inactive_time = 10 # Seconds
self.inactive_fade_time = 20 # Seconds
self.fade_opacity = 1.0
self.fade_start = 0
@ -124,13 +124,16 @@ class VoiceOverlayWindow(OverlayWindow):
self.guild_ids = tuple()
self.force_location()
get_surface(self.recv_avatar,
"share/icons/hicolor/256x256/apps/discover-overlay-default.png",
"discover-overlay-default",
'def', self.avatar_size)
self.set_title("Discover Voice")
self.redraw()
def reset_action_timer(self):
# Reset time since last voice activity
self.fade_opacity = 1.0
# Remove both fading-out effect and timer set last time this happened
if self.inactive_timeout:
GLib.source_remove(self.inactive_timeout)
self.inactive_timeout = None
@ -138,25 +141,33 @@ class VoiceOverlayWindow(OverlayWindow):
GLib.source_remove(self.fadeout_timeout)
self.fadeout_timeout = None
# If we're using this feature, schedule a new iactivity timer
if self.fade_out_inactive:
self.inactive_timeout = GLib.timeout_add_seconds(self.inactive_time, self.overlay_inactive)
self.inactive_timeout = GLib.timeout_add_seconds(
self.inactive_time, self.overlay_inactive)
def overlay_inactive(self):
self.fade_start= perf_counter()
self.fadeout_timeout = GLib.timeout_add(self.inactive_fade_time/200 * 1000, self.overlay_fadeout)
# Inactivity has hit the first threshold, start fading out
self.fade_start = perf_counter()
# Fade out in 200 steps over X seconds.
self.fadeout_timeout = GLib.timeout_add(
self.inactive_fade_time/200 * 1000, self.overlay_fadeout)
self.inactive_timeout = None
return False
def overlay_fadeout(self):
self.set_needs_redraw()
# There's no guarantee over the granularity of the callback here, so use our time-since to work out how faded out we should be
# Might look choppy on systems under high cpu usage but that's just how it's going to be
now = perf_counter()
time_percent = (now - self.fade_start) / self.inactive_fade_time
if time_percent>=1.0:
if time_percent >= 1.0:
self.fade_opacity = self.fade_out_limit
self.fadeout_timeout = None
return False
self.fade_opacity = self.fade_out_limit + ((1.0 - self.fade_out_limit) * (1.0 - time_percent))
self.fade_opacity = self.fade_out_limit + \
((1.0 - self.fade_out_limit) * (1.0 - time_percent))
return True
def col(self, col, alpha=1.0):
@ -166,23 +177,13 @@ class VoiceOverlayWindow(OverlayWindow):
if alpha == None:
self.context.set_source_rgba(col[0], col[1], col[2], col[3])
else:
self.context.set_source_rgba(col[0], col[1], col[2], col[3] * alpha * self.fade_opacity)
self.context.set_source_rgba(
col[0], col[1], col[2], col[3] * alpha * self.fade_opacity)
def set_icon_transparency(self, trans):
if self.icon_transparency == trans:
return
self.icon_transparency = trans
#get_surface(self.recv_avatar,
# "share/icons/hicolor/256x256/apps/discover-overlay-default.png",
# 'def', self.avatar_size)
#self.avatars = {}
#self.avatar_masks = {}
#self.channel_icon = None
#self.channel_mask = None
self.set_needs_redraw()
if self.icon_transparency != trans:
self.icon_transparency = trans
self.set_needs_redraw()
def set_blank(self):
self.userlist = []
@ -193,183 +194,210 @@ class VoiceOverlayWindow(OverlayWindow):
self.set_needs_redraw()
def set_fade_out_inactive(self, enabled, fade_time, fade_duration, fade_to):
if self.fade_out_inactive == enabled and self.inactive_time == fade_time and self.inactive_fade_time == fade_duration and self.fade_out_limit == fade_to:
return
self.fade_out_inactive = enabled
self.inactive_time = fade_time
self.inactive_fade_time = fade_duration
self.fade_out_limit = fade_to
self.reset_action_timer()
if self.fade_out_inactive != enabled or self.inactive_time != fade_time or self.inactive_fade_time != fade_duration or self.fade_out_limit != fade_to:
self.fade_out_inactive = enabled
self.inactive_time = fade_time
self.inactive_fade_time = fade_duration
self.fade_out_limit = fade_to
self.reset_action_timer()
def set_title_font(self, font):
self.title_font = font
self.set_needs_redraw()
if self.title_font != font:
self.title_font = font
self.set_needs_redraw()
def set_show_connection(self, show_connection):
self.show_connection = show_connection
self.set_needs_redraw()
if self.show_connection != show_connection:
self.show_connection = show_connection
self.set_needs_redraw()
def set_show_avatar(self, show_avatar):
self.show_avatar = show_avatar
self.set_needs_redraw()
if self.show_avatar != show_avatar:
self.show_avatar = show_avatar
self.set_needs_redraw()
def set_show_title(self, show_title):
self.show_title = show_title
self.set_needs_redraw()
if self.show_title != show_title:
self.show_title = show_title
self.set_needs_redraw()
def set_show_disconnected(self, show_disconnected):
self.show_disconnected = show_disconnected
self.set_needs_redraw()
if self.show_disconnected != show_disconnected:
self.show_disconnected = show_disconnected
self.set_needs_redraw()
def set_show_dummy(self, show_dummy):
"""
Toggle use of dummy userdata to help choose settings
"""
self.use_dummy = show_dummy
self.set_needs_redraw()
if self.use_dummy != show_dummy:
self.use_dummy = show_dummy
self.set_needs_redraw()
def set_dummy_count(self, dummy_count):
self.dummy_count = dummy_count
self.set_needs_redraw()
if self.dummy_count != dummy_count:
self.dummy_count = dummy_count
self.set_needs_redraw()
def set_overflow(self, overflow):
"""
How should excessive numbers of users be dealt with?
"""
self.overflow = overflow
self.set_needs_redraw()
if self.overflow != overflow:
self.overflow = overflow
self.set_needs_redraw()
def set_bg(self, background_colour):
"""
Set the background colour
"""
self.norm_col = background_colour
self.set_needs_redraw()
if self.norm_col != background_colour:
self.norm_col = background_colour
self.set_needs_redraw()
def set_fg(self, foreground_colour):
"""
Set the text colour
"""
self.text_col = foreground_colour
self.set_needs_redraw()
if self.text_col != foreground_colour:
self.text_col = foreground_colour
self.set_needs_redraw()
def set_tk(self, talking_colour):
"""
Set the border colour for users who are talking
"""
self.talk_col = talking_colour
self.set_needs_redraw()
if self.talk_col != talking_colour:
self.talk_col = talking_colour
self.set_needs_redraw()
def set_mt(self, mute_colour):
"""
Set the colour of mute and deafen logos
"""
self.mute_col = mute_colour
self.set_needs_redraw()
if self.mute_col != mute_colour:
self.mute_col = mute_colour
self.set_needs_redraw()
def set_mute_bg(self, mute_bg_col):
"""
Set the background colour for mute/deafen icon
"""
self.mute_bg_col = mute_bg_col
self.set_needs_redraw()
if self.mute_bg_col != mute_bg_col:
self.mute_bg_col = mute_bg_col
self.set_needs_redraw()
def set_avatar_bg_col(self, avatar_bg_col):
"""
Set Avatar background colour
"""
self.avatar_bg_col = avatar_bg_col
self.set_needs_redraw()
if self.avatar_bg_col != avatar_bg_col:
self.avatar_bg_col = avatar_bg_col
self.set_needs_redraw()
def set_hi(self, highlight_colour):
"""
Set the colour of background for speaking users
"""
self.hili_col = highlight_colour
self.set_needs_redraw()
if self.hili_col != highlight_colour:
self.hili_col = highlight_colour
self.set_needs_redraw()
def set_fg_hi(self, highlight_colour):
"""
Set the colour of background for speaking users
"""
self.text_hili_col = highlight_colour
self.set_needs_redraw()
if self.text_hili_col != highlight_colour:
self.text_hili_col = highlight_colour
self.set_needs_redraw()
def set_bo(self, border_colour):
"""
Set the colour for idle border
"""
self.border_col = border_colour
self.set_needs_redraw()
if self.border_col != border_colour:
self.border_col = border_colour
self.set_needs_redraw()
def set_avatar_size(self, size):
"""
Set the size of the avatar icons
"""
self.avatar_size = size
self.set_needs_redraw()
if self.avatar_size != size:
self.avatar_size = size
self.set_needs_redraw()
def set_nick_length(self, size):
"""
Set the length of nickname
"""
self.nick_length = size
self.set_needs_redraw()
if self.nick_length != size:
self.nick_length = size
self.set_needs_redraw()
def set_icon_spacing(self, i):
"""
Set the spacing between avatar icons
"""
self.icon_spacing = i
self.set_needs_redraw()
if self.icon_spacing != i:
self.icon_spacing = i
self.set_needs_redraw()
def set_text_padding(self, i):
"""
Set padding between text and border
"""
self.text_pad = i
self.set_needs_redraw()
if self.text_pad != i:
self.text_pad = i
self.set_needs_redraw()
def set_text_baseline_adj(self, i):
"""
Set padding between text and border
"""
self.text_baseline_adj = i
self.set_needs_redraw()
if self.text_baseline_adj != i:
self.text_baseline_adj = i
self.set_needs_redraw()
def set_vert_edge_padding(self, i):
"""
Set padding between top/bottom of screen and overlay contents
"""
self.vert_edge_padding = i
self.set_needs_redraw()
if self.vert_edge_padding != i:
self.vert_edge_padding = i
self.set_needs_redraw()
def set_horz_edge_padding(self, i):
"""
Set padding between left/right of screen and overlay contents
"""
self.horz_edge_padding = i
self.set_needs_redraw()
if self.horz_edge_padding != i:
self.horz_edge_padding = i
self.set_needs_redraw()
def set_square_avatar(self, i):
"""
Set if the overlay should crop avatars to a circle or show full square image
"""
self.round_avatar = not i
self.set_needs_redraw()
if self.round_avatar == i:
self.round_avatar = not i
self.set_needs_redraw()
def set_fancy_border(self, border):
"""
Sets if border should wrap around non-square avatar images
"""
self.fancy_border = border
self.set_needs_redraw()
if self.fancy_border != border:
self.fancy_border = border
self.set_needs_redraw()
def set_only_speaking(self, only_speaking):
"""
Set if overlay should only show people who are talking
"""
self.only_speaking = only_speaking
if self.only_speaking != only_speaking:
self.only_speaking = only_speaking
self.set_needs_redraw()
def set_only_speaking_grace_period(self, grace_period):
"""
@ -382,30 +410,36 @@ class VoiceOverlayWindow(OverlayWindow):
"""
Set if the overlay should highlight the user
"""
self.highlight_self = highlight_self
if self.highlight_self != highlight_self:
self.highlight_self = highlight_self
self.set_needs_redraw()
def set_order(self, i):
"""
Set the method used to order avatar icons & names
"""
self.order = i
self.sort_list(self.userlist)
self.set_needs_redraw()
if self.order != i:
self.order = i
self.sort_list(self.userlist)
self.set_needs_redraw()
def set_icon_only(self, i):
"""
Set if the overlay should draw only the icon
"""
self.icon_only = i
self.set_needs_redraw()
if self.icon_only != i:
self.icon_only = i
self.set_needs_redraw()
def set_border_width(self, width):
self.border_width = width
self.set_needs_redraw()
if self.border_width != width:
self.border_width = width
self.set_needs_redraw()
def set_horizontal(self, horizontal=False):
self.horizontal = horizontal
self.set_needs_redraw()
if self.horizontal != horizontal:
self.horizontal = horizontal
self.set_needs_redraw()
def set_guild_ids(self, guild_ids=tuple()):
if self.discover.connection:
@ -442,7 +476,9 @@ class VoiceOverlayWindow(OverlayWindow):
"""
Set title above voice list
"""
self.channel_title = channel_title
if self.channel_title != channel_title:
self.channel_title = channel_title
self.set_needs_redraw()
def set_channel_icon(self, url):
"""
@ -1015,7 +1051,7 @@ class VoiceOverlayWindow(OverlayWindow):
context.clip()
context.set_operator(cairo.OPERATOR_OVER)
draw_img_to_rect(pixbuf, context, pos_x, pos_y,
avatar_size, avatar_size, False, False, 0,0,self.fade_opacity * self.icon_transparency)
avatar_size, avatar_size, False, False, 0, 0, self.fade_opacity * self.icon_transparency)
context.restore()
def draw_mute(self, context, pos_x, pos_y, bg_col, avatar_size):
@ -1100,7 +1136,7 @@ class VoiceOverlayWindow(OverlayWindow):
# Add a dark background
context.set_operator(cairo.OPERATOR_ATOP)
context.rectangle(0.0, 0.0, 1.0, 1.0)
self.col(bg_col,None)
self.col(bg_col, None)
context.fill()
context.set_operator(cairo.OPERATOR_OVER)