From 17a9bc5f028aa66d9f55dfdc809d80f4c57e5b3f Mon Sep 17 00:00:00 2001 From: trigg Date: Mon, 8 Jul 2024 23:32:59 +0100 Subject: [PATCH 1/6] - First pass at pylint clean-up --- .pylintrc | 4 + discover_overlay/autostart.py | 9 +- discover_overlay/discord_connector.py | 58 +-- discover_overlay/discover_overlay.py | 47 +-- discover_overlay/draggable_window.py | 16 +- discover_overlay/draggable_window_wayland.py | 28 +- discover_overlay/image_getter.py | 32 +- discover_overlay/notification_overlay.py | 159 ++++---- discover_overlay/overlay.py | 61 ++- discover_overlay/settings_window.py | 380 ++++++++++--------- discover_overlay/text_overlay.py | 104 ++--- discover_overlay/voice_overlay.py | 292 ++++++-------- 12 files changed, 587 insertions(+), 603 deletions(-) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..daee0a9 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,4 @@ +[MAIN] +max-line-length=150 +max-module-lines=2000 +generated-member=cairo.* \ No newline at end of file diff --git a/discover_overlay/autostart.py b/discover_overlay/autostart.py index 7e4f9c1..7276806 100644 --- a/discover_overlay/autostart.py +++ b/discover_overlay/autostart.py @@ -28,7 +28,7 @@ class Autostart: def __init__(self, app_name): if not app_name.endswith(".desktop"): - app_name = "%s.desktop" % (app_name) + app_name = f"{app_name}.desktop" self.app_name = app_name self.auto_locations = [os.path.join( xdg_config_home, 'autostart/'), '/etc/xdg/autostart/'] @@ -77,7 +77,7 @@ class BazziteAutostart: def __init__(self): self.auto = False - with open("/etc/default/discover-overlay") as f: + with open("/etc/default/discover-overlay", encoding="utf-8") as f: content = f.readlines() for line in content: if line.startswith("AUTO_LAUNCH_DISCOVER_OVERLAY="): @@ -94,14 +94,15 @@ class BazziteAutostart: self.auto = enable def change_file(self, value): + """Alter bazzite config via pkexec and sed""" root = '' if shutil.which('pkexec'): root = 'pkexec' 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 = f" sed -i 's/AUTO_LAUNCH_DISCOVER_OVERLAY=./AUTO_LAUNCH_DISCOVER_OVERLAY={ + value}/g' /etc/default/discover-overlay" command_with_permissions = root + command os.system(command_with_permissions) diff --git a/discover_overlay/discord_connector.py b/discover_overlay/discord_connector.py index 501024f..b398a1e 100644 --- a/discover_overlay/discord_connector.py +++ b/discover_overlay/discord_connector.py @@ -29,7 +29,6 @@ import calendar import websocket import requests -import gi from gi.repository import GLib log = logging.getLogger(__name__) @@ -94,9 +93,14 @@ class DiscordConnector: """ url = "https://streamkit.discord.com/overlay/token" myobj = {"code": code1} - response = requests.post(url, json=myobj) + response = requests.post(url, json=myobj, timeout=10) try: jsonresponse = json.loads(response.text) + except requests.exceptions.Timeout: + # TODO This probably needs a retry, not a quit + jsonresponse = {} + except requests.exceptions.TooManyRedirects: + jsonresponse = {} except json.JSONDecodeError: jsonresponse = {} if "access_token" in jsonresponse: @@ -192,8 +196,7 @@ class DiscordConnector: """ Update a line of text """ - for idx in range(0, len(self.text)): - message = self.text[idx] + for idx, message in enumerate(self.text): if message['id'] == message_in['id']: new_message = {'id': message['id'], 'content': self.get_message_from_message(message_in), @@ -209,8 +212,7 @@ class DiscordConnector: """ Delete a line of text """ - for idx in range(0, len(self.text)): - message = self.text[idx] + for idx, message in enumerate(self.text): if message['id'] == message_in['id']: del self.text[idx] self.text_altered = True @@ -386,7 +388,8 @@ class DiscordConnector: self.dump_channel_data() return elif j["cmd"] == "GET_GUILD": - # We currently only get here because of a "CHANNEL_CREATE" event. Stupidly long winded way around + # We currently only get here because of a "CHANNEL_CREATE" event. + # Stupidly long winded way around if j["data"]: guild = j["data"] self.dump_channel_data() @@ -417,7 +420,8 @@ class DiscordConnector: self.set_channel(j['data']['id'], j['data']['guild_id']) self.discover.voice_overlay.set_channel_title( j["data"]["name"]) - if self.current_guild in self.guilds and 'icon_url' in self.guilds[self.current_guild]: + if (self.current_guild in self.guilds and + 'icon_url' in self.guilds[self.current_guild]): self.discover.voice_overlay.set_channel_icon( self.guilds[self.current_guild]['icon_url']) else: @@ -460,7 +464,8 @@ class DiscordConnector: log.warning(j) def dump_channel_data(self): - with open(self.discover.channel_file, 'w') as f: + """ Write all channel data out to file""" + with open(self.discover.channel_file, 'w', encoding="utf-8") as f: f.write(json.dumps( {'channels': self.channels, 'guild': self.guilds})) @@ -536,7 +541,7 @@ class DiscordConnector: if guild in self.guilds: self.rate_limited_channels.append(guild) else: - log.warning(f"Didn't find guild with id {guild}") + log.warning("Didn't find guild with id %s", guild) def req_channel_details(self, channel, nonce=None): """message @@ -669,6 +674,7 @@ class DiscordConnector: self.websocket.send(json.dumps(cmd)) def set_mute(self, muted): + """ Set client muted status """ cmd = { "cmd": "SET_VOICE_SETTINGS", "args": {"mute": muted}, @@ -679,6 +685,7 @@ class DiscordConnector: return False def set_deaf(self, deaf): + """ Set client deafened status """ cmd = { "cmd": "SET_VOICE_SETTINGS", "args": {"deaf": deaf}, @@ -688,14 +695,14 @@ class DiscordConnector: self.websocket.send(json.dumps(cmd)) return False - def change_voice_room(self, id): + def change_voice_room(self, room_id): """ Switch to another voice room """ cmd = { "cmd": "SELECT_VOICE_CHANNEL", "args": { - "channel_id": id, + "channel_id": room_id, "force": True }, "nonce": "deadbeef" @@ -703,14 +710,14 @@ class DiscordConnector: if self.websocket: self.websocket.send(json.dumps(cmd)) - def change_text_room(self, id): + def change_text_room(self, room_id): """ Switch to another text room """ cmd = { "cmd": "SELECT_TEXT_CHANNEL", "args": { - "channel_id": id + "channel_id": room_id }, "nonce": "deadbeef" } @@ -718,7 +725,8 @@ class DiscordConnector: self.websocket.send(json.dumps(cmd)) def update_overlays_from_data(self): - if self.websocket == None: + """Send new data out to overlay windows""" + if self.websocket is None: self.discover.voice_overlay.set_blank() if self.discover.text_overlay: self.discover.text_overlay.set_blank() @@ -773,12 +781,13 @@ class DiscordConnector: This will be mixed in with 'None' in the list where a voice channel is """ - if (guild_id == 0): + if guild_id == 0: return self.rate_limited_channels.append(guild_id) def schedule_reconnect(self): - if self.reconnect_cb == None: + """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) else: @@ -792,25 +801,30 @@ class DiscordConnector: """ log.info("Connecting...") if self.websocket: - log.warn("Already connected?") + log.warning("Already connected?") return if self.reconnect_cb: GLib.source_remove(self.reconnect_cb) self.reconnect_cb = None try: self.websocket = websocket.create_connection( - "ws://127.0.0.1:6463/?v=1&client_id=%s" % (self.oauth_token), + f"ws://127.0.0.1:6463/?v=1&client_id={self.oauth_token}", origin="http://localhost:3000", timeout=0.1 ) if self.socket_watch: GLib.source_remove(self.socket_watch) self.socket_watch = GLib.io_add_watch( - self.websocket.sock, GLib.PRIORITY_DEFAULT_IDLE, GLib.IOCondition.HUP | GLib.IOCondition.IN | GLib.IOCondition.ERR, self.socket_glib) - except ConnectionError as error: + self.websocket.sock, + GLib.PRIORITY_DEFAULT_IDLE, + GLib.IOCondition.HUP | GLib.IOCondition.IN | GLib.IOCondition.ERR, + self.socket_glib + ) + except ConnectionError as _error: self.schedule_reconnect() - def socket_glib(self, fd, condition): + def socket_glib(self, _fd, condition): + """Handle new data on socket""" if condition == GLib.IO_IN and self.websocket: recv, _w, _e = select.select((self.websocket.sock,), (), (), 0) while recv: diff --git a/discover_overlay/discover_overlay.py b/discover_overlay/discover_overlay.py index 07712cf..9fe7e51 100755 --- a/discover_overlay/discover_overlay.py +++ b/discover_overlay/discover_overlay.py @@ -13,16 +13,15 @@ """Main application class""" import gettext import os -import time import sys import re import traceback import logging -import pkg_resources import json import signal -import gi from configparser import ConfigParser +import gi +import pkg_resources from .settings_window import MainSettingsWindow from .voice_overlay import VoiceOverlayWindow @@ -33,7 +32,7 @@ from .audio_assist import DiscoverAudioAssist gi.require_version("Gtk", "3.0") # pylint: disable=wrong-import-position,wrong-import-order -from gi.repository import Gtk, GLib, Gio, Gdk # nopep8 +from gi.repository import Gtk, GLib, Gio # nopep8 try: from xdg.BaseDirectory import xdg_config_home @@ -99,7 +98,7 @@ class Discover: Read in arg list from command or RPC and act accordingly """ if "--help" in data or "-h" in data: - print("%s: discover-overlay [OPTIONS]... " % (_("Usage"))) + print(f"{_("Usage")}: discover-overlay [OPTIONS]... ") print(_("Show an X11 or wlroots overlay with information")) print(_("from Discord client")) print("") @@ -158,14 +157,16 @@ class Discover: self.connection.request_text_rooms_for_guild(match.group(1)) def config_set(self, context, key, value): + """Set a config value and save to disk""" config = self.config() if not context in config.sections(): config.add_section(context) config.set(context, key, value) - with open(self.config_file, 'w') as file: + with open(self.config_file, 'w', encoding="utf-8") as file: config.write(file) def config(self): + """Read config from disk""" config = ConfigParser(interpolation=None) config.read(self.config_file) return config @@ -174,7 +175,7 @@ class Discover: """ Called when the RPC file has been altered """ - with open(self.rpc_file, "r") as tfile: + with open(self.rpc_file, "r", encoding="utf-8") as tfile: data = tfile.readlines() if len(data) >= 1: self.do_args(data[0].strip().split(" "), False) @@ -249,9 +250,7 @@ class Discover: self.voice_overlay.set_horizontal(config.getboolean( "main", "horizontal", fallback=False)) - self.voice_overlay.set_guild_ids(self.parse_guild_ids( - config.get("main", "guild_ids", fallback=""))) - self.voice_overlay.set_overflow( + self.voice_overlay.set_overflow_style( config.getint("main", "overflow", fallback=0)) self.voice_overlay.set_show_connection(config.getboolean( "main", "show_connection", fallback=False)) @@ -259,7 +258,7 @@ class Discover: "main", "show_title", fallback=False)) self.voice_overlay.set_show_disconnected(config.getboolean( "main", "show_disconnected", fallback=False)) - self.voice_overlay.set_border_width( + self.voice_overlay.set_drawn_border_width( config.getint("main", "border_width", fallback=2)) self.voice_overlay.set_icon_transparency(config.getfloat( "main", "icon_transparency", fallback=1.0)) @@ -432,6 +431,7 @@ class Discover: self.config_file, self.rpc_file, self.channel_file, []) def toggle_show(self, _obj=None): + """Toggle all overlays off or on""" if self.voice_overlay: hide = not self.voice_overlay.hidden self.voice_overlay.set_hidden(hide) @@ -457,6 +457,8 @@ class Discover: self.notification_overlay.set_force_xshape(force) def set_show_task(self, visible): + """Set if the overlay should allow itself to appear on taskbar. + Not working at last check""" if self.voice_overlay: self.voice_overlay.set_task(visible) if self.text_overlay: @@ -465,11 +467,13 @@ class Discover: self.notification_overlay.set_task(visible) def set_mute_async(self, mute): - if mute != None: + """Set mute status from another thread""" + if mute is not None: GLib.idle_add(self.connection.set_mute, mute) def set_deaf_async(self, deaf): - if deaf != None: + """Set deaf status from another thread""" + if deaf is not None: GLib.idle_add(self.connection.set_deaf, deaf) @@ -499,13 +503,12 @@ def entrypoint(): # Prepare logger logging.getLogger().setLevel(logging.INFO) - FORMAT = "%(levelname)s - %(name)s - %(message)s" + log_format = "%(levelname)s - %(name)s - %(message)s" if "--debug" in sys.argv or "-v" in sys.argv: logging.getLogger().setLevel(logging.DEBUG) - logging.basicConfig(filename=debug_file, format=FORMAT) + logging.basicConfig(filename=debug_file, format=log_format) else: - logging.basicConfig(format=FORMAT) - log = logging.getLogger(__name__) + logging.basicConfig(format=log_format) log.info("Starting Discover Overlay: %s", pkg_resources.get_distribution('discover_overlay').version) @@ -520,26 +523,26 @@ def entrypoint(): # Send command to overlay line = "" for arg in sys.argv[1:]: - line = "%s %s" % (line, arg) - with open(rpc_file, "w") as tfile: + line = f"{line} {arg}" + with open(rpc_file, "w", encoding="utf-8") as tfile: tfile.write(line) log.warning("Sent RPC command") else: if "-c" in sys.argv or "--configure" in sys.argv: # Show config window - settings = MainSettingsWindow( + _settings = MainSettingsWindow( config_file, rpc_file, channel_file, sys.argv[1:]) Gtk.main() else: # Tell any other running overlay to close - with open(rpc_file, "w") as tfile: + with open(rpc_file, "w", encoding="utf-8") as tfile: tfile.write("--close") # Show the overlay Discover(rpc_file, config_file, channel_file, debug_file, sys.argv[1:]) return - except Exception as ex: + except Exception as ex: # pylint: disable=broad-except log.error(ex) log.error(traceback.format_exc()) sys.exit(1) diff --git a/discover_overlay/draggable_window.py b/discover_overlay/draggable_window.py index bd8a1da..3dfd964 100644 --- a/discover_overlay/draggable_window.py +++ b/discover_overlay/draggable_window.py @@ -11,9 +11,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """An X11 window which can be moved and resized""" +import logging import gi import cairo -import logging gi.require_version("Gtk", "3.0") # pylint: disable=wrong-import-position from gi.repository import Gtk, Gdk # nopep8 @@ -24,10 +24,12 @@ log = logging.getLogger(__name__) class DraggableWindow(Gtk.Window): """An X11 window which can be moved and resized""" - def __init__(self, pos_x=0.0, pos_y=0.0, width=0.1, height=0.1, message="Message", settings=None, monitor=None): + def __init__(self, pos_x=0.0, pos_y=0.0, width=0.1, height=0.1, + message="Message", settings=None, monitor=None): Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP) self.monitor = monitor - (screen_x, screen_y, screen_width, screen_height) = self.get_display_coords() + (_screen_x, _screen_y, screen_width, + screen_height) = self.get_display_coords() self.pos_x = pos_x * screen_width self.pos_y = pos_y * screen_height self.width = max(40, width * screen_width) @@ -84,8 +86,8 @@ class DraggableWindow(Gtk.Window): if event.state & Gdk.ModifierType.BUTTON1_MASK: if self.drag_type == 1: # Center is move - (screen_x, screen_y, screen_width, - screen_height) = self.get_display_coords() + (screen_x, screen_y, _screen_width, + _screen_height) = self.get_display_coords() self.pos_x = (event.x_root - screen_x) - self.drag_x self.pos_y = (event.y_root - screen_y) - self.drag_y self.force_location() @@ -151,6 +153,7 @@ class DraggableWindow(Gtk.Window): context.fill() def get_display_coords(self): + """Get coordinates for this display""" display = Gdk.Display.get_default() if "get_monitor" in dir(display): monitor = display.get_monitor(self.monitor) @@ -171,4 +174,5 @@ class DraggableWindow(Gtk.Window): height = float(height) pos_x = pos_x / scale pos_y = pos_y / scale - return (pos_x / screen_width, pos_y / screen_height, width / screen_width, height / screen_height) + return (pos_x / screen_width, pos_y / screen_height, + width / screen_width, height / screen_height) diff --git a/discover_overlay/draggable_window_wayland.py b/discover_overlay/draggable_window_wayland.py index b69d5f6..530d2df 100644 --- a/discover_overlay/draggable_window_wayland.py +++ b/discover_overlay/draggable_window_wayland.py @@ -11,9 +11,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """A Wayland full-screen window which can be moved and resized""" +import logging import cairo import gi -import logging gi.require_version("Gtk", "3.0") # pylint: disable=wrong-import-position from gi.repository import Gtk, Gdk # nopep8 @@ -29,12 +29,14 @@ log = logging.getLogger(__name__) class DraggableWindowWayland(Gtk.Window): """A Wayland full-screen window which can be moved and resized""" - def __init__(self, pos_x=0.0, pos_y=0.0, width=0.1, height=0.1, message="Message", settings=None, steamos=False, monitor=None): + def __init__(self, pos_x=0.0, pos_y=0.0, width=0.1, height=0.1, + message="Message", settings=None, steamos=False, monitor=None): Gtk.Window.__init__(self, type=Gtk.WindowType.TOPLEVEL) if steamos: monitor = 0 self.monitor = monitor - (screen_x, screen_y, screen_width, screen_height) = self.get_display_coords() + (_screen_x, _screen_y, screen_width, + screen_height) = self.get_display_coords() self.pos_x = pos_x * screen_width self.pos_y = pos_y * screen_height self.width = max(40, width * screen_width) @@ -48,8 +50,8 @@ class DraggableWindowWayland(Gtk.Window): self.connect('button-press-event', self.button_press) self.connect('button-release-event', self.button_release) - log.info("Starting: %d,%d %d x %d" % - (self.pos_x, self.pos_y, self.width, self.height)) + log.info("Starting: %d,%d %d x %d", + self.pos_x, self.pos_y, self.width, self.height) self.set_app_paintable(True) @@ -76,21 +78,24 @@ class DraggableWindowWayland(Gtk.Window): self.force_location() def set_steamos_window_size(self): + """Prepare window for a gamescope steamos session""" # Huge bunch of assumptions. # Gamescope only has one monitor # Gamescope has no scale factor + # Probably never possible to reach here, as Gamescope/SteamOS + # is X11 for overlays display = Gdk.Display.get_default() if "get_monitor" in dir(display): monitor = display.get_monitor(0) if monitor: geometry = monitor.get_geometry() - scale_factor = monitor.get_scale_factor() - log.info("%d %d" % (geometry.width, geometry.height)) + log.info("%d %d", geometry.width, geometry.height) self.set_size_request(geometry.width, geometry.height) def force_location(self): """Move the window to previously given co-ords. In wayland just clip to current screen""" - (screen_x, screen_y, screen_width, screen_height) = self.get_display_coords() + (_screen_x, _screen_y, screen_width, + screen_height) = self.get_display_coords() self.width = min(self.width, screen_width) self.height = min(self.height, screen_height) self.pos_x = max(0, self.pos_x) @@ -189,6 +194,7 @@ class DraggableWindowWayland(Gtk.Window): context.restore() def get_display_coords(self): + """Get coordinates from display""" display = Gdk.Display.get_default() if "get_monitor" in dir(display): monitor = display.get_monitor(self.monitor) @@ -199,5 +205,7 @@ class DraggableWindowWayland(Gtk.Window): def get_coords(self): """Return the position and size of the window""" - (screen_x, screen_y, screen_width, screen_height) = self.get_display_coords() - return (float(self.pos_x) / screen_width, float(self.pos_y) / screen_height, float(self.width) / screen_width, float(self.height) / screen_height) + (_screen_x, _screen_y, screen_width, + screen_height) = self.get_display_coords() + return (float(self.pos_x) / screen_width, float(self.pos_y) / screen_height, + float(self.width) / screen_width, float(self.height) / screen_height) diff --git a/discover_overlay/image_getter.py b/discover_overlay/image_getter.py index 2c8d5d5..9fc3466 100644 --- a/discover_overlay/image_getter.py +++ b/discover_overlay/image_getter.py @@ -11,21 +11,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """Functions & Classes to assist image loading.""" -import urllib import threading import logging +import os +import copy import gi import requests import cairo import PIL import PIL.Image as Image -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, Gtk # nopep8 +from gi.repository import Gtk # nopep8 log = logging.getLogger(__name__) @@ -43,7 +41,7 @@ class SurfaceGetter(): """Downloads and decodes""" try: resp = requests.get( - self.url, stream=True, headers={ + self.url, stream=True, timeout=10, headers={ 'Referer': 'https://streamkit.discord.com/overlay/voice', 'User-Agent': 'Mozilla/5.0' } @@ -69,6 +67,7 @@ class SurfaceGetter(): log.error("Unknown image type: %s", self.url) def get_file(self): + """Attempt to load the file""" errors = [] # Grab icon from icon theme icon_theme = Gtk.IconTheme.get_default() @@ -99,13 +98,13 @@ class SurfaceGetter(): try: image = Image.open(mixpath) except ValueError: - errors.append("Value Error - Unable to read %s" % (mixpath)) + errors.append(f"Value Error - Unable to read {mixpath}") except TypeError: - errors.append("Type Error - Unable to read %s" % (mixpath)) + errors.append(f"Type Error - Unable to read {mixpath}") except PIL.UnidentifiedImageError: - errors.append("Unknown image type: %s" % (mixpath)) + errors.append(f"Unknown image type: {mixpath}") except FileNotFoundError: - errors.append("File not found: %s" % (mixpath)) + errors.append(f"File not found: {mixpath}") if image: (surface, mask) = from_pil(image) if surface: @@ -115,7 +114,7 @@ class SurfaceGetter(): log.error(error) -def from_pil(image, alpha=1.0, format='BGRa'): +def from_pil(image, alpha=1.0, image_format='BGRa'): """ :param im: Pillow Image :param alpha: 0..1 alpha to add to non-alpha images @@ -125,10 +124,10 @@ def from_pil(image, alpha=1.0, format='BGRa'): mask = bytearray() if 'A' not in image.getbands(): image.putalpha(int(alpha * 255.0)) - arr = bytearray(image.tobytes('raw', format)) + arr = bytearray(image.tobytes('raw', image_format)) mask = arr else: - arr = bytearray(image.tobytes('raw', format)) + arr = bytearray(image.tobytes('raw', image_format)) mask = copy.deepcopy((arr)) idx = 0 while idx < len(arr): @@ -148,9 +147,12 @@ def from_pil(image, alpha=1.0, format='BGRa'): def to_pil(surface): + """Return a PIL Image from the Cairo 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", 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", surface.get_stride()) def get_surface(func, identifier, ava, size): diff --git a/discover_overlay/notification_overlay.py b/discover_overlay/notification_overlay.py index f834715..8793116 100644 --- a/discover_overlay/notification_overlay.py +++ b/discover_overlay/notification_overlay.py @@ -13,16 +13,15 @@ """Notification window for text""" import logging import time -import re -import cairo import math + +import cairo import gi -from .image_getter import get_surface, draw_img_to_rect, get_aspected_size +from .image_getter import get_surface, draw_img_to_rect from .overlay import OverlayWindow -gi.require_version("Gtk", "3.0") gi.require_version('PangoCairo', '1.0') # pylint: disable=wrong-import-position,wrong-import-order -from gi.repository import Gtk, Pango, PangoCairo # nopep8 +from gi.repository import Pango, PangoCairo # nopep8 log = logging.getLogger(__name__) @@ -34,12 +33,54 @@ class NotificationOverlayWindow(OverlayWindow): OverlayWindow.__init__(self, discover, piggyback) self.text_spacing = 4 self.content = [] - self.test_content = [{"icon": "https://cdn.discordapp.com/icons/951077080769114172/991abffc0d2a5c040444be4d1a4085f4.webp?size=96", "title": "Title1"}, - {"title": "Title2", "body": "Body", "icon": None}, - {"icon": "https://cdn.discordapp.com/icons/951077080769114172/991abffc0d2a5c040444be4d1a4085f4.webp?size=96", "title": "Title 3", - "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."}, - {"icon": None, "title": "Title 3", "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."}, - {"icon": "https://cdn.discordapp.com/avatars/147077941317206016/6a6935192076489fa6dc1eb5dafbf6e7.webp?size=128", "title": "PM", "body": "Birdy test"}] + self.test_content = [ + { + "icon": ( + "https://cdn.discordapp.com/" + "icons/951077080769114172/991abffc0d2a5c040444be4d1a4085f4.webp?size=96" + ), + "title": "Title1" + }, + { + "title": "Title2", + "body": "Body", + "icon": None + }, + { + "icon": ("https://cdn.discordapp.com/" + "icons/951077080769114172/991abffc0d2a5c040444be4d1a4085f4.webp?size=96" + ), + "title": "Title 3", + "body": ("Lorem ipsum dolor sit amet, consectetur adipiscing elit," + " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in " + "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla " + "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa " + "qui officia deserunt mollit anim id est laborum." + ) + }, + { + "icon": None, + "title": "Title 3", + "body": ("Lorem ipsum dolor sit amet, consectetur adipiscing elit, " + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + "Ut enim ad minim veniam, quis nostrud exercitation ullamco " + "laboris nisi ut aliquip ex ea commodo consequat. Duis aute " + "irure dolor in reprehenderit in voluptate velit esse cillum " + "dolore eu fugiat nulla pariatur. Excepteur sint occaecat " + "cupidatat non proident, sunt in culpa qui officia deserunt " + "mollit anim id est laborum." + ) + }, + { + "icon": ("https://cdn.discordapp.com/" + "avatars/147077941317206016/6a6935192076489fa6dc1eb5dafbf6e7.webp?size=128" + ), + "title": "PM", + "body": "Birdy test" + } + ] self.text_font = None self.text_size = 13 self.text_time = None @@ -67,11 +108,12 @@ class NotificationOverlayWindow(OverlayWindow): self.redraw() def set_blank(self): + """Set to no data and redraw""" self.content = [] self.set_needs_redraw() def tick(self): - # This doesn't really belong in overlay or settings + """Remove old messages from dataset""" now = time.time() newlist = [] oldsize = len(self.content) @@ -85,6 +127,7 @@ class NotificationOverlayWindow(OverlayWindow): self.set_needs_redraw() def add_notification_message(self, data): + """Add new message to dataset""" noti = None data = data['data'] message_id = data['message']['id'] @@ -108,59 +151,50 @@ class NotificationOverlayWindow(OverlayWindow): self.get_all_images() def set_padding(self, padding): - """ - Set the padding between notifications - """ + """Config option: Padding between notifications, in window-space pixels""" if self.padding != padding: self.padding = padding self.set_needs_redraw() def set_border_radius(self, radius): - """ - Set the radius of the border - """ + """Config option: Radius of the border, in window-space pixels""" if self.border_radius != radius: self.border_radius = radius self.set_needs_redraw() def set_icon_size(self, size): - """ - Set Icon size - """ + """Config option: Size of icons, in window-space pixels""" 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 - """ + """Config option: Padding between icon and message, in window-space pixels""" if self.icon_pad != pad: self.icon_pad = pad self.set_needs_redraw() def set_icon_left(self, left): + """Config option: Icon on left or right of text""" if self.icon_left != left: self.icon_left = left self.set_needs_redraw() def set_text_time(self, timer): - """ - Set the duration that a message will be visible for. - """ + """Config option: Duration that a message will be visible for, in seconds""" self.text_time = timer self.timer_after_draw = timer def set_limit_width(self, limit): - """ - Set the word wrap limit in pixels + """Config option: Word wrap limit, in window-space pixels """ if self.limit_width != limit: self.limit_width = limit self.set_needs_redraw() def get_all_images(self): + """Return a list of all downloaded images""" the_list = self.content if self.testing: the_list = self.test_content @@ -171,47 +205,38 @@ class NotificationOverlayWindow(OverlayWindow): get_surface(self.recv_icon, icon, icon, self.icon_size) - def recv_icon(self, identifier, pix, mask): - """ - Called when image_getter has downloaded an image - """ + def recv_icon(self, identifier, pix, _mask): + """Callback from image_getter for icons""" self.image_list[identifier] = pix self.set_needs_redraw() def set_fg(self, fg_col): - """ - Set default text colour - """ + """Config option: Set default text colour""" if self.fg_col != fg_col: self.fg_col = fg_col self.set_needs_redraw() def set_bg(self, bg_col): - """ - Set background colour - """ + """Config option: Set background colour""" 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 - """ + """Config option: Set if icons should be shown inline""" if self.show_icon != icon: self.show_icon = icon self.set_needs_redraw() self.get_all_images() def set_reverse_order(self, rev): + """Config option: Reverse order of messages""" if self.reverse_order != rev: self.reverse_order = rev self.set_needs_redraw() def set_font(self, font): - """ - Set font used to render text - """ + """Config option: Font used to render text""" if self.text_font != font: self.text_font = font @@ -222,13 +247,13 @@ class NotificationOverlayWindow(OverlayWindow): self.set_needs_redraw() def recv_attach(self, identifier, pix): - """ - Called when an image has been downloaded by image_getter - """ + """Callback from image_getter for attachments""" self.icons[identifier] = pix self.set_needs_redraw() def calc_all_height(self): + """Return the height in window-space pixels required + to draw this overlay with current dataset""" h = 0 my_list = self.content if self.testing: @@ -240,6 +265,7 @@ class NotificationOverlayWindow(OverlayWindow): return h def calc_height(self, line): + """Return height in window-space pixels required to draw individual notification""" icon_width = 0 icon_pad = 0 icon = line['icon'] @@ -257,9 +283,8 @@ class NotificationOverlayWindow(OverlayWindow): layout = self.create_pango_layout(message) layout.set_auto_dir(True) layout.set_markup(message, -1) - attr = layout.get_attributes() - (floating_x, floating_y, floating_width, - floating_height) = self.get_floating_coords() + (_floating_x, _floating_y, floating_width, + _floating_height) = self.get_floating_coords() width = self.limit_width if floating_width > self.limit_width else floating_width layout.set_width((Pango.SCALE * (width - (self.border_radius * 4 + icon_width + icon_pad)))) @@ -267,12 +292,13 @@ class NotificationOverlayWindow(OverlayWindow): if self.text_font: font = Pango.FontDescription(self.text_font) layout.set_font_description(font) - text_width, text_height = layout.get_pixel_size() + _text_width, text_height = layout.get_pixel_size() if text_height < icon_width: text_height = icon_width return text_height + (self.border_radius*4) + self.padding def has_content(self): + """Return true if this overlay has meaningful content to show""" if not self.enabled: return False if self.hidden: @@ -282,15 +308,13 @@ class NotificationOverlayWindow(OverlayWindow): return self.content def overlay_draw(self, w, context, data=None): - """ - Draw the overlay - """ + """Draw the overlay""" if self.piggyback: self.piggyback.overlay_draw(w, context, data) if not self.enabled: return self.context = context - (width, height) = self.get_size() + (_width, height) = self.get_size() if not self.piggyback_parent: context.set_antialias(cairo.ANTIALIAS_GOOD) @@ -319,7 +343,6 @@ class NotificationOverlayWindow(OverlayWindow): current_y = 0 if self.align_vert == 1: # Center. Oh god why current_y = (height/2.0) - (self.calc_all_height() / 2.0) - tnow = time.time() if self.testing: the_list = self.test_content else: @@ -354,10 +377,7 @@ class NotificationOverlayWindow(OverlayWindow): self.context = None def draw_text(self, pos_y, text, icon): - """ - Draw a text message, returning the Y position of the next message - """ - + """Draw a text message, returning the Y position of the next message""" icon_width = self.icon_size icon_pad = self.icon_pad if not self.show_icon: @@ -371,8 +391,8 @@ class NotificationOverlayWindow(OverlayWindow): layout.set_markup(text, -1) attr = layout.get_attributes() - (floating_x, floating_y, floating_width, - floating_height) = self.get_floating_coords() + (_floating_x, _floating_y, floating_width, + _floating_height) = self.get_floating_coords() width = self.limit_width if floating_width > self.limit_width else floating_width layout.set_width((Pango.SCALE * (width - (self.border_radius * 4 + icon_width + icon_pad)))) @@ -475,7 +495,6 @@ class NotificationOverlayWindow(OverlayWindow): self.get_pango_context(), self.render_custom, None) text = layout.get_text() - count = 0 layout.set_attributes(attr) @@ -486,7 +505,6 @@ class NotificationOverlayWindow(OverlayWindow): self.get_pango_context(), self.render_custom, None) text = layout.get_text() - count = 0 layout.set_attributes(attr) @@ -501,11 +519,9 @@ class NotificationOverlayWindow(OverlayWindow): return next_y def render_custom(self, ctx, shape, path, _data): - """ - Draw an inline image as a custom emoticon - """ + """Draw an inline image as a custom emoticon""" if shape.data >= len(self.image_list): - log.warning(f"{shape.data} >= {len(self.image_list)}") + log.warning("%s >= %s", shape.data, len(self.image_list)) return # key is the url to the image key = self.image_list[shape.data] @@ -521,9 +537,7 @@ class NotificationOverlayWindow(OverlayWindow): return True def sanitize_string(self, string): - """ - Sanitize a text message so that it doesn't intefere with Pango's XML format - """ + """Sanitize a text message so that it doesn't intefere with Pango's XML format""" string = string.replace("&", "&") string = string.replace("<", "<") string = string .replace(">", ">") @@ -532,6 +546,7 @@ class NotificationOverlayWindow(OverlayWindow): return string def set_testing(self, testing): + """Toggle placeholder images for testing""" self.testing = testing self.set_needs_redraw() self.get_all_images() diff --git a/discover_overlay/overlay.py b/discover_overlay/overlay.py index 7f1269d..bbb20e7 100644 --- a/discover_overlay/overlay.py +++ b/discover_overlay/overlay.py @@ -19,7 +19,6 @@ import sys import logging import gi import cairo -import Xlib from Xlib.display import Display from Xlib import X, Xatom gi.require_version("Gtk", "3.0") @@ -46,7 +45,7 @@ class OverlayWindow(Gtk.Window): """ window = Gtk.Window() screen = window.get_screen() - screen_type = "%s" % (screen) + screen_type = f"{screen}" self.is_wayland = False if "Wayland" in screen_type: self.is_wayland = True @@ -124,10 +123,12 @@ class OverlayWindow(Gtk.Window): # this process hanging if it happens self.connect('destroy', self.window_exited) - def window_exited(self, window=None): + def window_exited(self, _window=None): + """Window closed. Exit app""" sys.exit(1) def set_gamescope_xatom(self, enabled): + """Set Gamescope XAtom to identify self as an overlay candidate""" if self.piggyback_parent: return @@ -136,7 +137,7 @@ class OverlayWindow(Gtk.Window): self.is_xatom_set = enabled display = Display() atom = display.intern_atom("GAMESCOPE_EXTERNAL_OVERLAY") - opaq = display.intern_atom("_NET_WM_WINDOW_OPACITY") + # Since unused: _NET_WM_WINDOW_OPACITY if self.get_toplevel().get_window(): topw = display.create_resource_object( @@ -148,7 +149,7 @@ class OverlayWindow(Gtk.Window): log.info("Setting GAMESCOPE_EXTERNAL_OVERLAY to %s", enabled) display.sync() else: - log.warn("Unable to set GAMESCOPE_EXTERNAL_OVERLAY") + log.warning("Unable to set GAMESCOPE_EXTERNAL_OVERLAY") def set_wayland_state(self): """ @@ -169,13 +170,16 @@ class OverlayWindow(Gtk.Window): GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.TOP, True) def set_piggyback(self, other_overlay): + """Sets as piggybacking off the given (other) overlay""" other_overlay.piggyback = self self.piggyback_parent = other_overlay def has_content(self): + """Return true if overlay has meaningful content""" return False def overlay_draw_pre(self, _w, context, data=None): + """Prepare for drawing the overlay. Calls overlay_draw after preparations""" content = self.has_content() if self.piggyback and self.piggyback.has_content(): content = True @@ -224,16 +228,18 @@ class OverlayWindow(Gtk.Window): """ if width > 1.0 and height > 1.0: # Old data. - (screen_x, screen_y, screen_width, + (_screen_x, _screen_y, screen_width, screen_height) = self.get_display_coords() pos_x = float(pos_x) / screen_width pos_y = float(pos_y) / screen_height width = float(width) / screen_width height = float(height) / screen_height - if self.floating != floating or self.pos_x != pos_x or self.pos_y != pos_y or self.width != width or self.height != height: + 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': + if ('XDG_SESSION_DESKTOP' in os.environ and + os.environ['XDG_SESSION_DESKTOP'] == 'cinnamon'): floating = True self.floating = floating @@ -258,6 +264,7 @@ class OverlayWindow(Gtk.Window): self.input_shape_combine_region(reg) def set_hide_on_mouseover(self, hide): + """Set if the overlay should hide when mouse moves over it""" if self.hide_on_mouseover != hide: self.hide_on_mouseover = hide if self.hide_on_mouseover: @@ -266,6 +273,7 @@ class OverlayWindow(Gtk.Window): self.set_untouchable() def set_mouseover_timer(self, time): + """Set the time until the overlay reappears after mouse over""" self.timeout_mouse_over = time def unset_shape(self): @@ -299,6 +307,7 @@ class OverlayWindow(Gtk.Window): self.set_needs_redraw() def get_display_coords(self): + """Get screen space co-ordinates of the monitor""" if self.piggyback_parent: return self.piggyback_parent.get_display_coords() monitor = self.get_monitor_from_plug() @@ -311,29 +320,35 @@ class OverlayWindow(Gtk.Window): return (0, 0, 1920, 1080) def get_floating_coords(self): + """Get screen space co-ordinates of the window""" (screen_x, screen_y, screen_width, screen_height) = self.get_display_coords() if self.floating: - if self.pos_x == None or self.pos_y == None or self.width == None or self.height == None: + if (self.pos_x is None or self.pos_y is None or + self.width is None or self.height is None): log.error("No usable floating position") if not self.is_wayland: - return (screen_x + self.pos_x * screen_width, screen_y + self.pos_y * screen_height, self.width * screen_width, self.height * screen_height) - return (self.pos_x * screen_width, self.pos_y * screen_height, self.width * screen_width, self.height * screen_height) + return (screen_x + self.pos_x * screen_width, screen_y + self.pos_y * screen_height, + self.width * screen_width, self.height * screen_height) + return (self.pos_x * screen_width, self.pos_y * screen_height, + self.width * screen_width, self.height * screen_height) else: return (screen_x, screen_y, screen_width, screen_height) def set_needs_redraw(self, be_pushy=False): + """Schedule this overlay for a redraw. If part of a + piggyback chain, pass it up to be redrawn by topmost parent""" if (not self.hidden and self.enabled) or be_pushy: if self.piggyback_parent: self.piggyback_parent.set_needs_redraw(be_pushy=True) - if self.redraw_id == None: + if self.redraw_id is None: self.redraw_id = GLib.idle_add(self.redraw) else: log.debug("Already awaiting paint") # If this overlay has data that expires after draw, plan for that here - if self.timer_after_draw != None: + if self.timer_after_draw is not None: GLib.timeout_add_seconds(self.timer_after_draw, self.redraw) def redraw(self): @@ -364,6 +379,7 @@ class OverlayWindow(Gtk.Window): return False def set_hidden(self, hidden): + """Set if the overlay should be hidden""" self.hidden = hidden self.set_enabled(self.enabled) @@ -371,7 +387,7 @@ class OverlayWindow(Gtk.Window): """ Set the monitor this overlay should display on. """ - plug_name = "%s" % (idx) + plug_name = f"{idx}" if self.monitor != plug_name: self.monitor = plug_name if self.is_wayland: @@ -387,6 +403,8 @@ class OverlayWindow(Gtk.Window): self.set_needs_redraw() def get_monitor_from_plug(self): + """Return a GDK Monitor filtered by plug name + (HDMI-1, eDP-1, VGA etc)""" if not self.monitor or self.monitor == "Any": return None display = Gdk.Display.get_default() @@ -454,26 +472,31 @@ class OverlayWindow(Gtk.Window): self.hide() def set_task(self, visible): + """Set visible on taskbar. Not working at last check""" 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 + """Callback for compositing started/stopped in X11""" self.redraw() - def screen_changed(self, screen=None): + def screen_changed(self, _screen=None): + """Callback to set monitor to display on""" self.set_monitor(self.monitor) - def mouseover(self, a=None, b=None): + def mouseover(self, _a=None, _b=None): + """Callback when mouseover occurs, hides overlay""" self.draw_blank = True self.set_needs_redraw() return True - def mouseout(self, a=None, b=None): + def mouseout(self, _a=None, _b=None): + """Callback when mouseout occurs, sets a timer to show overlay""" GLib.timeout_add_seconds(self.timeout_mouse_over, self.mouseout_timed) return True - def mouseout_timed(self, a=None, b=None): + def mouseout_timed(self, _a=None, _b=None): + """Callback a short while after mouseout occured, shows overlay""" self.draw_blank = False self.set_needs_redraw() return False diff --git a/discover_overlay/settings_window.py b/discover_overlay/settings_window.py index 9519983..14556e0 100644 --- a/discover_overlay/settings_window.py +++ b/discover_overlay/settings_window.py @@ -11,18 +11,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """Settings window holding all settings tab""" +# pylint: disable=missing-function-docstring import gettext -import gi import logging -import pkg_resources import sys import os import json +from configparser import ConfigParser +import gi +import pkg_resources from .autostart import Autostart, BazziteAutostart from .draggable_window import DraggableWindow from .draggable_window_wayland import DraggableWindowWayland -from configparser import ConfigParser gi.require_version("Gtk", "3.0") # pylint: disable=wrong-import-position,wrong-import-order from gi.repository import Gtk, Gdk, Gio # nopep8 @@ -75,6 +76,14 @@ class MainSettingsWindow(): self.current_guild = "0" self.current_channel = "0" self.hidden_overlay = False + self.voice_floating_x = 0 + self.voice_floating_y = 0 + self.voice_floating_w = 0 + self.voice_floating_h = 0 + self.text_floating_x = 0 + self.text_floating_y = 0 + self.text_floating_w = 0 + self.text_floating_h = 0 self.menu = self.make_menu() self.make_sys_tray_icon(self.menu) @@ -111,16 +120,30 @@ class MainSettingsWindow(): if name.endswith("_all"): widget.set_label(_(widget.get_label())) - self.widget['overview_main_text'].set_markup("%s (%s)\n\n%s\n\n%s (https://discord.gg/jRKWMuDy5V) %s (https://github.com/trigg/Discover)\n\n\n\n\n\n" % ( - _("Welcome to Discover Overlay"), - pkg_resources.get_distribution('discover_overlay').version, - _("Discover-Overlay is a GTK3 overlay written in Python3. It can be configured to show who is currently talking on discord or it can be set to display text and images from a preconfigured channel. It is fully customisable and can be configured to display anywhere on the screen. We fully support X11 and wlroots based environments. We felt the need to make this project due to the shortcomings in support on Linux by the official discord client."), - _("Please visit our discord"), - _(" for support. Or open an issue on our GitHub ") - )) + self.widget['overview_main_text'].set_markup( + "%s%s (%s)%s%s\n\n%s %s %s %s%s\n\n\n\n\n\n" % ( + "", + _("Welcome to Discover Overlay"), + pkg_resources.get_distribution('discover_overlay').version, + "\n\n", + _(("Discover-Overlay is a GTK3 overlay written in Python3." + " It can be configured to show who is currently talking" + " on discord or it can be set to display text and images" + " from a preconfigured channel. It is fully customisable" + " and can be configured to display anywhere on the screen." + " We fully support X11 and wlroots based environments. We " + "felt the need to make this project due to the shortcomings" + " in support on Linux by the official discord client.")), + _("Please visit our discord"), + "(https://discord.gg/jRKWMuDy5V)", + _(" for support. Or open an issue on our GitHub "), + "(", + "https://github.com/trigg/Discover)" + ) + ) screen = window.get_screen() - screen_type = "%s" % (screen) + screen_type = f"{screen}" self.is_wayland = False if "Wayland" in screen_type: self.is_wayland = True @@ -185,6 +208,7 @@ class MainSettingsWindow(): self.widget['window'].set_default_icon_name(self.icon_name) def set_steamos_window_size(self): + """Set window based on steamos usage""" # Huge bunch of assumptions. # Gamescope only has one monitor # Gamescope has no scale factor @@ -193,23 +217,21 @@ class MainSettingsWindow(): monitor = display.get_monitor(0) if monitor: geometry = monitor.get_geometry() - scale_factor = monitor.get_scale_factor() - log.info("%d %d" % (geometry.width, geometry.height)) + log.info("%d %d", geometry.width, geometry.height) self.window.set_size_request(geometry.width, geometry.height) def keypress_in_settings(self, window, event): + """Callback to steal keypresses to assist SteamOS gamepad control""" if self.spinning_focus: match event.keyval: case Gdk.KEY_Right: step = self.spinning_focus.get_increments().step value = self.spinning_focus.get_value() self.spinning_focus.set_value(value + step) - pass case Gdk.KEY_Left: step = self.spinning_focus.get_increments().step value = self.spinning_focus.get_value() self.spinning_focus.set_value(value - step) - pass case Gdk.KEY_Up: step = self.spinning_focus.get_increments().step value = self.spinning_focus.get_value() @@ -230,11 +252,9 @@ class MainSettingsWindow(): case Gdk.KEY_Right: value = self.scale_focus.get_value() self.scale_focus.set_value(value + 0.1) - pass case Gdk.KEY_Left: value = self.scale_focus.get_value() self.scale_focus.set_value(value - 0.1) - pass case Gdk.KEY_Up: value = self.scale_focus.get_value() self.scale_focus.set_value(value + 0.1) @@ -267,7 +287,7 @@ class MainSettingsWindow(): widget = self.window.get_focus() if widget: # I really want there to be a better way... - widget_type = "%s" % (widget) + widget_type = f"{widget}" if 'Gtk.SpinButton' in widget_type: self.spinning_focus = widget @@ -285,16 +305,19 @@ class MainSettingsWindow(): return True def request_channels_from_guild(self, guild_id): - with open(self.rpc_file, 'w') as f: - f.write('--rpc --guild-request=%s' % (guild_id)) + """Send RPC to overlay to request updated channel list""" + with open(self.rpc_file, 'w', encoding="utf-8") as f: + f.write(f"--rpc --guild-request={guild_id}") def populate_guild_menu(self, _a=None, _b=None, _c=None, _d=None): + """Read guild data and repopulate widget. + Disable signal handling meanwhile to avoid recursive logic""" g = self.widget['text_server'] c = self.widget['text_channel'] g.handler_block(self.server_handler) c.handler_block(self.channel_handler) try: - with open(self.channel_file, "r") as tfile: + with open(self.channel_file, "r", encoding="utf-8") as tfile: data = tfile.readlines() if len(data) >= 1: data = json.loads(data[0]) @@ -322,21 +345,22 @@ class MainSettingsWindow(): c.handler_unblock(self.channel_handler) def populate_monitor_menus(self, _a=None, _b=None): - v = self.widget['voice_monitor'] - t = self.widget['text_monitor'] - m = self.widget['notification_monitor'] + """Get Monitor list from GTK and repopulate widget""" + voice = self.widget['voice_monitor'] + text = self.widget['text_monitor'] + notify = self.widget['notification_monitor'] - v_value = v.get_active() - t_value = t.get_active() - m_value = m.get_active() + v_value = voice.get_active() + t_value = text.get_active() + m_value = notify.get_active() - v.remove_all() - t.remove_all() - m.remove_all() + voice.remove_all() + text.remove_all() + notify.remove_all() - v.append_text("Any") - t.append_text("Any") - m.append_text("Any") + voice.append_text("Any") + text.append_text("Any") + notify.append_text("Any") display = Gdk.Display.get_default() screen = self.window.get_screen() @@ -348,41 +372,38 @@ class MainSettingsWindow(): manufacturer = this_mon.get_manufacturer() model = this_mon.get_model() connector = screen.get_monitor_plug_name(i) - monitor_label = "%s %s\n%s" % ( - manufacturer, model, connector) - v.append_text(monitor_label) - t.append_text(monitor_label) - m.append_text(monitor_label) + monitor_label = f"{manufacturer} {model}\n{connector}" + voice.append_text(monitor_label) + text.append_text(monitor_label) + notify.append_text(monitor_label) - v.set_active(v_value) - t.set_active(t_value) - m.set_active(m_value) + voice.set_active(v_value) + text.set_active(t_value) + notify.set_active(m_value) - def close_window(self, widget=None, event=None): - """ - Hide the settings window for use at a later date - """ + def close_window(self, _widget=None, _event=None): + """Hide the settings window for use at a later date""" self.window.hide() - if self.ind == None and self.tray == None: + if self.ind is None and self.tray is None: sys.exit(0) - if self.ind != None: + if self.ind is not None: # pylint: disable=import-outside-toplevel from gi.repository import AppIndicator3 if self.ind.get_status() == AppIndicator3.IndicatorStatus.PASSIVE: sys.exit(0) return True - def close_app(self, widget=None, event=None): + def close_app(self, _widget=None, _event=None): + """Close the app""" sys.exit(0) def present_settings(self, _a=None): - """ - Show the settings window - """ + """Show the settings window""" self.widget['notebook'].set_current_page(0) self.window.show() def set_alignment_labels(self, horz): + """Relabel alignment pulldowns""" m1 = self.widget['voice_align_1'].get_model() m2 = self.widget['voice_align_2'].get_model() i = m1.get_iter_first() @@ -409,6 +430,7 @@ class MainSettingsWindow(): m2.set_value(i2, 0, _("Bottom")) def read_config(self): + """Read config from disk""" self.loading_config = True # Read config and put into gui @@ -522,8 +544,8 @@ class MainSettingsWindow(): self.widget['voice_square_avatar'].set_active(config.getboolean( "main", "square_avatar", fallback=True)) - self.widget['voice_fancy_avatar_shapes'].set_active(config.getboolean("main", - "fancy_border", fallback=True)) + self.widget['voice_fancy_avatar_shapes'].set_active( + config.getboolean("main", "fancy_border", fallback=True)) self.widget['voice_order_avatars_by'].set_active( config.getint("main", "order", fallback=0)) @@ -719,6 +741,7 @@ class MainSettingsWindow(): self.loading_config = False def make_colour(self, col): + """Create a Gdk Color from a col tuple""" col = json.loads(col) return Gdk.RGBA(col[0], col[1], col[2], col[3]) @@ -732,6 +755,7 @@ class MainSettingsWindow(): return guild_ids def get_monitor_index_from_plug(self, monitor): + """Get monitor index from plug name""" if not monitor or monitor == "Any": return 0 display = Gdk.Display.get_default() @@ -746,9 +770,7 @@ class MainSettingsWindow(): return 0 def get_monitor_obj(self, idx): - """ - Helper function to find the monitor object of the monitor - """ + """Helper function to find the monitor object of the monitor""" display = Gdk.Display.get_default() return display.get_monitor(idx) @@ -780,17 +802,13 @@ class MainSettingsWindow(): self.tray.set_visible(False) def show_menu(self, obj, button, time): - """ - Show menu when System Tray icon is clicked - """ + """Show menu when System Tray icon is clicked""" self.menu.show_all() self.menu.popup( None, None, Gtk.StatusIcon.position_menu, obj, button, time) def set_sys_tray_icon_visible(self, visible): - """ - Sets whether the tray icon is visible - """ + """Sets whether the tray icon is visible""" if self.ind is not None: # pylint: disable=import-outside-toplevel from gi.repository import AppIndicator3 @@ -800,9 +818,7 @@ class MainSettingsWindow(): self.tray.set_visible(visible) def make_menu(self): - """ - Create System Menu - """ + """Create System Menu""" menu = Gtk.Menu() settings_opt = Gtk.MenuItem.new_with_label(_("Settings")) self.toggle_opt = Gtk.MenuItem.new_with_label(_("Hide overlay")) @@ -822,11 +838,13 @@ class MainSettingsWindow(): return menu def toggle_overlay(self, _a=None, _b=None): + """Toggle overlay visibility""" self.hidden_overlay = not self.hidden_overlay - self.config_set("general", "hideoverlay", "%s" % (self.hidden_overlay)) + self.config_set("general", "hideoverlay", f"{self.hidden_overlay}") self.update_toggle_overlay() def update_toggle_overlay(self, _a=None, _b=None): + """Update gui to reflect state of overlay visibility""" self.widget['core_hide_overlay'].handler_block( self.hidden_overlay_handler) @@ -840,14 +858,17 @@ class MainSettingsWindow(): self.toggle_opt.set_label(_("Hide overlay")) def close_overlay(self, _a=None, _b=None): - with open(self.rpc_file, 'w') as f: + """Send RPC to tell the overlay to close""" + with open(self.rpc_file, 'w', encoding="utf-8") as f: f.write('--rpc --close') - def overview_close(self, button): + def overview_close(self, _button): + """Gui callback to close overlay. Remove and use close_overlay?""" log.info("Quit pressed") self.close_overlay() def voice_place_window(self, button): + """Toggle the voice placement""" if self.voice_placement_window: (pos_x, pos_y, width, height) = self.voice_placement_window.get_coords() self.voice_floating_x = pos_x @@ -857,18 +878,14 @@ class MainSettingsWindow(): config = ConfigParser(interpolation=None) config.read(self.config_file) - if not "main" in config.sections(): + if "main" not in config.sections(): config.add_section("main") - config.set("main", "floating_x", "%f" % - (self.voice_floating_x)) - config.set("main", "floating_y", "%f" % - (self.voice_floating_y)) - config.set("main", "floating_w", "%f" % - (self.voice_floating_w)) - config.set("main", "floating_h", "%f" % - (self.voice_floating_h)) + config.set("main", "floating_x", f"{self.voice_floating_x:f}") + config.set("main", "floating_y", f"{self.voice_floating_y:f}") + config.set("main", "floating_w", f"{self.voice_floating_w:f}") + config.set("main", "floating_h", f"{self.voice_floating_h:f}") - with open(self.config_file, 'w') as file: + with open(self.config_file, 'w', encoding="utf-8") as file: config.write(file) if button: button.set_label(_("Place Window")) @@ -890,11 +907,13 @@ class MainSettingsWindow(): self.voice_placement_window = DraggableWindow( pos_x=self.voice_floating_x, pos_y=self.voice_floating_y, width=self.voice_floating_w, height=self.voice_floating_h, - message=_("Place & resize this window then press Save!"), settings=self, monitor=self.widget['voice_monitor'].get_active()-1) + message=_("Place & resize this window then press Save!"), + settings=self, monitor=self.widget['voice_monitor'].get_active()-1) if button: button.set_label(_("Save this position")) def text_place_window(self, button): + """Toggle the text placement""" if self.text_placement_window: (pos_x, pos_y, width, height) = self.text_placement_window.get_coords() self.text_floating_x = pos_x @@ -904,18 +923,14 @@ class MainSettingsWindow(): config = ConfigParser(interpolation=None) config.read(self.config_file) - if not "text" in config.sections(): + if "text" not in config.sections(): config.add_section("text") - config.set("text", "floating_x", "%f" % - (self.text_floating_x)) - config.set("text", "floating_y", "%f" % - (self.text_floating_y)) - config.set("text", "floating_w", "%f" % - (self.text_floating_w)) - config.set("text", "floating_h", "%f" % - (self.text_floating_h)) + config.set("text", "floating_x", f"{self.text_floating_x:f}") + config.set("text", "floating_y", f"{self.text_floating_y:f}") + config.set("text", "floating_w", f"{self.text_floating_w:f}") + config.set("text", "floating_h", f"{self.text_floating_h:f}") - with open(self.config_file, 'w') as file: + with open(self.config_file, 'w', encoding="utf-8") as file: config.write(file) if button: button.set_label(_("Place Window")) @@ -937,21 +952,25 @@ class MainSettingsWindow(): self.text_placement_window = DraggableWindow( pos_x=self.text_floating_x, pos_y=self.text_floating_y, width=self.text_floating_w, height=self.text_floating_h, - message=_("Place & resize this window then press Save!"), settings=self, monitor=self.widget['text_monitor'].get_active()-1) + message=_("Place & resize this window then press Save!"), + settings=self, monitor=self.widget['text_monitor'].get_active()-1) if button: button.set_label(_("Save this position")) def change_placement(self, placement_window): + """Finish window placement""" if placement_window == self.text_placement_window: self.text_place_window(None) elif placement_window == self.voice_placement_window: self.voice_place_window(None) - def text_server_refresh(self, button): - with open(self.rpc_file, 'w') as f: + def text_server_refresh(self, _button): + """Send RPC to overlay to request a list of text channels""" + with open(self.rpc_file, 'w', encoding="utf-8") as f: f.write('--rpc --refresh-guilds') def config_set(self, context, key, value): + """Write one key to config and save to disk""" if self.loading_config: return config = ConfigParser(interpolation=None) @@ -959,10 +978,11 @@ class MainSettingsWindow(): if not context in config.sections(): config.add_section(context) config.set(context, key, value) - with open(self.config_file, 'w') as file: + with open(self.config_file, 'w', encoding="utf-8") as file: config.write(file) def config_remove_section(self, context): + """Remove a section from config and save to disk""" if self.loading_config: return config = ConfigParser(interpolation=None) @@ -970,12 +990,12 @@ class MainSettingsWindow(): if context in config.sections(): config.remove_section(context) else: - log.error("Unable to remove section %s" % (context)) - with open(self.config_file, 'w') as file: + log.error("Unable to remove section %s", context) + with open(self.config_file, 'w', encoding="utf-8") as file: config.write(file) def voice_anchor_float_changed(self, button): - self.config_set("main", "floating", "%s" % (button.get_active() == 0)) + self.config_set("main", "floating", f"{(button.get_active() == 0)}") self.update_floating_anchor() def update_floating_anchor(self): @@ -1003,10 +1023,10 @@ class MainSettingsWindow(): self.config_set("main", "monitor", plug) def voice_align_1_changed(self, button): - self.config_set("main", "rightalign", "%s" % (button.get_active())) + self.config_set("main", "rightalign", f"{button.get_active()}") def voice_align_2_changed(self, button): - self.config_set("main", "topalign", "%s" % (button.get_active())) + self.config_set("main", "topalign", f"{button.get_active()}") def voice_font_changed(self, button): self.config_set("main", "font", button.get_font()) @@ -1015,41 +1035,39 @@ class MainSettingsWindow(): self.config_set("main", "title_font", button.get_font()) def voice_icon_spacing_changed(self, button): - self.config_set("main", "icon_spacing", "%s" % - (int(button.get_value()))) + self.config_set("main", "icon_spacing", f"{int(button.get_value())}") def voice_text_padding_changed(self, button): - self.config_set("main", "text_padding", "%s" % - (int(button.get_value()))) + self.config_set("main", "text_padding", f"{int(button.get_value())}") def voice_text_vertical_offset_changed(self, button): - self.config_set("main", "text_baseline_adj", "%s" % - (int(button.get_value()))) + self.config_set("main", "text_baseline_adj", + f"{int(button.get_value())}") def voice_vertical_padding_changed(self, button): - self.config_set("main", "vert_edge_padding", "%s" % - (int(button.get_value()))) + self.config_set("main", "vert_edge_padding", + f"{int(button.get_value())}") def voice_horizontal_padding_changed(self, button): - self.config_set("main", "horz_edge_padding", "%s" % - (int(button.get_value()))) + self.config_set("main", "horz_edge_padding", + f"{int(button.get_value())}") def voice_display_horizontally_changed(self, button): - self.config_set("main", "horizontal", "%s" % (button.get_active())) + self.config_set("main", "horizontal", f"{button.get_active()}") self.set_alignment_labels(button.get_active()) def voice_highlight_self_changed(self, button): - self.config_set("main", "highlight_self", "%s" % (button.get_active())) + self.config_set("main", "highlight_self", f"{button.get_active()}") def voice_display_speakers_only(self, button): - self.config_set("main", "only_speaking", "%s" % (button.get_active())) + self.config_set("main", "only_speaking", f"{button.get_active()}") def voice_display_speakers_grace_period(self, button): - self.config_set("main", "only_speaking_grace", "%s" % - (int(button.get_value()))) + self.config_set("main", "only_speaking_grace", + f"{int(button.get_value())}") def voice_toggle_test_content(self, button): - self.config_set("main", "show_dummy", "%s" % (button.get_active())) + self.config_set("main", "show_dummy", f"{button.get_active()}") def voice_talking_foreground_changed(self, button): colour = button.get_rgba() @@ -1097,54 +1115,48 @@ class MainSettingsWindow(): self.config_set("main", "avatar_bg_col", json.dumps(colour)) def voice_avatar_opacity_changed(self, button): - self.config_set("main", "icon_transparency", "%.2f" % - (button.get_value())) + self.config_set("main", "icon_transparency", + f"{button.get_value():.2f}") def voice_avatar_size_changed(self, button): - self.config_set("main", "avatar_size", "%s" % - (int(button.get_value()))) + self.config_set("main", "avatar_size", f"{int(button.get_value())}") def voice_nick_length_changed(self, button): - self.config_set("main", "nick_length", "%s" % - (int(button.get_value()))) + self.config_set("main", "nick_length", f"{int(button.get_value())}") def voice_display_icon_only_changed(self, button): - self.config_set("main", "icon_only", "%s" % (not button.get_active())) + self.config_set("main", "icon_only", f"{(not button.get_active())}") self.voice_show_name_hide_others(button.get_active()) def voice_square_avatar_changed(self, button): - self.config_set("main", "square_avatar", "%s" % (button.get_active())) + self.config_set("main", "square_avatar", f"{button.get_active()}") def voice_fancy_avatar_shapes_changed(self, button): - self.config_set("main", "fancy_border", "%s" % (button.get_active())) + self.config_set("main", "fancy_border", f"{button.get_active()}") def voice_order_avatars_by_changed(self, button): - self.config_set("main", "order", "%s" % (button.get_active())) + self.config_set("main", "order", f"{button.get_active()}") def voice_border_width_changed(self, button): - self.config_set("main", "border_width", "%s" % - (int(button.get_value()))) + self.config_set("main", "border_width", f"{int(button.get_value())}") def voice_overflow_style_changed(self, button): - self.config_set("main", "overflow", "%s" % (int(button.get_active()))) + self.config_set("main", "overflow", f"{int(button.get_active())}") def voice_show_title_changed(self, button): - self.config_set("main", "show_title", "%s" % (button.get_active())) + self.config_set("main", "show_title", f"{button.get_active()}") def voice_show_connection_status_changed(self, button): - self.config_set("main", "show_connection", "%s" % - (button.get_active())) + self.config_set("main", "show_connection", f"{button.get_active()}") def voice_show_disconnected_changed(self, button): - self.config_set("main", "show_disconnected", "%s" % - (button.get_active())) + self.config_set("main", "show_disconnected", f"{button.get_active()}") def voice_dummy_count_changed(self, button): - self.config_set("main", "dummy_count", "%s" % - (int(button.get_value()))) + self.config_set("main", "dummy_count", f"{int(button.get_value())}") def voice_show_avatar_changed(self, button): - self.config_set("main", "show_avatar", "%s" % (button.get_active())) + self.config_set("main", "show_avatar", f"{button.get_active()}") self.voice_show_avatar_hide_others(button.get_active()) def voice_show_name_hide_others(self, val): @@ -1176,13 +1188,13 @@ class MainSettingsWindow(): self.widget['voice_avatar_opacity'].set_sensitive(False) def text_enable_changed(self, button): - self.config_set("text", "enabled", "%s" % (button.get_active())) + self.config_set("text", "enabled", f"{button.get_active()}") def text_popup_style_changed(self, button): - self.config_set("text", "popup_style", "%s" % (button.get_active())) + self.config_set("text", "popup_style", f"{button.get_active()}") def text_popup_time_changed(self, button): - self.config_set("text", "text_time", "%s" % (int(button.get_value()))) + self.config_set("text", "text_time", f"{int(button.get_value())}") def text_server_changed(self, button): if button.get_active() < 0: @@ -1225,25 +1237,24 @@ class MainSettingsWindow(): self.config_set("text", "monitor", plug) def text_show_attachments_changed(self, button): - self.config_set("text", "show_attach", "%s" % (button.get_active())) + self.config_set("text", "show_attach", f"{button.get_active()}") def text_line_limit_changed(self, button): - self.config_set("text", "line_limit", "%s" % (int(button.get_value()))) + self.config_set("text", "line_limit", f"{int(button.get_value())}") def notification_enable_changed(self, button): - self.config_set("notification", "enabled", "%s" % - (button.get_active())) + self.config_set("notification", "enabled", f"{button.get_active()}") def notification_reverse_order_changed(self, button): - self.config_set("notification", "rev", "%s" % (button.get_active())) + self.config_set("notification", "rev", f"{button.get_active()}") def notification_popup_timer_changed(self, button): - self.config_set("notification", "text_time", "%s" % - (int(button.get_value()))) + self.config_set("notification", "text_time", + f"{int(button.get_value())}") def notification_limit_popup_width_changed(self, button): - self.config_set("notification", "limit_width", "%s" % - (int(button.get_value()))) + self.config_set("notification", "limit_width", + f"{int(button.get_value())}") def notification_font_changed(self, button): self.config_set("notification", "font", button.get_font()) @@ -1267,40 +1278,36 @@ class MainSettingsWindow(): self.config_set("notification", "monitor", plug) def notification_align_1_changed(self, button): - self.config_set("notification", "rightalign", "%s" % - (button.get_active())) + self.config_set("notification", "rightalign", f"{button.get_active()}") def notification_align_2_changed(self, button): - self.config_set("notification", "topalign", "%s" % - (button.get_active())) + self.config_set("notification", "topalign", f"{button.get_active()}") def notification_show_icon(self, button): - self.config_set("notification", "show_icon", "%s" % - (button.get_active())) + self.config_set("notification", "show_icon", f"{button.get_active()}") def notification_icon_position_changed(self, button): - self.config_set("notification", "icon_left", "%s" % - (int(button.get_active() != 1))) + self.config_set("notification", "icon_left", f"{ + int(button.get_active() != 1)}") def notification_icon_padding_changed(self, button): - self.config_set("notification", "icon_padding", "%s" % - (int(button.get_value()))) + self.config_set("notification", "icon_padding", + f"{int(button.get_value())}") def notification_icon_size_changed(self, button): - self.config_set("notification", "icon_size", "%s" % - (int(button.get_value()))) + self.config_set("notification", "icon_size", + f"{int(button.get_value())}") def notification_padding_between_changed(self, button): - self.config_set("notification", "padding", "%s" % - (int(button.get_value()))) + self.config_set("notification", "padding", + f"{int(button.get_value())}") def notification_border_radius_changed(self, button): - self.config_set("notification", "border_radius", "%s" % - (int(button.get_value()))) + self.config_set("notification", "border_radius", + f"{int(button.get_value())}") def notification_show_test_content_changed(self, button): - self.config_set("notification", "show_dummy", "%s" % - (button.get_active())) + self.config_set("notification", "show_dummy", f"{button.get_active()}") def core_run_on_startup_changed(self, button): self.autostart_helper.set_autostart(button.get_active()) @@ -1309,65 +1316,60 @@ class MainSettingsWindow(): self.autostart_helper_conf.set_autostart(button.get_active()) def core_force_xshape_changed(self, button): - self.config_set("general", "xshape", "%s" % (button.get_active())) + self.config_set("general", "xshape", f"{button.get_active()}") def core_show_tray_icon_changed(self, button): self.set_sys_tray_icon_visible(button.get_active()) - self.config_set("general", "showsystray", "%s" % (button.get_active())) + self.config_set("general", "showsystray", f"{button.get_active()}") self.widget['core_settings_min'].set_sensitive(button.get_active()) - def core_hide_overlay_changed(self, button): + def core_hide_overlay_changed(self, _button): self.toggle_overlay() def core_settings_min_changed(self, button): - self.config_set("general", "start_min", "%s" % (button.get_active())) + self.config_set("general", "start_min", f"{button.get_active()}") - def core_reset_all(self, button): + def core_reset_all(self, _button): self.config_remove_section("general") self.read_config() - def voice_reset_all(self, button): + def voice_reset_all(self, _button): self.config_remove_section("main") self.read_config() - def text_reset_all(self, button): + def text_reset_all(self, _button): self.config_remove_section("text") self.read_config() - def notification_reset_all(self, button): + def notification_reset_all(self, _button): self.config_remove_section("notification") self.read_config() def voice_hide_mouseover_changed(self, button): - self.config_set("main", "autohide", "%s" % (button.get_active())) + self.config_set("main", "autohide", f"{button.get_active()}") def text_hide_mouseover_changed(self, button): - self.config_set("text", "autohide", "%s" % (button.get_active())) + self.config_set("text", "autohide", f"{button.get_active()}") def voice_mouseover_timeout_changed(self, button): - self.config_set("main", "autohide_timer", "%s" % - (int(button.get_value()))) + self.config_set("main", "autohide_timer", f"{int(button.get_value())}") def text_mouseover_timeout_changed(self, button): - self.config_set("text", "autohide_timer", "%s" % - (int(button.get_value()))) + self.config_set("text", "autohide_timer", f"{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", f"{button.get_active()}") def inactive_fade_opacity_changed(self, button): - self.config_set("main", "fade_out_limit", "%.2f" % - (button.get_value())) + self.config_set("main", "fade_out_limit", + f"{button.get_value():.2f}") def inactive_time_changed(self, button): - self.config_set("main", "inactive_time", "%s" % - (int(button.get_value()))) + self.config_set("main", "inactive_time", f"{int(button.get_value())}") def inactive_fade_time_changed(self, button): - self.config_set("main", "inactive_fade_time", "%s" % - (int(button.get_value()))) + self.config_set("main", "inactive_fade_time", + f"{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", f"{button.get_active()}") diff --git a/discover_overlay/text_overlay.py b/discover_overlay/text_overlay.py index db86682..9638d57 100644 --- a/discover_overlay/text_overlay.py +++ b/discover_overlay/text_overlay.py @@ -21,7 +21,7 @@ from .overlay import OverlayWindow gi.require_version("Gtk", "3.0") gi.require_version('PangoCairo', '1.0') # pylint: disable=wrong-import-position,wrong-import-order -from gi.repository import Pango, PangoCairo, GLib # nopep8 +from gi.repository import Pango, PangoCairo # nopep8 log = logging.getLogger(__name__) @@ -56,10 +56,12 @@ class TextOverlayWindow(OverlayWindow): self.redraw() def set_blank(self): + """ Set contents blank and redraw """ self.content = [] self.set_needs_redraw() def tick(self): + """ Check for old images """ if len(self.attachment) > self.line_limit: # We've probably got old images! oldlist = self.attachment @@ -72,57 +74,43 @@ class TextOverlayWindow(OverlayWindow): self.attachment[url] = oldlist[url] def set_text_time(self, timer): - """ - Set the duration that a message will be visible for. - """ + """Config option: Time before messages disappear from overlay""" 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): - """ - Update the list of text messages to show - """ + """Change contents of overlay""" self.content = tlist[-self.line_limit:] if altered: self.set_needs_redraw() def set_fg(self, fg_col): - """ - Set default text colour - """ + """Config option: Sets the text colour""" if self.fg_col != fg_col: self.fg_col = fg_col self.set_needs_redraw() def set_bg(self, bg_col): - """ - Set background colour - """ + """Config option: Set the background colour""" 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 - """ + """Config option: Show image attachments""" 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 - """ + """Config option: Messages should disappear after being shown for some time""" if self.popup_style != boolean: self.popup_style = boolean def set_font(self, font): - """ - Set font used to render text - """ + """Config option: Set font used for rendering""" if self.text_font != font: self.text_font = font @@ -133,28 +121,24 @@ class TextOverlayWindow(OverlayWindow): self.set_needs_redraw() def set_line_limit(self, limit): - """ - Change maximum number of lines in overlay - """ + """Config option: Limit number of lines rendered""" if self.line_limit != limit: self.line_limit = limit def make_line(self, message): - """ - Decode a recursive JSON object into pango markup. - """ + """Decode a recursive JSON object into pango markup.""" ret = "" if isinstance(message, list): for inner_message in message: - ret = "%s%s" % (ret, self.make_line(inner_message)) + ret = f"{ret}{self.make_line(inner_message)}" elif isinstance(message, str): ret = self.sanitize_string(message) elif message['type'] == 'strong': - ret = "%s" % (self.make_line(message['content'])) + ret = f"{self.make_line(message['content'])}" elif message['type'] == 'text': ret = self.sanitize_string(message['content']) elif message['type'] == 'link': - ret = "%s" % (self.make_line(message['content'])) + ret = f"{self.make_line(message['content'])}" elif message['type'] == 'emoji': if 'surrogate' in message: # ['src'] is SVG URL @@ -163,20 +147,21 @@ class TextOverlayWindow(OverlayWindow): else: ### Add Image ### self.image_list.append( - f"https://cdn.discordapp.com/emojis/{message['emojiId']}.png?v=1" + f"https://cdn.discordapp.com/emojis/{ + message['emojiId']}.png?v=1" ) ret = "`" elif (message['type'] == 'inlineCode' or message['type'] == 'codeBlock' or message['type'] == 'blockQuote'): - ret = "%s" % ( - self.make_line(message['content'])) + ret = f"{ + self.make_line(message['content'])}" elif message['type'] == 'u': - ret = "%s" % (self.make_line(message['content'])) + ret = f"{self.make_line(message['content'])}" elif message['type'] == 'em': - ret = "%s" % (self.make_line(message['content'])) + ret = f"{self.make_line(message['content'])}" elif message['type'] == 's': - ret = "%s" % (self.make_line(message['content'])) + ret = f"{self.make_line(message['content'])}" elif message['type'] == 'channel': ret = self.make_line(message['content']) elif message['type'] == 'mention': @@ -189,14 +174,13 @@ class TextOverlayWindow(OverlayWindow): self.warned_filetypes.append(message['type']) return ret - def recv_attach(self, identifier, pix, mask): - """ - Called when an image has been downloaded by image_getter - """ + def recv_attach(self, identifier, pix, _mask): + """Callback from image_getter""" self.attachment[identifier] = pix self.set_needs_redraw() def has_content(self): + """Returns true if overlay has meaningful content to render""" if self.piggyback and self.piggyback.has_content(): return True if not self.enabled: @@ -206,15 +190,12 @@ class TextOverlayWindow(OverlayWindow): return self.content def overlay_draw(self, w, context, data=None): - """ - Draw the overlay - """ + """Draw the overlay""" if self.piggyback: self.piggyback.overlay_draw(w, context, data) if not self.enabled: return self.context = context - (width, height) = self.get_size() if not self.piggyback_parent: context.set_antialias(cairo.ANTIALIAS_GOOD) context.set_source_rgba(0.0, 0.0, 0.0, 0.0) @@ -234,7 +215,6 @@ class TextOverlayWindow(OverlayWindow): context.translate(floating_x, floating_y) context.rectangle(0, 0, floating_width, floating_height) context.clip() - pass (floating_x, floating_y, floating_width, floating_height) = self.get_floating_coords() current_y = floating_height @@ -249,7 +229,7 @@ class TextOverlayWindow(OverlayWindow): if 'nick_col' in line and line['nick_col']: col = line['nick_col'] for in_line in line['content']: - out_line = "%s%s" % (out_line, self.make_line(in_line)) + out_line = f"{out_line}{self.make_line(in_line)}" if line['attach'] and self.show_attach: attachment = line['attach'][0] url = attachment['url'] @@ -267,10 +247,8 @@ class TextOverlayWindow(OverlayWindow): else: log.warning("Unknown file extension '%s'", extension) # cy = self.draw_text(cy, "%s" % (line['attach'])) - message = "%s: %s" % (self.sanitize_string(col), - self.sanitize_string( - line["nick"]), - out_line) + message = f"{self.sanitize_string( + line["nick"])}: {out_line}" current_y = self.draw_text(current_y, message) if current_y <= 0: # We've done enough @@ -279,10 +257,8 @@ class TextOverlayWindow(OverlayWindow): self.context = None def draw_attach(self, pos_y, url): - """ - Draw an attachment - """ - (floating_x, floating_y, floating_width, + """Draw an attachment""" + (_floating_x, _floating_y, floating_width, floating_height) = self.get_floating_coords() if url in self.attachment and self.attachment[url]: pix = self.attachment[url] @@ -302,16 +278,14 @@ class TextOverlayWindow(OverlayWindow): return pos_y def draw_text(self, pos_y, text): - """ - Draw a text message, returning the Y position of the next message - """ + """Draw a text message, returning the Y position of the next message""" layout = self.create_pango_layout(text) layout.set_auto_dir(True) layout.set_markup(text, -1) attr = layout.get_attributes() - (floating_x, floating_y, floating_width, - floating_height) = self.get_floating_coords() + (_floating_x, _floating_y, floating_width, + _floating_height) = self.get_floating_coords() layout.set_width(Pango.SCALE * floating_width) layout.set_spacing(Pango.SCALE * 3) if self.text_font: @@ -351,11 +325,9 @@ class TextOverlayWindow(OverlayWindow): return pos_y - text_height def render_custom(self, ctx, shape, path, _data): - """ - Draw an inline image as a custom emoticon - """ + """Draw an inline image as a custom emoticon""" if shape.data >= len(self.image_list): - log.warning(f"{shape.data} >= {len(self.image_list)}") + log.warning("%s >= %s", shape.data, len(self.image_list)) return # key is the url to the image key = self.image_list[shape.data] @@ -371,9 +343,7 @@ class TextOverlayWindow(OverlayWindow): return True def sanitize_string(self, string): - """ - Sanitize a text message so that it doesn't intefere with Pango's XML format - """ + """Sanitize a text message so that it doesn't intefere with Pango's XML format""" string = string.replace("&", "&") string = string.replace("<", "<") string = string .replace(">", ">") diff --git a/discover_overlay/voice_overlay.py b/discover_overlay/voice_overlay.py index 218c61d..75745b7 100644 --- a/discover_overlay/voice_overlay.py +++ b/discover_overlay/voice_overlay.py @@ -15,16 +15,15 @@ import random import gettext import logging import math -import cairo import sys import locale -import pkg_resources from time import perf_counter +import cairo +import pkg_resources from .overlay import OverlayWindow from .image_getter import get_surface, draw_img_to_rect, draw_img_to_mask # pylint: disable=wrong-import-order import gi -gi.require_version("Gtk", "3.0") gi.require_version('PangoCairo', '1.0') # pylint: disable=wrong-import-position,wrong-import-order from gi.repository import Pango, PangoCairo, GLib # nopep8 @@ -55,12 +54,11 @@ class VoiceOverlayWindow(OverlayWindow): scream = '' if random.randint(0, 20) == 2: scream = random.randint(8, 15)*'a' - name = "Player %d %s" % (i, scream) + name = f"Player {i} {scream}" self.dummy_data.append({ "id": i, "username": name, "avatar": None, - "mute": False, "deaf": mostly_false[random.randint(0, 7)], "mute": mostly_false[random.randint(0, 7)], "speaking": speaking, @@ -82,6 +80,7 @@ class VoiceOverlayWindow(OverlayWindow): self.highlight_self = None self.order = None self.def_avatar = None + self.def_avatar_mask = None self.channel_icon = None self.channel_mask = None self.channel_icon_url = None @@ -95,6 +94,7 @@ class VoiceOverlayWindow(OverlayWindow): self.border_width = 2 self.icon_transparency = 0.0 self.fancy_border = False + self.only_speaking_grace_period = 0 self.fade_out_inactive = True self.fade_out_limit = 0.1 @@ -130,7 +130,7 @@ class VoiceOverlayWindow(OverlayWindow): self.redraw() def reset_action_timer(self): - # Reset time since last voice activity + """Reset time since last voice activity""" self.fade_opacity = 1.0 # Remove both fading-out effect and timer set last time this happened @@ -141,13 +141,13 @@ 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 we're using this feature, schedule a new inactivity timer if self.fade_out_inactive: self.inactive_timeout = GLib.timeout_add_seconds( self.inactive_time, self.overlay_inactive) def overlay_inactive(self): - # Inactivity has hit the first threshold, start fading out + """Timed callback when inactivity limit is hit""" self.fade_start = perf_counter() # Fade out in 200 steps over X seconds. self.fadeout_timeout = GLib.timeout_add( @@ -156,8 +156,10 @@ class VoiceOverlayWindow(OverlayWindow): return False def overlay_fadeout(self): + """Repeated callback after inactivity started""" 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 + # 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 @@ -171,21 +173,22 @@ class VoiceOverlayWindow(OverlayWindow): return True def col(self, col, alpha=1.0): - """ - Convenience function to set the cairo context next colour. Altered to account for fade-out function - """ - if alpha == None: + """Convenience function to set the cairo context next colour. + Altered to account for fade-out function""" + if alpha is None: self.context.set_source_rgba(col[0], col[1], col[2], col[3]) else: self.context.set_source_rgba( col[0], col[1], col[2], col[3] * alpha * self.fade_opacity) def set_icon_transparency(self, trans): + """Config option: icon transparency""" if self.icon_transparency != trans: self.icon_transparency = trans self.set_needs_redraw() def set_blank(self): + """Set data to blank and redraw""" self.userlist = [] self.channel_icon = None self.channel_icon_url = None @@ -194,7 +197,9 @@ 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 or self.inactive_time != fade_time or self.inactive_fade_time != fade_duration or self.fade_out_limit != fade_to: + """Config option: fade out options""" + 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 @@ -202,288 +207,235 @@ class VoiceOverlayWindow(OverlayWindow): self.reset_action_timer() def set_title_font(self, font): + """Config option: font used to render title""" if self.title_font != font: self.title_font = font self.set_needs_redraw() def set_show_connection(self, show_connection): + """Config option: show connection status alongside users""" if self.show_connection != show_connection: self.show_connection = show_connection self.set_needs_redraw() def set_show_avatar(self, show_avatar): + """Config option: show avatar icons""" if self.show_avatar != show_avatar: self.show_avatar = show_avatar self.set_needs_redraw() def set_show_title(self, show_title): + """Config option: show channel title alongside users""" if self.show_title != show_title: self.show_title = show_title self.set_needs_redraw() def set_show_disconnected(self, show_disconnected): + """Config option: show even when disconnected from voice chat""" 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 - """ + """Config option: Show placeholder information""" if self.use_dummy != show_dummy: self.use_dummy = show_dummy self.set_needs_redraw() def set_dummy_count(self, dummy_count): + """Config option: Change the count of placeholders""" 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? - """ + def set_overflow_style(self, overflow): + """Config option: Change handling of too many users to render""" if self.overflow != overflow: self.overflow = overflow self.set_needs_redraw() def set_bg(self, background_colour): - """ - Set the background colour - """ + """Config option: Set background colour. Used to draw the transparent window. + Should not be changed as then the entire screen is obscured""" 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 - """ + """Config option: Set foreground colour. Used to render text""" 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 - """ + """Config option: Set talking border colour. + Used to render border around users who are talking""" 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 - """ + """Config option: Set mute colour. Used to render mute and deaf images""" 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 - """ + """Config option: Set mute background colour. + Used to tint the user avatar before rendering the mute or deaf image above it""" 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 - """ + """Config option: Set avatar background colour. + Drawn before user avatar but only visible if default fallback avatar can't be found""" 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 - """ + """Config option: Set talking background colour. + Used to render the background behind users name.""" 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 - """ + """Config option: Set talking text colour. + Used to render the usernames of users who are talking""" 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 - """ + """Config option: Set border colour. Used to render border around users""" 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 - """ + """Config option: Set avatar size in window-space pixels""" if self.avatar_size != size: self.avatar_size = size self.set_needs_redraw() def set_nick_length(self, size): - """ - Set the length of nickname - """ + """Config option: Limit username length""" 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 - """ + """Config option: Space between users in the list, in window-space pixels""" 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 - """ + """Config option: Space between user avatar and username, in window-space pixels""" 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 - """ + """Config option: Vertical offset used to render all text, in window-space pixels""" 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 - """ + """Config option: Vertical offset from edge of window, in window-space pixels""" 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 - """ + """Config option: Horizontal offset from edge of window, in window-space pixels""" 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 - """ + """Config option: Mask avatar with a circle before rendering""" 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 - """ + """Config option: Use transparent edges of image as border, + instead of mask (square/circle)""" 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 - """ + """Config option: Filter user list to only those who + are talking and those who have stopped talking recently""" if self.only_speaking != only_speaking: self.only_speaking = only_speaking self.set_needs_redraw() def set_only_speaking_grace_period(self, grace_period): - """ - Set grace period before hiding people who are not talking - """ + """Config option: How long after stopping speaking the user remains shown""" self.only_speaking_grace_period = grace_period self.timer_after_draw = grace_period def set_highlight_self(self, highlight_self): - """ - Set if the overlay should highlight the user - """ + """Config option: Local User should be kept at top of list""" 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 - """ + """Config option: Set method used to order user list""" 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 - """ + """Config option: Show only the avatar, without text or its background""" if self.icon_only != i: self.icon_only = i self.set_needs_redraw() - def set_border_width(self, width): + def set_drawn_border_width(self, width): + """Config option: Set width of border around username and avatar""" if self.border_width != width: self.border_width = width self.set_needs_redraw() def set_horizontal(self, horizontal=False): + """Config option: Userlist should be drawn horizontally""" if self.horizontal != horizontal: self.horizontal = horizontal self.set_needs_redraw() - def set_guild_ids(self, guild_ids=tuple()): - if self.discover.connection: - for _id in guild_ids: - if _id not in self.guild_ids: - self.discover.connection.req_channels(_id) - self.guild_ids = guild_ids - def set_wind_col(self): - """ - Use window colour to draw - """ + """Use window colour to draw""" self.col(self.wind_col, None) def set_norm_col(self): - """ - Use background colour to draw - """ + """Use background colour to draw""" self.col(self.norm_col) def set_talk_col(self, alpha=1.0): - """ - Use talking colour to draw - """ + """Use talking colour to draw""" self.col(self.talk_col, alpha) def set_mute_col(self): - """ - Use mute colour to draw - """ + """Use mute colour to draw""" self.col(self.mute_col) def set_channel_title(self, channel_title): - """ - Set title above voice list - """ + """Set title above voice list""" if self.channel_title != channel_title: self.channel_title = channel_title self.set_needs_redraw() def set_channel_icon(self, url): - """ - Change the icon for channel - """ + """Change the icon for channel""" if not url: self.channel_icon = None self.channel_icon_url = None @@ -493,9 +445,7 @@ class VoiceOverlayWindow(OverlayWindow): self.channel_icon_url = url def set_user_list(self, userlist, alt): - """ - Set the users in list to draw - """ + """Set the users in list to draw""" self.userlist = userlist for user in userlist: if "nick" in user: @@ -508,14 +458,13 @@ class VoiceOverlayWindow(OverlayWindow): self.set_needs_redraw() def set_connection_status(self, connection): - """ - Set if discord has a clean connection to server - """ + """Set if discord has a clean connection to server""" if self.connection_status != connection['state']: self.connection_status = connection['state'] self.set_needs_redraw() def sort_list(self, in_list): + """Take a userlist and sort it according to config option""" if self.order == 1: # ID Sort in_list.sort(key=lambda x: x["id"]) elif self.order == 2: # Spoken sort @@ -526,6 +475,7 @@ class VoiceOverlayWindow(OverlayWindow): return in_list def has_content(self): + """Returns true if overlay has meaningful content to render""" if not self.enabled: return False if self.hidden: @@ -535,9 +485,7 @@ class VoiceOverlayWindow(OverlayWindow): return self.userlist def overlay_draw(self, w, context, data=None): - """ - Draw the Overlay - """ + """Draw the Overlay""" self.context = context context.set_antialias(cairo.ANTIALIAS_GOOD) # Get size of window @@ -566,7 +514,8 @@ class VoiceOverlayWindow(OverlayWindow): context.clip() context.set_operator(cairo.OPERATOR_OVER) - if not self.show_disconnected and self.connection_status == "DISCONNECTED" and not self.use_dummy: + if (not self.show_disconnected and self.connection_status == "DISCONNECTED" + and not self.use_dummy): return connection = self.discover.connection @@ -624,14 +573,14 @@ class VoiceOverlayWindow(OverlayWindow): avatars_per_row = sys.maxsize # Calculate height needed to show overlay - doTitle = False - doConnection = False + do_title = False + do_connection = False if self.show_connection: users_to_draw.insert(0, None) - doConnection = True + do_connection = True if self.show_title and self.channel_title: users_to_draw.insert(0, None) - doTitle = True + do_title = True if self.horizontal: needed_width = (len(users_to_draw) * line_height) + \ @@ -655,7 +604,7 @@ class VoiceOverlayWindow(OverlayWindow): rows_to_draw = [] while len(users_to_draw) > 0: row = [] - for i in range(0, min(avatars_per_row, len(users_to_draw))): + for _i in range(0, min(avatars_per_row, len(users_to_draw))): row.append(users_to_draw.pop(0)) rows_to_draw.append(row) for row in rows_to_draw: @@ -668,14 +617,14 @@ class VoiceOverlayWindow(OverlayWindow): for user in row: if not user: - if doTitle: - doTitle = False + if do_title: + do_title = False text_width = self.draw_title( context, current_x, current_y, avatar_size, line_height) - elif doConnection: + elif do_connection: text_width = self.draw_connection( context, current_x, current_y, avatar_size, line_height) - doConnection = False + do_connection = False else: self.draw_avatar(context, user, current_x, current_y, avatar_size, line_height) @@ -712,7 +661,7 @@ class VoiceOverlayWindow(OverlayWindow): cols_to_draw = [] while len(users_to_draw) > 0: col = [] - for i in range(0, min(avatars_per_row, len(users_to_draw))): + for _i in range(0, min(avatars_per_row, len(users_to_draw))): col.append(users_to_draw.pop(0)) cols_to_draw.append(col) for col in cols_to_draw: @@ -725,22 +674,22 @@ class VoiceOverlayWindow(OverlayWindow): largest_text_width = 0 for user in col: if not user: - if doTitle: + if do_title: # Draw header text_width = self.draw_title( context, current_x, current_y, avatar_size, line_height) largest_text_width = max( text_width, largest_text_width) current_y += line_height + self.icon_spacing - doTitle = False - elif doConnection: + do_title = False + elif do_connection: # Draw header text_width = self.draw_connection( context, current_x, current_y, avatar_size, line_height) largest_text_width = max( text_width, largest_text_width) current_y += line_height + self.icon_spacing - doConnection = False + do_connection = False else: text_width = self.draw_avatar( @@ -758,9 +707,7 @@ class VoiceOverlayWindow(OverlayWindow): self.context = None def recv_avatar(self, identifier, pix, mask): - """ - Called when image_getter has downloaded an image - """ + """Called when image_getter has downloaded an image""" if identifier == 'def': self.def_avatar = pix self.def_avatar_mask = mask @@ -773,16 +720,12 @@ class VoiceOverlayWindow(OverlayWindow): self.set_needs_redraw() def delete_avatar(self, identifier): - """ - Remove avatar image - """ + """Remove avatar image""" if identifier in self.avatars: del self.avatars[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 - """ + """Draw title at given Y position. Includes both text and image based on settings""" tw = 0 if not self.horizontal and not self.icon_only: title = self.channel_title @@ -826,9 +769,7 @@ class VoiceOverlayWindow(OverlayWindow): _("VOICE_CONNECTED") 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 - """ + """Draw title at given Y position. Includes both text and image based on settings""" tw = 0 if not self.horizontal and not self.icon_only: tw = self.draw_text( @@ -846,13 +787,11 @@ class VoiceOverlayWindow(OverlayWindow): 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 - """ + """Draw avatar at given Y position. Includes both text and image based on settings""" # Ensure pixbuf for avatar if user["id"] not in self.avatars and user["avatar"] and avatar_size > 0: - url = "https://cdn.discordapp.com/avatars/%s/%s.png" % ( - user['id'], user['avatar']) + url = f"https://cdn.discordapp.com/avatars/{ + user['id']}/{user['avatar']}.png" get_surface(self.recv_avatar, url, user["id"], self.avatar_size) @@ -907,19 +846,18 @@ class VoiceOverlayWindow(OverlayWindow): self.mute_bg_col, avatar_size) return tw - def draw_text(self, context, string, pos_x, pos_y, tx_col, bg_col, avatar_size, line_height, font): - """ - Draw username & background at given position - """ + def draw_text(self, context, string, pos_x, pos_y, + tx_col, bg_col, avatar_size, line_height, font): + """Draw username & background at given position""" if self.nick_length < 32 and len(string) > self.nick_length: - string = string[:(self.nick_length-1)] + u"\u2026" + string = string[:(self.nick_length-1)] + "\u2026" context.save() layout = self.create_pango_layout(string) layout.set_auto_dir(True) layout.set_markup(string, -1) - (floating_x, floating_y, floating_width, - floating_height) = self.get_floating_coords() + (_floating_x, _floating_y, floating_width, + _floating_height) = self.get_floating_coords() layout.set_width(Pango.SCALE * floating_width) layout.set_spacing(Pango.SCALE * 3) if font: @@ -971,6 +909,7 @@ class VoiceOverlayWindow(OverlayWindow): return text_width 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 + @@ -983,9 +922,7 @@ class VoiceOverlayWindow(OverlayWindow): context.restore() def draw_avatar_pix(self, context, pixbuf, mask, pos_x, pos_y, border_colour, avatar_size): - """ - Draw avatar image at given position - """ + """Draw avatar image at given position""" if not self.show_avatar: return # Empty the space for this @@ -1021,13 +958,16 @@ class VoiceOverlayWindow(OverlayWindow): 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) + (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.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() @@ -1053,13 +993,12 @@ 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): - """ - Draw Mute logo - """ + """Draw Mute logo""" if avatar_size <= 0: return context.save() @@ -1126,9 +1065,7 @@ class VoiceOverlayWindow(OverlayWindow): context.restore() def draw_deaf(self, context, pos_x, pos_y, bg_col, avatar_size): - """ - Draw deaf logo - """ + """Draw deaf logo""" if avatar_size <= 0: return context.save() @@ -1188,6 +1125,7 @@ class VoiceOverlayWindow(OverlayWindow): context.restore() def draw_connection_icon(self, context, pos_x, pos_y, avatar_size): + """Draw a series of bars to show connectivity state""" context.save() context.translate(pos_x, pos_y) context.scale(avatar_size, avatar_size) From 8e459fb6a69007d1b254581a253355ab73798a8e Mon Sep 17 00:00:00 2001 From: trigg Date: Tue, 9 Jul 2024 01:13:59 +0100 Subject: [PATCH 2/6] - Fix warnings on 'any' monitor choice --- discover_overlay/settings_window.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/discover_overlay/settings_window.py b/discover_overlay/settings_window.py index 14556e0..cd59c6c 100644 --- a/discover_overlay/settings_window.py +++ b/discover_overlay/settings_window.py @@ -1231,7 +1231,9 @@ class MainSettingsWindow(): def text_monitor_changed(self, button): screen = self.window.get_screen() plug = "Any" - monitor = screen.get_monitor_plug_name(button.get_active()-1) + monitor = None + if(button.get_active()>0): + monitor = screen.get_monitor_plug_name(button.get_active()-1) if monitor: plug = monitor self.config_set("text", "monitor", plug) @@ -1272,7 +1274,9 @@ class MainSettingsWindow(): def notification_monitor_changed(self, button): screen = self.window.get_screen() plug = "Any" - monitor = screen.get_monitor_plug_name(button.get_active()-1) + monitor = None + if(button.get_active()>0): + monitor = screen.get_monitor_plug_name(button.get_active()-1) if monitor: plug = monitor self.config_set("notification", "monitor", plug) From bda6f7e3e77308a63288246e32773317974fdf52 Mon Sep 17 00:00:00 2001 From: trigg Date: Tue, 9 Jul 2024 01:17:14 +0100 Subject: [PATCH 3/6] - more lint --- discover_overlay/settings_window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discover_overlay/settings_window.py b/discover_overlay/settings_window.py index cd59c6c..3b76030 100644 --- a/discover_overlay/settings_window.py +++ b/discover_overlay/settings_window.py @@ -1232,7 +1232,7 @@ class MainSettingsWindow(): screen = self.window.get_screen() plug = "Any" monitor = None - if(button.get_active()>0): + if button.get_active()>0: monitor = screen.get_monitor_plug_name(button.get_active()-1) if monitor: plug = monitor @@ -1275,7 +1275,7 @@ class MainSettingsWindow(): screen = self.window.get_screen() plug = "Any" monitor = None - if(button.get_active()>0): + if button.get_active()>0: monitor = screen.get_monitor_plug_name(button.get_active()-1) if monitor: plug = monitor From 24e815b85eebab97b417b92111675bf1b2a82a72 Mon Sep 17 00:00:00 2001 From: trigg Date: Tue, 9 Jul 2024 02:04:53 +0100 Subject: [PATCH 4/6] - switch exit to sigterm to sidestep threaded exit --- discover_overlay/discover_overlay.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discover_overlay/discover_overlay.py b/discover_overlay/discover_overlay.py index 9fe7e51..421f1fd 100755 --- a/discover_overlay/discover_overlay.py +++ b/discover_overlay/discover_overlay.py @@ -81,8 +81,8 @@ class Discover: self.connection.connect() self.audio_assist = DiscoverAudioAssist(self) - rpc_file = Gio.File.new_for_path(rpc_file) - monitor = rpc_file.monitor_file(0, None) + rpc_file_gio = Gio.File.new_for_path(rpc_file) + monitor = rpc_file_gio.monitor_file(0, None) monitor.connect("changed", self.rpc_changed) config_file = Gio.File.new_for_path(config_file) @@ -125,7 +125,7 @@ class Discover: if normal_close: sys.exit(0) if "--close" in data or "-x" in data: - sys.exit(0) + os.kill(os.getpid(), signal.SIGTERM) if "--steamos" in data or "-s" in data: self.steamos = True if "--hide" in data: From debad09705968d090fd9df65b9359903e56f7501 Mon Sep 17 00:00:00 2001 From: trigg Date: Tue, 9 Jul 2024 02:15:10 +0100 Subject: [PATCH 5/6] - Switch to signal-exit for most exits --- discover_overlay/discord_connector.py | 5 ++--- discover_overlay/discover_overlay.py | 5 ++++- discover_overlay/overlay.py | 7 +++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/discover_overlay/discord_connector.py b/discover_overlay/discord_connector.py index b398a1e..3d59df3 100644 --- a/discover_overlay/discord_connector.py +++ b/discover_overlay/discord_connector.py @@ -23,7 +23,6 @@ CHANNEL - Often called 'Rooms'. Both voice and text channels are types of channe import select import time import json -import sys import logging import calendar import websocket @@ -110,7 +109,7 @@ class DiscordConnector: log.error("No access token in json response") log.error(response.text) log.error("The user most likely denied permission for this app") - sys.exit(1) + self.discover.exit() def set_channel(self, channel, guild, need_req=True): """ @@ -280,7 +279,7 @@ class DiscordConnector: self.get_access_token_stage2(j["data"]["code"]) else: log.error("Authorization rejected") - sys.exit(0) + self.discover.exit() return elif j["cmd"] == "DISPATCH": if j["evt"] == "READY": diff --git a/discover_overlay/discover_overlay.py b/discover_overlay/discover_overlay.py index 421f1fd..60d88d0 100755 --- a/discover_overlay/discover_overlay.py +++ b/discover_overlay/discover_overlay.py @@ -125,7 +125,7 @@ class Discover: if normal_close: sys.exit(0) if "--close" in data or "-x" in data: - os.kill(os.getpid(), signal.SIGTERM) + self.exit() if "--steamos" in data or "-s" in data: self.steamos = True if "--hide" in data: @@ -156,6 +156,9 @@ class Discover: if self.connection: self.connection.request_text_rooms_for_guild(match.group(1)) + def exit(self): + os.kill(os.getpid(), signal.SIGTERM) + def config_set(self, context, key, value): """Set a config value and save to disk""" config = self.config() diff --git a/discover_overlay/overlay.py b/discover_overlay/overlay.py index bbb20e7..2a71f15 100644 --- a/discover_overlay/overlay.py +++ b/discover_overlay/overlay.py @@ -15,7 +15,6 @@ Overlay parent class. Helpful if we need more overlay types without copy-and-pasting too much code """ import os -import sys import logging import gi import cairo @@ -75,7 +74,7 @@ class OverlayWindow(Gtk.Window): if not self.get_display().supports_input_shapes(): log.info( "Input shapes not available. Quitting") - sys.exit(1) + self.discover.exit() if visual: # Set the visual even if we can't use it right now self.set_visual(visual) @@ -125,7 +124,7 @@ class OverlayWindow(Gtk.Window): def window_exited(self, _window=None): """Window closed. Exit app""" - sys.exit(1) + self.discover.exit() def set_gamescope_xatom(self, enabled): """Set Gamescope XAtom to identify self as an overlay candidate""" @@ -160,7 +159,7 @@ class OverlayWindow(Gtk.Window): log.info( "GTK Layer Shell is not supported on this wayland compositor") log.info("Currently not possible: Gnome, Weston") - sys.exit(0) + self.discover.exit() if not GtkLayerShell.is_layer_window(self): GtkLayerShell.init_for_window(self) GtkLayerShell.set_layer(self, GtkLayerShell.Layer.OVERLAY) From 1f5c76aed21bbfe736518338bfdf7d0debd69739 Mon Sep 17 00:00:00 2001 From: trigg Date: Sat, 27 Jul 2024 14:16:19 +0100 Subject: [PATCH 6/6] - Rejig last-spoken logic as we had two split implementations - User joining shows in 'last spoken' mode --- discover_overlay/discord_connector.py | 3 ++- discover_overlay/voice_overlay.py | 14 ++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/discover_overlay/discord_connector.py b/discover_overlay/discord_connector.py index 3d59df3..89418d2 100644 --- a/discover_overlay/discord_connector.py +++ b/discover_overlay/discord_connector.py @@ -307,6 +307,7 @@ class DiscordConnector: # We've joined a room... but where? if j["data"]["user"]["id"] == self.user["id"]: self.find_user() + self.userlist[thisuser["id"]]["lastspoken"] = time.perf_counter() elif j["evt"] == "VOICE_STATE_DELETE": self.list_altered = True self.set_in_room(j["data"]["user"]["id"], False) @@ -319,7 +320,7 @@ class DiscordConnector: elif j["evt"] == "SPEAKING_START": self.list_altered = True self.userlist[j["data"]["user_id"]]["speaking"] = True - self.userlist[j["data"]["user_id"]]["lastspoken"] = time.time() + self.userlist[j["data"]["user_id"]]["lastspoken"] = time.perf_counter() self.set_in_room(j["data"]["user_id"], True) elif j["evt"] == "SPEAKING_STOP": self.list_altered = True diff --git a/discover_overlay/voice_overlay.py b/discover_overlay/voice_overlay.py index 75745b7..23fe463 100644 --- a/discover_overlay/voice_overlay.py +++ b/discover_overlay/voice_overlay.py @@ -44,9 +44,6 @@ class VoiceOverlayWindow(OverlayWindow): self.avatars = {} self.avatar_masks = {} - # Cache for when somebody last spoke, used for "only_speaking" grace period - self.speaker_cache = {} - self.dummy_data = [] mostly_false = [False, False, False, False, False, False, False, True] for i in range(0, 100): @@ -529,6 +526,8 @@ class VoiceOverlayWindow(OverlayWindow): if self.use_dummy: # Sorting every frame is an awful idea. Maybe put this off elsewhere? users_to_draw = self.sort_list(self.dummy_data[0:self.dummy_count]) userlist = self.dummy_data + now = perf_counter() + for user in userlist: # Bad object equality here, so we need to reassign if "id" in self_user and user["id"] == self_user["id"]: @@ -544,17 +543,16 @@ class VoiceOverlayWindow(OverlayWindow): if self.only_speaking: speaking = "speaking" in user and user["speaking"] - # Update the speaker cache + # Extend timer if mid-speaking if speaking: - self.speaker_cache[user["username"]] = perf_counter() - + user['lastspoken'] = perf_counter() if not speaking: grace = self.only_speaking_grace_period if ( grace > 0 - and (last_spoke := self.speaker_cache.get(user["username"])) - and (perf_counter() - last_spoke) < grace + and (last_spoke := user['lastspoken']) + and (now - last_spoke) < grace ): # The user spoke within the grace period, so don't hide # them just yet