generated from SteamDeckHomebrew/decky-plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.py
230 lines (194 loc) · 7.8 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
from aiohttp.web import (
Application,
get,
WebSocketResponse,
AppRunner,
TCPSite,
)
from asyncio import sleep, create_task, create_subprocess_exec
import aiohttp_cors
from json import dumps
from pathlib import Path
from subprocess import PIPE
import os, sys
sys.path.append(os.path.dirname(__file__))
from tab_utils.tab import (
create_discord_tab,
setup_discord_tab,
boot_discord,
setOSK,
)
from tab_utils.cdp import Tab, get_tab
from discord_client.event_handler import EventHandler
from decky_plugin import logger, DECKY_PLUGIN_DIR
from logging import INFO
logger.setLevel(INFO)
async def stream_watcher(stream, is_err=False):
async for line in stream:
line = line.decode("utf-8")
if not line.strip():
continue
if is_err:
logger.debug("ERROR: " + line)
else:
logger.debug(line)
async def initialize():
tab = await create_discord_tab()
await setup_discord_tab(tab)
await boot_discord(tab)
create_task(watchdog(tab))
async def watchdog(tab: Tab):
while True:
while not tab.websocket.closed:
await sleep(1)
logger.info("Discord tab websocket is no longer open. Trying to reconnect...")
try:
await tab.open_websocket()
logger.info("Reconnected")
except:
break
logger.info("Discord has died. Re-initializing...")
while True:
try:
await initialize()
break
except:
await sleep(1)
class Plugin:
server = Application()
cors = aiohttp_cors.setup(
server,
defaults={
"*": aiohttp_cors.ResourceOptions(
expose_headers="*", allow_headers="*", allow_credentials=True
)
},
)
evt_handler = EventHandler()
async def _main(self):
logger.info("Starting Deckcord backend")
await initialize()
logger.info("Discord initialized")
Plugin.server.add_routes(
[
get("/openkb", Plugin._openkb),
get("/socket", Plugin._websocket_handler),
get("/frontend_socket", Plugin._frontend_socket_handler)
]
)
for r in list(Plugin.server.router.routes())[:-1]:
Plugin.cors.add(r)
Plugin.runner = AppRunner(Plugin.server, access_log=None)
await Plugin.runner.setup()
logger.info("Starting server.")
await TCPSite(Plugin.runner, "0.0.0.0", 65123).start()
Plugin.shared_js_tab = await get_tab("SharedJSContext")
await Plugin.shared_js_tab.open_websocket()
create_task(Plugin._notification_dispatcher())
Plugin.webrtc_server = await create_subprocess_exec(
"/usr/bin/python",
str(Path(DECKY_PLUGIN_DIR) / "gst_webrtc.py"),
env={
"LD_LIBRARY_PATH": str(Path(DECKY_PLUGIN_DIR) / "bin"),
"GI_TYPELIB_PATH": str(Path(DECKY_PLUGIN_DIR) / "bin/girepository-1.0"),
"GST_PLUGIN_PATH": str(Path(DECKY_PLUGIN_DIR) / "bin/gstreamer-1.0"),
"GST_VAAPI_ALL_DRIVERS": "1",
"OPENSSL_CONF": "/etc/ssl/openssl.cnf",
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
"XDG_RUNTIME_DIR": "/run/user/1000",
"XDG_DATA_DIRS": "/home/deck/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share",
"LIBVA_DRIVER_NAME": "radeonsi",
},
stdout=PIPE,
stderr=PIPE,
)
create_task(stream_watcher(Plugin.webrtc_server.stdout))
create_task(stream_watcher(Plugin.webrtc_server.stderr, True))
while True:
await sleep(3600)
async def _openkb(request):
await Plugin.shared_js_tab.ensure_open()
await setOSK(Plugin.shared_js_tab, True)
logger.info("Setting discord visibility to true")
return "OK"
async def _websocket_handler(request):
logger.info("Received websocket connection!")
ws = WebSocketResponse(max_msg_size=0)
await ws.prepare(request)
await Plugin.evt_handler.main(ws)
last_ws: WebSocketResponse = None
async def _frontend_socket_handler(request):
if Plugin.last_ws:
await Plugin.last_ws.close()
logger.info("Received frontend websocket connection!")
ws = WebSocketResponse(max_msg_size=0)
Plugin.last_ws = ws
await ws.prepare(request)
async for state in Plugin.evt_handler.yield_new_state():
await ws.send_json(state)
async def _notification_dispatcher():
async for notification in Plugin.evt_handler.yield_notification():
logger.info("Dispatching notification")
payload = dumps(
{"title": notification["title"], "body": notification["body"]}
)
await Plugin.shared_js_tab.ensure_open()
await Plugin.shared_js_tab.evaluate(f"window.DECKCORD.dispatchNotification(JSON.parse('{payload}'));")
async def connect_ws(*args):
await Plugin.shared_js_tab.ensure_open()
await Plugin.shared_js_tab.evaluate(f"window.DECKCORD.connectWs()")
async def get_state(*args):
return Plugin.evt_handler.build_state_dict()
async def toggle_mute(*args):
logger.info("Toggling mute")
return await Plugin.evt_handler.toggle_mute(act=True)
async def toggle_deafen(*args):
logger.info("Toggling deafen")
return await Plugin.evt_handler.toggle_deafen(act=True)
async def disconnect_vc(*args):
logger.info("Disconnecting vc")
return await Plugin.evt_handler.disconnect_vc()
async def set_ptt(plugin, value):
await Plugin.evt_handler.ws.send_json({"type": "$ptt", "value": value})
async def enable_ptt(plugin, enabled):
await Plugin.evt_handler.ws.send_json({"type": "$setptt", "enabled": enabled})
async def set_rpc(plugin, game):
logger.info("Setting RPC")
await Plugin.evt_handler.ws.send_json({"type": "$rpc", "game": game})
async def get_last_channels(plugin):
return await plugin.evt_handler.api.get_last_channels()
async def post_screenshot(plugin, channel_id, data):
logger.info("Posting screenshot to " + channel_id)
r = await Plugin.evt_handler.api.post_screenshot(channel_id, data)
if r:
return True
payload = dumps({"title": "Deckcord", "body": "Error while posting screenshot"})
await Plugin.shared_js_tab.ensure_open()
await Plugin.shared_js_tab.evaluate(
f"DeckyPluginLoader.toaster.toast(JSON.parse('{payload}'));"
)
async def get_screen_bounds(plugin):
return await plugin.evt_handler.api.get_screen_bounds()
async def go_live(plugin):
await plugin.evt_handler.ws.send_json({"type": "$golive", "stop": False})
async def stop_go_live(plugin):
await plugin.evt_handler.ws.send_json({"type": "$golive", "stop": True})
async def mic_webrtc_answer(plugin, answer):
await plugin.evt_handler.ws.send_json({"type": "$webrtc", "payload": answer})
async def _unload(*args):
if hasattr(Plugin, "webrtc_server"):
Plugin.webrtc_server.kill()
await Plugin.webrtc_server.wait()
if hasattr(Plugin, "runner"):
await Plugin.runner.shutdown()
await Plugin.runner.cleanup()
if hasattr(Plugin, "shared_js_tab"):
await Plugin.shared_js_tab.ensure_open()
await Plugin.shared_js_tab.evaluate(
"""
window.DISCORD_TAB.m_browserView.SetVisible(false);
window.DISCORD_TAB.Destroy();
window.DISCORD_TAB = undefined;
"""
)
await Plugin.shared_js_tab.close_websocket()