parent
86c5988fe7
commit
f5b5eb1dfc
11 changed files with 619 additions and 183 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(">", ">")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue