diff --git a/discover_overlay/discord_connector.py b/discover_overlay/discord_connector.py index 290c1e1..0cc1b67 100644 --- a/discover_overlay/discord_connector.py +++ b/discover_overlay/discord_connector.py @@ -14,6 +14,11 @@ The connector for discord. Connects in as if it was Streamkit for OBS or Xsplit and communicates to get voice & text info to display + +Terminology: +GUILDS - Often called 'Servers' in dicord. It is the group of users and channels that make up + one server. +CHANNEL - Often called 'Rooms'. Both voice and text channels are types of channel """ import select import time @@ -59,6 +64,9 @@ class DiscordConnector: self.last_text_channel = None def get_access_token_stage1(self): + """ + First stage of getting an access token. Request authorization from Discord client + """ cmd = { "cmd": "AUTHORIZE", "args": @@ -72,6 +80,9 @@ class DiscordConnector: self.websocket.send(json.dumps(cmd)) def get_access_token_stage2(self, code1): + """ + Second stage of getting an access token. Give auth code to streamkit + """ url = "https://streamkit.discord.com/overlay/token" myobj = {"code": code1} response = requests.post(url, json=myobj) @@ -86,6 +97,9 @@ class DiscordConnector: sys.exit(1) def set_channel(self, channel, need_req=True): + """ + Set currently active voice channel + """ if not channel: self.current_voice = "0" return @@ -99,6 +113,9 @@ class DiscordConnector: self.req_channel_details(channel) def set_text_channel(self, channel, need_req=True): + """ + Set currently active text channel + """ if not channel: self.current_text = "0" return @@ -110,6 +127,9 @@ class DiscordConnector: self.req_channel_details(channel) def set_in_room(self, userid, present): + """ + Set user currently in given room + """ if present: if userid not in self.in_room: self.in_room.append(userid) @@ -118,6 +138,9 @@ class DiscordConnector: self.in_room.remove(userid) def add_text(self, message): + """ + Add line of text to text list. Assumes the message is from the correct room + """ utc_time = time.strptime( message["timestamp"], "%Y-%m-%dT%H:%M:%S.%f%z") epoch_time = calendar.timegm(utc_time) @@ -138,6 +161,9 @@ class DiscordConnector: self.text_altered = True def update_text(self, message_in): + """ + Update a line of text + """ for idx in range(0, len(self.text)): message = self.text[idx] if message['id'] == message_in['id']: @@ -152,6 +178,9 @@ class DiscordConnector: return def delete_text(self, message_in): + """ + Delete a line of text + """ for idx in range(0, len(self.text)): message = self.text[idx] if message['id'] == message_in['id']: @@ -160,6 +189,10 @@ class DiscordConnector: return def get_message_from_message(self, message): + """ + Messages are sent as JSON objects, with varying information. + Decides which bits are shown and which are discarded + """ if "content_parsed" in message: return message["content_parsed"] elif "content" in message and len(message["content"]) > 0: @@ -174,11 +207,19 @@ class DiscordConnector: return "" def get_attachment_from_message(self, message): + """ + Messages with attachments come in different forms, decide what is and is + not an attachment + """ if len(message["attachments"]) == 1: return message["attachments"] return None def update_user(self, user): + """ + Update user information + Pass along our custom user information from version to version + """ if user["id"] in self.userlist: olduser = self.userlist[user["id"]] if "mute" not in user and "mute" in olduser: @@ -200,6 +241,9 @@ class DiscordConnector: self.userlist[user["id"]] = user def on_message(self, message): + """ + Recieve websocket message super-function + """ j = json.loads(message) if j["cmd"] == "AUTHORIZE": self.get_access_token_stage2(j["data"]["code"]) @@ -322,7 +366,9 @@ class DiscordConnector: logging.info(j) def check_guilds(self): - # Check if all of the guilds contain a channel + """ + Check if all of the guilds contain a channel + """ for guild in self.guilds.values(): if "channels" not in guild: return @@ -330,6 +376,9 @@ class DiscordConnector: self.on_connected() def on_connected(self): + """ + Called when connection is finalised + """ for guild in self.guilds.values(): channels = "" for channel in guild["channels"]: @@ -342,13 +391,22 @@ class DiscordConnector: self.sub_text_channel(self.last_text_channel) def on_error(self, error): + """ + Called when an error has occured + """ logging.error("ERROR : %s", error) def on_close(self): + """ + Called when connection is closed + """ logging.info("Connection closed") self.websocket = None def req_auth(self): + """ + Request authentication token + """ cmd = { "cmd": "AUTHENTICATE", "args": { @@ -359,6 +417,9 @@ class DiscordConnector: self.websocket.send(json.dumps(cmd)) def req_guilds(self): + """ + Request all guilds information for logged in user + """ cmd = { "cmd": "GET_GUILDS", "args": {}, @@ -367,6 +428,9 @@ class DiscordConnector: self.websocket.send(json.dumps(cmd)) def req_channels(self, guild): + """ + Request all channels information for given guild + """ cmd = { "cmd": "GET_CHANNELS", "args": { @@ -377,6 +441,9 @@ class DiscordConnector: self.websocket.send(json.dumps(cmd)) def req_channel_details(self, channel): + """ + Request information about a specific channel + """ cmd = { "cmd": "GET_CHANNEL", "args": { @@ -387,6 +454,17 @@ class DiscordConnector: self.websocket.send(json.dumps(cmd)) def find_user(self): + """ + ***Potential overload issue*** + + Asks the server for information about every single voice channel (type==2) + in the hope that one of them will say the user is present + + because if asks about every single one without waiting for reply it is heavy even + if the user is relatively simple to find + + It might be worth limiting the usage of this + """ count = 0 for channel in self.channels: if self.channels[channel]["type"] == 2: @@ -395,6 +473,9 @@ class DiscordConnector: logging.warning("Getting %s rooms", count) def sub_raw(self, event, args, nonce): + """ + Subscribe to event helper function + """ cmd = { "cmd": "SUBSCRIBE", "args": args, @@ -404,18 +485,34 @@ class DiscordConnector: self.websocket.send(json.dumps(cmd)) def sub_server(self): + """ + Subscribe to helpful events that report connectivity issues & + when the user has intentionally changed channel + + Unfortunatly no event has been found to alert to being forcibly moved + or that reports the users current location + """ self.sub_raw("VOICE_CHANNEL_SELECT", {}, "VOICE_CHANNEL_SELECT") self.sub_raw("VOICE_CONNECTION_STATUS", {}, "VOICE_CONNECTION_STATUS") def sub_channel(self, event, channel): + """ + Subscribe to event on channel + """ self.sub_raw(event, {"channel_id": channel}, channel) def sub_text_channel(self, channel): + """ + Subscribe to text-based events. + """ self.sub_channel("MESSAGE_CREATE", channel) self.sub_channel("MESSAGE_UPDATE", channel) self.sub_channel("MESSAGE_DELETE", channel) def sub_voice_channel(self, channel): + """ + Subscribe to voice-based events + """ self.sub_channel("VOICE_STATE_CREATE", channel) self.sub_channel("VOICE_STATE_UPDATE", channel) self.sub_channel("VOICE_STATE_DELETE", channel) @@ -423,6 +520,15 @@ class DiscordConnector: self.sub_channel("SPEAKING_STOP", channel) def do_read(self): + """ + Poorly named logic center. + + Checks for new data on socket, passes to on_message + + Also passes out text data to text overlay and voice data to voice overlay + + Called at 60Hz approximately but has near zero bearing on rendering + """ # Ensure connection if not self.websocket: self.connect() @@ -466,12 +572,22 @@ class DiscordConnector: return True def start_listening_text(self, channel): + """ + Subscribe to text events on channel, or remember the channel for when we've connected + + Helper function to avoid race conditions of reading config vs connecting to websocket + """ if self.websocket: self.sub_text_channel(channel) else: self.last_text_channel = channel def connect(self): + """ + Attempt to connect to websocket + + Should not throw simply for being unable to connect, only for more serious issues + """ if self.websocket: return try: diff --git a/discover_overlay/discover_overlay.py b/discover_overlay/discover_overlay.py index 99cd726..ccc81ae 100755 --- a/discover_overlay/discover_overlay.py +++ b/discover_overlay/discover_overlay.py @@ -53,6 +53,9 @@ class Discover: Gtk.main() def do_args(self, data): + """ + Read in arg list from command or RPC and act accordingly + """ if "--help" in data: pass elif "--about" in data: @@ -63,12 +66,18 @@ class Discover: sys.exit(0) def rpc_changed(self, _a=None, _b=None, _c=None, _d=None): + """ + Called when the RPC file has been altered + """ with open(self.rpc_file, "r") as tfile: data = tfile.readlines() if len(data) >= 1: self.do_args(data[0]) def create_gui(self): + """ + Create Systray & associated menu, overlays & settings windows + """ self.voice_overlay = VoiceOverlayWindow(self) self.text_overlay = TextOverlayWindow(self) self.menu = self.make_menu() @@ -77,7 +86,10 @@ class Discover: self.text_overlay, self.voice_overlay) def make_sys_tray_icon(self, menu): - # Create AppIndicator + """ + Attempt to create an AppIndicator icon, failing that attempt to make + a systemtray icon + """ try: gi.require_version('AppIndicator3', '0.1') # pylint: disable=import-outside-toplevel @@ -95,7 +107,9 @@ class Discover: self.tray.connect('popup-menu', self.show_menu) def make_menu(self): - # Create System Menu + """ + Create System Menu + """ menu = Gtk.Menu() settings_opt = Gtk.MenuItem.new_with_label("Settings") close_opt = Gtk.MenuItem.new_with_label("Close") @@ -109,18 +123,36 @@ class Discover: return menu def show_menu(self, obj, button, time): + """ + 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 show_settings(self, _obj=None, _data=None): + """ + Show settings window + """ self.settings.present_settings() def close(self, _a=None, _b=None, _c=None): + """ + End of the program + """ Gtk.main_quit() def entrypoint(): + """ + Entry Point. + + Check for PID & RPC. + + If an overlay is already running then pass the args along and close + + Otherwise start up the overlay! + """ config_dir = os.path.join(xdg_config_home, "discover_overlay") os.makedirs(config_dir, exist_ok=True) line = "" diff --git a/discover_overlay/general_settings.py b/discover_overlay/general_settings.py index 0325c82..f764917 100644 --- a/discover_overlay/general_settings.py +++ b/discover_overlay/general_settings.py @@ -38,6 +38,9 @@ class GeneralSettingsWindow(SettingsWindow): self.create_gui() def read_config(self): + """ + Read in the 'general' section of config and set overlays + """ config = ConfigParser(interpolation=None) config.read(self.config_file) self.xshape = config.getboolean("general", "xshape", fallback=False) @@ -47,6 +50,9 @@ class GeneralSettingsWindow(SettingsWindow): self.overlay2.set_force_xshape(self.xshape) def save_config(self): + """ + Save the 'general' section of config + """ config = ConfigParser(interpolation=None) config.read(self.config_file) if not config.has_section("general"): @@ -58,6 +64,9 @@ class GeneralSettingsWindow(SettingsWindow): config.write(file) def create_gui(self): + """ + Prepare the GUI + """ box = Gtk.Grid() # Auto start @@ -80,10 +89,16 @@ class GeneralSettingsWindow(SettingsWindow): self.add(box) def change_autostart(self, button): + """ + Autostart setting changed + """ autostart = button.get_active() self.autostart_helper.set_autostart(autostart) def change_xshape(self, button): + """ + XShape setting changed + """ self.overlay.set_force_xshape(button.get_active()) self.overlay2.set_force_xshape(button.get_active()) self.xshape = button.get_active() diff --git a/discover_overlay/image_getter.py b/discover_overlay/image_getter.py index 7fe4024..9b39580 100644 --- a/discover_overlay/image_getter.py +++ b/discover_overlay/image_getter.py @@ -33,6 +33,9 @@ class ImageGetter(): self.size = size def get_url(self): + """ + Download and decode + """ req = urllib.request.Request(self.url) req.add_header( 'Referer', 'https://streamkit.discord.com/overlay/voice') @@ -150,11 +153,14 @@ def draw_img_to_rect(img, ctx, width, height, path=False, aspect=False, anchor=0, hanchor=0): - """Draw cairo surface onto context""" - # Path - only add the path do not fill : True/False - # Aspect - keep aspect ratio : True/False - # Anchor - with aspect : 0=left 1=middle 2=right - # HAnchor - with apect : 0=bottom 1=middle 2=top + """Draw cairo surface onto context + + Path - only add the path do not fill : True/False + Aspect - keep aspect ratio : True/False + Anchor - with aspect : 0=left 1=middle 2=right + HAnchor - with apect : 0=bottom 1=middle 2=top + """ + ctx.save() offset_x = 0 offset_y = 0 diff --git a/discover_overlay/overlay.py b/discover_overlay/overlay.py index 3b41110..c501643 100644 --- a/discover_overlay/overlay.py +++ b/discover_overlay/overlay.py @@ -34,6 +34,9 @@ class OverlayWindow(Gtk.Window): """ def detect_type(self): + """ + Helper function to determine if Wayland is being used and return the Window type needed + """ window = Gtk.Window() screen = window.get_screen() screen_type = "%s" % (screen) @@ -87,6 +90,10 @@ class OverlayWindow(Gtk.Window): self.context = None def set_wayland_state(self): + """ + If wayland is in use then attempt to set up a GtkLayerShell + I have no idea how this should register a fail for Weston/KDE/Gnome + """ if self.is_wayland: GtkLayerShell.init_for_window(self) GtkLayerShell.set_layer(self, GtkLayerShell.Layer.TOP) @@ -96,14 +103,22 @@ class OverlayWindow(Gtk.Window): GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.TOP, True) def overlay_draw(self, _w, context, data=None): - pass + """ + Draw overlay + """ def set_font(self, name, size): + """ + Set the font used by the overlay + """ self.text_font = name self.text_size = size self.redraw() def set_floating(self, floating, pos_x, pos_y, width, height): + """ + Set if the window is floating and what dimensions to use + """ self.floating = floating self.pos_x = pos_x self.pos_y = pos_y @@ -112,6 +127,10 @@ class OverlayWindow(Gtk.Window): self.force_location() def set_untouchable(self): + """ + Create a custom input shape and tell it that all of the window is a cut-out + This allows us to have a window above everything but that never gets clicked on + """ (width, height) = self.get_size() surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) surface_ctx = cairo.Context(surface) @@ -122,9 +141,16 @@ class OverlayWindow(Gtk.Window): self.input_shape_combine_region(reg) def unset_shape(self): + """ + Remove XShape (not input shape) + """ self.get_window().shape_combine_region(None, 0, 0) def force_location(self): + """ + On X11 enforce the location and sane defaults + On Wayland just store for later + """ if not self.is_wayland: self.set_decorated(False) self.set_keep_above(True) @@ -155,6 +181,11 @@ class OverlayWindow(Gtk.Window): self.redraw() def redraw(self): + """ + Request a redraw. + If we're using XShape (optionally or forcibly) then render the image into the shape + so that we only cut out clear sections + """ gdkwin = self.get_window() if not self.floating: (width, height) = self.get_size() @@ -175,6 +206,9 @@ class OverlayWindow(Gtk.Window): self.queue_draw() def set_monitor(self, idx=None, mon=None): + """ + Set the monitor this overlay should display on. + """ self.monitor = idx if self.is_wayland: if mon: @@ -183,17 +217,42 @@ class OverlayWindow(Gtk.Window): self.redraw() def set_align_x(self, align_right): + """ + Set the alignment (True for right, False for left) + """ self.align_right = align_right self.force_location() self.redraw() def set_align_y(self, align_vert): + """ + Set the veritcal alignment + """ self.align_vert = align_vert self.force_location() self.redraw() def col(self, col, alpha=1.0): + """ + Convenience function to set the cairo context next colour + """ self.context.set_source_rgba(col[0], col[1], col[2], col[3] * alpha) def set_force_xshape(self, force): + """ + Set if XShape should be forced + """ self.force_xshape = force + + if self.is_wayland: + # Wayland and XShape are a bad idea unless you're a fan on artifacts + self.force_xshape = False + + def set_enabled(self, enabled): + """ + Set if this overlay should be visible + """ + if enabled: + self.show_all() + else: + self.hide() diff --git a/discover_overlay/settings.py b/discover_overlay/settings.py index 4258f22..74ea1c5 100644 --- a/discover_overlay/settings.py +++ b/discover_overlay/settings.py @@ -17,8 +17,10 @@ overlay types without copy-and-pasting too much code import os import logging import gi +from .draggable_window import DraggableWindow +from .draggable_window_wayland import DraggableWindowWayland gi.require_version("Gtk", "3.0") -# pylint: disable=wrong-import-position +# pylint: disable=wrong-import-position,wrong-import-order from gi.repository import Gtk, Gdk @@ -40,18 +42,34 @@ class SettingsWindow(Gtk.VBox): self.config_dir = None self.config_file = None self.overlay = None + self.floating = None self.floating_x = None self.floating_y = None self.floating_w = None self.floating_h = None + self.align_x_widget = None + self.align_y_widget = None + self.align_monitor_widget = None + self.align_placement_widget = None + self.monitor = None + self.align_x = None + self.align_y = None + self.enabled = None def init_config(self): + """ + Locate the config and then read + """ self.config_dir = os.path.join(xdg_config_home, "discover_overlay") os.makedirs(self.config_dir, exist_ok=True) self.config_file = os.path.join(self.config_dir, "config.ini") self.read_config() def close_window(self, _a=None, _b=None): + """ + Helper to ensure we don't lose changes to floating windows + Hide for later + """ if self.placement_window: (pos_x, pos_y) = self.placement_window.get_position() (width, height) = self.placement_window.get_size() @@ -67,6 +85,9 @@ class SettingsWindow(Gtk.VBox): return True def get_monitor_index(self, name): + """ + Helper function to find the index number of the monitor + """ display = Gdk.Display.get_default() if "get_n_monitors" in dir(display): for i in range(0, display.get_n_monitors()): @@ -77,6 +98,9 @@ class SettingsWindow(Gtk.VBox): return 0 def get_monitor_obj(self, name): + """ + Helper function to find the monitor object of the monitor + """ display = Gdk.Display.get_default() if "get_n_monitors" in dir(display): for i in range(0, display.get_n_monitors()): @@ -87,10 +111,118 @@ class SettingsWindow(Gtk.VBox): return None def present_settings(self): + """ + Show settings + """ self.show_all() def read_config(self): - pass + """ + Stub called when settings are needed to be read + """ def save_config(self): - pass + """ + Stub called when settings are needed to be written + """ + + def change_placement(self, button): + """ + Placement window button pressed. + """ + if self.placement_window: + (pos_x, pos_y, width, height) = self.placement_window.get_coords() + self.floating_x = pos_x + self.floating_y = pos_y + self.floating_w = width + self.floating_h = height + self.overlay.set_floating(True, pos_x, pos_y, width, height) + self.save_config() + if not self.overlay.is_wayland: + button.set_label("Place Window") + + self.placement_window.close() + self.placement_window = None + else: + if self.overlay.is_wayland: + self.placement_window = DraggableWindowWayland( + pos_x=self.floating_x, pos_y=self.floating_y, + width=self.floating_w, height=self.floating_h, + message="Place & resize this window then press Green!", settings=self) + else: + self.placement_window = DraggableWindow( + pos_x=self.floating_x, pos_y=self.floating_y, + width=self.floating_w, height=self.floating_h, + message="Place & resize this window then press Save!", settings=self) + if not self.overlay.is_wayland: + button.set_label("Save this position") + + def change_align_type_edge(self, button): + """ + Alignment setting changed + """ + if button.get_active(): + self.overlay.set_floating( + False, self.floating_x, self.floating_y, self.floating_w, self.floating_h) + self.floating = False + self.save_config() + + # Re-sort the screen + self.align_x_widget.show() + self.align_y_widget.show() + self.align_monitor_widget.show() + self.align_placement_widget.hide() + + def change_align_type_floating(self, button): + """ + Alignment setting changed + """ + if button.get_active(): + self.overlay.set_floating( + True, self.floating_x, self.floating_y, self.floating_w, self.floating_h) + self.floating = True + self.save_config() + self.align_x_widget.hide() + self.align_y_widget.hide() + self.align_monitor_widget.hide() + self.align_placement_widget.show() + + def change_monitor(self, button): + """ + Alignment setting changed + """ + display = Gdk.Display.get_default() + if "get_monitor" in dir(display): + mon = display.get_monitor(button.get_active()) + m_s = mon.get_model() + self.overlay.set_monitor(button.get_active(), mon) + + self.monitor = m_s + self.save_config() + + def change_align_x(self, button): + """ + Alignment setting changed + """ + self.overlay.set_align_x(button.get_active() == 1) + + self.align_x = (button.get_active() == 1) + self.save_config() + + def change_align_y(self, button): + """ + Alignment setting changed + """ + self.overlay.set_align_y(button.get_active()) + + self.align_y = button.get_active() + self.save_config() + + def change_enabled(self, button): + """ + Overlay active state toggled + """ + self.overlay.set_enabled(button.get_active()) + + self.enabled = button.get_active() + self.save_config() diff --git a/discover_overlay/settings_window.py b/discover_overlay/settings_window.py index bf17d13..b581036 100644 --- a/discover_overlay/settings_window.py +++ b/discover_overlay/settings_window.py @@ -53,6 +53,9 @@ class MainSettingsWindow(Gtk.Window): self.notebook = notebook def close_window(self, widget=None, event=None): + """ + Hide the settings window for use at a later date + """ self.text_settings.close_window(widget, event) self.voice_settings.close_window(widget, event) self.core_settings.close_window(widget, event) @@ -60,6 +63,9 @@ class MainSettingsWindow(Gtk.Window): return True def present_settings(self): + """ + Show the settings window + """ self.voice_settings.present_settings() self.text_settings.present_settings() self.core_settings.present_settings() diff --git a/discover_overlay/text_overlay.py b/discover_overlay/text_overlay.py index 784d648..50f49f0 100644 --- a/discover_overlay/text_overlay.py +++ b/discover_overlay/text_overlay.py @@ -52,35 +52,50 @@ class TextOverlayWindow(OverlayWindow): self.set_title("Discover Text") def set_text_time(self, timer): + """ + Set the duration that a message will be visible for. + """ self.text_time = timer def set_text_list(self, tlist, altered): + """ + Update the list of text messages to show + """ self.content = tlist if altered: self.redraw() - def set_enabled(self, enabled): - if enabled: - self.show_all() - else: - self.hide() - def set_fg(self, fg_col): + """ + Set default text colour + """ self.fg_col = fg_col self.redraw() def set_bg(self, bg_col): + """ + Set background colour + """ self.bg_col = bg_col self.redraw() def set_show_attach(self, attachment): + """ + Set if attachments should be shown inline + """ self.show_attach = attachment self.redraw() def set_popup_style(self, boolean): + """ + Set if message disappear after a certain duration + """ self.popup_style = boolean def set_font(self, name, size): + """ + Set font used to render text + """ self.text_font = name self.text_size = size self.pango_rect = Pango.Rectangle() @@ -89,6 +104,9 @@ class TextOverlayWindow(OverlayWindow): self.redraw() def make_line(self, message): + """ + Decode a recursive JSON object into pango markup. + """ ret = "" if isinstance(message, list): for inner_message in message: @@ -135,10 +153,16 @@ class TextOverlayWindow(OverlayWindow): return ret def recv_attach(self, identifier, pix): + """ + Called when an image has been downloaded by image_getter + """ self.attachment[identifier] = pix self.redraw() def overlay_draw(self, _w, context, data=None): + """ + Draw the overlay + """ self.context = context context.set_antialias(cairo.ANTIALIAS_GOOD) (width, height) = self.get_size() @@ -193,6 +217,9 @@ class TextOverlayWindow(OverlayWindow): context.restore() def draw_attach(self, pos_y, url): + """ + Draw an attachment + """ if url in self.attachment and self.attachment[url]: pix = self.attachment[url] image_width = min(pix.get_width(), self.width) @@ -211,7 +238,9 @@ 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 + """ layout = self.create_pango_layout(text) layout.set_markup(text, -1) attr = layout.get_attributes() @@ -255,6 +284,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 + """ key = self.image_list[shape.data]['url'] if key not in self.attachment: get_surface(self.recv_attach, @@ -268,7 +300,9 @@ class TextOverlayWindow(OverlayWindow): return True def sanitize_string(self, string): - # I hate that Pango has nothing for this. + """ + Sanitize a text message so that it doesn't intefere with Pango's XML format + """ string.replace("&", "&") string.replace("<", "<") string .replace(">", ">") diff --git a/discover_overlay/text_settings.py b/discover_overlay/text_settings.py index 8945198..e4b1299 100644 --- a/discover_overlay/text_settings.py +++ b/discover_overlay/text_settings.py @@ -15,8 +15,6 @@ import json import logging from configparser import ConfigParser import gi -from .draggable_window import DraggableWindow -from .draggable_window_wayland import DraggableWindowWayland from .settings import SettingsWindow gi.require_version("Gtk", "3.0") @@ -70,6 +68,11 @@ class TextSettingsWindow(SettingsWindow): self.create_gui() def update_channel_model(self): + """ + Update the Channel selector. + + Populate with all channels from guild if a guild is chosen or all channels generall if not + """ # potentially organize channels by their group/parent_id # https://discord.com/developers/docs/resources/channel#channel-object-channel-structure c_model = Gtk.ListStore(str, bool) @@ -107,11 +110,19 @@ class TextSettingsWindow(SettingsWindow): idx += 1 def add_connector(self, conn): + """ + Add the discord_connector reference + + If the user has previously selected a text channel then tell it to subscribe + """ self.connector = conn if self.channel: self.connector.start_listening_text(self.channel) def present_settings(self): + """ + Show contents of tab and update lists + """ self.show_all() if not self.floating: self.align_x_widget.show() @@ -153,6 +164,9 @@ class TextSettingsWindow(SettingsWindow): idxg += 1 def guild_list(self): + """ + Return a list of all guilds + """ guilds = [] done = [] for guild in self.list_guilds.values(): @@ -162,6 +176,9 @@ class TextSettingsWindow(SettingsWindow): return guilds def set_channels(self, in_list): + """ + Set the contents of list_channels + """ self.list_channels = in_list self.list_channels_keys = [] for key in in_list.keys(): @@ -172,6 +189,9 @@ class TextSettingsWindow(SettingsWindow): self.list_channels_keys.sort() def set_guilds(self, in_list): + """ + Set the contents of list_guilds + """ self.list_guilds = in_list self.list_guilds_keys = [] for key in in_list.keys(): @@ -179,6 +199,9 @@ class TextSettingsWindow(SettingsWindow): self.list_guilds_keys.sort() def read_config(self): + """ + Read in the 'text' section of the config + """ config = ConfigParser(interpolation=None) config.read(self.config_file) self.enabled = config.getboolean("text", "enabled", fallback=False) @@ -221,6 +244,9 @@ class TextSettingsWindow(SettingsWindow): self.overlay.set_show_attach(self.show_attach) def save_config(self): + """ + Save the current settings to the 'text' section of the config + """ config = ConfigParser(interpolation=None) config.read(self.config_file) if not config.has_section("text"): @@ -250,6 +276,9 @@ class TextSettingsWindow(SettingsWindow): config.write(file) def create_gui(self): + """ + Prepare the gui + """ box = Gtk.Grid() # Enabled @@ -404,6 +433,9 @@ class TextSettingsWindow(SettingsWindow): self.add(box) def change_font(self, button): + """ + Font settings changed + """ font = button.get_font() desc = Pango.FontDescription.from_string(font) size = desc.get_size() @@ -415,6 +447,9 @@ class TextSettingsWindow(SettingsWindow): self.save_config() def change_channel(self, button): + """ + Channel setting changed + """ if self.ignore_channel_change: return @@ -424,6 +459,9 @@ class TextSettingsWindow(SettingsWindow): self.save_config() def change_guild(self, button): + """ + Guild setting changed + """ if self.ignore_guild_change: return guild_id = self.guild_lookup[button.get_active()] @@ -431,87 +469,10 @@ class TextSettingsWindow(SettingsWindow): self.save_config() self.update_channel_model() - def change_placement(self, button): - if self.placement_window: - (pos_x, pos_y, width, height) = self.placement_window.get_coords() - self.floating_x = pos_x - self.floating_y = pos_y - self.floating_w = width - self.floating_h = height - self.overlay.set_floating(True, pos_x, pos_y, width, height) - self.save_config() - if not self.overlay.is_wayland: - button.set_label("Place Window") - - self.placement_window.close() - self.placement_window = None - else: - if self.overlay.is_wayland: - self.placement_window = DraggableWindowWayland( - pos_x=self.floating_x, pos_y=self.floating_y, - width=self.floating_w, height=self.floating_h, - message="Place & resize this window then press Green!", settings=self) - else: - self.placement_window = DraggableWindow( - pos_x=self.floating_x, pos_y=self.floating_y, - width=self.floating_w, height=self.floating_h, - message="Place & resize this window then press Save!", settings=self) - if not self.overlay.is_wayland: - button.set_label("Save this position") - - def change_align_type_edge(self, button): - if button.get_active(): - self.overlay.set_floating( - False, self.floating_x, self.floating_y, self.floating_w, self.floating_h) - self.floating = False - self.save_config() - - # Re-sort the screen - self.align_x_widget.show() - self.align_y_widget.show() - self.align_monitor_widget.show() - self.align_placement_widget.hide() - - def change_align_type_floating(self, button): - if button.get_active(): - self.overlay.set_floating( - True, self.floating_x, self.floating_y, self.floating_w, self.floating_h) - self.floating = True - self.save_config() - self.align_x_widget.hide() - self.align_y_widget.hide() - self.align_monitor_widget.hide() - self.align_placement_widget.show() - - def change_monitor(self, button): - display = Gdk.Display.get_default() - if "get_monitor" in dir(display): - mon = display.get_monitor(button.get_active()) - m_s = mon.get_model() - self.overlay.set_monitor(button.get_active(), mon) - - self.monitor = m_s - self.save_config() - - def change_align_x(self, button): - self.overlay.set_align_x(button.get_active() == 1) - - self.align_x = (button.get_active() == 1) - self.save_config() - - def change_align_y(self, button): - self.overlay.set_align_y(button.get_active()) - - self.align_y = button.get_active() - self.save_config() - - def change_enabled(self, button): - self.overlay.set_enabled(button.get_active()) - - self.enabled = button.get_active() - self.save_config() - def change_popup_style(self, button): + """ + Popup style setting changed + """ self.overlay.set_popup_style(button.get_active()) self.popup_style = button.get_active() @@ -526,15 +487,24 @@ class TextSettingsWindow(SettingsWindow): self.text_time_label_widget.hide() def change_text_time(self, button): + """ + Popup style setting changed + """ self.overlay.set_text_time(button.get_value()) self.text_time = button.get_value() self.save_config() def get_channel(self): + """ + Return selected channel + """ return self.channel def change_bg(self, button): + """ + Background colour changed + """ colour = button.get_rgba() colour = [colour.red, colour.green, colour.blue, colour.alpha] self.overlay.set_bg(colour) @@ -543,6 +513,9 @@ class TextSettingsWindow(SettingsWindow): self.save_config() def change_fg(self, button): + """ + Foreground colour changed + """ colour = button.get_rgba() colour = [colour.red, colour.green, colour.blue, colour.alpha] self.overlay.set_fg(colour) @@ -551,6 +524,9 @@ class TextSettingsWindow(SettingsWindow): self.save_config() def change_show_attach(self, button): + """ + Attachment setting changed + """ self.overlay.set_show_attach(button.get_active()) self.show_attach = button.get_active() diff --git a/discover_overlay/voice_overlay.py b/discover_overlay/voice_overlay.py index 917cab5..889d0d2 100644 --- a/discover_overlay/voice_overlay.py +++ b/discover_overlay/voice_overlay.py @@ -55,74 +55,134 @@ class VoiceOverlayWindow(OverlayWindow): self.set_title("Discover Voice") def set_bg(self, background_colour): + """ + Set the background colour + """ self.norm_col = background_colour self.redraw() def set_fg(self, foreground_colour): + """ + Set the text colour + """ self.text_col = foreground_colour self.redraw() def set_tk(self, talking_colour): + """ + Set the border colour for users who are talking + """ self.talk_col = talking_colour self.redraw() def set_mt(self, mute_colour): + """ + Set the colour of mute and deafen logos + """ self.mute_col = mute_colour self.redraw() def set_avatar_size(self, size): + """ + Set the size of the avatar icons + """ self.avatar_size = size self.redraw() def set_icon_spacing(self, i): + """ + Set the spacing between avatar icons + """ self.icon_spacing = i self.redraw() def set_text_padding(self, i): + """ + Set padding between text and border + """ self.text_pad = i self.redraw() def set_vert_edge_padding(self, i): + """ + Set padding between top/bottom of screen and overlay contents + """ self.vert_edge_padding = i self.redraw() def set_horz_edge_padding(self, i): + """ + Set padding between left/right of screen and overlay contents + """ self.horz_edge_padding = i self.redraw() def set_square_avatar(self, i): + """ + Set if the overlay should crop avatars to a circle or show full square image + """ self.round_avatar = not i self.redraw() def set_only_speaking(self, only_speaking): + """ + Set if overlay should only show people who are talking + """ self.only_speaking = only_speaking def set_highlight_self(self, highlight_self): + """ + Set if the overlay should highlight the user + """ self.highlight_self = highlight_self def set_order(self, i): + """ + Set the method used to order avatar icons & names + """ self.order = i def set_icon_only(self, i): + """ + Set if the overlay should draw only the icon + """ self.icon_only = i self.redraw() def set_wind_col(self): + """ + Use window colour to draw + """ self.col(self.wind_col) def set_text_col(self): + """ + Use text colour to draw + """ self.col(self.text_col) def set_norm_col(self): + """ + Use background colour to draw + """ self.col(self.norm_col) def set_talk_col(self, alpha=1.0): + """ + Use talking colour to draw + """ self.col(self.talk_col, alpha) def set_mute_col(self, alpha=1.0): + """ + Use mute colour to draw + """ self.col(self.mute_col, alpha) def set_user_list(self, userlist, alt): + """ + Set the users in list to draw + """ self.userlist = userlist for user in userlist: if "nick" in user: @@ -145,12 +205,18 @@ class VoiceOverlayWindow(OverlayWindow): self.redraw() def set_connection(self, connection): + """ + Set if discord has a clean connection to server + """ is_connected = connection == "VOICE_CONNECTED" if self.connected != is_connected: self.connected = is_connected self.redraw() def overlay_draw(self, _w, context, _data=None): + """ + Draw the Overlay + """ self.context = context context.set_antialias(cairo.ANTIALIAS_GOOD) # Get size of window @@ -228,17 +294,19 @@ class VoiceOverlayWindow(OverlayWindow): self.context = None def recv_avatar(self, identifier, pix): + """ + Called when image_getter has downloaded an image + """ if identifier == 'def': self.def_avatar = pix else: self.avatars[identifier] = pix self.redraw() - def delete_avatar(self, identifier): - if id in self.avatars: - del self.avatars[identifier] - def draw_avatar(self, context, user, pos_y): + """ + 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"]: url = "https://cdn.discordapp.com/avatars/%s/%s.jpg" % ( @@ -298,6 +366,9 @@ class VoiceOverlayWindow(OverlayWindow): self.draw_mute(context, self.horz_edge_padding, pos_y) def draw_text(self, context, string, pos_x, pos_y): + """ + Draw username & background at given position + """ if self.text_font: context.set_font_face(cairo.ToyFontFace( self.text_font, cairo.FontSlant.NORMAL, cairo.FontWeight.NORMAL)) @@ -338,6 +409,9 @@ class VoiceOverlayWindow(OverlayWindow): context.show_text(string) def draw_avatar_pix(self, context, pixbuf, pos_x, pos_y, border_colour): + """ + Draw avatar image at given position + """ if not pixbuf: pixbuf = self.def_avatar if not pixbuf: @@ -375,6 +449,9 @@ class VoiceOverlayWindow(OverlayWindow): context.stroke() def draw_mute(self, context, pos_x, pos_y): + """ + Draw Mute logo + """ context.save() context.translate(pos_x, pos_y) context.scale(self.avatar_size, self.avatar_size) @@ -429,6 +506,9 @@ class VoiceOverlayWindow(OverlayWindow): context.restore() def draw_deaf(self, context, pos_x, pos_y): + """ + Draw deaf logo + """ context.save() context.translate(pos_x, pos_y) context.scale(self.avatar_size, self.avatar_size) diff --git a/discover_overlay/voice_settings.py b/discover_overlay/voice_settings.py index 20149b1..a759931 100644 --- a/discover_overlay/voice_settings.py +++ b/discover_overlay/voice_settings.py @@ -14,8 +14,6 @@ import json from configparser import ConfigParser import gi -from .draggable_window import DraggableWindow -from .draggable_window_wayland import DraggableWindowWayland from .settings import SettingsWindow gi.require_version("Gtk", "3.0") # pylint: disable=wrong-import-position,wrong-import-order @@ -56,6 +54,9 @@ class VoiceSettingsWindow(SettingsWindow): self.create_gui() def present_settings(self): + """ + Show tab + """ self.show_all() if not self.floating: self.align_x_widget.show() @@ -69,6 +70,9 @@ class VoiceSettingsWindow(SettingsWindow): self.align_placement_widget.show() def read_config(self): + """ + Read 'main' section of the config file + """ config = ConfigParser(interpolation=None) config.read(self.config_file) self.align_x = config.getboolean("main", "rightalign", fallback=True) @@ -136,6 +140,9 @@ class VoiceSettingsWindow(SettingsWindow): self.overlay.set_font(desc.get_family(), size) def save_config(self): + """ + Write settings out to the 'main' section of the config file + """ config = ConfigParser(interpolation=None) config.read(self.config_file) if not config.has_section("main"): @@ -172,6 +179,9 @@ class VoiceSettingsWindow(SettingsWindow): config.write(file) def create_gui(self): + """ + Prepare the gui + """ box = Gtk.Grid() # Font chooser @@ -372,60 +382,10 @@ class VoiceSettingsWindow(SettingsWindow): self.add(box) - def change_placement(self, button): - if self.placement_window: - (pos_x, pos_y, width, height) = self.placement_window.get_coords() - self.floating_x = pos_x - self.floating_y = pos_y - self.floating_w = width - self.floating_h = height - self.overlay.set_floating(True, pos_x, pos_y, width, height) - self.save_config() - if not self.overlay.is_wayland: - button.set_label("Place Window") - self.placement_window.close() - self.placement_window = None - else: - if self.overlay.is_wayland: - self.placement_window = DraggableWindowWayland( - pos_x=self.floating_x, pos_y=self.floating_y, - width=self.floating_w, height=self.floating_h, - message="Place & resize this window then press Green!", settings=self) - else: - self.placement_window = DraggableWindow( - pos_x=self.floating_x, pos_y=self.floating_y, - width=self.floating_w, height=self.floating_h, - message="Place & resize this window then press Save!", settings=self) - if not self.overlay.is_wayland: - button.set_label("Save this position") - - def change_align_type_edge(self, button): - if button.get_active(): - self.overlay.set_floating( - False, self.floating_x, self.floating_y, - self.floating_w, self.floating_h) - self.floating = False - self.save_config() - - # Re-sort the screen - self.align_x_widget.show() - self.align_y_widget.show() - self.align_monitor_widget.show() - self.align_placement_widget.hide() - - def change_align_type_floating(self, button): - if button.get_active(): - self.overlay.set_floating( - True, self.floating_x, self.floating_y, - self.floating_w, self.floating_h) - self.floating = True - self.save_config() - self.align_x_widget.hide() - self.align_y_widget.hide() - self.align_monitor_widget.hide() - self.align_placement_widget.show() - def change_font(self, button): + """ + Font settings changed + """ font = button.get_font() desc = Pango.FontDescription.from_string(font) size = desc.get_size() @@ -437,6 +397,9 @@ class VoiceSettingsWindow(SettingsWindow): self.save_config() def change_bg(self, button): + """ + Background colour changed + """ colour = button.get_rgba() colour = [colour.red, colour.green, colour.blue, colour.alpha] self.overlay.set_bg(colour) @@ -445,6 +408,9 @@ class VoiceSettingsWindow(SettingsWindow): self.save_config() def change_fg(self, button): + """ + Text colour changed + """ colour = button.get_rgba() colour = [colour.red, colour.green, colour.blue, colour.alpha] self.overlay.set_fg(colour) @@ -453,6 +419,9 @@ class VoiceSettingsWindow(SettingsWindow): self.save_config() def change_tk(self, button): + """ + Talking colour changed + """ colour = button.get_rgba() colour = [colour.red, colour.green, colour.blue, colour.alpha] self.overlay.set_tk(colour) @@ -461,6 +430,9 @@ class VoiceSettingsWindow(SettingsWindow): self.save_config() def change_mt(self, button): + """ + Mute colour changed + """ colour = button.get_rgba() colour = [colour.red, colour.green, colour.blue, colour.alpha] self.overlay.set_mt(colour) @@ -469,82 +441,90 @@ class VoiceSettingsWindow(SettingsWindow): self.save_config() def change_avatar_size(self, button): + """ + Avatar size setting changed + """ self.overlay.set_avatar_size(button.get_value()) self.avatar_size = button.get_value() self.save_config() - def change_monitor(self, button): - display = Gdk.Display.get_default() - if "get_monitor" in dir(display): - mon = display.get_monitor(button.get_active()) - m_s = mon.get_model() - self.overlay.set_monitor(button.get_active(), mon) - - self.monitor = m_s - self.save_config() - - def change_align_x(self, button): - self.overlay.set_align_x(button.get_active() == 1) - - self.align_x = (button.get_active() == 1) - self.save_config() - - def change_align_y(self, button): - self.overlay.set_align_y(button.get_active()) - - self.align_y = button.get_active() - self.save_config() - def change_icon_spacing(self, button): + """ + Inter-icon spacing changed + """ self.overlay.set_icon_spacing(button.get_value()) self.icon_spacing = int(button.get_value()) self.save_config() def change_text_padding(self, button): + """ + Text padding changed + """ self.overlay.set_text_padding(button.get_value()) self.text_padding = button.get_value() self.save_config() def change_vert_edge_padding(self, button): + """ + Vertical padding changed + """ self.overlay.set_vert_edge_padding(button.get_value()) self.vert_edge_padding = button.get_value() self.save_config() def change_horz_edge_padding(self, button): + """ + Horizontal padding changed + """ self.overlay.set_horz_edge_padding(button.get_value()) self.horz_edge_padding = button.get_value() self.save_config() def change_square_avatar(self, button): + """ + Square avatar setting changed + """ self.overlay.set_square_avatar(button.get_active()) self.square_avatar = button.get_active() self.save_config() def change_only_speaking(self, button): + """ + Show only speaking users setting changed + """ self.overlay.set_only_speaking(button.get_active()) self.only_speaking = button.get_active() self.save_config() def change_highlight_self(self, button): + """ + Highlight self setting changed + """ self.overlay.set_highlight_self(button.get_active()) self.highlight_self = button.get_active() self.save_config() def change_icon_only(self, button): + """ + Icon only setting changed + """ self.overlay.set_icon_only(button.get_active()) self.icon_only = button.get_active() self.save_config() def change_order(self, button): + """ + Order user setting changed + """ self.overlay.set_order(button.get_active()) self.order = button.get_active()