- First pass at pylint clean-up

This commit is contained in:
trigg 2024-07-08 23:32:59 +01:00
parent 062e8b102f
commit 17a9bc5f02
12 changed files with 587 additions and 603 deletions

4
.pylintrc Normal file
View file

@ -0,0 +1,4 @@
[MAIN]
max-line-length=150
max-module-lines=2000
generated-member=cairo.*

View file

@ -28,7 +28,7 @@ class Autostart:
def __init__(self, app_name):
if not app_name.endswith(".desktop"):
app_name = "%s.desktop" % (app_name)
app_name = f"{app_name}.desktop"
self.app_name = app_name
self.auto_locations = [os.path.join(
xdg_config_home, 'autostart/'), '/etc/xdg/autostart/']
@ -77,7 +77,7 @@ class BazziteAutostart:
def __init__(self):
self.auto = False
with open("/etc/default/discover-overlay") as f:
with open("/etc/default/discover-overlay", encoding="utf-8") as f:
content = f.readlines()
for line in content:
if line.startswith("AUTO_LAUNCH_DISCOVER_OVERLAY="):
@ -94,14 +94,15 @@ class BazziteAutostart:
self.auto = enable
def change_file(self, value):
"""Alter bazzite config via pkexec and sed"""
root = ''
if shutil.which('pkexec'):
root = 'pkexec'
else:
log.error("No ability to request root privs. Cancel")
return
command = " sed -i 's/AUTO_LAUNCH_DISCOVER_OVERLAY=./AUTO_LAUNCH_DISCOVER_OVERLAY=%s/g' /etc/default/discover-overlay" % (
value)
command = f" sed -i 's/AUTO_LAUNCH_DISCOVER_OVERLAY=./AUTO_LAUNCH_DISCOVER_OVERLAY={
value}/g' /etc/default/discover-overlay"
command_with_permissions = root + command
os.system(command_with_permissions)

View file

@ -29,7 +29,6 @@ import calendar
import websocket
import requests
import gi
from gi.repository import GLib
log = logging.getLogger(__name__)
@ -94,9 +93,14 @@ class DiscordConnector:
"""
url = "https://streamkit.discord.com/overlay/token"
myobj = {"code": code1}
response = requests.post(url, json=myobj)
response = requests.post(url, json=myobj, timeout=10)
try:
jsonresponse = json.loads(response.text)
except requests.exceptions.Timeout:
# TODO This probably needs a retry, not a quit
jsonresponse = {}
except requests.exceptions.TooManyRedirects:
jsonresponse = {}
except json.JSONDecodeError:
jsonresponse = {}
if "access_token" in jsonresponse:
@ -192,8 +196,7 @@ class DiscordConnector:
"""
Update a line of text
"""
for idx in range(0, len(self.text)):
message = self.text[idx]
for idx, message in enumerate(self.text):
if message['id'] == message_in['id']:
new_message = {'id': message['id'],
'content': self.get_message_from_message(message_in),
@ -209,8 +212,7 @@ class DiscordConnector:
"""
Delete a line of text
"""
for idx in range(0, len(self.text)):
message = self.text[idx]
for idx, message in enumerate(self.text):
if message['id'] == message_in['id']:
del self.text[idx]
self.text_altered = True
@ -386,7 +388,8 @@ class DiscordConnector:
self.dump_channel_data()
return
elif j["cmd"] == "GET_GUILD":
# We currently only get here because of a "CHANNEL_CREATE" event. Stupidly long winded way around
# We currently only get here because of a "CHANNEL_CREATE" event.
# Stupidly long winded way around
if j["data"]:
guild = j["data"]
self.dump_channel_data()
@ -417,7 +420,8 @@ class DiscordConnector:
self.set_channel(j['data']['id'], j['data']['guild_id'])
self.discover.voice_overlay.set_channel_title(
j["data"]["name"])
if self.current_guild in self.guilds and 'icon_url' in self.guilds[self.current_guild]:
if (self.current_guild in self.guilds and
'icon_url' in self.guilds[self.current_guild]):
self.discover.voice_overlay.set_channel_icon(
self.guilds[self.current_guild]['icon_url'])
else:
@ -460,7 +464,8 @@ class DiscordConnector:
log.warning(j)
def dump_channel_data(self):
with open(self.discover.channel_file, 'w') as f:
""" Write all channel data out to file"""
with open(self.discover.channel_file, 'w', encoding="utf-8") as f:
f.write(json.dumps(
{'channels': self.channels, 'guild': self.guilds}))
@ -536,7 +541,7 @@ class DiscordConnector:
if guild in self.guilds:
self.rate_limited_channels.append(guild)
else:
log.warning(f"Didn't find guild with id {guild}")
log.warning("Didn't find guild with id %s", guild)
def req_channel_details(self, channel, nonce=None):
"""message
@ -669,6 +674,7 @@ class DiscordConnector:
self.websocket.send(json.dumps(cmd))
def set_mute(self, muted):
""" Set client muted status """
cmd = {
"cmd": "SET_VOICE_SETTINGS",
"args": {"mute": muted},
@ -679,6 +685,7 @@ class DiscordConnector:
return False
def set_deaf(self, deaf):
""" Set client deafened status """
cmd = {
"cmd": "SET_VOICE_SETTINGS",
"args": {"deaf": deaf},
@ -688,14 +695,14 @@ class DiscordConnector:
self.websocket.send(json.dumps(cmd))
return False
def change_voice_room(self, id):
def change_voice_room(self, room_id):
"""
Switch to another voice room
"""
cmd = {
"cmd": "SELECT_VOICE_CHANNEL",
"args": {
"channel_id": id,
"channel_id": room_id,
"force": True
},
"nonce": "deadbeef"
@ -703,14 +710,14 @@ class DiscordConnector:
if self.websocket:
self.websocket.send(json.dumps(cmd))
def change_text_room(self, id):
def change_text_room(self, room_id):
"""
Switch to another text room
"""
cmd = {
"cmd": "SELECT_TEXT_CHANNEL",
"args": {
"channel_id": id
"channel_id": room_id
},
"nonce": "deadbeef"
}
@ -718,7 +725,8 @@ class DiscordConnector:
self.websocket.send(json.dumps(cmd))
def update_overlays_from_data(self):
if self.websocket == None:
"""Send new data out to overlay windows"""
if self.websocket is None:
self.discover.voice_overlay.set_blank()
if self.discover.text_overlay:
self.discover.text_overlay.set_blank()
@ -773,12 +781,13 @@ class DiscordConnector:
This will be mixed in with 'None' in the list where a voice channel is
"""
if (guild_id == 0):
if guild_id == 0:
return
self.rate_limited_channels.append(guild_id)
def schedule_reconnect(self):
if self.reconnect_cb == None:
"""Set a timer to attempt reconnection"""
if self.reconnect_cb is None:
log.info("Scheduled a reconnect")
self.reconnect_cb = GLib.timeout_add_seconds(60, self.connect)
else:
@ -792,25 +801,30 @@ class DiscordConnector:
"""
log.info("Connecting...")
if self.websocket:
log.warn("Already connected?")
log.warning("Already connected?")
return
if self.reconnect_cb:
GLib.source_remove(self.reconnect_cb)
self.reconnect_cb = None
try:
self.websocket = websocket.create_connection(
"ws://127.0.0.1:6463/?v=1&client_id=%s" % (self.oauth_token),
f"ws://127.0.0.1:6463/?v=1&client_id={self.oauth_token}",
origin="http://localhost:3000",
timeout=0.1
)
if self.socket_watch:
GLib.source_remove(self.socket_watch)
self.socket_watch = GLib.io_add_watch(
self.websocket.sock, GLib.PRIORITY_DEFAULT_IDLE, GLib.IOCondition.HUP | GLib.IOCondition.IN | GLib.IOCondition.ERR, self.socket_glib)
except ConnectionError as error:
self.websocket.sock,
GLib.PRIORITY_DEFAULT_IDLE,
GLib.IOCondition.HUP | GLib.IOCondition.IN | GLib.IOCondition.ERR,
self.socket_glib
)
except ConnectionError as _error:
self.schedule_reconnect()
def socket_glib(self, fd, condition):
def socket_glib(self, _fd, condition):
"""Handle new data on socket"""
if condition == GLib.IO_IN and self.websocket:
recv, _w, _e = select.select((self.websocket.sock,), (), (), 0)
while recv:

View file

@ -13,16 +13,15 @@
"""Main application class"""
import gettext
import os
import time
import sys
import re
import traceback
import logging
import pkg_resources
import json
import signal
import gi
from configparser import ConfigParser
import gi
import pkg_resources
from .settings_window import MainSettingsWindow
from .voice_overlay import VoiceOverlayWindow
@ -33,7 +32,7 @@ from .audio_assist import DiscoverAudioAssist
gi.require_version("Gtk", "3.0")
# pylint: disable=wrong-import-position,wrong-import-order
from gi.repository import Gtk, GLib, Gio, Gdk # nopep8
from gi.repository import Gtk, GLib, Gio # nopep8
try:
from xdg.BaseDirectory import xdg_config_home
@ -99,7 +98,7 @@ class Discover:
Read in arg list from command or RPC and act accordingly
"""
if "--help" in data or "-h" in data:
print("%s: discover-overlay [OPTIONS]... " % (_("Usage")))
print(f"{_("Usage")}: discover-overlay [OPTIONS]... ")
print(_("Show an X11 or wlroots overlay with information"))
print(_("from Discord client"))
print("")
@ -158,14 +157,16 @@ class Discover:
self.connection.request_text_rooms_for_guild(match.group(1))
def config_set(self, context, key, value):
"""Set a config value and save to disk"""
config = self.config()
if not context in config.sections():
config.add_section(context)
config.set(context, key, value)
with open(self.config_file, 'w') as file:
with open(self.config_file, 'w', encoding="utf-8") as file:
config.write(file)
def config(self):
"""Read config from disk"""
config = ConfigParser(interpolation=None)
config.read(self.config_file)
return config
@ -174,7 +175,7 @@ class Discover:
"""
Called when the RPC file has been altered
"""
with open(self.rpc_file, "r") as tfile:
with open(self.rpc_file, "r", encoding="utf-8") as tfile:
data = tfile.readlines()
if len(data) >= 1:
self.do_args(data[0].strip().split(" "), False)
@ -249,9 +250,7 @@ class Discover:
self.voice_overlay.set_horizontal(config.getboolean(
"main", "horizontal", fallback=False))
self.voice_overlay.set_guild_ids(self.parse_guild_ids(
config.get("main", "guild_ids", fallback="")))
self.voice_overlay.set_overflow(
self.voice_overlay.set_overflow_style(
config.getint("main", "overflow", fallback=0))
self.voice_overlay.set_show_connection(config.getboolean(
"main", "show_connection", fallback=False))
@ -259,7 +258,7 @@ class Discover:
"main", "show_title", fallback=False))
self.voice_overlay.set_show_disconnected(config.getboolean(
"main", "show_disconnected", fallback=False))
self.voice_overlay.set_border_width(
self.voice_overlay.set_drawn_border_width(
config.getint("main", "border_width", fallback=2))
self.voice_overlay.set_icon_transparency(config.getfloat(
"main", "icon_transparency", fallback=1.0))
@ -432,6 +431,7 @@ class Discover:
self.config_file, self.rpc_file, self.channel_file, [])
def toggle_show(self, _obj=None):
"""Toggle all overlays off or on"""
if self.voice_overlay:
hide = not self.voice_overlay.hidden
self.voice_overlay.set_hidden(hide)
@ -457,6 +457,8 @@ class Discover:
self.notification_overlay.set_force_xshape(force)
def set_show_task(self, visible):
"""Set if the overlay should allow itself to appear on taskbar.
Not working at last check"""
if self.voice_overlay:
self.voice_overlay.set_task(visible)
if self.text_overlay:
@ -465,11 +467,13 @@ class Discover:
self.notification_overlay.set_task(visible)
def set_mute_async(self, mute):
if mute != None:
"""Set mute status from another thread"""
if mute is not None:
GLib.idle_add(self.connection.set_mute, mute)
def set_deaf_async(self, deaf):
if deaf != None:
"""Set deaf status from another thread"""
if deaf is not None:
GLib.idle_add(self.connection.set_deaf, deaf)
@ -499,13 +503,12 @@ def entrypoint():
# Prepare logger
logging.getLogger().setLevel(logging.INFO)
FORMAT = "%(levelname)s - %(name)s - %(message)s"
log_format = "%(levelname)s - %(name)s - %(message)s"
if "--debug" in sys.argv or "-v" in sys.argv:
logging.getLogger().setLevel(logging.DEBUG)
logging.basicConfig(filename=debug_file, format=FORMAT)
logging.basicConfig(filename=debug_file, format=log_format)
else:
logging.basicConfig(format=FORMAT)
log = logging.getLogger(__name__)
logging.basicConfig(format=log_format)
log.info("Starting Discover Overlay: %s",
pkg_resources.get_distribution('discover_overlay').version)
@ -520,26 +523,26 @@ def entrypoint():
# Send command to overlay
line = ""
for arg in sys.argv[1:]:
line = "%s %s" % (line, arg)
with open(rpc_file, "w") as tfile:
line = f"{line} {arg}"
with open(rpc_file, "w", encoding="utf-8") as tfile:
tfile.write(line)
log.warning("Sent RPC command")
else:
if "-c" in sys.argv or "--configure" in sys.argv:
# Show config window
settings = MainSettingsWindow(
_settings = MainSettingsWindow(
config_file, rpc_file, channel_file, sys.argv[1:])
Gtk.main()
else:
# Tell any other running overlay to close
with open(rpc_file, "w") as tfile:
with open(rpc_file, "w", encoding="utf-8") as tfile:
tfile.write("--close")
# Show the overlay
Discover(rpc_file, config_file, channel_file,
debug_file, sys.argv[1:])
return
except Exception as ex:
except Exception as ex: # pylint: disable=broad-except
log.error(ex)
log.error(traceback.format_exc())
sys.exit(1)

View file

@ -11,9 +11,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""An X11 window which can be moved and resized"""
import logging
import gi
import cairo
import logging
gi.require_version("Gtk", "3.0")
# pylint: disable=wrong-import-position
from gi.repository import Gtk, Gdk # nopep8
@ -24,10 +24,12 @@ log = logging.getLogger(__name__)
class DraggableWindow(Gtk.Window):
"""An X11 window which can be moved and resized"""
def __init__(self, pos_x=0.0, pos_y=0.0, width=0.1, height=0.1, message="Message", settings=None, monitor=None):
def __init__(self, pos_x=0.0, pos_y=0.0, width=0.1, height=0.1,
message="Message", settings=None, monitor=None):
Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP)
self.monitor = monitor
(screen_x, screen_y, screen_width, screen_height) = self.get_display_coords()
(_screen_x, _screen_y, screen_width,
screen_height) = self.get_display_coords()
self.pos_x = pos_x * screen_width
self.pos_y = pos_y * screen_height
self.width = max(40, width * screen_width)
@ -84,8 +86,8 @@ class DraggableWindow(Gtk.Window):
if event.state & Gdk.ModifierType.BUTTON1_MASK:
if self.drag_type == 1:
# Center is move
(screen_x, screen_y, screen_width,
screen_height) = self.get_display_coords()
(screen_x, screen_y, _screen_width,
_screen_height) = self.get_display_coords()
self.pos_x = (event.x_root - screen_x) - self.drag_x
self.pos_y = (event.y_root - screen_y) - self.drag_y
self.force_location()
@ -151,6 +153,7 @@ class DraggableWindow(Gtk.Window):
context.fill()
def get_display_coords(self):
"""Get coordinates for this display"""
display = Gdk.Display.get_default()
if "get_monitor" in dir(display):
monitor = display.get_monitor(self.monitor)
@ -171,4 +174,5 @@ class DraggableWindow(Gtk.Window):
height = float(height)
pos_x = pos_x / scale
pos_y = pos_y / scale
return (pos_x / screen_width, pos_y / screen_height, width / screen_width, height / screen_height)
return (pos_x / screen_width, pos_y / screen_height,
width / screen_width, height / screen_height)

View file

@ -11,9 +11,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""A Wayland full-screen window which can be moved and resized"""
import logging
import cairo
import gi
import logging
gi.require_version("Gtk", "3.0")
# pylint: disable=wrong-import-position
from gi.repository import Gtk, Gdk # nopep8
@ -29,12 +29,14 @@ log = logging.getLogger(__name__)
class DraggableWindowWayland(Gtk.Window):
"""A Wayland full-screen window which can be moved and resized"""
def __init__(self, pos_x=0.0, pos_y=0.0, width=0.1, height=0.1, message="Message", settings=None, steamos=False, monitor=None):
def __init__(self, pos_x=0.0, pos_y=0.0, width=0.1, height=0.1,
message="Message", settings=None, steamos=False, monitor=None):
Gtk.Window.__init__(self, type=Gtk.WindowType.TOPLEVEL)
if steamos:
monitor = 0
self.monitor = monitor
(screen_x, screen_y, screen_width, screen_height) = self.get_display_coords()
(_screen_x, _screen_y, screen_width,
screen_height) = self.get_display_coords()
self.pos_x = pos_x * screen_width
self.pos_y = pos_y * screen_height
self.width = max(40, width * screen_width)
@ -48,8 +50,8 @@ class DraggableWindowWayland(Gtk.Window):
self.connect('button-press-event', self.button_press)
self.connect('button-release-event', self.button_release)
log.info("Starting: %d,%d %d x %d" %
(self.pos_x, self.pos_y, self.width, self.height))
log.info("Starting: %d,%d %d x %d",
self.pos_x, self.pos_y, self.width, self.height)
self.set_app_paintable(True)
@ -76,21 +78,24 @@ class DraggableWindowWayland(Gtk.Window):
self.force_location()
def set_steamos_window_size(self):
"""Prepare window for a gamescope steamos session"""
# Huge bunch of assumptions.
# Gamescope only has one monitor
# Gamescope has no scale factor
# Probably never possible to reach here, as Gamescope/SteamOS
# is X11 for overlays
display = Gdk.Display.get_default()
if "get_monitor" in dir(display):
monitor = display.get_monitor(0)
if monitor:
geometry = monitor.get_geometry()
scale_factor = monitor.get_scale_factor()
log.info("%d %d" % (geometry.width, geometry.height))
log.info("%d %d", geometry.width, geometry.height)
self.set_size_request(geometry.width, geometry.height)
def force_location(self):
"""Move the window to previously given co-ords. In wayland just clip to current screen"""
(screen_x, screen_y, screen_width, screen_height) = self.get_display_coords()
(_screen_x, _screen_y, screen_width,
screen_height) = self.get_display_coords()
self.width = min(self.width, screen_width)
self.height = min(self.height, screen_height)
self.pos_x = max(0, self.pos_x)
@ -189,6 +194,7 @@ class DraggableWindowWayland(Gtk.Window):
context.restore()
def get_display_coords(self):
"""Get coordinates from display"""
display = Gdk.Display.get_default()
if "get_monitor" in dir(display):
monitor = display.get_monitor(self.monitor)
@ -199,5 +205,7 @@ class DraggableWindowWayland(Gtk.Window):
def get_coords(self):
"""Return the position and size of the window"""
(screen_x, screen_y, screen_width, screen_height) = self.get_display_coords()
return (float(self.pos_x) / screen_width, float(self.pos_y) / screen_height, float(self.width) / screen_width, float(self.height) / screen_height)
(_screen_x, _screen_y, screen_width,
screen_height) = self.get_display_coords()
return (float(self.pos_x) / screen_width, float(self.pos_y) / screen_height,
float(self.width) / screen_width, float(self.height) / screen_height)

View file

@ -11,21 +11,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Functions & Classes to assist image loading."""
import urllib
import threading
import logging
import os
import copy
import gi
import requests
import cairo
import PIL
import PIL.Image as Image
import os
import io
import copy
gi.require_version('GdkPixbuf', '2.0')
gi.require_version("Gtk", "3.0")
# pylint: disable=wrong-import-position
from gi.repository import Gio, GdkPixbuf, Gtk # nopep8
from gi.repository import Gtk # nopep8
log = logging.getLogger(__name__)
@ -43,7 +41,7 @@ class SurfaceGetter():
"""Downloads and decodes"""
try:
resp = requests.get(
self.url, stream=True, headers={
self.url, stream=True, timeout=10, headers={
'Referer': 'https://streamkit.discord.com/overlay/voice',
'User-Agent': 'Mozilla/5.0'
}
@ -69,6 +67,7 @@ class SurfaceGetter():
log.error("Unknown image type: %s", self.url)
def get_file(self):
"""Attempt to load the file"""
errors = []
# Grab icon from icon theme
icon_theme = Gtk.IconTheme.get_default()
@ -99,13 +98,13 @@ class SurfaceGetter():
try:
image = Image.open(mixpath)
except ValueError:
errors.append("Value Error - Unable to read %s" % (mixpath))
errors.append(f"Value Error - Unable to read {mixpath}")
except TypeError:
errors.append("Type Error - Unable to read %s" % (mixpath))
errors.append(f"Type Error - Unable to read {mixpath}")
except PIL.UnidentifiedImageError:
errors.append("Unknown image type: %s" % (mixpath))
errors.append(f"Unknown image type: {mixpath}")
except FileNotFoundError:
errors.append("File not found: %s" % (mixpath))
errors.append(f"File not found: {mixpath}")
if image:
(surface, mask) = from_pil(image)
if surface:
@ -115,7 +114,7 @@ class SurfaceGetter():
log.error(error)
def from_pil(image, alpha=1.0, format='BGRa'):
def from_pil(image, alpha=1.0, image_format='BGRa'):
"""
:param im: Pillow Image
:param alpha: 0..1 alpha to add to non-alpha images
@ -125,10 +124,10 @@ def from_pil(image, alpha=1.0, format='BGRa'):
mask = bytearray()
if 'A' not in image.getbands():
image.putalpha(int(alpha * 255.0))
arr = bytearray(image.tobytes('raw', format))
arr = bytearray(image.tobytes('raw', image_format))
mask = arr
else:
arr = bytearray(image.tobytes('raw', format))
arr = bytearray(image.tobytes('raw', image_format))
mask = copy.deepcopy((arr))
idx = 0
while idx < len(arr):
@ -148,9 +147,12 @@ def from_pil(image, alpha=1.0, format='BGRa'):
def to_pil(surface):
"""Return a PIL Image from the Cairo surface"""
if surface.get_format() == cairo.Format.ARGB32:
return Image.frombuffer('RGBA', (surface.get_width(), surface.get_height()), surface.get_data(), 'raw', "BGRA", surface.get_stride())
return Image.frombuffer("RGB", (surface.get_width(), surface.get_height()), surface.get_data(), 'raw', "BGRX", surface.get_stride())
return Image.frombuffer('RGBA', (surface.get_width(), surface.get_height()),
surface.get_data(), 'raw', "BGRA", surface.get_stride())
return Image.frombuffer("RGB", (surface.get_width(), surface.get_height()),
surface.get_data(), 'raw', "BGRX", surface.get_stride())
def get_surface(func, identifier, ava, size):

View file

@ -13,16 +13,15 @@
"""Notification window for text"""
import logging
import time
import re
import cairo
import math
import cairo
import gi
from .image_getter import get_surface, draw_img_to_rect, get_aspected_size
from .image_getter import get_surface, draw_img_to_rect
from .overlay import OverlayWindow
gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
# pylint: disable=wrong-import-position,wrong-import-order
from gi.repository import Gtk, Pango, PangoCairo # nopep8
from gi.repository import Pango, PangoCairo # nopep8
log = logging.getLogger(__name__)
@ -34,12 +33,54 @@ class NotificationOverlayWindow(OverlayWindow):
OverlayWindow.__init__(self, discover, piggyback)
self.text_spacing = 4
self.content = []
self.test_content = [{"icon": "https://cdn.discordapp.com/icons/951077080769114172/991abffc0d2a5c040444be4d1a4085f4.webp?size=96", "title": "Title1"},
{"title": "Title2", "body": "Body", "icon": None},
{"icon": "https://cdn.discordapp.com/icons/951077080769114172/991abffc0d2a5c040444be4d1a4085f4.webp?size=96", "title": "Title 3",
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
{"icon": None, "title": "Title 3", "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
{"icon": "https://cdn.discordapp.com/avatars/147077941317206016/6a6935192076489fa6dc1eb5dafbf6e7.webp?size=128", "title": "PM", "body": "Birdy test"}]
self.test_content = [
{
"icon": (
"https://cdn.discordapp.com/"
"icons/951077080769114172/991abffc0d2a5c040444be4d1a4085f4.webp?size=96"
),
"title": "Title1"
},
{
"title": "Title2",
"body": "Body",
"icon": None
},
{
"icon": ("https://cdn.discordapp.com/"
"icons/951077080769114172/991abffc0d2a5c040444be4d1a4085f4.webp?size=96"
),
"title": "Title 3",
"body": ("Lorem ipsum dolor sit amet, consectetur adipiscing elit,"
" sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris "
"nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in "
"reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla "
"pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa "
"qui officia deserunt mollit anim id est laborum."
)
},
{
"icon": None,
"title": "Title 3",
"body": ("Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
"Ut enim ad minim veniam, quis nostrud exercitation ullamco "
"laboris nisi ut aliquip ex ea commodo consequat. Duis aute "
"irure dolor in reprehenderit in voluptate velit esse cillum "
"dolore eu fugiat nulla pariatur. Excepteur sint occaecat "
"cupidatat non proident, sunt in culpa qui officia deserunt "
"mollit anim id est laborum."
)
},
{
"icon": ("https://cdn.discordapp.com/"
"avatars/147077941317206016/6a6935192076489fa6dc1eb5dafbf6e7.webp?size=128"
),
"title": "PM",
"body": "Birdy test"
}
]
self.text_font = None
self.text_size = 13
self.text_time = None
@ -67,11 +108,12 @@ class NotificationOverlayWindow(OverlayWindow):
self.redraw()
def set_blank(self):
"""Set to no data and redraw"""
self.content = []
self.set_needs_redraw()
def tick(self):
# This doesn't really belong in overlay or settings
"""Remove old messages from dataset"""
now = time.time()
newlist = []
oldsize = len(self.content)
@ -85,6 +127,7 @@ class NotificationOverlayWindow(OverlayWindow):
self.set_needs_redraw()
def add_notification_message(self, data):
"""Add new message to dataset"""
noti = None
data = data['data']
message_id = data['message']['id']
@ -108,59 +151,50 @@ class NotificationOverlayWindow(OverlayWindow):
self.get_all_images()
def set_padding(self, padding):
"""
Set the padding between notifications
"""
"""Config option: Padding between notifications, in window-space pixels"""
if self.padding != padding:
self.padding = padding
self.set_needs_redraw()
def set_border_radius(self, radius):
"""
Set the radius of the border
"""
"""Config option: Radius of the border, in window-space pixels"""
if self.border_radius != radius:
self.border_radius = radius
self.set_needs_redraw()
def set_icon_size(self, size):
"""
Set Icon size
"""
"""Config option: Size of icons, in window-space pixels"""
if self.icon_size != size:
self.icon_size = size
self.image_list = {}
self.get_all_images()
def set_icon_pad(self, pad):
"""
Set padding between icon and message
"""
"""Config option: Padding between icon and message, in window-space pixels"""
if self.icon_pad != pad:
self.icon_pad = pad
self.set_needs_redraw()
def set_icon_left(self, left):
"""Config option: Icon on left or right of text"""
if self.icon_left != left:
self.icon_left = left
self.set_needs_redraw()
def set_text_time(self, timer):
"""
Set the duration that a message will be visible for.
"""
"""Config option: Duration that a message will be visible for, in seconds"""
self.text_time = timer
self.timer_after_draw = timer
def set_limit_width(self, limit):
"""
Set the word wrap limit in pixels
"""Config option: Word wrap limit, in window-space pixels
"""
if self.limit_width != limit:
self.limit_width = limit
self.set_needs_redraw()
def get_all_images(self):
"""Return a list of all downloaded images"""
the_list = self.content
if self.testing:
the_list = self.test_content
@ -171,47 +205,38 @@ class NotificationOverlayWindow(OverlayWindow):
get_surface(self.recv_icon, icon, icon,
self.icon_size)
def recv_icon(self, identifier, pix, mask):
"""
Called when image_getter has downloaded an image
"""
def recv_icon(self, identifier, pix, _mask):
"""Callback from image_getter for icons"""
self.image_list[identifier] = pix
self.set_needs_redraw()
def set_fg(self, fg_col):
"""
Set default text colour
"""
"""Config option: Set default text colour"""
if self.fg_col != fg_col:
self.fg_col = fg_col
self.set_needs_redraw()
def set_bg(self, bg_col):
"""
Set background colour
"""
"""Config option: Set background colour"""
if self.bg_col != bg_col:
self.bg_col = bg_col
self.set_needs_redraw()
def set_show_icon(self, icon):
"""
Set if icons should be shown inline
"""
"""Config option: Set if icons should be shown inline"""
if self.show_icon != icon:
self.show_icon = icon
self.set_needs_redraw()
self.get_all_images()
def set_reverse_order(self, rev):
"""Config option: Reverse order of messages"""
if self.reverse_order != rev:
self.reverse_order = rev
self.set_needs_redraw()
def set_font(self, font):
"""
Set font used to render text
"""
"""Config option: Font used to render text"""
if self.text_font != font:
self.text_font = font
@ -222,13 +247,13 @@ class NotificationOverlayWindow(OverlayWindow):
self.set_needs_redraw()
def recv_attach(self, identifier, pix):
"""
Called when an image has been downloaded by image_getter
"""
"""Callback from image_getter for attachments"""
self.icons[identifier] = pix
self.set_needs_redraw()
def calc_all_height(self):
"""Return the height in window-space pixels required
to draw this overlay with current dataset"""
h = 0
my_list = self.content
if self.testing:
@ -240,6 +265,7 @@ class NotificationOverlayWindow(OverlayWindow):
return h
def calc_height(self, line):
"""Return height in window-space pixels required to draw individual notification"""
icon_width = 0
icon_pad = 0
icon = line['icon']
@ -257,9 +283,8 @@ class NotificationOverlayWindow(OverlayWindow):
layout = self.create_pango_layout(message)
layout.set_auto_dir(True)
layout.set_markup(message, -1)
attr = layout.get_attributes()
(floating_x, floating_y, floating_width,
floating_height) = self.get_floating_coords()
(_floating_x, _floating_y, floating_width,
_floating_height) = self.get_floating_coords()
width = self.limit_width if floating_width > self.limit_width else floating_width
layout.set_width((Pango.SCALE * (width -
(self.border_radius * 4 + icon_width + icon_pad))))
@ -267,12 +292,13 @@ class NotificationOverlayWindow(OverlayWindow):
if self.text_font:
font = Pango.FontDescription(self.text_font)
layout.set_font_description(font)
text_width, text_height = layout.get_pixel_size()
_text_width, text_height = layout.get_pixel_size()
if text_height < icon_width:
text_height = icon_width
return text_height + (self.border_radius*4) + self.padding
def has_content(self):
"""Return true if this overlay has meaningful content to show"""
if not self.enabled:
return False
if self.hidden:
@ -282,15 +308,13 @@ class NotificationOverlayWindow(OverlayWindow):
return self.content
def overlay_draw(self, w, context, data=None):
"""
Draw the overlay
"""
"""Draw the overlay"""
if self.piggyback:
self.piggyback.overlay_draw(w, context, data)
if not self.enabled:
return
self.context = context
(width, height) = self.get_size()
(_width, height) = self.get_size()
if not self.piggyback_parent:
context.set_antialias(cairo.ANTIALIAS_GOOD)
@ -319,7 +343,6 @@ class NotificationOverlayWindow(OverlayWindow):
current_y = 0
if self.align_vert == 1: # Center. Oh god why
current_y = (height/2.0) - (self.calc_all_height() / 2.0)
tnow = time.time()
if self.testing:
the_list = self.test_content
else:
@ -354,10 +377,7 @@ class NotificationOverlayWindow(OverlayWindow):
self.context = None
def draw_text(self, pos_y, text, icon):
"""
Draw a text message, returning the Y position of the next message
"""
"""Draw a text message, returning the Y position of the next message"""
icon_width = self.icon_size
icon_pad = self.icon_pad
if not self.show_icon:
@ -371,8 +391,8 @@ class NotificationOverlayWindow(OverlayWindow):
layout.set_markup(text, -1)
attr = layout.get_attributes()
(floating_x, floating_y, floating_width,
floating_height) = self.get_floating_coords()
(_floating_x, _floating_y, floating_width,
_floating_height) = self.get_floating_coords()
width = self.limit_width if floating_width > self.limit_width else floating_width
layout.set_width((Pango.SCALE * (width -
(self.border_radius * 4 + icon_width + icon_pad))))
@ -475,7 +495,6 @@ class NotificationOverlayWindow(OverlayWindow):
self.get_pango_context(), self.render_custom, None)
text = layout.get_text()
count = 0
layout.set_attributes(attr)
@ -486,7 +505,6 @@ class NotificationOverlayWindow(OverlayWindow):
self.get_pango_context(), self.render_custom, None)
text = layout.get_text()
count = 0
layout.set_attributes(attr)
@ -501,11 +519,9 @@ class NotificationOverlayWindow(OverlayWindow):
return next_y
def render_custom(self, ctx, shape, path, _data):
"""
Draw an inline image as a custom emoticon
"""
"""Draw an inline image as a custom emoticon"""
if shape.data >= len(self.image_list):
log.warning(f"{shape.data} >= {len(self.image_list)}")
log.warning("%s >= %s", shape.data, len(self.image_list))
return
# key is the url to the image
key = self.image_list[shape.data]
@ -521,9 +537,7 @@ class NotificationOverlayWindow(OverlayWindow):
return True
def sanitize_string(self, string):
"""
Sanitize a text message so that it doesn't intefere with Pango's XML format
"""
"""Sanitize a text message so that it doesn't intefere with Pango's XML format"""
string = string.replace("&", "&amp;")
string = string.replace("<", "&lt;")
string = string .replace(">", "&gt;")
@ -532,6 +546,7 @@ class NotificationOverlayWindow(OverlayWindow):
return string
def set_testing(self, testing):
"""Toggle placeholder images for testing"""
self.testing = testing
self.set_needs_redraw()
self.get_all_images()

View file

@ -19,7 +19,6 @@ import sys
import logging
import gi
import cairo
import Xlib
from Xlib.display import Display
from Xlib import X, Xatom
gi.require_version("Gtk", "3.0")
@ -46,7 +45,7 @@ class OverlayWindow(Gtk.Window):
"""
window = Gtk.Window()
screen = window.get_screen()
screen_type = "%s" % (screen)
screen_type = f"{screen}"
self.is_wayland = False
if "Wayland" in screen_type:
self.is_wayland = True
@ -124,10 +123,12 @@ class OverlayWindow(Gtk.Window):
# this process hanging if it happens
self.connect('destroy', self.window_exited)
def window_exited(self, window=None):
def window_exited(self, _window=None):
"""Window closed. Exit app"""
sys.exit(1)
def set_gamescope_xatom(self, enabled):
"""Set Gamescope XAtom to identify self as an overlay candidate"""
if self.piggyback_parent:
return
@ -136,7 +137,7 @@ class OverlayWindow(Gtk.Window):
self.is_xatom_set = enabled
display = Display()
atom = display.intern_atom("GAMESCOPE_EXTERNAL_OVERLAY")
opaq = display.intern_atom("_NET_WM_WINDOW_OPACITY")
# Since unused: _NET_WM_WINDOW_OPACITY
if self.get_toplevel().get_window():
topw = display.create_resource_object(
@ -148,7 +149,7 @@ class OverlayWindow(Gtk.Window):
log.info("Setting GAMESCOPE_EXTERNAL_OVERLAY to %s", enabled)
display.sync()
else:
log.warn("Unable to set GAMESCOPE_EXTERNAL_OVERLAY")
log.warning("Unable to set GAMESCOPE_EXTERNAL_OVERLAY")
def set_wayland_state(self):
"""
@ -169,13 +170,16 @@ class OverlayWindow(Gtk.Window):
GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.TOP, True)
def set_piggyback(self, other_overlay):
"""Sets as piggybacking off the given (other) overlay"""
other_overlay.piggyback = self
self.piggyback_parent = other_overlay
def has_content(self):
"""Return true if overlay has meaningful content"""
return False
def overlay_draw_pre(self, _w, context, data=None):
"""Prepare for drawing the overlay. Calls overlay_draw after preparations"""
content = self.has_content()
if self.piggyback and self.piggyback.has_content():
content = True
@ -224,16 +228,18 @@ class OverlayWindow(Gtk.Window):
"""
if width > 1.0 and height > 1.0:
# Old data.
(screen_x, screen_y, screen_width,
(_screen_x, _screen_y, screen_width,
screen_height) = self.get_display_coords()
pos_x = float(pos_x) / screen_width
pos_y = float(pos_y) / screen_height
width = float(width) / screen_width
height = float(height) / screen_height
if self.floating != floating or self.pos_x != pos_x or self.pos_y != pos_y or self.width != width or self.height != height:
if (self.floating != floating or self.pos_x != pos_x or
self.pos_y != pos_y or self.width != width or self.height != height):
# Special case for Cinnamon desktop : see https://github.com/trigg/Discover/issues/322
if 'XDG_SESSION_DESKTOP' in os.environ and os.environ['XDG_SESSION_DESKTOP'] == 'cinnamon':
if ('XDG_SESSION_DESKTOP' in os.environ and
os.environ['XDG_SESSION_DESKTOP'] == 'cinnamon'):
floating = True
self.floating = floating
@ -258,6 +264,7 @@ class OverlayWindow(Gtk.Window):
self.input_shape_combine_region(reg)
def set_hide_on_mouseover(self, hide):
"""Set if the overlay should hide when mouse moves over it"""
if self.hide_on_mouseover != hide:
self.hide_on_mouseover = hide
if self.hide_on_mouseover:
@ -266,6 +273,7 @@ class OverlayWindow(Gtk.Window):
self.set_untouchable()
def set_mouseover_timer(self, time):
"""Set the time until the overlay reappears after mouse over"""
self.timeout_mouse_over = time
def unset_shape(self):
@ -299,6 +307,7 @@ class OverlayWindow(Gtk.Window):
self.set_needs_redraw()
def get_display_coords(self):
"""Get screen space co-ordinates of the monitor"""
if self.piggyback_parent:
return self.piggyback_parent.get_display_coords()
monitor = self.get_monitor_from_plug()
@ -311,29 +320,35 @@ class OverlayWindow(Gtk.Window):
return (0, 0, 1920, 1080)
def get_floating_coords(self):
"""Get screen space co-ordinates of the window"""
(screen_x, screen_y, screen_width, screen_height) = self.get_display_coords()
if self.floating:
if self.pos_x == None or self.pos_y == None or self.width == None or self.height == None:
if (self.pos_x is None or self.pos_y is None or
self.width is None or self.height is None):
log.error("No usable floating position")
if not self.is_wayland:
return (screen_x + self.pos_x * screen_width, screen_y + self.pos_y * screen_height, self.width * screen_width, self.height * screen_height)
return (self.pos_x * screen_width, self.pos_y * screen_height, self.width * screen_width, self.height * screen_height)
return (screen_x + self.pos_x * screen_width, screen_y + self.pos_y * screen_height,
self.width * screen_width, self.height * screen_height)
return (self.pos_x * screen_width, self.pos_y * screen_height,
self.width * screen_width, self.height * screen_height)
else:
return (screen_x, screen_y, screen_width, screen_height)
def set_needs_redraw(self, be_pushy=False):
"""Schedule this overlay for a redraw. If part of a
piggyback chain, pass it up to be redrawn by topmost parent"""
if (not self.hidden and self.enabled) or be_pushy:
if self.piggyback_parent:
self.piggyback_parent.set_needs_redraw(be_pushy=True)
if self.redraw_id == None:
if self.redraw_id is None:
self.redraw_id = GLib.idle_add(self.redraw)
else:
log.debug("Already awaiting paint")
# If this overlay has data that expires after draw, plan for that here
if self.timer_after_draw != None:
if self.timer_after_draw is not None:
GLib.timeout_add_seconds(self.timer_after_draw, self.redraw)
def redraw(self):
@ -364,6 +379,7 @@ class OverlayWindow(Gtk.Window):
return False
def set_hidden(self, hidden):
"""Set if the overlay should be hidden"""
self.hidden = hidden
self.set_enabled(self.enabled)
@ -371,7 +387,7 @@ class OverlayWindow(Gtk.Window):
"""
Set the monitor this overlay should display on.
"""
plug_name = "%s" % (idx)
plug_name = f"{idx}"
if self.monitor != plug_name:
self.monitor = plug_name
if self.is_wayland:
@ -387,6 +403,8 @@ class OverlayWindow(Gtk.Window):
self.set_needs_redraw()
def get_monitor_from_plug(self):
"""Return a GDK Monitor filtered by plug name
(HDMI-1, eDP-1, VGA etc)"""
if not self.monitor or self.monitor == "Any":
return None
display = Gdk.Display.get_default()
@ -454,26 +472,31 @@ class OverlayWindow(Gtk.Window):
self.hide()
def set_task(self, visible):
"""Set visible on taskbar. Not working at last check"""
self.set_skip_pager_hint(not visible)
self.set_skip_taskbar_hint(not visible)
def check_composite(self, _a=None, _b=None):
# Called when an X11 session switched compositing on or off
"""Callback for compositing started/stopped in X11"""
self.redraw()
def screen_changed(self, screen=None):
def screen_changed(self, _screen=None):
"""Callback to set monitor to display on"""
self.set_monitor(self.monitor)
def mouseover(self, a=None, b=None):
def mouseover(self, _a=None, _b=None):
"""Callback when mouseover occurs, hides overlay"""
self.draw_blank = True
self.set_needs_redraw()
return True
def mouseout(self, a=None, b=None):
def mouseout(self, _a=None, _b=None):
"""Callback when mouseout occurs, sets a timer to show overlay"""
GLib.timeout_add_seconds(self.timeout_mouse_over, self.mouseout_timed)
return True
def mouseout_timed(self, a=None, b=None):
def mouseout_timed(self, _a=None, _b=None):
"""Callback a short while after mouseout occured, shows overlay"""
self.draw_blank = False
self.set_needs_redraw()
return False

View file

@ -11,18 +11,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Settings window holding all settings tab"""
# pylint: disable=missing-function-docstring
import gettext
import gi
import logging
import pkg_resources
import sys
import os
import json
from configparser import ConfigParser
import gi
import pkg_resources
from .autostart import Autostart, BazziteAutostart
from .draggable_window import DraggableWindow
from .draggable_window_wayland import DraggableWindowWayland
from configparser import ConfigParser
gi.require_version("Gtk", "3.0")
# pylint: disable=wrong-import-position,wrong-import-order
from gi.repository import Gtk, Gdk, Gio # nopep8
@ -75,6 +76,14 @@ class MainSettingsWindow():
self.current_guild = "0"
self.current_channel = "0"
self.hidden_overlay = False
self.voice_floating_x = 0
self.voice_floating_y = 0
self.voice_floating_w = 0
self.voice_floating_h = 0
self.text_floating_x = 0
self.text_floating_y = 0
self.text_floating_w = 0
self.text_floating_h = 0
self.menu = self.make_menu()
self.make_sys_tray_icon(self.menu)
@ -111,16 +120,30 @@ class MainSettingsWindow():
if name.endswith("_all"):
widget.set_label(_(widget.get_label()))
self.widget['overview_main_text'].set_markup("<span size=\"larger\">%s (%s)</span>\n\n%s\n\n%s (<a href=\"https://discord.gg/jRKWMuDy5V\">https://discord.gg/jRKWMuDy5V</a>) %s (<a href=\"https://github.com/trigg/Discover\">https://github.com/trigg/Discover</a>)\n\n\n\n\n\n" % (
self.widget['overview_main_text'].set_markup(
"%s%s (%s)%s%s\n\n%s %s %s %s%s\n\n\n\n\n\n" % (
"<span size=\"larger\">",
_("Welcome to Discover Overlay"),
pkg_resources.get_distribution('discover_overlay').version,
_("Discover-Overlay is a GTK3 overlay written in Python3. It can be configured to show who is currently talking on discord or it can be set to display text and images from a preconfigured channel. It is fully customisable and can be configured to display anywhere on the screen. We fully support X11 and wlroots based environments. We felt the need to make this project due to the shortcomings in support on Linux by the official discord client."),
"</span>\n\n",
_(("Discover-Overlay is a GTK3 overlay written in Python3."
" It can be configured to show who is currently talking"
" on discord or it can be set to display text and images"
" from a preconfigured channel. It is fully customisable"
" and can be configured to display anywhere on the screen."
" We fully support X11 and wlroots based environments. We "
"felt the need to make this project due to the shortcomings"
" in support on Linux by the official discord client.")),
_("Please visit our discord"),
_(" for support. Or open an issue on our GitHub ")
))
"(<a href=\"https://discord.gg/jRKWMuDy5V\">https://discord.gg/jRKWMuDy5V</a>)",
_(" for support. Or open an issue on our GitHub "),
"(<a href=\"https://github.com/trigg/Discover\">",
"https://github.com/trigg/Discover</a>)"
)
)
screen = window.get_screen()
screen_type = "%s" % (screen)
screen_type = f"{screen}"
self.is_wayland = False
if "Wayland" in screen_type:
self.is_wayland = True
@ -185,6 +208,7 @@ class MainSettingsWindow():
self.widget['window'].set_default_icon_name(self.icon_name)
def set_steamos_window_size(self):
"""Set window based on steamos usage"""
# Huge bunch of assumptions.
# Gamescope only has one monitor
# Gamescope has no scale factor
@ -193,23 +217,21 @@ class MainSettingsWindow():
monitor = display.get_monitor(0)
if monitor:
geometry = monitor.get_geometry()
scale_factor = monitor.get_scale_factor()
log.info("%d %d" % (geometry.width, geometry.height))
log.info("%d %d", geometry.width, geometry.height)
self.window.set_size_request(geometry.width, geometry.height)
def keypress_in_settings(self, window, event):
"""Callback to steal keypresses to assist SteamOS gamepad control"""
if self.spinning_focus:
match event.keyval:
case Gdk.KEY_Right:
step = self.spinning_focus.get_increments().step
value = self.spinning_focus.get_value()
self.spinning_focus.set_value(value + step)
pass
case Gdk.KEY_Left:
step = self.spinning_focus.get_increments().step
value = self.spinning_focus.get_value()
self.spinning_focus.set_value(value - step)
pass
case Gdk.KEY_Up:
step = self.spinning_focus.get_increments().step
value = self.spinning_focus.get_value()
@ -230,11 +252,9 @@ class MainSettingsWindow():
case Gdk.KEY_Right:
value = self.scale_focus.get_value()
self.scale_focus.set_value(value + 0.1)
pass
case Gdk.KEY_Left:
value = self.scale_focus.get_value()
self.scale_focus.set_value(value - 0.1)
pass
case Gdk.KEY_Up:
value = self.scale_focus.get_value()
self.scale_focus.set_value(value + 0.1)
@ -267,7 +287,7 @@ class MainSettingsWindow():
widget = self.window.get_focus()
if widget:
# I really want there to be a better way...
widget_type = "%s" % (widget)
widget_type = f"{widget}"
if 'Gtk.SpinButton' in widget_type:
self.spinning_focus = widget
@ -285,16 +305,19 @@ class MainSettingsWindow():
return True
def request_channels_from_guild(self, guild_id):
with open(self.rpc_file, 'w') as f:
f.write('--rpc --guild-request=%s' % (guild_id))
"""Send RPC to overlay to request updated channel list"""
with open(self.rpc_file, 'w', encoding="utf-8") as f:
f.write(f"--rpc --guild-request={guild_id}")
def populate_guild_menu(self, _a=None, _b=None, _c=None, _d=None):
"""Read guild data and repopulate widget.
Disable signal handling meanwhile to avoid recursive logic"""
g = self.widget['text_server']
c = self.widget['text_channel']
g.handler_block(self.server_handler)
c.handler_block(self.channel_handler)
try:
with open(self.channel_file, "r") as tfile:
with open(self.channel_file, "r", encoding="utf-8") as tfile:
data = tfile.readlines()
if len(data) >= 1:
data = json.loads(data[0])
@ -322,21 +345,22 @@ class MainSettingsWindow():
c.handler_unblock(self.channel_handler)
def populate_monitor_menus(self, _a=None, _b=None):
v = self.widget['voice_monitor']
t = self.widget['text_monitor']
m = self.widget['notification_monitor']
"""Get Monitor list from GTK and repopulate widget"""
voice = self.widget['voice_monitor']
text = self.widget['text_monitor']
notify = self.widget['notification_monitor']
v_value = v.get_active()
t_value = t.get_active()
m_value = m.get_active()
v_value = voice.get_active()
t_value = text.get_active()
m_value = notify.get_active()
v.remove_all()
t.remove_all()
m.remove_all()
voice.remove_all()
text.remove_all()
notify.remove_all()
v.append_text("Any")
t.append_text("Any")
m.append_text("Any")
voice.append_text("Any")
text.append_text("Any")
notify.append_text("Any")
display = Gdk.Display.get_default()
screen = self.window.get_screen()
@ -348,41 +372,38 @@ class MainSettingsWindow():
manufacturer = this_mon.get_manufacturer()
model = this_mon.get_model()
connector = screen.get_monitor_plug_name(i)
monitor_label = "%s %s\n%s" % (
manufacturer, model, connector)
v.append_text(monitor_label)
t.append_text(monitor_label)
m.append_text(monitor_label)
monitor_label = f"{manufacturer} {model}\n{connector}"
voice.append_text(monitor_label)
text.append_text(monitor_label)
notify.append_text(monitor_label)
v.set_active(v_value)
t.set_active(t_value)
m.set_active(m_value)
voice.set_active(v_value)
text.set_active(t_value)
notify.set_active(m_value)
def close_window(self, widget=None, event=None):
"""
Hide the settings window for use at a later date
"""
def close_window(self, _widget=None, _event=None):
"""Hide the settings window for use at a later date"""
self.window.hide()
if self.ind == None and self.tray == None:
if self.ind is None and self.tray is None:
sys.exit(0)
if self.ind != None:
if self.ind is not None:
# pylint: disable=import-outside-toplevel
from gi.repository import AppIndicator3
if self.ind.get_status() == AppIndicator3.IndicatorStatus.PASSIVE:
sys.exit(0)
return True
def close_app(self, widget=None, event=None):
def close_app(self, _widget=None, _event=None):
"""Close the app"""
sys.exit(0)
def present_settings(self, _a=None):
"""
Show the settings window
"""
"""Show the settings window"""
self.widget['notebook'].set_current_page(0)
self.window.show()
def set_alignment_labels(self, horz):
"""Relabel alignment pulldowns"""
m1 = self.widget['voice_align_1'].get_model()
m2 = self.widget['voice_align_2'].get_model()
i = m1.get_iter_first()
@ -409,6 +430,7 @@ class MainSettingsWindow():
m2.set_value(i2, 0, _("Bottom"))
def read_config(self):
"""Read config from disk"""
self.loading_config = True
# Read config and put into gui
@ -522,8 +544,8 @@ class MainSettingsWindow():
self.widget['voice_square_avatar'].set_active(config.getboolean(
"main", "square_avatar", fallback=True))
self.widget['voice_fancy_avatar_shapes'].set_active(config.getboolean("main",
"fancy_border", fallback=True))
self.widget['voice_fancy_avatar_shapes'].set_active(
config.getboolean("main", "fancy_border", fallback=True))
self.widget['voice_order_avatars_by'].set_active(
config.getint("main", "order", fallback=0))
@ -719,6 +741,7 @@ class MainSettingsWindow():
self.loading_config = False
def make_colour(self, col):
"""Create a Gdk Color from a col tuple"""
col = json.loads(col)
return Gdk.RGBA(col[0], col[1], col[2], col[3])
@ -732,6 +755,7 @@ class MainSettingsWindow():
return guild_ids
def get_monitor_index_from_plug(self, monitor):
"""Get monitor index from plug name"""
if not monitor or monitor == "Any":
return 0
display = Gdk.Display.get_default()
@ -746,9 +770,7 @@ class MainSettingsWindow():
return 0
def get_monitor_obj(self, idx):
"""
Helper function to find the monitor object of the monitor
"""
"""Helper function to find the monitor object of the monitor"""
display = Gdk.Display.get_default()
return display.get_monitor(idx)
@ -780,17 +802,13 @@ class MainSettingsWindow():
self.tray.set_visible(False)
def show_menu(self, obj, button, time):
"""
Show menu when System Tray icon is clicked
"""
"""Show menu when System Tray icon is clicked"""
self.menu.show_all()
self.menu.popup(
None, None, Gtk.StatusIcon.position_menu, obj, button, time)
def set_sys_tray_icon_visible(self, visible):
"""
Sets whether the tray icon is visible
"""
"""Sets whether the tray icon is visible"""
if self.ind is not None:
# pylint: disable=import-outside-toplevel
from gi.repository import AppIndicator3
@ -800,9 +818,7 @@ class MainSettingsWindow():
self.tray.set_visible(visible)
def make_menu(self):
"""
Create System Menu
"""
"""Create System Menu"""
menu = Gtk.Menu()
settings_opt = Gtk.MenuItem.new_with_label(_("Settings"))
self.toggle_opt = Gtk.MenuItem.new_with_label(_("Hide overlay"))
@ -822,11 +838,13 @@ class MainSettingsWindow():
return menu
def toggle_overlay(self, _a=None, _b=None):
"""Toggle overlay visibility"""
self.hidden_overlay = not self.hidden_overlay
self.config_set("general", "hideoverlay", "%s" % (self.hidden_overlay))
self.config_set("general", "hideoverlay", f"{self.hidden_overlay}")
self.update_toggle_overlay()
def update_toggle_overlay(self, _a=None, _b=None):
"""Update gui to reflect state of overlay visibility"""
self.widget['core_hide_overlay'].handler_block(
self.hidden_overlay_handler)
@ -840,14 +858,17 @@ class MainSettingsWindow():
self.toggle_opt.set_label(_("Hide overlay"))
def close_overlay(self, _a=None, _b=None):
with open(self.rpc_file, 'w') as f:
"""Send RPC to tell the overlay to close"""
with open(self.rpc_file, 'w', encoding="utf-8") as f:
f.write('--rpc --close')
def overview_close(self, button):
def overview_close(self, _button):
"""Gui callback to close overlay. Remove and use close_overlay?"""
log.info("Quit pressed")
self.close_overlay()
def voice_place_window(self, button):
"""Toggle the voice placement"""
if self.voice_placement_window:
(pos_x, pos_y, width, height) = self.voice_placement_window.get_coords()
self.voice_floating_x = pos_x
@ -857,18 +878,14 @@ class MainSettingsWindow():
config = ConfigParser(interpolation=None)
config.read(self.config_file)
if not "main" in config.sections():
if "main" not in config.sections():
config.add_section("main")
config.set("main", "floating_x", "%f" %
(self.voice_floating_x))
config.set("main", "floating_y", "%f" %
(self.voice_floating_y))
config.set("main", "floating_w", "%f" %
(self.voice_floating_w))
config.set("main", "floating_h", "%f" %
(self.voice_floating_h))
config.set("main", "floating_x", f"{self.voice_floating_x:f}")
config.set("main", "floating_y", f"{self.voice_floating_y:f}")
config.set("main", "floating_w", f"{self.voice_floating_w:f}")
config.set("main", "floating_h", f"{self.voice_floating_h:f}")
with open(self.config_file, 'w') as file:
with open(self.config_file, 'w', encoding="utf-8") as file:
config.write(file)
if button:
button.set_label(_("Place Window"))
@ -890,11 +907,13 @@ class MainSettingsWindow():
self.voice_placement_window = DraggableWindow(
pos_x=self.voice_floating_x, pos_y=self.voice_floating_y,
width=self.voice_floating_w, height=self.voice_floating_h,
message=_("Place & resize this window then press Save!"), settings=self, monitor=self.widget['voice_monitor'].get_active()-1)
message=_("Place & resize this window then press Save!"),
settings=self, monitor=self.widget['voice_monitor'].get_active()-1)
if button:
button.set_label(_("Save this position"))
def text_place_window(self, button):
"""Toggle the text placement"""
if self.text_placement_window:
(pos_x, pos_y, width, height) = self.text_placement_window.get_coords()
self.text_floating_x = pos_x
@ -904,18 +923,14 @@ class MainSettingsWindow():
config = ConfigParser(interpolation=None)
config.read(self.config_file)
if not "text" in config.sections():
if "text" not in config.sections():
config.add_section("text")
config.set("text", "floating_x", "%f" %
(self.text_floating_x))
config.set("text", "floating_y", "%f" %
(self.text_floating_y))
config.set("text", "floating_w", "%f" %
(self.text_floating_w))
config.set("text", "floating_h", "%f" %
(self.text_floating_h))
config.set("text", "floating_x", f"{self.text_floating_x:f}")
config.set("text", "floating_y", f"{self.text_floating_y:f}")
config.set("text", "floating_w", f"{self.text_floating_w:f}")
config.set("text", "floating_h", f"{self.text_floating_h:f}")
with open(self.config_file, 'w') as file:
with open(self.config_file, 'w', encoding="utf-8") as file:
config.write(file)
if button:
button.set_label(_("Place Window"))
@ -937,21 +952,25 @@ class MainSettingsWindow():
self.text_placement_window = DraggableWindow(
pos_x=self.text_floating_x, pos_y=self.text_floating_y,
width=self.text_floating_w, height=self.text_floating_h,
message=_("Place & resize this window then press Save!"), settings=self, monitor=self.widget['text_monitor'].get_active()-1)
message=_("Place & resize this window then press Save!"),
settings=self, monitor=self.widget['text_monitor'].get_active()-1)
if button:
button.set_label(_("Save this position"))
def change_placement(self, placement_window):
"""Finish window placement"""
if placement_window == self.text_placement_window:
self.text_place_window(None)
elif placement_window == self.voice_placement_window:
self.voice_place_window(None)
def text_server_refresh(self, button):
with open(self.rpc_file, 'w') as f:
def text_server_refresh(self, _button):
"""Send RPC to overlay to request a list of text channels"""
with open(self.rpc_file, 'w', encoding="utf-8") as f:
f.write('--rpc --refresh-guilds')
def config_set(self, context, key, value):
"""Write one key to config and save to disk"""
if self.loading_config:
return
config = ConfigParser(interpolation=None)
@ -959,10 +978,11 @@ class MainSettingsWindow():
if not context in config.sections():
config.add_section(context)
config.set(context, key, value)
with open(self.config_file, 'w') as file:
with open(self.config_file, 'w', encoding="utf-8") as file:
config.write(file)
def config_remove_section(self, context):
"""Remove a section from config and save to disk"""
if self.loading_config:
return
config = ConfigParser(interpolation=None)
@ -970,12 +990,12 @@ class MainSettingsWindow():
if context in config.sections():
config.remove_section(context)
else:
log.error("Unable to remove section %s" % (context))
with open(self.config_file, 'w') as file:
log.error("Unable to remove section %s", context)
with open(self.config_file, 'w', encoding="utf-8") as file:
config.write(file)
def voice_anchor_float_changed(self, button):
self.config_set("main", "floating", "%s" % (button.get_active() == 0))
self.config_set("main", "floating", f"{(button.get_active() == 0)}")
self.update_floating_anchor()
def update_floating_anchor(self):
@ -1003,10 +1023,10 @@ class MainSettingsWindow():
self.config_set("main", "monitor", plug)
def voice_align_1_changed(self, button):
self.config_set("main", "rightalign", "%s" % (button.get_active()))
self.config_set("main", "rightalign", f"{button.get_active()}")
def voice_align_2_changed(self, button):
self.config_set("main", "topalign", "%s" % (button.get_active()))
self.config_set("main", "topalign", f"{button.get_active()}")
def voice_font_changed(self, button):
self.config_set("main", "font", button.get_font())
@ -1015,41 +1035,39 @@ class MainSettingsWindow():
self.config_set("main", "title_font", button.get_font())
def voice_icon_spacing_changed(self, button):
self.config_set("main", "icon_spacing", "%s" %
(int(button.get_value())))
self.config_set("main", "icon_spacing", f"{int(button.get_value())}")
def voice_text_padding_changed(self, button):
self.config_set("main", "text_padding", "%s" %
(int(button.get_value())))
self.config_set("main", "text_padding", f"{int(button.get_value())}")
def voice_text_vertical_offset_changed(self, button):
self.config_set("main", "text_baseline_adj", "%s" %
(int(button.get_value())))
self.config_set("main", "text_baseline_adj",
f"{int(button.get_value())}")
def voice_vertical_padding_changed(self, button):
self.config_set("main", "vert_edge_padding", "%s" %
(int(button.get_value())))
self.config_set("main", "vert_edge_padding",
f"{int(button.get_value())}")
def voice_horizontal_padding_changed(self, button):
self.config_set("main", "horz_edge_padding", "%s" %
(int(button.get_value())))
self.config_set("main", "horz_edge_padding",
f"{int(button.get_value())}")
def voice_display_horizontally_changed(self, button):
self.config_set("main", "horizontal", "%s" % (button.get_active()))
self.config_set("main", "horizontal", f"{button.get_active()}")
self.set_alignment_labels(button.get_active())
def voice_highlight_self_changed(self, button):
self.config_set("main", "highlight_self", "%s" % (button.get_active()))
self.config_set("main", "highlight_self", f"{button.get_active()}")
def voice_display_speakers_only(self, button):
self.config_set("main", "only_speaking", "%s" % (button.get_active()))
self.config_set("main", "only_speaking", f"{button.get_active()}")
def voice_display_speakers_grace_period(self, button):
self.config_set("main", "only_speaking_grace", "%s" %
(int(button.get_value())))
self.config_set("main", "only_speaking_grace",
f"{int(button.get_value())}")
def voice_toggle_test_content(self, button):
self.config_set("main", "show_dummy", "%s" % (button.get_active()))
self.config_set("main", "show_dummy", f"{button.get_active()}")
def voice_talking_foreground_changed(self, button):
colour = button.get_rgba()
@ -1097,54 +1115,48 @@ class MainSettingsWindow():
self.config_set("main", "avatar_bg_col", json.dumps(colour))
def voice_avatar_opacity_changed(self, button):
self.config_set("main", "icon_transparency", "%.2f" %
(button.get_value()))
self.config_set("main", "icon_transparency",
f"{button.get_value():.2f}")
def voice_avatar_size_changed(self, button):
self.config_set("main", "avatar_size", "%s" %
(int(button.get_value())))
self.config_set("main", "avatar_size", f"{int(button.get_value())}")
def voice_nick_length_changed(self, button):
self.config_set("main", "nick_length", "%s" %
(int(button.get_value())))
self.config_set("main", "nick_length", f"{int(button.get_value())}")
def voice_display_icon_only_changed(self, button):
self.config_set("main", "icon_only", "%s" % (not button.get_active()))
self.config_set("main", "icon_only", f"{(not button.get_active())}")
self.voice_show_name_hide_others(button.get_active())
def voice_square_avatar_changed(self, button):
self.config_set("main", "square_avatar", "%s" % (button.get_active()))
self.config_set("main", "square_avatar", f"{button.get_active()}")
def voice_fancy_avatar_shapes_changed(self, button):
self.config_set("main", "fancy_border", "%s" % (button.get_active()))
self.config_set("main", "fancy_border", f"{button.get_active()}")
def voice_order_avatars_by_changed(self, button):
self.config_set("main", "order", "%s" % (button.get_active()))
self.config_set("main", "order", f"{button.get_active()}")
def voice_border_width_changed(self, button):
self.config_set("main", "border_width", "%s" %
(int(button.get_value())))
self.config_set("main", "border_width", f"{int(button.get_value())}")
def voice_overflow_style_changed(self, button):
self.config_set("main", "overflow", "%s" % (int(button.get_active())))
self.config_set("main", "overflow", f"{int(button.get_active())}")
def voice_show_title_changed(self, button):
self.config_set("main", "show_title", "%s" % (button.get_active()))
self.config_set("main", "show_title", f"{button.get_active()}")
def voice_show_connection_status_changed(self, button):
self.config_set("main", "show_connection", "%s" %
(button.get_active()))
self.config_set("main", "show_connection", f"{button.get_active()}")
def voice_show_disconnected_changed(self, button):
self.config_set("main", "show_disconnected", "%s" %
(button.get_active()))
self.config_set("main", "show_disconnected", f"{button.get_active()}")
def voice_dummy_count_changed(self, button):
self.config_set("main", "dummy_count", "%s" %
(int(button.get_value())))
self.config_set("main", "dummy_count", f"{int(button.get_value())}")
def voice_show_avatar_changed(self, button):
self.config_set("main", "show_avatar", "%s" % (button.get_active()))
self.config_set("main", "show_avatar", f"{button.get_active()}")
self.voice_show_avatar_hide_others(button.get_active())
def voice_show_name_hide_others(self, val):
@ -1176,13 +1188,13 @@ class MainSettingsWindow():
self.widget['voice_avatar_opacity'].set_sensitive(False)
def text_enable_changed(self, button):
self.config_set("text", "enabled", "%s" % (button.get_active()))
self.config_set("text", "enabled", f"{button.get_active()}")
def text_popup_style_changed(self, button):
self.config_set("text", "popup_style", "%s" % (button.get_active()))
self.config_set("text", "popup_style", f"{button.get_active()}")
def text_popup_time_changed(self, button):
self.config_set("text", "text_time", "%s" % (int(button.get_value())))
self.config_set("text", "text_time", f"{int(button.get_value())}")
def text_server_changed(self, button):
if button.get_active() < 0:
@ -1225,25 +1237,24 @@ class MainSettingsWindow():
self.config_set("text", "monitor", plug)
def text_show_attachments_changed(self, button):
self.config_set("text", "show_attach", "%s" % (button.get_active()))
self.config_set("text", "show_attach", f"{button.get_active()}")
def text_line_limit_changed(self, button):
self.config_set("text", "line_limit", "%s" % (int(button.get_value())))
self.config_set("text", "line_limit", f"{int(button.get_value())}")
def notification_enable_changed(self, button):
self.config_set("notification", "enabled", "%s" %
(button.get_active()))
self.config_set("notification", "enabled", f"{button.get_active()}")
def notification_reverse_order_changed(self, button):
self.config_set("notification", "rev", "%s" % (button.get_active()))
self.config_set("notification", "rev", f"{button.get_active()}")
def notification_popup_timer_changed(self, button):
self.config_set("notification", "text_time", "%s" %
(int(button.get_value())))
self.config_set("notification", "text_time",
f"{int(button.get_value())}")
def notification_limit_popup_width_changed(self, button):
self.config_set("notification", "limit_width", "%s" %
(int(button.get_value())))
self.config_set("notification", "limit_width",
f"{int(button.get_value())}")
def notification_font_changed(self, button):
self.config_set("notification", "font", button.get_font())
@ -1267,40 +1278,36 @@ class MainSettingsWindow():
self.config_set("notification", "monitor", plug)
def notification_align_1_changed(self, button):
self.config_set("notification", "rightalign", "%s" %
(button.get_active()))
self.config_set("notification", "rightalign", f"{button.get_active()}")
def notification_align_2_changed(self, button):
self.config_set("notification", "topalign", "%s" %
(button.get_active()))
self.config_set("notification", "topalign", f"{button.get_active()}")
def notification_show_icon(self, button):
self.config_set("notification", "show_icon", "%s" %
(button.get_active()))
self.config_set("notification", "show_icon", f"{button.get_active()}")
def notification_icon_position_changed(self, button):
self.config_set("notification", "icon_left", "%s" %
(int(button.get_active() != 1)))
self.config_set("notification", "icon_left", f"{
int(button.get_active() != 1)}")
def notification_icon_padding_changed(self, button):
self.config_set("notification", "icon_padding", "%s" %
(int(button.get_value())))
self.config_set("notification", "icon_padding",
f"{int(button.get_value())}")
def notification_icon_size_changed(self, button):
self.config_set("notification", "icon_size", "%s" %
(int(button.get_value())))
self.config_set("notification", "icon_size",
f"{int(button.get_value())}")
def notification_padding_between_changed(self, button):
self.config_set("notification", "padding", "%s" %
(int(button.get_value())))
self.config_set("notification", "padding",
f"{int(button.get_value())}")
def notification_border_radius_changed(self, button):
self.config_set("notification", "border_radius", "%s" %
(int(button.get_value())))
self.config_set("notification", "border_radius",
f"{int(button.get_value())}")
def notification_show_test_content_changed(self, button):
self.config_set("notification", "show_dummy", "%s" %
(button.get_active()))
self.config_set("notification", "show_dummy", f"{button.get_active()}")
def core_run_on_startup_changed(self, button):
self.autostart_helper.set_autostart(button.get_active())
@ -1309,65 +1316,60 @@ class MainSettingsWindow():
self.autostart_helper_conf.set_autostart(button.get_active())
def core_force_xshape_changed(self, button):
self.config_set("general", "xshape", "%s" % (button.get_active()))
self.config_set("general", "xshape", f"{button.get_active()}")
def core_show_tray_icon_changed(self, button):
self.set_sys_tray_icon_visible(button.get_active())
self.config_set("general", "showsystray", "%s" % (button.get_active()))
self.config_set("general", "showsystray", f"{button.get_active()}")
self.widget['core_settings_min'].set_sensitive(button.get_active())
def core_hide_overlay_changed(self, button):
def core_hide_overlay_changed(self, _button):
self.toggle_overlay()
def core_settings_min_changed(self, button):
self.config_set("general", "start_min", "%s" % (button.get_active()))
self.config_set("general", "start_min", f"{button.get_active()}")
def core_reset_all(self, button):
def core_reset_all(self, _button):
self.config_remove_section("general")
self.read_config()
def voice_reset_all(self, button):
def voice_reset_all(self, _button):
self.config_remove_section("main")
self.read_config()
def text_reset_all(self, button):
def text_reset_all(self, _button):
self.config_remove_section("text")
self.read_config()
def notification_reset_all(self, button):
def notification_reset_all(self, _button):
self.config_remove_section("notification")
self.read_config()
def voice_hide_mouseover_changed(self, button):
self.config_set("main", "autohide", "%s" % (button.get_active()))
self.config_set("main", "autohide", f"{button.get_active()}")
def text_hide_mouseover_changed(self, button):
self.config_set("text", "autohide", "%s" % (button.get_active()))
self.config_set("text", "autohide", f"{button.get_active()}")
def voice_mouseover_timeout_changed(self, button):
self.config_set("main", "autohide_timer", "%s" %
(int(button.get_value())))
self.config_set("main", "autohide_timer", f"{int(button.get_value())}")
def text_mouseover_timeout_changed(self, button):
self.config_set("text", "autohide_timer", "%s" %
(int(button.get_value())))
self.config_set("text", "autohide_timer", f"{int(button.get_value())}")
def inactive_fade_changed(self, button):
self.config_set("main", "fade_out_inactive", "%s" %
(button.get_active()))
self.config_set("main", "fade_out_inactive", f"{button.get_active()}")
def inactive_fade_opacity_changed(self, button):
self.config_set("main", "fade_out_limit", "%.2f" %
(button.get_value()))
self.config_set("main", "fade_out_limit",
f"{button.get_value():.2f}")
def inactive_time_changed(self, button):
self.config_set("main", "inactive_time", "%s" %
(int(button.get_value())))
self.config_set("main", "inactive_time", f"{int(button.get_value())}")
def inactive_fade_time_changed(self, button):
self.config_set("main", "inactive_fade_time", "%s" %
(int(button.get_value())))
self.config_set("main", "inactive_fade_time",
f"{int(button.get_value())}")
def core_audio_assist_changed(self, button):
self.config_set("general", "audio_assist", "%s" %
(button.get_active()))
self.config_set("general", "audio_assist", f"{button.get_active()}")

View file

@ -21,7 +21,7 @@ from .overlay import OverlayWindow
gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
# pylint: disable=wrong-import-position,wrong-import-order
from gi.repository import Pango, PangoCairo, GLib # nopep8
from gi.repository import Pango, PangoCairo # nopep8
log = logging.getLogger(__name__)
@ -56,10 +56,12 @@ class TextOverlayWindow(OverlayWindow):
self.redraw()
def set_blank(self):
""" Set contents blank and redraw """
self.content = []
self.set_needs_redraw()
def tick(self):
""" Check for old images """
if len(self.attachment) > self.line_limit:
# We've probably got old images!
oldlist = self.attachment
@ -72,57 +74,43 @@ class TextOverlayWindow(OverlayWindow):
self.attachment[url] = oldlist[url]
def set_text_time(self, timer):
"""
Set the duration that a message will be visible for.
"""
"""Config option: Time before messages disappear from overlay"""
if self.text_time != timer or self.timer_after_draw != timer:
self.text_time = timer
self.timer_after_draw = timer
self.set_needs_redraw()
def set_text_list(self, tlist, altered):
"""
Update the list of text messages to show
"""
"""Change contents of overlay"""
self.content = tlist[-self.line_limit:]
if altered:
self.set_needs_redraw()
def set_fg(self, fg_col):
"""
Set default text colour
"""
"""Config option: Sets the text colour"""
if self.fg_col != fg_col:
self.fg_col = fg_col
self.set_needs_redraw()
def set_bg(self, bg_col):
"""
Set background colour
"""
"""Config option: Set the background colour"""
if self.bg_col != bg_col:
self.bg_col = bg_col
self.set_needs_redraw()
def set_show_attach(self, attachment):
"""
Set if attachments should be shown inline
"""
"""Config option: Show image attachments"""
if self.attachment != attachment:
self.show_attach = attachment
self.set_needs_redraw()
def set_popup_style(self, boolean):
"""
Set if message disappear after a certain duration
"""
"""Config option: Messages should disappear after being shown for some time"""
if self.popup_style != boolean:
self.popup_style = boolean
def set_font(self, font):
"""
Set font used to render text
"""
"""Config option: Set font used for rendering"""
if self.text_font != font:
self.text_font = font
@ -133,28 +121,24 @@ class TextOverlayWindow(OverlayWindow):
self.set_needs_redraw()
def set_line_limit(self, limit):
"""
Change maximum number of lines in overlay
"""
"""Config option: Limit number of lines rendered"""
if self.line_limit != limit:
self.line_limit = limit
def make_line(self, message):
"""
Decode a recursive JSON object into pango markup.
"""
"""Decode a recursive JSON object into pango markup."""
ret = ""
if isinstance(message, list):
for inner_message in message:
ret = "%s%s" % (ret, self.make_line(inner_message))
ret = f"{ret}{self.make_line(inner_message)}"
elif isinstance(message, str):
ret = self.sanitize_string(message)
elif message['type'] == 'strong':
ret = "<b>%s</b>" % (self.make_line(message['content']))
ret = f"<b>{self.make_line(message['content'])}</b>"
elif message['type'] == 'text':
ret = self.sanitize_string(message['content'])
elif message['type'] == 'link':
ret = "<u>%s</u>" % (self.make_line(message['content']))
ret = f"<u>{self.make_line(message['content'])}</u>"
elif message['type'] == 'emoji':
if 'surrogate' in message:
# ['src'] is SVG URL
@ -163,20 +147,21 @@ class TextOverlayWindow(OverlayWindow):
else:
### Add Image ###
self.image_list.append(
f"https://cdn.discordapp.com/emojis/{message['emojiId']}.png?v=1"
f"https://cdn.discordapp.com/emojis/{
message['emojiId']}.png?v=1"
)
ret = "`"
elif (message['type'] == 'inlineCode' or
message['type'] == 'codeBlock' or
message['type'] == 'blockQuote'):
ret = "<span font_family=\"monospace\" background=\"#0004\">%s</span>" % (
self.make_line(message['content']))
ret = f"<span font_family=\"monospace\" background=\"#0004\">{
self.make_line(message['content'])}</span>"
elif message['type'] == 'u':
ret = "<u>%s</u>" % (self.make_line(message['content']))
ret = f"<u>{self.make_line(message['content'])}</u>"
elif message['type'] == 'em':
ret = "<i>%s</i>" % (self.make_line(message['content']))
ret = f"<i>{self.make_line(message['content'])}</i>"
elif message['type'] == 's':
ret = "<s>%s</s>" % (self.make_line(message['content']))
ret = f"<s>{self.make_line(message['content'])}</s>"
elif message['type'] == 'channel':
ret = self.make_line(message['content'])
elif message['type'] == 'mention':
@ -189,14 +174,13 @@ class TextOverlayWindow(OverlayWindow):
self.warned_filetypes.append(message['type'])
return ret
def recv_attach(self, identifier, pix, mask):
"""
Called when an image has been downloaded by image_getter
"""
def recv_attach(self, identifier, pix, _mask):
"""Callback from image_getter"""
self.attachment[identifier] = pix
self.set_needs_redraw()
def has_content(self):
"""Returns true if overlay has meaningful content to render"""
if self.piggyback and self.piggyback.has_content():
return True
if not self.enabled:
@ -206,15 +190,12 @@ class TextOverlayWindow(OverlayWindow):
return self.content
def overlay_draw(self, w, context, data=None):
"""
Draw the overlay
"""
"""Draw the overlay"""
if self.piggyback:
self.piggyback.overlay_draw(w, context, data)
if not self.enabled:
return
self.context = context
(width, height) = self.get_size()
if not self.piggyback_parent:
context.set_antialias(cairo.ANTIALIAS_GOOD)
context.set_source_rgba(0.0, 0.0, 0.0, 0.0)
@ -234,7 +215,6 @@ class TextOverlayWindow(OverlayWindow):
context.translate(floating_x, floating_y)
context.rectangle(0, 0, floating_width, floating_height)
context.clip()
pass
(floating_x, floating_y, floating_width,
floating_height) = self.get_floating_coords()
current_y = floating_height
@ -249,7 +229,7 @@ class TextOverlayWindow(OverlayWindow):
if 'nick_col' in line and line['nick_col']:
col = line['nick_col']
for in_line in line['content']:
out_line = "%s%s" % (out_line, self.make_line(in_line))
out_line = f"{out_line}{self.make_line(in_line)}"
if line['attach'] and self.show_attach:
attachment = line['attach'][0]
url = attachment['url']
@ -267,10 +247,8 @@ class TextOverlayWindow(OverlayWindow):
else:
log.warning("Unknown file extension '%s'", extension)
# cy = self.draw_text(cy, "%s" % (line['attach']))
message = "<span foreground='%s'>%s</span>: %s" % (self.sanitize_string(col),
self.sanitize_string(
line["nick"]),
out_line)
message = f"<span foreground='{self.sanitize_string(col)}'>{self.sanitize_string(
line["nick"])}</span>: {out_line}"
current_y = self.draw_text(current_y, message)
if current_y <= 0:
# We've done enough
@ -279,10 +257,8 @@ class TextOverlayWindow(OverlayWindow):
self.context = None
def draw_attach(self, pos_y, url):
"""
Draw an attachment
"""
(floating_x, floating_y, floating_width,
"""Draw an attachment"""
(_floating_x, _floating_y, floating_width,
floating_height) = self.get_floating_coords()
if url in self.attachment and self.attachment[url]:
pix = self.attachment[url]
@ -302,16 +278,14 @@ class TextOverlayWindow(OverlayWindow):
return pos_y
def draw_text(self, pos_y, text):
"""
Draw a text message, returning the Y position of the next message
"""
"""Draw a text message, returning the Y position of the next message"""
layout = self.create_pango_layout(text)
layout.set_auto_dir(True)
layout.set_markup(text, -1)
attr = layout.get_attributes()
(floating_x, floating_y, floating_width,
floating_height) = self.get_floating_coords()
(_floating_x, _floating_y, floating_width,
_floating_height) = self.get_floating_coords()
layout.set_width(Pango.SCALE * floating_width)
layout.set_spacing(Pango.SCALE * 3)
if self.text_font:
@ -351,11 +325,9 @@ class TextOverlayWindow(OverlayWindow):
return pos_y - text_height
def render_custom(self, ctx, shape, path, _data):
"""
Draw an inline image as a custom emoticon
"""
"""Draw an inline image as a custom emoticon"""
if shape.data >= len(self.image_list):
log.warning(f"{shape.data} >= {len(self.image_list)}")
log.warning("%s >= %s", shape.data, len(self.image_list))
return
# key is the url to the image
key = self.image_list[shape.data]
@ -371,9 +343,7 @@ class TextOverlayWindow(OverlayWindow):
return True
def sanitize_string(self, string):
"""
Sanitize a text message so that it doesn't intefere with Pango's XML format
"""
"""Sanitize a text message so that it doesn't intefere with Pango's XML format"""
string = string.replace("&", "&amp;")
string = string.replace("<", "&lt;")
string = string .replace(">", "&gt;")

View file

@ -15,16 +15,15 @@ import random
import gettext
import logging
import math
import cairo
import sys
import locale
import pkg_resources
from time import perf_counter
import cairo
import pkg_resources
from .overlay import OverlayWindow
from .image_getter import get_surface, draw_img_to_rect, draw_img_to_mask
# pylint: disable=wrong-import-order
import gi
gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
# pylint: disable=wrong-import-position,wrong-import-order
from gi.repository import Pango, PangoCairo, GLib # nopep8
@ -55,12 +54,11 @@ class VoiceOverlayWindow(OverlayWindow):
scream = ''
if random.randint(0, 20) == 2:
scream = random.randint(8, 15)*'a'
name = "Player %d %s" % (i, scream)
name = f"Player {i} {scream}"
self.dummy_data.append({
"id": i,
"username": name,
"avatar": None,
"mute": False,
"deaf": mostly_false[random.randint(0, 7)],
"mute": mostly_false[random.randint(0, 7)],
"speaking": speaking,
@ -82,6 +80,7 @@ class VoiceOverlayWindow(OverlayWindow):
self.highlight_self = None
self.order = None
self.def_avatar = None
self.def_avatar_mask = None
self.channel_icon = None
self.channel_mask = None
self.channel_icon_url = None
@ -95,6 +94,7 @@ class VoiceOverlayWindow(OverlayWindow):
self.border_width = 2
self.icon_transparency = 0.0
self.fancy_border = False
self.only_speaking_grace_period = 0
self.fade_out_inactive = True
self.fade_out_limit = 0.1
@ -130,7 +130,7 @@ class VoiceOverlayWindow(OverlayWindow):
self.redraw()
def reset_action_timer(self):
# Reset time since last voice activity
"""Reset time since last voice activity"""
self.fade_opacity = 1.0
# Remove both fading-out effect and timer set last time this happened
@ -141,13 +141,13 @@ class VoiceOverlayWindow(OverlayWindow):
GLib.source_remove(self.fadeout_timeout)
self.fadeout_timeout = None
# If we're using this feature, schedule a new iactivity timer
# If we're using this feature, schedule a new inactivity timer
if self.fade_out_inactive:
self.inactive_timeout = GLib.timeout_add_seconds(
self.inactive_time, self.overlay_inactive)
def overlay_inactive(self):
# Inactivity has hit the first threshold, start fading out
"""Timed callback when inactivity limit is hit"""
self.fade_start = perf_counter()
# Fade out in 200 steps over X seconds.
self.fadeout_timeout = GLib.timeout_add(
@ -156,8 +156,10 @@ class VoiceOverlayWindow(OverlayWindow):
return False
def overlay_fadeout(self):
"""Repeated callback after inactivity started"""
self.set_needs_redraw()
# There's no guarantee over the granularity of the callback here, so use our time-since to work out how faded out we should be
# There's no guarantee over the granularity of the callback here,
# so use our time-since to work out how faded out we should be
# Might look choppy on systems under high cpu usage but that's just how it's going to be
now = perf_counter()
time_percent = (now - self.fade_start) / self.inactive_fade_time
@ -171,21 +173,22 @@ class VoiceOverlayWindow(OverlayWindow):
return True
def col(self, col, alpha=1.0):
"""
Convenience function to set the cairo context next colour. Altered to account for fade-out function
"""
if alpha == None:
"""Convenience function to set the cairo context next colour.
Altered to account for fade-out function"""
if alpha is None:
self.context.set_source_rgba(col[0], col[1], col[2], col[3])
else:
self.context.set_source_rgba(
col[0], col[1], col[2], col[3] * alpha * self.fade_opacity)
def set_icon_transparency(self, trans):
"""Config option: icon transparency"""
if self.icon_transparency != trans:
self.icon_transparency = trans
self.set_needs_redraw()
def set_blank(self):
"""Set data to blank and redraw"""
self.userlist = []
self.channel_icon = None
self.channel_icon_url = None
@ -194,7 +197,9 @@ class VoiceOverlayWindow(OverlayWindow):
self.set_needs_redraw()
def set_fade_out_inactive(self, enabled, fade_time, fade_duration, fade_to):
if self.fade_out_inactive != enabled or self.inactive_time != fade_time or self.inactive_fade_time != fade_duration or self.fade_out_limit != fade_to:
"""Config option: fade out options"""
if (self.fade_out_inactive != enabled or self.inactive_time != fade_time or
self.inactive_fade_time != fade_duration or self.fade_out_limit != fade_to):
self.fade_out_inactive = enabled
self.inactive_time = fade_time
self.inactive_fade_time = fade_duration
@ -202,288 +207,235 @@ class VoiceOverlayWindow(OverlayWindow):
self.reset_action_timer()
def set_title_font(self, font):
"""Config option: font used to render title"""
if self.title_font != font:
self.title_font = font
self.set_needs_redraw()
def set_show_connection(self, show_connection):
"""Config option: show connection status alongside users"""
if self.show_connection != show_connection:
self.show_connection = show_connection
self.set_needs_redraw()
def set_show_avatar(self, show_avatar):
"""Config option: show avatar icons"""
if self.show_avatar != show_avatar:
self.show_avatar = show_avatar
self.set_needs_redraw()
def set_show_title(self, show_title):
"""Config option: show channel title alongside users"""
if self.show_title != show_title:
self.show_title = show_title
self.set_needs_redraw()
def set_show_disconnected(self, show_disconnected):
"""Config option: show even when disconnected from voice chat"""
if self.show_disconnected != show_disconnected:
self.show_disconnected = show_disconnected
self.set_needs_redraw()
def set_show_dummy(self, show_dummy):
"""
Toggle use of dummy userdata to help choose settings
"""
"""Config option: Show placeholder information"""
if self.use_dummy != show_dummy:
self.use_dummy = show_dummy
self.set_needs_redraw()
def set_dummy_count(self, dummy_count):
"""Config option: Change the count of placeholders"""
if self.dummy_count != dummy_count:
self.dummy_count = dummy_count
self.set_needs_redraw()
def set_overflow(self, overflow):
"""
How should excessive numbers of users be dealt with?
"""
def set_overflow_style(self, overflow):
"""Config option: Change handling of too many users to render"""
if self.overflow != overflow:
self.overflow = overflow
self.set_needs_redraw()
def set_bg(self, background_colour):
"""
Set the background colour
"""
"""Config option: Set background colour. Used to draw the transparent window.
Should not be changed as then the entire screen is obscured"""
if self.norm_col != background_colour:
self.norm_col = background_colour
self.set_needs_redraw()
def set_fg(self, foreground_colour):
"""
Set the text colour
"""
"""Config option: Set foreground colour. Used to render text"""
if self.text_col != foreground_colour:
self.text_col = foreground_colour
self.set_needs_redraw()
def set_tk(self, talking_colour):
"""
Set the border colour for users who are talking
"""
"""Config option: Set talking border colour.
Used to render border around users who are talking"""
if self.talk_col != talking_colour:
self.talk_col = talking_colour
self.set_needs_redraw()
def set_mt(self, mute_colour):
"""
Set the colour of mute and deafen logos
"""
"""Config option: Set mute colour. Used to render mute and deaf images"""
if self.mute_col != mute_colour:
self.mute_col = mute_colour
self.set_needs_redraw()
def set_mute_bg(self, mute_bg_col):
"""
Set the background colour for mute/deafen icon
"""
"""Config option: Set mute background colour.
Used to tint the user avatar before rendering the mute or deaf image above it"""
if self.mute_bg_col != mute_bg_col:
self.mute_bg_col = mute_bg_col
self.set_needs_redraw()
def set_avatar_bg_col(self, avatar_bg_col):
"""
Set Avatar background colour
"""
"""Config option: Set avatar background colour.
Drawn before user avatar but only visible if default fallback avatar can't be found"""
if self.avatar_bg_col != avatar_bg_col:
self.avatar_bg_col = avatar_bg_col
self.set_needs_redraw()
def set_hi(self, highlight_colour):
"""
Set the colour of background for speaking users
"""
"""Config option: Set talking background colour.
Used to render the background behind users name."""
if self.hili_col != highlight_colour:
self.hili_col = highlight_colour
self.set_needs_redraw()
def set_fg_hi(self, highlight_colour):
"""
Set the colour of background for speaking users
"""
"""Config option: Set talking text colour.
Used to render the usernames of users who are talking"""
if self.text_hili_col != highlight_colour:
self.text_hili_col = highlight_colour
self.set_needs_redraw()
def set_bo(self, border_colour):
"""
Set the colour for idle border
"""
"""Config option: Set border colour. Used to render border around users"""
if self.border_col != border_colour:
self.border_col = border_colour
self.set_needs_redraw()
def set_avatar_size(self, size):
"""
Set the size of the avatar icons
"""
"""Config option: Set avatar size in window-space pixels"""
if self.avatar_size != size:
self.avatar_size = size
self.set_needs_redraw()
def set_nick_length(self, size):
"""
Set the length of nickname
"""
"""Config option: Limit username length"""
if self.nick_length != size:
self.nick_length = size
self.set_needs_redraw()
def set_icon_spacing(self, i):
"""
Set the spacing between avatar icons
"""
"""Config option: Space between users in the list, in window-space pixels"""
if self.icon_spacing != i:
self.icon_spacing = i
self.set_needs_redraw()
def set_text_padding(self, i):
"""
Set padding between text and border
"""
"""Config option: Space between user avatar and username, in window-space pixels"""
if self.text_pad != i:
self.text_pad = i
self.set_needs_redraw()
def set_text_baseline_adj(self, i):
"""
Set padding between text and border
"""
"""Config option: Vertical offset used to render all text, in window-space pixels"""
if self.text_baseline_adj != i:
self.text_baseline_adj = i
self.set_needs_redraw()
def set_vert_edge_padding(self, i):
"""
Set padding between top/bottom of screen and overlay contents
"""
"""Config option: Vertical offset from edge of window, in window-space pixels"""
if self.vert_edge_padding != i:
self.vert_edge_padding = i
self.set_needs_redraw()
def set_horz_edge_padding(self, i):
"""
Set padding between left/right of screen and overlay contents
"""
"""Config option: Horizontal offset from edge of window, in window-space pixels"""
if self.horz_edge_padding != i:
self.horz_edge_padding = i
self.set_needs_redraw()
def set_square_avatar(self, i):
"""
Set if the overlay should crop avatars to a circle or show full square image
"""
"""Config option: Mask avatar with a circle before rendering"""
if self.round_avatar == i:
self.round_avatar = not i
self.set_needs_redraw()
def set_fancy_border(self, border):
"""
Sets if border should wrap around non-square avatar images
"""
"""Config option: Use transparent edges of image as border,
instead of mask (square/circle)"""
if self.fancy_border != border:
self.fancy_border = border
self.set_needs_redraw()
def set_only_speaking(self, only_speaking):
"""
Set if overlay should only show people who are talking
"""
"""Config option: Filter user list to only those who
are talking and those who have stopped talking recently"""
if self.only_speaking != only_speaking:
self.only_speaking = only_speaking
self.set_needs_redraw()
def set_only_speaking_grace_period(self, grace_period):
"""
Set grace period before hiding people who are not talking
"""
"""Config option: How long after stopping speaking the user remains shown"""
self.only_speaking_grace_period = grace_period
self.timer_after_draw = grace_period
def set_highlight_self(self, highlight_self):
"""
Set if the overlay should highlight the user
"""
"""Config option: Local User should be kept at top of list"""
if self.highlight_self != highlight_self:
self.highlight_self = highlight_self
self.set_needs_redraw()
def set_order(self, i):
"""
Set the method used to order avatar icons & names
"""
"""Config option: Set method used to order user list"""
if self.order != i:
self.order = i
self.sort_list(self.userlist)
self.set_needs_redraw()
def set_icon_only(self, i):
"""
Set if the overlay should draw only the icon
"""
"""Config option: Show only the avatar, without text or its background"""
if self.icon_only != i:
self.icon_only = i
self.set_needs_redraw()
def set_border_width(self, width):
def set_drawn_border_width(self, width):
"""Config option: Set width of border around username and avatar"""
if self.border_width != width:
self.border_width = width
self.set_needs_redraw()
def set_horizontal(self, horizontal=False):
"""Config option: Userlist should be drawn horizontally"""
if self.horizontal != horizontal:
self.horizontal = horizontal
self.set_needs_redraw()
def set_guild_ids(self, guild_ids=tuple()):
if self.discover.connection:
for _id in guild_ids:
if _id not in self.guild_ids:
self.discover.connection.req_channels(_id)
self.guild_ids = guild_ids
def set_wind_col(self):
"""
Use window colour to draw
"""
"""Use window colour to draw"""
self.col(self.wind_col, None)
def set_norm_col(self):
"""
Use background colour to draw
"""
"""Use background colour to draw"""
self.col(self.norm_col)
def set_talk_col(self, alpha=1.0):
"""
Use talking colour to draw
"""
"""Use talking colour to draw"""
self.col(self.talk_col, alpha)
def set_mute_col(self):
"""
Use mute colour to draw
"""
"""Use mute colour to draw"""
self.col(self.mute_col)
def set_channel_title(self, channel_title):
"""
Set title above voice list
"""
"""Set title above voice list"""
if self.channel_title != channel_title:
self.channel_title = channel_title
self.set_needs_redraw()
def set_channel_icon(self, url):
"""
Change the icon for channel
"""
"""Change the icon for channel"""
if not url:
self.channel_icon = None
self.channel_icon_url = None
@ -493,9 +445,7 @@ class VoiceOverlayWindow(OverlayWindow):
self.channel_icon_url = url
def set_user_list(self, userlist, alt):
"""
Set the users in list to draw
"""
"""Set the users in list to draw"""
self.userlist = userlist
for user in userlist:
if "nick" in user:
@ -508,14 +458,13 @@ class VoiceOverlayWindow(OverlayWindow):
self.set_needs_redraw()
def set_connection_status(self, connection):
"""
Set if discord has a clean connection to server
"""
"""Set if discord has a clean connection to server"""
if self.connection_status != connection['state']:
self.connection_status = connection['state']
self.set_needs_redraw()
def sort_list(self, in_list):
"""Take a userlist and sort it according to config option"""
if self.order == 1: # ID Sort
in_list.sort(key=lambda x: x["id"])
elif self.order == 2: # Spoken sort
@ -526,6 +475,7 @@ class VoiceOverlayWindow(OverlayWindow):
return in_list
def has_content(self):
"""Returns true if overlay has meaningful content to render"""
if not self.enabled:
return False
if self.hidden:
@ -535,9 +485,7 @@ class VoiceOverlayWindow(OverlayWindow):
return self.userlist
def overlay_draw(self, w, context, data=None):
"""
Draw the Overlay
"""
"""Draw the Overlay"""
self.context = context
context.set_antialias(cairo.ANTIALIAS_GOOD)
# Get size of window
@ -566,7 +514,8 @@ class VoiceOverlayWindow(OverlayWindow):
context.clip()
context.set_operator(cairo.OPERATOR_OVER)
if not self.show_disconnected and self.connection_status == "DISCONNECTED" and not self.use_dummy:
if (not self.show_disconnected and self.connection_status == "DISCONNECTED"
and not self.use_dummy):
return
connection = self.discover.connection
@ -624,14 +573,14 @@ class VoiceOverlayWindow(OverlayWindow):
avatars_per_row = sys.maxsize
# Calculate height needed to show overlay
doTitle = False
doConnection = False
do_title = False
do_connection = False
if self.show_connection:
users_to_draw.insert(0, None)
doConnection = True
do_connection = True
if self.show_title and self.channel_title:
users_to_draw.insert(0, None)
doTitle = True
do_title = True
if self.horizontal:
needed_width = (len(users_to_draw) * line_height) + \
@ -655,7 +604,7 @@ class VoiceOverlayWindow(OverlayWindow):
rows_to_draw = []
while len(users_to_draw) > 0:
row = []
for i in range(0, min(avatars_per_row, len(users_to_draw))):
for _i in range(0, min(avatars_per_row, len(users_to_draw))):
row.append(users_to_draw.pop(0))
rows_to_draw.append(row)
for row in rows_to_draw:
@ -668,14 +617,14 @@ class VoiceOverlayWindow(OverlayWindow):
for user in row:
if not user:
if doTitle:
doTitle = False
if do_title:
do_title = False
text_width = self.draw_title(
context, current_x, current_y, avatar_size, line_height)
elif doConnection:
elif do_connection:
text_width = self.draw_connection(
context, current_x, current_y, avatar_size, line_height)
doConnection = False
do_connection = False
else:
self.draw_avatar(context, user, current_x,
current_y, avatar_size, line_height)
@ -712,7 +661,7 @@ class VoiceOverlayWindow(OverlayWindow):
cols_to_draw = []
while len(users_to_draw) > 0:
col = []
for i in range(0, min(avatars_per_row, len(users_to_draw))):
for _i in range(0, min(avatars_per_row, len(users_to_draw))):
col.append(users_to_draw.pop(0))
cols_to_draw.append(col)
for col in cols_to_draw:
@ -725,22 +674,22 @@ class VoiceOverlayWindow(OverlayWindow):
largest_text_width = 0
for user in col:
if not user:
if doTitle:
if do_title:
# Draw header
text_width = self.draw_title(
context, current_x, current_y, avatar_size, line_height)
largest_text_width = max(
text_width, largest_text_width)
current_y += line_height + self.icon_spacing
doTitle = False
elif doConnection:
do_title = False
elif do_connection:
# Draw header
text_width = self.draw_connection(
context, current_x, current_y, avatar_size, line_height)
largest_text_width = max(
text_width, largest_text_width)
current_y += line_height + self.icon_spacing
doConnection = False
do_connection = False
else:
text_width = self.draw_avatar(
@ -758,9 +707,7 @@ class VoiceOverlayWindow(OverlayWindow):
self.context = None
def recv_avatar(self, identifier, pix, mask):
"""
Called when image_getter has downloaded an image
"""
"""Called when image_getter has downloaded an image"""
if identifier == 'def':
self.def_avatar = pix
self.def_avatar_mask = mask
@ -773,16 +720,12 @@ class VoiceOverlayWindow(OverlayWindow):
self.set_needs_redraw()
def delete_avatar(self, identifier):
"""
Remove avatar image
"""
"""Remove avatar image"""
if identifier in self.avatars:
del self.avatars[identifier]
def draw_title(self, context, pos_x, pos_y, avatar_size, line_height):
"""
Draw title at given Y position. Includes both text and image based on settings
"""
"""Draw title at given Y position. Includes both text and image based on settings"""
tw = 0
if not self.horizontal and not self.icon_only:
title = self.channel_title
@ -826,9 +769,7 @@ class VoiceOverlayWindow(OverlayWindow):
_("VOICE_CONNECTED")
def draw_connection(self, context, pos_x, pos_y, avatar_size, line_height):
"""
Draw title at given Y position. Includes both text and image based on settings
"""
"""Draw title at given Y position. Includes both text and image based on settings"""
tw = 0
if not self.horizontal and not self.icon_only:
tw = self.draw_text(
@ -846,13 +787,11 @@ class VoiceOverlayWindow(OverlayWindow):
return tw
def draw_avatar(self, context, user, pos_x, pos_y, avatar_size, line_height):
"""
Draw avatar at given Y position. Includes both text and image based on settings
"""
"""Draw avatar at given Y position. Includes both text and image based on settings"""
# Ensure pixbuf for avatar
if user["id"] not in self.avatars and user["avatar"] and avatar_size > 0:
url = "https://cdn.discordapp.com/avatars/%s/%s.png" % (
user['id'], user['avatar'])
url = f"https://cdn.discordapp.com/avatars/{
user['id']}/{user['avatar']}.png"
get_surface(self.recv_avatar, url, user["id"],
self.avatar_size)
@ -907,19 +846,18 @@ class VoiceOverlayWindow(OverlayWindow):
self.mute_bg_col, avatar_size)
return tw
def draw_text(self, context, string, pos_x, pos_y, tx_col, bg_col, avatar_size, line_height, font):
"""
Draw username & background at given position
"""
def draw_text(self, context, string, pos_x, pos_y,
tx_col, bg_col, avatar_size, line_height, font):
"""Draw username & background at given position"""
if self.nick_length < 32 and len(string) > self.nick_length:
string = string[:(self.nick_length-1)] + u"\u2026"
string = string[:(self.nick_length-1)] + "\u2026"
context.save()
layout = self.create_pango_layout(string)
layout.set_auto_dir(True)
layout.set_markup(string, -1)
(floating_x, floating_y, floating_width,
floating_height) = self.get_floating_coords()
(_floating_x, _floating_y, floating_width,
_floating_height) = self.get_floating_coords()
layout.set_width(Pango.SCALE * floating_width)
layout.set_spacing(Pango.SCALE * 3)
if font:
@ -971,6 +909,7 @@ class VoiceOverlayWindow(OverlayWindow):
return text_width
def blank_avatar(self, context, pos_x, pos_y, avatar_size):
"""Draw a cut-out of the previous shape with a forcible transparent hole"""
context.save()
if self.round_avatar:
context.arc(pos_x + (avatar_size / 2), pos_y +
@ -983,9 +922,7 @@ class VoiceOverlayWindow(OverlayWindow):
context.restore()
def draw_avatar_pix(self, context, pixbuf, mask, pos_x, pos_y, border_colour, avatar_size):
"""
Draw avatar image at given position
"""
"""Draw avatar image at given position"""
if not self.show_avatar:
return
# Empty the space for this
@ -1021,13 +958,16 @@ class VoiceOverlayWindow(OverlayWindow):
if self.round_avatar:
context.new_path()
context.arc(pos_x + (avatar_size / 2), pos_y +
(avatar_size / 2), avatar_size / 2 + (self.border_width/2.0), 0, 2 * math.pi)
(avatar_size / 2), avatar_size / 2 +
(self.border_width/2.0), 0, 2 * math.pi)
context.set_line_width(self.border_width)
context.stroke()
else:
context.new_path()
context.rectangle(pos_x - (self.border_width/2), pos_y - (self.border_width/2),
avatar_size + self.border_width, avatar_size + self.border_width)
context.rectangle(pos_x - (self.border_width/2),
pos_y - (self.border_width/2),
avatar_size + self.border_width,
avatar_size + self.border_width)
context.set_line_width(self.border_width)
context.stroke()
@ -1053,13 +993,12 @@ class VoiceOverlayWindow(OverlayWindow):
context.clip()
context.set_operator(cairo.OPERATOR_OVER)
draw_img_to_rect(pixbuf, context, pos_x, pos_y,
avatar_size, avatar_size, False, False, 0, 0, self.fade_opacity * self.icon_transparency)
avatar_size, avatar_size, False, False, 0, 0,
self.fade_opacity * self.icon_transparency)
context.restore()
def draw_mute(self, context, pos_x, pos_y, bg_col, avatar_size):
"""
Draw Mute logo
"""
"""Draw Mute logo"""
if avatar_size <= 0:
return
context.save()
@ -1126,9 +1065,7 @@ class VoiceOverlayWindow(OverlayWindow):
context.restore()
def draw_deaf(self, context, pos_x, pos_y, bg_col, avatar_size):
"""
Draw deaf logo
"""
"""Draw deaf logo"""
if avatar_size <= 0:
return
context.save()
@ -1188,6 +1125,7 @@ class VoiceOverlayWindow(OverlayWindow):
context.restore()
def draw_connection_icon(self, context, pos_x, pos_y, avatar_size):
"""Draw a series of bars to show connectivity state"""
context.save()
context.translate(pos_x, pos_y)
context.scale(avatar_size, avatar_size)