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