- Separated classes into files

- Ran a formatter
- Moved all globals bar entry point into classes
- This addresses #25 There are likely a few mistakes in discord connector so testing is needed
This commit is contained in:
trigg 2020-09-29 18:01:00 +01:00
parent 59d4530fc9
commit 524e09d16c
9 changed files with 1926 additions and 1821 deletions

View file

@ -0,0 +1,397 @@
import websocket
import select
import time
import json
import requests
class DiscordConnector:
def __init__(self, text_settings, voice_settings, text_overlay, voice_overlay):
self.text_settings = text_settings
self.text_overlay = text_overlay
self.voice_settings = voice_settings
self.voice_overlay = voice_overlay
self.ws = None
self.access_token = "none"
# TODO Magic number
self.oauth_token = "207646673902501888"
self.access_delay = 0
self.warn_connection = True
self.error_connection = True
self.guilds = {}
self.channels = {}
self.user = {}
self.userlist = {}
self.in_room = []
self.current_voice = "0"
self.current_text = "0"
self.list_altered = False
self.text_altered = False
self.last_connection = ""
self.text = []
self.authed = False
def get_access_token_stage1(self):
global oauth_token
self.ws.send("{\"cmd\":\"AUTHORIZE\",\"args\":{\"client_id\":\"%s\",\"scopes\":[\"rpc\",\"messages.read\"],\"prompt\":\"none\"},\"nonce\":\"deadbeef\"}" % (
self.oauth_token))
def get_access_token_stage2(self, code1):
url = "https://streamkit.discord.com/overlay/token"
myobj = {"code": code1}
x = requests.post(url, json=myobj)
try:
j = json.loads(x.text)
except:
j = {}
if "access_token" in j:
self.access_token = j["access_token"]
self.req_auth()
else:
sys.exit(1)
def set_channel(self, channel, need_req=True):
if not channel:
self.current_voice = "0"
return
if channel != self.current_voice:
cn = self.channels[channel]['name']
print("Joined room: %s" % (cn))
self.current_voice = channel
if need_req:
self.req_channel_details(channel)
def set_text_channel(self, channel, need_req=True):
if not channel:
self.current_text = "0"
return
if channel != self.current_text:
self.current_text = channel
print("Changing text room: %s" % (channel))
if need_req:
self.req_channel_details(channel)
def set_in_room(self, userid, present):
if present:
if userid not in self.in_room:
self.in_room.append(userid)
else:
if userid in self.in_room:
self.in_room.remove(userid)
def add_text(self, message):
un = message["author"]["username"]
if "nick" in message and message['nick'] and len(message["nick"]) > 1:
un = message["nick"]
ac = "#ffffff"
if "author_color" in message:
ac = message["author_color"]
self.text.append({'id': message["id"],
'content': self.get_message_from_message(message),
'nick': un,
'nick_col': ac,
})
self.text_altered = True
def update_text(self, message_in):
for idx in range(0, len(self.text)):
message = self.text[idx]
if message['id'] == message_in['id']:
new_message = {'id': message['id'],
'content': self.get_message_from_message(message_in),
'nick': message['nick'],
'nick_col': message['nick_col']}
self.text[idx] = new_message
self.text_altered = True
return
def delete_text(self, message_in):
global text, text_altered
for idx in range(0, len(self.text)):
message = self.text[idx]
if message['id'] == message_in['id']:
del self.text[idx]
self.text_altered = True
return
def get_message_from_message(self, message):
if "content" in message and len(message["content"]) > 0:
return message["content"]
elif len(message["embeds"]) == 1:
if "rawDescription" in message["embeds"][0]:
return message["embeds"][0]["rawDescription"]
if "author" in message["embeds"][0]:
return message["embeds"][0]["author"]["name"]
elif len(message["attachments"]) == 1:
# Need to care
return "-- attachment --"
return ""
def update_user(self, user):
if user["id"] in self.userlist:
if not "mute" in user and "mute" in self.userlist[user["id"]]:
user["mute"] = self.userlist[user["id"]]["mute"]
if not "deaf" in user and "deaf" in self.userlist[user["id"]]:
user["deaf"] = self.userlist[user["id"]]["deaf"]
if not "speaking" in user and "speaking" in self.userlist[user["id"]]:
user["speaking"] = self.userlist[user["id"]]["speaking"]
self.userlist[user["id"]] = user
def on_message(self, message):
j = json.loads(message)
if j["cmd"] == "AUTHORIZE":
self.get_access_token_stage2(j["data"]["code"])
return
elif j["cmd"] == "DISPATCH":
if j["evt"] == "READY":
self.req_auth()
elif j["evt"] == "VOICE_STATE_UPDATE":
self.list_altered = True
thisuser = j["data"]["user"]
un = j["data"]["user"]["username"]
mute = j["data"]["voice_state"]["mute"] or j["data"]["voice_state"]["self_mute"]
deaf = j["data"]["voice_state"]["deaf"] or j["data"]["voice_state"]["self_deaf"]
thisuser["mute"] = mute
thisuser["deaf"] = deaf
self.update_user(thisuser)
elif j["evt"] == "VOICE_STATE_CREATE":
self.list_altered = True
self.update_user(j["data"]["user"])
# If someone joins any voice room grab it fresh from server
self.req_channel_details(self.current_voice)
un = j["data"]["user"]["username"]
if j["data"]["user"]["id"] == self.user["id"]:
self.find_user()
elif j["evt"] == "VOICE_STATE_DELETE":
self.list_altered = True
self.set_in_room(j["data"]["user"]["id"], False)
if j["data"]["user"]["id"] == self.user["id"]:
self.in_room = []
self.sub_all_voice()
else:
un = j["data"]["user"]["username"]
elif j["evt"] == "SPEAKING_START":
self.list_altered = True
# It's only possible to get alerts for the room you're in
self.set_channel(j["data"]["channel_id"])
self.userlist[j["data"]["user_id"]]["speaking"] = True
self.set_in_room(j["data"]["user_id"], True)
elif j["evt"] == "SPEAKING_STOP":
self.list_altered = True
# It's only possible to get alerts for the room you're in
self.set_channel(j["data"]["channel_id"])
if j["data"]["user_id"] in self.userlist:
self.userlist[j["data"]["user_id"]]["speaking"] = False
self.set_in_room(j["data"]["user_id"], True)
elif j["evt"] == "VOICE_CHANNEL_SELECT":
self.set_channel(j["data"]["channel_id"])
elif j["evt"] == "VOICE_CONNECTION_STATUS":
# VOICE_CONNECTED > CONNECTING > AWAITING_ENDPOINT > DISCONNECTED
self.last_connection = j["data"]["state"]
elif j["evt"] == "MESSAGE_CREATE":
if self.current_text == j["data"]["channel_id"]:
self.add_text(j["data"]["message"])
elif j["evt"] == "MESSAGE_UPDATE":
if self.current_text == j["data"]["channel_id"]:
self.update_text(j["data"]["message"])
elif j["evt"] == "MESSAGE_DELETE":
if self.current_text == j["data"]["channel_id"]:
self.delete_text(j["data"]["message"])
else:
print(j)
return
elif j["cmd"] == "AUTHENTICATE":
if j["evt"] == "ERROR":
self.get_access_token_stage1()
return
else:
self.req_guilds()
self.user = j["data"]["user"]
print("ID is %s" % (self.user["id"]))
print("Logged in as %s" % (self.user["username"]))
self.authed = True
return
elif j["cmd"] == "GET_GUILDS":
for guild in j["data"]["guilds"]:
self.req_channels(guild["id"])
self.guilds[guild["id"]] = guild
return
elif j["cmd"] == "GET_CHANNELS":
self.guilds[j['nonce']]["channels"] = j["data"]["channels"]
for channel in j["data"]["channels"]:
channel['guild_id'] = j['nonce']
channel['guild_name'] = self.guilds[j['nonce']]["name"]
self.channels[channel["id"]] = channel
if channel["type"] == 2:
self.req_channel_details(channel["id"])
self.check_guilds()
self.sub_all_voice_guild(j["nonce"])
self.sub_all_text_guild(j["nonce"])
return
elif j["cmd"] == "SUBSCRIBE":
return
elif j["cmd"] == "GET_CHANNEL":
if j["evt"] == "ERROR":
print("Could not get room")
return
for voice in j["data"]["voice_states"]:
if voice["user"]["id"] == self.user["id"]:
self.set_channel(j["data"]["id"], False)
if j["data"]["id"] == self.current_voice:
self.list_altered = True
self.in_room = []
for voice in j["data"]["voice_states"]:
self.update_user(voice["user"])
self.set_in_room(voice["user"]["id"], True)
if self.current_text == j["data"]["id"]:
self.text = []
for message in j["data"]["messages"]:
self.add_text(message)
return
print(j)
def check_guilds(self):
# Check if all of the guilds contain a channel
for guild in self.guilds.values():
if "channels" not in guild:
return
# All guilds are filled!
self.on_connected()
def on_connected(self):
for guild in self.guilds.values():
channels = ""
for channel in guild["channels"]:
channels = channels + " " + channel["name"]
print("%s: %s" % (guild["name"], channels))
self.sub_server()
self.find_user()
def on_error(self, error):
print("ERROR : %s" % (error))
def on_close(self):
print("Connection closed")
self.ws = None
def req_auth(self):
self.ws.send("{\"cmd\":\"AUTHENTICATE\",\"args\":{\"access_token\":\"%s\"},\"nonce\":\"deadbeef\"}" % (
self.access_token))
def req_guilds(self):
self.ws.send("{\"cmd\":\"GET_GUILDS\",\"args\":{},\"nonce\":\"3333\"}")
def req_channels(self, guild):
self.ws.send("{\"cmd\":\"GET_CHANNELS\",\"args\":{\"guild_id\":\"%s\"},\"nonce\":\"%s\"}" % (
guild, guild))
def req_channel_details(self, channel):
self.ws.send("{\"cmd\":\"GET_CHANNEL\",\"args\":{\"channel_id\":\"%s\"},\"nonce\":\"%s\"}" % (
channel, channel))
def find_user(self):
for channel in self.channels:
if self.channels[channel]["type"] == 2:
self.req_channel_details(channel)
def sub_raw(self, cmd, channel, nonce):
self.ws.send("{\"cmd\":\"SUBSCRIBE\",\"args\":{%s},\"evt\":\"%s\",\"nonce\":\"%s\"}" % (
channel, cmd, nonce))
def sub_server(self):
# Experimental
self.sub_raw("VOICE_CHANNEL_SELECT", "", "VOICE_CHANNEL_SELECT")
self.sub_raw("VOICE_CONNECTION_STATUS", "", "VOICE_CONNECTION_STATUS")
#sub_raw(ws,"ACTIVITY_JOIN", "","ACTIVITY_JOIN")
#sub_raw(ws,"ACTIVITY_JOIN_REQUEST", "","ACTIVITY_JOIN_REQUEST")
#sub_raw(ws,"ACTIVITY_SPECTATE", "", "ACTIVITY_SPECTATE")
# sub_raw(ws,"ACTIVITY_INVITE","","ACTIVITY_INVITE")
#sub_raw(ws,"GAME_JOIN", "", "GAME_JOIN")
#sub_raw(ws,"GAME_SPECTATE", "", "GAME_SPECTATE")
#sub_raw(ws,"VOICE_SETTINGS_UPDATE", "", "VOICE_SETTINGS_UPDATE")
#sub_raw(ws,"GUILD_STATUS", "\"guild_id\":\"147073008450666496\"", "GUILD_STATUS")
def sub_channel(self, cmd, channel):
self.sub_raw(cmd, "\"channel_id\":\"%s\"" % (channel), channel)
def sub_text_channel(self, channel):
self.sub_channel("MESSAGE_CREATE", channel)
self.sub_channel("MESSAGE_UPDATE", channel)
self.sub_channel("MESSAGE_DELETE", channel)
def sub_voice_channel(self, channel):
self.sub_channel("VOICE_STATE_CREATE", channel)
self.sub_channel("VOICE_STATE_UPDATE", channel)
self.sub_channel("VOICE_STATE_DELETE", channel)
self.sub_channel("SPEAKING_START", channel)
self.sub_channel("SPEAKING_STOP", channel)
def sub_all_voice_guild(self, gid):
for channel in self.guilds[gid]["channels"]:
if channel["type"] == 2:
self.sub_voice_channel(channel["id"])
def sub_all_text_guild(self, gid):
for channel in self.guilds[gid]["channels"]:
if channel["type"] == 0:
self.sub_text_channel(channel["id"])
def sub_all_voice(self):
for guild in self.guilds:
self.sub_all_voice_guild(guild)
def sub_all_text(self):
for guild in self.guilds:
self.sub_all_text_guild(guild)
def do_read(self):
# Ensure connection
if not self.ws:
self.connect()
if self.warn_connection:
print("Unable to connect to Discord client")
self.warn_connection = False
return True
# Recreate a list of users in current room
newlist = []
for userid in self.in_room:
newlist.append(self.userlist[userid])
self.voice_overlay.set_user_list(newlist, self.list_altered)
self.voice_overlay.set_connection(self.last_connection)
list_altered = False
# Update text list
if self.text_altered:
self.text_overlay.set_text_list(self.text, self.text_altered)
self.text_altered = False
# Update text channels
self.text_settings.set_channels(self.channels)
# Check for changed channel
if self.authed:
self.set_text_channel(self.text_settings.get_channel())
# Poll socket for new information
r, w, e = select.select((self.ws.sock,), (), (), 0)
while r:
try:
# Recieve & send to on_message
msg = self.ws.recv()
self.on_message(msg)
r, w, e = select.select((self.ws.sock,), (), (), 0)
except websocket._exceptions.WebSocketConnectionClosedException:
self.on_close()
return True
return True
def connect(self):
if self.ws:
return
try:
self.ws = websocket.create_connection("ws://127.0.0.1:6463/?v=1&client_id=%s" % (self.oauth_token),
origin="https://streamkit.discord.com")
except Exception as e:
if self.error_connection:
print(e)
self.error_connection = False
pass

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,124 @@
import gi
gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
gi.require_version('GdkPixbuf', '2.0')
import cairo
from gi.repository.GdkPixbuf import Pixbuf
from gi.repository import Gtk, GLib, Gio, GdkPixbuf, Gdk, Pango, PangoCairo
class DraggableWindow(Gtk.Window):
def __init__(self, x=0, y=0, w=300, h=300, message="Message"):
Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP)
if w < 100:
w = 100
if h < 100:
h = 100
self.x = x
self.y = y
self.w = w
self.h = h
self.message = message
self.set_size_request(50, 50)
self.connect('draw', self.draw)
self.connect('motion-notify-event', self.drag)
self.connect('button-press-event', self.button_press)
self.connect('button-release-event', self.button_release)
self.compositing = False
# Set RGBA
screen = self.get_screen()
visual = screen.get_rgba_visual()
if visual:
# Set the visual even if we can't use it right now
self.set_visual(visual)
if screen.is_composited():
self.compositing = True
self.set_app_paintable(True)
self.monitor = 0
self.drag_type = None
self.drag_x = 0
self.drag_y = 0
self.force_location()
self.show_all()
def force_location(self):
self.set_decorated(False)
self.set_keep_above(True)
display = Gdk.Display.get_default()
monitor = display.get_monitor(self.monitor)
geometry = monitor.get_geometry()
scale_factor = monitor.get_scale_factor()
w = scale_factor * geometry.width
h = scale_factor * geometry.height
x = geometry.x
y = geometry.y
#self.resize(400, h)
self.move(self.x, self.y)
self.resize(self.w, self.h)
def drag(self, w, event):
if event.state & Gdk.ModifierType.BUTTON1_MASK:
if self.drag_type == 1:
# Center is move
self.x = event.x_root - self.drag_x
self.y = event.y_root - self.drag_y
self.force_location()
elif self.drag_type == 2:
# Right edge
self.w += event.x - self.drag_x
self.drag_x = event.x
self.force_location()
elif self.drag_type == 3:
# Bottom edge
self.h += event.y - self.drag_y
self.drag_y = event.y
self.force_location()
else:
# Bottom Right
self.w += event.x - self.drag_x
self.h += event.y - self.drag_y
self.drag_x = event.x
self.drag_y = event.y
self.force_location()
def button_press(self, w, event):
(w, h) = self.get_size()
if not self.drag_type:
self.drag_type = 1
# Where in the window did we press?
if event.y > h - 32:
self.drag_type += 2
if event.x > w - 32:
self.drag_type += 1
self.drag_x = event.x
self.drag_y = event.y
def button_release(self, w, event):
self.drag_type = None
def draw(self, widget, context):
context.set_source_rgba(1.0, 1.0, 0.0, 0.7)
# Don't layer drawing over each other, always replace
context.set_operator(cairo.OPERATOR_SOURCE)
context.paint()
context.set_operator(cairo.OPERATOR_OVER)
# Get size of window
(sw, sh) = self.get_size()
# Draw text
context.set_source_rgba(0.0, 0.0, 0.0, 1.0)
xb, yb, w, h, dx, dy = context.text_extents(self.message)
context.move_to(sw / 2 - w / 2, sh / 2 - h / 2)
context.show_text(self.message)
# Draw resizing edges
context.set_source_rgba(0.0, 0.0, 1.0, 0.5)
context.rectangle(sw - 32, 0, 32, sh)
context.fill()
context.rectangle(0, sh - 32, sw, 32)
context.fill()

124
discover_overlay/overlay.py Normal file
View file

@ -0,0 +1,124 @@
import gi
gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
gi.require_version('GdkPixbuf', '2.0')
from gi.repository.GdkPixbuf import Pixbuf
from gi.repository import Gtk, GLib, Gio, GdkPixbuf, Gdk, Pango, PangoCairo
import cairo
class OverlayWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP)
self.set_size_request(50, 50)
self.connect('draw', self.draw)
self.compositing = False
# Set RGBA
screen = self.get_screen()
visual = screen.get_rgba_visual()
if not self.get_display().supports_input_shapes():
print("Input shapes not available. Quitting")
sys.exit(1)
if visual:
# Set the visual even if we can't use it right now
self.set_visual(visual)
if screen.is_composited():
self.compositing = True
self.set_app_paintable(True)
self.set_untouchable()
self.set_skip_pager_hint(True)
self.set_skip_taskbar_hint(True)
self.show_all()
self.monitor = 0
self.align_right = True
self.align_vert = 1
self.floating = False
def draw(self, widget, context):
pass
def do_draw(self, context):
pass
def set_font(self, name, size):
self.text_font = name
self.text_size = size
self.redraw()
def set_floating(self, floating, x, y, w, h):
self.floating = floating
self.x = x
self.y = y
self.w = w
self.h = h
self.force_location()
def set_untouchable(self):
(w, h) = self.get_size()
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
surface_ctx = cairo.Context(surface)
surface_ctx.set_source_rgba(0.0, 0.0, 0.0, 0.0)
surface_ctx.set_operator(cairo.OPERATOR_SOURCE)
surface_ctx.paint()
reg = Gdk.cairo_region_create_from_surface(surface)
self.input_shape_combine_region(reg)
# self.shape_combine_region(reg)
def unset_shape(self):
self.get_window().shape_combine_region(None, 0, 0)
def force_location(self):
self.set_decorated(False)
self.set_keep_above(True)
display = Gdk.Display.get_default()
monitor = display.get_monitor(self.monitor)
geometry = monitor.get_geometry()
scale_factor = monitor.get_scale_factor()
if not self.floating:
w = scale_factor * geometry.width
h = scale_factor * geometry.height
x = geometry.x
y = geometry.y
self.resize(w, h)
self.move(x, y)
else:
self.move(self.x, self.y)
self.resize(self.w, self.h)
self.redraw()
def redraw(self):
gdkwin = self.get_window()
if gdkwin:
if not self.compositing:
(w, h) = self.get_size()
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
surface_ctx = cairo.Context(surface)
self.do_draw(surface_ctx)
reg = Gdk.cairo_region_create_from_surface(surface)
gdkwin.shape_combine_region(reg, 0, 0)
else:
gdkwin.shape_combine_region(None, 0, 0)
self.queue_draw()
def set_monitor(self, idx):
self.monitor = idx
self.force_location()
self.redraw()
def set_align_x(self, b):
self.align_right = b
self.force_location()
self.redraw()
def set_align_y(self, i):
self.align_vert = i
self.force_location()
self.redraw()
def col(self, c, a=1.0):
self.context.set_source_rgba(c[0], c[1], c[2], c[3] * a)

View file

@ -0,0 +1,48 @@
import gi
gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
gi.require_version('GdkPixbuf', '2.0')
import sys
import os
from gi.repository.GdkPixbuf import Pixbuf
from gi.repository import Gtk, GLib, Gio, GdkPixbuf, Gdk, Pango, PangoCairo
try:
from xdg.BaseDirectory import xdg_config_home
except ModuleNotFoundError:
from xdg import XDG_CONFIG_HOME as xdg_config_home
class SettingsWindow(Gtk.Window):
def init_config(self):
self.configDir = os.path.join(xdg_config_home, "discover")
os.makedirs(self.configDir, exist_ok=True)
self.configFile = os.path.join(self.configDir, "discover.ini")
self.read_config()
def close_window(self, a=None, b=None):
if self.placement_window:
(x, y) = self.placement_window.get_position()
(w, h) = self.placement_window.get_size()
self.floating_x = x
self.floating_y = y
self.floating_w = w
self.floating_h = h
self.overlay.set_floating(True, x, y, w, h)
self.save_config()
self.placement_window.close()
self.placement_window = None
self.hide()
return True
def get_monitor_index(self, name):
display = Gdk.Display.get_default()
for i in range(0, display.get_n_monitors()):
if display.get_monitor(i).get_model() == name:
return i
print("Could not find monitor : %s" % (name))
return 0
def present(self):
self.show_all()

View file

@ -0,0 +1,84 @@
import gi
gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
gi.require_version('GdkPixbuf', '2.0')
import math
from .overlay import OverlayWindow
from gi.repository.GdkPixbuf import Pixbuf
from gi.repository import Gtk, GLib, Gio, GdkPixbuf, Gdk, Pango, PangoCairo
import cairo
class TextOverlayWindow(OverlayWindow):
def __init__(self):
OverlayWindow.__init__(self)
self.text_spacing = 4
self.content = []
self.text_font = None
self.text_size = 13
self.connected = True
self.bg_col = [0.0, 0.6, 0.0, 0.1]
self.fg_col = [1.0, 1.0, 1.0, 1.0]
def set_text_list(self, tlist, alt):
self.content = tlist
if alt:
self.redraw()
def set_enabled(self, en):
if en:
self.show_all()
else:
self.hide()
def set_fg(self, fg_col):
self.fg_col = fg_col
self.redraw()
def set_bg(self, bg_col):
self.bg_col = bg_col
self.redraw()
def do_draw(self, context):
self.context = context
context.set_antialias(self.compositing)
(w, h) = self.get_size()
# Make background transparent
context.set_source_rgba(0.0, 0.0, 0.0, 0.4)
# Don't layer drawing over each other, always replace
self.col(self.bg_col)
context.paint()
context.set_operator(cairo.OPERATOR_OVER)
self.col(self.fg_col)
if not self.connected:
return
long_string = ""
for line in self.content:
col = "#fff"
if 'nick_col' in line and line['nick_col']:
col = line['nick_col']
long_string = "%s\n<span foreground='%s'>%s</span>: %s" % (
long_string,
self.santize_string(col),
self.santize_string(line["nick"]),
self.santize_string(line["content"]))
layout = self.create_pango_layout(long_string)
layout.set_markup(long_string, -1)
attr = Pango.AttrList()
layout.set_width(Pango.SCALE * w)
layout.set_spacing(Pango.SCALE * 3)
if(self.text_font):
font = Pango.FontDescription(
"%s %s" % (self.text_font, self.text_size))
layout.set_font_description(font)
tw, th = layout.get_pixel_size()
context.move_to(0, -th + h)
PangoCairo.show_layout(context, layout)
def santize_string(self, string):
# I hate that Pango has nothing for this.
return string.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("'", "&#39;").replace("\"", "&#34;")

View file

@ -0,0 +1,358 @@
import gi
gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
gi.require_version('GdkPixbuf', '2.0')
import json
from configparser import ConfigParser
from .draggable_window import DraggableWindow
from .settings import SettingsWindow
from gi.repository.GdkPixbuf import Pixbuf
from gi.repository import Gtk, GLib, Gio, GdkPixbuf, Gdk, Pango, PangoCairo
class TextSettingsWindow(SettingsWindow):
def __init__(self, overlay):
Gtk.Window.__init__(self)
self.overlay = overlay
self.set_size_request(400, 200)
self.connect("destroy", self.close_window)
self.connect("delete-event", self.close_window)
self.placement_window = None
self.init_config()
self.list_channels_keys = []
self.ignore_channel_change = False
self.create_gui()
def present(self):
self.show_all()
if not self.floating:
self.align_x_widget.show()
self.align_y_widget.show()
self.align_monitor_widget.show()
self.align_placement_widget.hide()
else:
self.align_x_widget.hide()
self.align_y_widget.hide()
self.align_monitor_widget.hide()
self.align_placement_widget.show()
model = monitor_store = Gtk.ListStore(str, bool)
# for c in self.list_channels_keys:
# print(self.list_channels[c])
# model.append([self.list_channels[c]["name"]])
self.channel_lookup = []
for guild in self.guild_list():
guild_id, guild_name = guild
self.channel_lookup.append('0')
model.append([guild_name, False])
for c in self.list_channels_keys:
chan = self.list_channels[c]
if chan['guild_id'] == guild_id:
model.append([chan["name"], True])
self.channel_lookup.append(c)
self.channel_widget.set_model(model)
self.channel_model = model
idx = 0
for c in self.channel_lookup:
if c == self.channel:
self.ignore_channel_change = True
self.channel_widget.set_active(idx)
self.ignore_channel_change = False
break
idx += 1
def guild_list(self):
guilds = []
done = []
for channel in self.list_channels.values():
if not channel["guild_id"] in done:
done.append(channel["guild_id"])
guilds.append([channel["guild_id"], channel["guild_name"]])
return guilds
def set_channels(self, in_list):
self.list_channels = in_list
self.list_channels_keys = []
for key in in_list.keys():
if in_list[key]["type"] == 0:
self.list_channels_keys.append(key)
self.list_channels_keys.sort()
def read_config(self):
config = ConfigParser(interpolation=None)
config.read(self.configFile)
self.enabled = config.getboolean("text", "enabled", fallback=False)
self.align_x = config.getboolean("text", "rightalign", fallback=True)
self.align_y = config.getint("text", "topalign", fallback=2)
self.monitor = config.get("text", "monitor", fallback="None")
self.floating = config.getboolean("text", "floating", fallback=True)
self.floating_x = config.getint("text", "floating_x", fallback=0)
self.floating_y = config.getint("text", "floating_y", fallback=0)
self.floating_w = config.getint("text", "floating_w", fallback=400)
self.floating_h = config.getint("text", "floating_h", fallback=400)
self.channel = config.get("text", "channel", fallback="0")
self.font = config.get("text", "font", fallback=None)
self.bg_col = json.loads(config.get(
"text", "bg_col", fallback="[0.0,0.0,0.0,0.5]"))
self.fg_col = json.loads(config.get(
"text", "fg_col", fallback="[1.0,1.0,1.0,1.0]"))
print("Loading saved channel %s" % (self.channel))
# Pass all of our config over to the overlay
self.overlay.set_enabled(self.enabled)
self.overlay.set_align_x(self.align_x)
self.overlay.set_align_y(self.align_y)
self.overlay.set_monitor(self.get_monitor_index(self.monitor))
self.overlay.set_floating(
self.floating, self.floating_x, self.floating_y, self.floating_w, self.floating_h)
self.overlay.set_bg(self.bg_col)
self.overlay.set_fg(self.fg_col)
def save_config(self):
config = ConfigParser(interpolation=None)
config.read(self.configFile)
if not config.has_section("text"):
config.add_section("text")
config.set("text", "rightalign", "%d" % (int(self.align_x)))
config.set("text", "topalign", "%d" % (self.align_y))
config.set("text", "monitor", self.monitor)
config.set("text", "enabled", "%d" % (int(self.enabled)))
config.set("text", "floating", "%s" % (int(self.floating)))
config.set("text", "floating_x", "%s" % (self.floating_x))
config.set("text", "floating_y", "%s" % (self.floating_y))
config.set("text", "floating_w", "%s" % (self.floating_w))
config.set("text", "floating_h", "%s" % (self.floating_h))
config.set("text", "channel", self.channel)
config.set("text", "bg_col", json.dumps(self.bg_col))
config.set("text", "fg_col", json.dumps(self.fg_col))
if self.font:
config.set("text", "font", self.font)
with open(self.configFile, 'w') as file:
config.write(file)
def create_gui(self):
box = Gtk.Grid()
# Enabled
enabled_label = Gtk.Label.new("Enable")
enabled = Gtk.CheckButton.new()
enabled.set_active(self.enabled)
enabled.connect("toggled", self.change_enabled)
# Font chooser
font_label = Gtk.Label.new("Font")
font = Gtk.FontButton()
if self.font:
font.set_font(self.font)
font.connect("font-set", self.change_font)
# Colours
bg_col_label = Gtk.Label.new("Background colour")
bg_col = Gtk.ColorButton.new_with_rgba(
Gdk.RGBA(self.bg_col[0], self.bg_col[1], self.bg_col[2], self.bg_col[3]))
fg_col_label = Gtk.Label.new("Text colour")
fg_col = Gtk.ColorButton.new_with_rgba(
Gdk.RGBA(self.fg_col[0], self.fg_col[1], self.fg_col[2], self.fg_col[3]))
bg_col.set_use_alpha(True)
fg_col.set_use_alpha(True)
bg_col.connect("color-set", self.change_bg)
fg_col.connect("color-set", self.change_fg)
# Monitor & Alignment
align_label = Gtk.Label.new("Overlay Location")
align_type_box = Gtk.HBox()
align_type_edge = Gtk.RadioButton.new_with_label(
None, "Anchor to edge")
align_type_floating = Gtk.RadioButton.new_with_label_from_widget(
align_type_edge, "Floating")
if self.floating:
align_type_floating.set_active(True)
align_type_box.add(align_type_edge)
align_type_box.add(align_type_floating)
monitor_store = Gtk.ListStore(str)
display = Gdk.Display.get_default()
for i in range(0, display.get_n_monitors()):
monitor_store.append([display.get_monitor(i).get_model()])
monitor = Gtk.ComboBox.new_with_model(monitor_store)
monitor.set_active(self.get_monitor_index(self.monitor))
monitor.connect("changed", self.change_monitor)
rt = Gtk.CellRendererText()
monitor.pack_start(rt, True)
monitor.add_attribute(rt, "text", 0)
align_x_store = Gtk.ListStore(str)
align_x_store.append(["Left"])
align_x_store.append(["Right"])
align_x = Gtk.ComboBox.new_with_model(align_x_store)
align_x.set_active(True if self.align_x else False)
align_x.connect("changed", self.change_align_x)
rt = Gtk.CellRendererText()
align_x.pack_start(rt, True)
align_x.add_attribute(rt, "text", 0)
align_y_store = Gtk.ListStore(str)
align_y_store.append(["Top"])
align_y_store.append(["Middle"])
align_y_store.append(["Bottom"])
align_y = Gtk.ComboBox.new_with_model(align_y_store)
align_y.set_active(self.align_y)
align_y.connect("changed", self.change_align_y)
rt = Gtk.CellRendererText()
align_y.pack_start(rt, True)
align_y.add_attribute(rt, "text", 0)
align_placement_button = Gtk.Button.new_with_label("Place Window")
align_type_edge.connect("toggled", self.change_align_type_edge)
align_type_floating.connect("toggled", self.change_align_type_floating)
align_placement_button.connect("pressed", self.change_placement)
channel_label = Gtk.Label.new("Channel")
channel = Gtk.ComboBox.new()
channel.connect("changed", self.change_channel)
rt = Gtk.CellRendererText()
#channel.set_row_separator_func(lambda model, path: model[path][1])
channel.pack_start(rt, True)
channel.add_attribute(rt, "text", 0)
channel.add_attribute(rt, 'sensitive', 1)
self.align_x_widget = align_x
self.align_y_widget = align_y
self.align_monitor_widget = monitor
self.align_placement_widget = align_placement_button
self.channel_widget = channel
box.attach(enabled_label, 0, 0, 1, 1)
box.attach(enabled, 1, 0, 1, 1)
box.attach(channel_label, 0, 1, 1, 1)
box.attach(channel, 1, 1, 1, 1)
box.attach(font_label, 0, 2, 1, 1)
box.attach(font, 1, 2, 1, 1)
box.attach(fg_col_label, 0, 3, 1, 1)
box.attach(fg_col, 1, 3, 1, 1)
box.attach(bg_col_label, 0, 4, 1, 1)
box.attach(bg_col, 1, 4, 1, 1)
box.attach(align_label, 0, 5, 1, 5)
box.attach(align_type_box, 1, 5, 1, 1)
box.attach(monitor, 1, 6, 1, 1)
box.attach(align_x, 1, 7, 1, 1)
box.attach(align_y, 1, 8, 1, 1)
box.attach(align_placement_button, 1, 9, 1, 1)
self.add(box)
def change_font(self, button):
font = button.get_font()
desc = Pango.FontDescription.from_string(font)
s = desc.get_size()
if not desc.get_size_is_absolute():
s = s / Pango.SCALE
self.overlay.set_font(desc.get_family(), s)
self.font = desc.to_string()
self.save_config()
def change_channel(self, button):
if self.ignore_channel_change:
return
c = self.channel_lookup[button.get_active()]
self.channel = c
self.save_config()
def change_placement(self, button):
if self.placement_window:
(x, y) = self.placement_window.get_position()
(w, h) = self.placement_window.get_size()
self.floating_x = x
self.floating_y = y
self.floating_w = w
self.floating_h = h
self.overlay.set_floating(True, x, y, w, h)
self.save_config()
button.set_label("Place Window")
self.placement_window.close()
self.placement_window = None
else:
self.placement_window = DraggableWindow(
x=self.floating_x, y=self.floating_y, w=self.floating_w, h=self.floating_h, message="Place & resize this window then press Save!")
button.set_label("Save this position")
def change_align_type_edge(self, button):
if button.get_active():
self.overlay.set_floating(
False, self.floating_x, self.floating_y, self.floating_w, self.floating_h)
self.floating = False
self.save_config()
# Re-sort the screen
self.align_x_widget.show()
self.align_y_widget.show()
self.align_monitor_widget.show()
self.align_placement_widget.hide()
def change_align_type_floating(self, button):
if button.get_active():
self.overlay.set_floating(
True, self.floating_x, self.floating_y, self.floating_w, self.floating_h)
self.floating = True
self.save_config()
self.align_x_widget.hide()
self.align_y_widget.hide()
self.align_monitor_widget.hide()
self.align_placement_widget.show()
def change_monitor(self, button):
display = Gdk.Display.get_default()
mon = display.get_monitor(button.get_active())
m_s = mon.get_model()
self.overlay.set_monitor(button.get_active())
self.monitor = m_s
self.save_config()
def change_align_x(self, button):
self.overlay.set_align_x(button.get_active() == 1)
self.align_x = (button.get_active() == 1)
self.save_config()
def change_align_y(self, button):
self.overlay.set_align_y(button.get_active())
self.align_y = button.get_active()
self.save_config()
def change_enabled(self, button):
self.overlay.set_enabled(button.get_active())
self.enabled = button.get_active()
self.save_config()
def get_channel(self):
return self.channel
def change_bg(self, button):
c = button.get_rgba()
c = [c.red, c.green, c.blue, c.alpha]
self.overlay.set_bg(c)
self.bg_col = c
self.save_config()
def change_fg(self, button):
c = button.get_rgba()
c = [c.red, c.green, c.blue, c.alpha]
self.overlay.set_fg(c)
self.fg_col = c
self.save_config()

View file

@ -0,0 +1,313 @@
import gi
gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
gi.require_version('GdkPixbuf', '2.0')
import math
from .overlay import OverlayWindow
from gi.repository.GdkPixbuf import Pixbuf
from gi.repository import Gtk, GLib, Gio, GdkPixbuf, Gdk, Pango, PangoCairo
import cairo
import urllib
class VoiceOverlayWindow(OverlayWindow):
def __init__(self):
OverlayWindow.__init__(self)
self.avatars = {}
self.avatar_size = 48
self.text_pad = 6
self.text_font = None
self.text_size = 13
self.icon_spacing = 8
self.edge_padding = 0
self.round_avatar = True
self.talk_col = [0.0, 0.6, 0.0, 0.1]
self.text_col = [1.0, 1.0, 1.0, 1.0]
self.norm_col = [0.0, 0.0, 0.0, 0.5]
self.wind_col = [0.0, 0.0, 0.0, 0.0]
self.mute_col = [0.7, 0.0, 0.0, 1.0]
self.userlist = []
self.connected = False
self.force_location()
self.def_avatar = self.get_img(
"https://cdn.discordapp.com/embed/avatars/3.png")
self.first_draw = True
def set_bg(self, bg):
self.norm_col = bg
self.redraw()
def set_fg(self, fg):
self.text_col = fg
self.redraw()
def set_tk(self, tk):
self.talk_col = tk
self.redraw()
def set_mt(self, mt):
self.mute_col = mt
self.redraw()
def set_avatar_size(self, size):
self.avatar_size = size
self.reset_avatar()
self.redraw()
def set_icon_spacing(self, i):
self.icon_spacing = i
self.redraw()
def set_text_padding(self, i):
self.text_pad = i
self.redraw()
def set_edge_padding(self, i):
self.edge_padding = i
self.redraw()
def set_square_avatar(self, i):
self.round_avatar = not i
self.redraw()
def set_wind_col(self):
self.col(self.wind_col)
def set_text_col(self):
self.col(self.text_col)
def set_norm_col(self):
self.col(self.norm_col)
def set_talk_col(self, a=1.0):
self.col(self.talk_col, a)
def set_mute_col(self, a=1.0):
self.col(self.mute_col, a)
def reset_avatar(self):
self.avatars = {}
self.def_avatar = self.get_img(
"https://cdn.discordapp.com/embed/avatars/3.png")
def set_user_list(self, userlist, alt):
self.userlist = userlist
self.userlist.sort(key=lambda x: x["username"])
screen = self.get_screen()
c = screen.is_composited()
if not self.compositing == c:
alt = True
self.compositing = c
if alt:
self.redraw()
def set_connection(self, connection):
is_connected = connection == "VOICE_CONNECTED"
if self.connected != is_connected:
self.connected = is_connected
self.redraw()
def draw(self, widget, context):
# Draw
self.do_draw(context)
def do_draw(self, context):
self.context = context
context.set_antialias(cairo.ANTIALIAS_GOOD)
# Make background transparent
self.set_wind_col()
# Don't layer drawing over each other, always replace
context.set_operator(cairo.OPERATOR_SOURCE)
context.paint()
context.set_operator(cairo.OPERATOR_OVER)
if not self.connected:
return
# Get size of window
(w, h) = self.get_size()
# Calculate height needed to show overlay
height = (len(self.userlist) * self.avatar_size) + \
(len(self.userlist) + 1) * self.icon_spacing
# Choose where to start drawing
rh = 0 + self.edge_padding
if self.align_vert == 1:
# Ignore padding?
rh = (h / 2) - (height / 2)
elif self.align_vert == 2:
rh = h - height - self.edge_padding
# Iterate users in room.
for user in self.userlist:
self.draw_avatar(context, user, rh)
# Shift the relative position down to next location
rh += self.avatar_size + self.icon_spacing
# Don't hold a ref
self.context = None
def get_img(self, url):
req = urllib.request.Request(url)
req.add_header(
'Referer', 'https://streamkit.discord.com/overlay/voice')
req.add_header('User-Agent', 'Mozilla/5.0')
try:
response = urllib.request.urlopen(req)
input_stream = Gio.MemoryInputStream.new_from_data(
response.read(), None)
pixbuf = Pixbuf.new_from_stream(input_stream, None)
pixbuf = pixbuf.scale_simple(self.avatar_size, self.avatar_size,
GdkPixbuf.InterpType.BILINEAR)
return pixbuf
except:
print("Could not access : %s" % (url))
return none
def draw_avatar(self, context, user, y):
# Ensure pixbuf for avatar
if user["id"] not in self.avatars and user["avatar"]:
url = "https://cdn.discordapp.com/avatars/%s/%s.jpg" % (
user["id"], user["avatar"])
p = self.get_img(url)
if p:
self.avatars[user["id"]] = p
(w, h) = self.get_size()
c = None
mute = False
alpha = 1.0
if "speaking" in user and user["speaking"]:
c = self.talk_col
if "mute" in user and user["mute"]:
mute = True
if "deaf" in user and user["deaf"]:
alpha = 0.5
pix = None
if user["id"] in self.avatars:
pix = self.avatars[user["id"]]
if self.align_right:
self.draw_text(context, user["username"], w - self.avatar_size, y)
self.draw_avatar_pix(
context, pix, w - self.avatar_size, y, c, alpha)
if mute:
self.draw_mute(context, w - self.avatar_size, y, alpha)
else:
self.draw_text(context, user["username"], self.avatar_size, y)
self.draw_avatar_pix(context, pix, 0, y, c, alpha)
if mute:
self.draw_mute(context, 0, y, alpha)
def draw_text(self, context, string, x, y):
if self.text_font:
context.set_font_face(cairo.ToyFontFace(
self.text_font, cairo.FontSlant.NORMAL, cairo.FontWeight.NORMAL))
context.set_font_size(self.text_size)
xb, yb, w, h, dx, dy = context.text_extents(string)
ho = (self.avatar_size / 2) - (h / 2)
if self.align_right:
context.move_to(0, 0)
self.set_norm_col()
context.rectangle(x - w - (self.text_pad * 2), y + ho - self.text_pad,
w + (self.text_pad * 4), h + (self.text_pad * 2))
context.fill()
self.set_text_col()
context.move_to(x - w - self.text_pad, y + ho + h)
context.show_text(string)
else:
context.move_to(0, 0)
self.set_norm_col()
context.rectangle(x - (self.text_pad * 2), y + ho - self.text_pad,
w + (self.text_pad * 4), h + (self.text_pad * 2))
context.fill()
self.set_text_col()
context.move_to(x + self.text_pad, y + ho + h)
context.show_text(string)
def draw_avatar_pix(self, context, pixbuf, x, y, c, alpha):
context.move_to(x, y)
context.save()
#context.set_source_pixbuf(pixbuf, 0.0, 0.0)
if self.round_avatar:
context.arc(x + (self.avatar_size / 2), y +
(self.avatar_size / 2), self.avatar_size / 2, 0, 2 * math.pi)
context.clip()
if not pixbuf:
pixbuf = self.def_avatar
self.set_wind_col()
context.set_operator(cairo.OPERATOR_SOURCE)
context.rectangle(x, y, self.avatar_size, self.avatar_size)
context.fill()
context.set_operator(cairo.OPERATOR_OVER)
Gdk.cairo_set_source_pixbuf(context, pixbuf, x, y)
context.paint_with_alpha(alpha)
context.restore()
if c:
if self.round_avatar:
context.arc(x + (self.avatar_size / 2), y +
(self.avatar_size / 2), self.avatar_size / 2, 0, 2 * math.pi)
self.col(c)
context.stroke()
else:
context.rectangle(x, y, self.avatar_size, self.avatar_size)
self.col(c)
context.stroke()
def draw_mute(self, context, x, y, a):
context.save()
context.translate(x, y)
context.scale(self.avatar_size, self.avatar_size)
self.set_mute_col(a)
context.save()
# Clip Strike-through
context.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
context.set_line_width(0.1)
context.move_to(0.0, 0.0)
context.line_to(1.0, 0.0)
context.line_to(1.0, 1.0)
context.line_to(0.0, 1.0)
context.line_to(0.0, 0.0)
context.close_path()
context.new_sub_path()
context.arc(0.9, 0.1, 0.05, 1.25 * math.pi, 2.25 * math.pi)
context.arc(0.1, 0.9, 0.05, .25 * math.pi, 1.25 * math.pi)
context.close_path()
context.clip()
# Center
context.set_line_width(0.07)
context.arc(0.5, 0.3, 0.1, math.pi, 2 * math.pi)
context.arc(0.5, 0.5, 0.1, 0, math.pi)
context.close_path()
context.fill()
context.set_line_width(0.05)
# Stand rounded
context.arc(0.5, 0.5, 0.15, 0, 1.0 * math.pi)
context.stroke()
# Stand vertical
context.move_to(0.5, 0.65)
context.line_to(0.5, 0.75)
context.stroke()
# Stand horizontal
context.move_to(0.35, 0.75)
context.line_to(0.65, 0.75)
context.stroke()
context.restore()
# Strike through
context.arc(0.7, 0.3, 0.035, 1.25 * math.pi, 2.25 * math.pi)
context.arc(0.3, 0.7, 0.035, .25 * math.pi, 1.25 * math.pi)
context.close_path()
context.fill()
context.restore()

View file

@ -0,0 +1,410 @@
import gi
gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
gi.require_version('GdkPixbuf', '2.0')
import json
from configparser import ConfigParser
from .draggable_window import DraggableWindow
from .settings import SettingsWindow
from gi.repository.GdkPixbuf import Pixbuf
from gi.repository import Gtk, GLib, Gio, GdkPixbuf, Gdk, Pango, PangoCairo
class VoiceSettingsWindow(SettingsWindow):
def __init__(self, overlay):
Gtk.Window.__init__(self)
self.overlay = overlay
self.set_size_request(400, 200)
self.connect("destroy", self.close_window)
self.connect("delete-event", self.close_window)
self.placement_window = None
self.init_config()
self.create_gui()
def present(self):
self.show_all()
if not self.floating:
self.align_x_widget.show()
self.align_y_widget.show()
self.align_monitor_widget.show()
self.align_placement_widget.hide()
else:
self.align_x_widget.hide()
self.align_y_widget.hide()
self.align_monitor_widget.hide()
self.align_placement_widget.show()
def read_config(self):
config = ConfigParser(interpolation=None)
config.read(self.configFile)
self.align_x = config.getboolean("main", "rightalign", fallback=True)
self.align_y = config.getint("main", "topalign", fallback=1)
self.bg_col = json.loads(config.get(
"main", "bg_col", fallback="[0.0,0.0,0.0,0.5]"))
self.fg_col = json.loads(config.get(
"main", "fg_col", fallback="[1.0,1.0,1.0,1.0]"))
self.tk_col = json.loads(config.get(
"main", "tk_col", fallback="[0.0,0.7,0.0,1.0]"))
self.mt_col = json.loads(config.get(
"main", "mt_col", fallback="[0.6,0.0,0.0,1.0]"))
self.avatar_size = config.getint("main", "avatar_size", fallback=48)
self.icon_spacing = config.getint("main", "icon_spacing", fallback=8)
self.text_padding = config.getint("main", "text_padding", fallback=6)
self.font = config.get("main", "font", fallback=None)
self.square_avatar = config.getboolean(
"main", "square_avatar", fallback=False)
self.monitor = config.get("main", "monitor", fallback="None")
self.edge_padding = config.getint("main", "edge_padding", fallback=0)
self.floating = config.getboolean("main", "floating", fallback=False)
self.floating_x = config.getint("main", "floating_x", fallback=0)
self.floating_y = config.getint("main", "floating_y", fallback=0)
self.floating_w = config.getint("main", "floating_w", fallback=400)
self.floating_h = config.getint("main", "floating_h", fallback=400)
# Pass all of our config over to the overlay
self.overlay.set_align_x(self.align_x)
self.overlay.set_align_y(self.align_y)
self.overlay.set_bg(self.bg_col)
self.overlay.set_fg(self.fg_col)
self.overlay.set_tk(self.tk_col)
self.overlay.set_mt(self.mt_col)
self.overlay.set_avatar_size(self.avatar_size)
self.overlay.set_icon_spacing(self.icon_spacing)
self.overlay.set_text_padding(self.text_padding)
self.overlay.set_square_avatar(self.square_avatar)
self.overlay.set_monitor(self.get_monitor_index(self.monitor))
self.overlay.set_edge_padding(self.edge_padding)
self.overlay.set_floating(
self.floating, self.floating_x, self.floating_y, self.floating_w, self.floating_h)
if self.font:
desc = Pango.FontDescription.from_string(self.font)
s = desc.get_size()
if not desc.get_size_is_absolute():
s = s / Pango.SCALE
self.overlay.set_font(desc.get_family(), s)
def save_config(self):
config = ConfigParser(interpolation=None)
config.read(self.configFile)
if not config.has_section("main"):
config.add_section("main")
config.set("main", "rightalign", "%d" % (int(self.align_x)))
config.set("main", "topalign", "%d" % (self.align_y))
config.set("main", "bg_col", json.dumps(self.bg_col))
config.set("main", "fg_col", json.dumps(self.fg_col))
config.set("main", "tk_col", json.dumps(self.tk_col))
config.set("main", "mt_col", json.dumps(self.mt_col))
config.set("main", "avatar_size", "%d" % (self.avatar_size))
config.set("main", "icon_spacing", "%d" % (self.icon_spacing))
config.set("main", "text_padding", "%d" % (self.text_padding))
if self.font:
config.set("main", "font", self.font)
config.set("main", "square_avatar", "%d" % (int(self.square_avatar)))
config.set("main", "monitor", self.monitor)
config.set("main", "edge_padding", "%d" % (self.edge_padding))
config.set("main", "floating", "%s" % (int(self.floating)))
config.set("main", "floating_x", "%s" % (self.floating_x))
config.set("main", "floating_y", "%s" % (self.floating_y))
config.set("main", "floating_w", "%s" % (self.floating_w))
config.set("main", "floating_h", "%s" % (self.floating_h))
with open(self.configFile, 'w') as file:
config.write(file)
def create_gui(self):
box = Gtk.Grid()
# Font chooser
font_label = Gtk.Label.new("Font")
font = Gtk.FontButton()
if self.font:
font.set_font(self.font)
font.connect("font-set", self.change_font)
# Colours
bg_col_label = Gtk.Label.new("Background colour")
bg_col = Gtk.ColorButton.new_with_rgba(
Gdk.RGBA(self.bg_col[0], self.bg_col[1], self.bg_col[2], self.bg_col[3]))
fg_col_label = Gtk.Label.new("Text colour")
fg_col = Gtk.ColorButton.new_with_rgba(
Gdk.RGBA(self.fg_col[0], self.fg_col[1], self.fg_col[2], self.fg_col[3]))
tk_col_label = Gtk.Label.new("Talk colour")
tk_col = Gtk.ColorButton.new_with_rgba(
Gdk.RGBA(self.tk_col[0], self.tk_col[1], self.tk_col[2], self.tk_col[3]))
mt_col_label = Gtk.Label.new("Mute colour")
mt_col = Gtk.ColorButton.new_with_rgba(
Gdk.RGBA(self.mt_col[0], self.mt_col[1], self.mt_col[2], self.mt_col[3]))
bg_col.set_use_alpha(True)
fg_col.set_use_alpha(True)
tk_col.set_use_alpha(True)
mt_col.set_use_alpha(True)
bg_col.connect("color-set", self.change_bg)
fg_col.connect("color-set", self.change_fg)
tk_col.connect("color-set", self.change_tk)
mt_col.connect("color-set", self.change_mt)
# Avatar size
avatar_size_label = Gtk.Label.new("Avatar size")
avatar_adjustment = Gtk.Adjustment.new(
self.avatar_size, 8, 128, 1, 1, 8)
avatar_size = Gtk.SpinButton.new(avatar_adjustment, 0, 0)
avatar_size.connect("value-changed", self.change_avatar_size)
# Monitor & Alignment
align_label = Gtk.Label.new("Overlay Location")
align_type_box = Gtk.HBox()
align_type_edge = Gtk.RadioButton.new_with_label(
None, "Anchor to edge")
align_type_floating = Gtk.RadioButton.new_with_label_from_widget(
align_type_edge, "Floating")
if self.floating:
align_type_floating.set_active(True)
align_type_box.add(align_type_edge)
align_type_box.add(align_type_floating)
monitor_store = Gtk.ListStore(str)
display = Gdk.Display.get_default()
for i in range(0, display.get_n_monitors()):
monitor_store.append([display.get_monitor(i).get_model()])
monitor = Gtk.ComboBox.new_with_model(monitor_store)
monitor.set_active(self.get_monitor_index(self.monitor))
monitor.connect("changed", self.change_monitor)
rt = Gtk.CellRendererText()
monitor.pack_start(rt, True)
monitor.add_attribute(rt, "text", 0)
align_x_store = Gtk.ListStore(str)
align_x_store.append(["Left"])
align_x_store.append(["Right"])
align_x = Gtk.ComboBox.new_with_model(align_x_store)
align_x.set_active(True if self.align_x else False)
align_x.connect("changed", self.change_align_x)
rt = Gtk.CellRendererText()
align_x.pack_start(rt, True)
align_x.add_attribute(rt, "text", 0)
align_y_store = Gtk.ListStore(str)
align_y_store.append(["Top"])
align_y_store.append(["Middle"])
align_y_store.append(["Bottom"])
align_y = Gtk.ComboBox.new_with_model(align_y_store)
align_y.set_active(self.align_y)
align_y.connect("changed", self.change_align_y)
rt = Gtk.CellRendererText()
align_y.pack_start(rt, True)
align_y.add_attribute(rt, "text", 0)
align_placement_button = Gtk.Button.new_with_label("Place Window")
align_type_edge.connect("toggled", self.change_align_type_edge)
align_type_floating.connect("toggled", self.change_align_type_floating)
align_placement_button.connect("pressed", self.change_placement)
self.align_x_widget = align_x
self.align_y_widget = align_y
self.align_monitor_widget = monitor
self.align_placement_widget = align_placement_button
# Icon spacing
icon_spacing_label = Gtk.Label.new("Icon Spacing")
icon_spacing_adjustment = Gtk.Adjustment.new(
self.icon_spacing, 0, 64, 1, 1, 0)
icon_spacing = Gtk.SpinButton.new(icon_spacing_adjustment, 0, 0)
icon_spacing.connect("value-changed", self.change_icon_spacing)
# Text padding
text_padding_label = Gtk.Label.new("Text Padding")
text_padding_adjustment = Gtk.Adjustment.new(
self.text_padding, 0, 64, 1, 1, 0)
text_padding = Gtk.SpinButton.new(text_padding_adjustment, 0, 0)
text_padding.connect("value-changed", self.change_text_padding)
# Edge padding
edge_padding_label = Gtk.Label.new("Edge Padding")
edge_padding_adjustment = Gtk.Adjustment.new(
self.edge_padding, 0, 1000, 1, 1, 0)
edge_padding = Gtk.SpinButton.new(edge_padding_adjustment, 0, 0)
edge_padding.connect("value-changed", self.change_edge_padding)
# Avatar shape
square_avatar_label = Gtk.Label.new("Square Avatar")
square_avatar = Gtk.CheckButton.new()
square_avatar.set_active(self.square_avatar)
square_avatar.connect("toggled", self.change_square_avatar)
box.attach(font_label, 0, 0, 1, 1)
box.attach(font, 1, 0, 1, 1)
box.attach(bg_col_label, 0, 1, 1, 1)
box.attach(bg_col, 1, 1, 1, 1)
box.attach(fg_col_label, 0, 2, 1, 1)
box.attach(fg_col, 1, 2, 1, 1)
box.attach(tk_col_label, 0, 3, 1, 1)
box.attach(tk_col, 1, 3, 1, 1)
box.attach(mt_col_label, 0, 4, 1, 1)
box.attach(mt_col, 1, 4, 1, 1)
box.attach(avatar_size_label, 0, 5, 1, 1)
box.attach(avatar_size, 1, 5, 1, 1)
box.attach(align_label, 0, 6, 1, 5)
box.attach(align_type_box, 1, 6, 1, 1)
box.attach(monitor, 1, 7, 1, 1)
box.attach(align_x, 1, 8, 1, 1)
box.attach(align_y, 1, 9, 1, 1)
box.attach(align_placement_button, 1, 10, 1, 1)
box.attach(icon_spacing_label, 0, 11, 1, 1)
box.attach(icon_spacing, 1, 11, 1, 1)
box.attach(text_padding_label, 0, 12, 1, 1)
box.attach(text_padding, 1, 12, 1, 1)
box.attach(edge_padding_label, 0, 13, 1, 1)
box.attach(edge_padding, 1, 13, 1, 1)
box.attach(square_avatar_label, 0, 14, 1, 1)
box.attach(square_avatar, 1, 14, 1, 1)
self.add(box)
pass
def change_placement(self, button):
if self.placement_window:
(x, y) = self.placement_window.get_position()
(w, h) = self.placement_window.get_size()
self.floating_x = x
self.floating_y = y
self.floating_w = w
self.floating_h = h
self.overlay.set_floating(True, x, y, w, h)
self.save_config
button.set_label("Place Window")
self.placement_window.close()
self.placement_window = None
else:
self.placement_window = DraggableWindow(
x=self.floating_x, y=self.floating_y,
w=self.floating_w, h=self.floating_h,
message="Place & resize this window then press Save!")
button.set_label("Save this position")
def change_align_type_edge(self, button):
if button.get_active():
self.overlay.set_floating(
False, self.floating_x, self.floating_y,
self.floating_w, self.floating_h)
self.floating = False
self.save_config()
# Re-sort the screen
self.align_x_widget.show()
self.align_y_widget.show()
self.align_monitor_widget.show()
self.align_placement_widget.hide()
def change_align_type_floating(self, button):
if button.get_active():
self.overlay.set_floating(
True, self.floating_x, self.floating_y,
self.floating_w, self.floating_h)
self.floating = True
self.save_config()
self.align_x_widget.hide()
self.align_y_widget.hide()
self.align_monitor_widget.hide()
self.align_placement_widget.show()
def change_font(self, button):
font = button.get_font()
desc = Pango.FontDescription.from_string(font)
s = desc.get_size()
if not desc.get_size_is_absolute():
s = s / Pango.SCALE
self.overlay.set_font(desc.get_family(), s)
self.font = desc.to_string()
self.save_config()
def change_bg(self, button):
c = button.get_rgba()
c = [c.red, c.green, c.blue, c.alpha]
self.overlay.set_bg(c)
self.bg_col = c
self.save_config()
def change_fg(self, button):
c = button.get_rgba()
c = [c.red, c.green, c.blue, c.alpha]
self.overlay.set_fg(c)
self.fg_col = c
self.save_config()
def change_tk(self, button):
c = button.get_rgba()
c = [c.red, c.green, c.blue, c.alpha]
self.overlay.set_tk(c)
self.tk_col = c
self.save_config()
def change_mt(self, button):
c = button.get_rgba()
c = [c.red, c.green, c.blue, c.alpha]
self.overlay.set_mt(c)
self.mt_col = c
self.save_config()
def change_avatar_size(self, button):
self.overlay.set_avatar_size(button.get_value())
self.avatar_size = button.get_value()
self.save_config()
def change_monitor(self, button):
display = Gdk.Display.get_default()
mon = display.get_monitor(button.get_active())
m_s = mon.get_model()
self.overlay.set_monitor(button.get_active())
self.monitor = m_s
self.save_config()
def change_align_x(self, button):
self.overlay.set_align_x(button.get_active() == 1)
self.align_x = (button.get_active() == 1)
self.save_config()
def change_align_y(self, button):
self.overlay.set_align_y(button.get_active())
self.align_y = button.get_active()
self.save_config()
def change_icon_spacing(self, button):
self.overlay.set_icon_spacing(button.get_value())
self.icon_spacing = int(button.get_value())
self.save_config()
def change_text_padding(self, button):
self.overlay.set_text_padding(button.get_value())
self.text_padding = button.get_value()
self.save_config()
def change_edge_padding(self, button):
self.overlay.set_edge_padding(button.get_value())
self.edge_padding = button.get_value()
self.save_config()
def change_square_avatar(self, button):
self.overlay.set_square_avatar(button.get_active())
self.square_avatar = button.get_active()
self.save_config()