1# -*- coding: utf-8 -*-
2
3import obspython as obs
4import asyncio
5import websockets
6import threading
7from time import sleep
8
9PORT_DEFAULT = 5678
10HOSTNAME_DEFAULT = "localhost"
11
12hotkey_id_first = None
13hotkey_id_prev = None
14hotkey_id_next = None
15hotkey_id_last = None
16hotkey_id_black = None
17hotkey_id_white = None
18
19HOTKEY_NAME_FIRST = 'powerpoint_slides.first'
20HOTKEY_NAME_PREV = 'powerpoint_slides.previous'
21HOTKEY_NAME_NEXT = 'powerpoint_slides.next'
22HOTKEY_NAME_LAST = 'powerpoint_slides.last'
23HOTKEY_NAME_BLACK = 'powerpoint_slides.black'
24HOTKEY_NAME_WHITE = 'powerpoint_slides.white'
25
26HOTKEY_DESC_FIRST = 'First PowerPoint slide'
27HOTKEY_DESC_PREV = 'Previous PowerPoint slide'
28HOTKEY_DESC_NEXT = 'Next PowerPoint slide'
29HOTKEY_DESC_LAST = 'Last PowerPoint slide'
30HOTKEY_DESC_BLACK = 'Black PowerPoint slide'
31HOTKEY_DESC_WHITE = 'White PowerPoint slide'
32
33global cmd
34global attempts
35cmd = ""
36hostname = HOSTNAME_DEFAULT
37port = PORT_DEFAULT
38attempts = 0
39
40async def communicate():
41 async with websockets.connect("ws://%s:%s" % (hostname, port), ping_interval=None) as websocket:
42 global cmd
43 global attempts
44 while True:
45 if cmd:
46 try:
47 await websocket.send('{"action": "%s"}' % cmd)
48 cmd = ""
49 except websockets.ConnectionClosed as exc:
50 attempts += 1
51 if attempts == 4:
52 print("Failed to send command after {} attempts - aborting connection".format(attempts))
53 attempts = 0
54 cmd = ""
55 raise websockets.exceptions.ConnectionClosedError(1006, "Sending command failed after {} attempts".format(attempts))
56 await asyncio.sleep(0.05 + 0.5*attempts**2)
57
58def run_ws():
59 while True:
60 try:
61 asyncio.set_event_loop(asyncio.new_event_loop())
62 asyncio.get_event_loop().run_until_complete(communicate())
63 except (OSError, websockets.exceptions.ConnectionClosedError):
64 # No server available - just keep trying
65 pass
66 except Exception as e:
67 print("Failed to connect to websocket: {} - {}".format(type(e), e))
68 finally:
69 sleep(1)
70
71#------------------------------------------------------------
72# global functions for script plugins
73
74def script_load(settings):
75 global hotkey_id_first
76 global hotkey_id_prev
77 global hotkey_id_next
78 global hotkey_id_last
79 global hotkey_id_black
80 global hotkey_id_white
81
82 hotkey_id_first = register_and_load_hotkey(settings, HOTKEY_NAME_FIRST, HOTKEY_DESC_FIRST, first_slide)
83 hotkey_id_prev = register_and_load_hotkey(settings, HOTKEY_NAME_PREV, HOTKEY_DESC_PREV, prev_slide)
84 hotkey_id_next = register_and_load_hotkey(settings, HOTKEY_NAME_NEXT, HOTKEY_DESC_NEXT, next_slide)
85 hotkey_id_last = register_and_load_hotkey(settings, HOTKEY_NAME_LAST, HOTKEY_DESC_LAST, last_slide)
86 hotkey_id_black = register_and_load_hotkey(settings, HOTKEY_NAME_BLACK, HOTKEY_DESC_BLACK, black)
87 hotkey_id_white = register_and_load_hotkey(settings, HOTKEY_NAME_WHITE, HOTKEY_DESC_WHITE, white)
88
89 ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)
90 ws_daemon.setDaemon(True)
91 ws_daemon.start()
92 print("Started websocket client")
93
94def script_unload():
95 obs.obs_hotkey_unregister(first_slide)
96 obs.obs_hotkey_unregister(prev_slide)
97 obs.obs_hotkey_unregister(next_slide)
98 obs.obs_hotkey_unregister(last_slide)
99 obs.obs_hotkey_unregister(black)
100 obs.obs_hotkey_unregister(white)
101
102def script_save(settings):
103 save_hotkey(settings, HOTKEY_NAME_FIRST, hotkey_id_first)
104 save_hotkey(settings, HOTKEY_NAME_PREV, hotkey_id_prev)
105 save_hotkey(settings, HOTKEY_NAME_NEXT, hotkey_id_next)
106 save_hotkey(settings, HOTKEY_NAME_LAST, hotkey_id_last)
107 save_hotkey(settings, HOTKEY_NAME_BLACK, hotkey_id_black)
108 save_hotkey(settings, HOTKEY_NAME_WHITE, hotkey_id_white)
109
110def script_description():
111 return """ppt-control client
112
113 Provides hotkeys for controlling PowerPoint slides using websockets.
114 Go to OBS settings -> Hotkeys to change hotkeys (none set by default)."""
115
116def script_defaults(settings):
117 obs.obs_data_set_default_string(settings, 'hostname', HOSTNAME_DEFAULT)
118 obs.obs_data_set_default_int(settings, 'port', PORT_DEFAULT)
119
120def script_properties():
121 props = obs.obs_properties_create()
122
123 obs.obs_properties_add_text(props, "hostname", "Hostname: ", obs.OBS_TEXT_DEFAULT)
124 obs.obs_properties_add_int(props, "port", "Port: ", 0, 9999, 1)
125 return props
126
127def script_update(settings):
128 global port
129 port = obs.obs_data_get_int(settings, "port")
130 hostname = obs.obs_data_get_string(settings, "hostname")
131
132def register_and_load_hotkey(settings, name, description, callback):
133 hotkey_id = obs.obs_hotkey_register_frontend(name, description, callback)
134 hotkey_save_array = obs.obs_data_get_array(settings, name)
135 obs.obs_hotkey_load(hotkey_id, hotkey_save_array)
136 obs.obs_data_array_release(hotkey_save_array)
137
138 return hotkey_id
139
140def save_hotkey(settings, name, hotkey_id):
141 hotkey_save_array = obs.obs_hotkey_save(hotkey_id)
142 obs.obs_data_set_array(settings, name, hotkey_save_array)
143 obs.obs_data_array_release(hotkey_save_array)
144
145#-------------------------------------
146
147def first_slide(pressed):
148 if pressed:
149 global cmd
150 cmd = "first"
151
152def prev_slide(pressed):
153 if pressed:
154 global cmd
155 cmd = "prev"
156
157def next_slide(pressed):
158 if pressed:
159 global cmd
160 cmd = "next"
161
162def last_slide(pressed):
163 if pressed:
164 global cmd
165 cmd = "last"
166
167def black(pressed):
168 if pressed:
169 global cmd
170 cmd = "black"
171
172def white(pressed):
173 if pressed:
174 global cmd
175 cmd = "white"