1import sys
2sys.coinit_flags= 0
3import win32com.client
4import pywintypes
5import os
6import shutil
7import http_server_39 as server
8#import http.server as server
9import socketserver
10import threading
11import asyncio
12import websockets
13import logging, json
14import urllib
15import posixpath
16import time
17import pythoncom
18
19logging.basicConfig()
20
21global current_slideshow
22current_slideshow = None
23CACHEDIR = r'''C:\Windows\Temp\ppt-cache'''
24CACHE_TIMEOUT = 2*60*60
25
26class Handler(server.SimpleHTTPRequestHandler):
27 def __init__(self, *args, **kwargs):
28 super().__init__(*args, directory=os.path.dirname(os.path.realpath(__file__)))
29
30 def translate_path(self, path):
31 """Translate a /-separated PATH to the local filename syntax.
32
33 Components that mean special things to the local file system
34 (e.g. drive or directory names) are ignored. (XXX They should
35 probably be diagnosed.)
36
37 """
38 # abandon query parameters
39 path = path.split('?',1)[0]
40 path = path.split('#',1)[0]
41 # Don't forget explicit trailing slash when normalizing. Issue17324
42 trailing_slash = path.rstrip().endswith('/')
43 try:
44 path = urllib.parse.unquote(path, errors='surrogatepass')
45 except UnicodeDecodeError:
46 path = urllib.parse.unquote(path)
47 path = posixpath.normpath(path)
48 words = path.split('/')
49 words = list(filter(None, words))
50 if len(words) > 0 and words[0] == "cache":
51 if current_slideshow:
52 path = CACHEDIR + "\\" + current_slideshow.name()
53 words.pop(0)
54 else:
55 path = self.directory
56 for word in words:
57 if os.path.dirname(word) or word in (os.curdir, os.pardir):
58 # Ignore components that are not a simple file/directory name
59 continue
60 path = os.path.join(path, word)
61 if trailing_slash:
62 path += '/'
63 print(path)
64 return path
65
66
67def run_http():
68 http_server = server.HTTPServer(("", 80), Handler)
69 http_server.serve_forever()
70
71STATE = {"connected": 0, "current": 0, "total": 0, "visible": 0}
72USERS = set()
73
74
75def state_event():
76 return json.dumps({"type": "state", **STATE})
77
78
79def users_event():
80 return json.dumps({"type": "users", "count": len(USERS)})
81
82
83async def notify_state():
84 global current_slideshow
85 if current_slideshow:
86 STATE["current"] = current_slideshow.current_slide()
87 STATE["total"] = current_slideshow.total_slides()
88 STATE["visible"] = current_slideshow.visible()
89 if USERS: # asyncio.wait doesn't accept an empty list
90 message = state_event()
91 await asyncio.wait([user.send(message) for user in USERS])
92
93
94async def notify_users():
95 if USERS: # asyncio.wait doesn't accept an empty list
96 message = users_event()
97 await asyncio.wait([user.send(message) for user in USERS])
98
99
100async def register(websocket):
101 USERS.add(websocket)
102 await notify_users()
103
104
105async def unregister(websocket):
106 USERS.remove(websocket)
107 await notify_users()
108
109
110async def ws_handle(websocket, path):
111 global current_slideshow
112 # register(websocket) sends user_event() to websocket
113 await register(websocket)
114 try:
115 await websocket.send(state_event())
116 async for message in websocket:
117 data = json.loads(message)
118 if data["action"] == "prev":
119 if current_slideshow:
120 current_slideshow.prev()
121 await notify_state()
122 elif data["action"] == "next":
123 if current_slideshow:
124 current_slideshow.next()
125 await notify_state()
126 elif data["action"] == "first":
127 if current_slideshow:
128 current_slideshow.first()
129 await notify_state()
130 elif data["action"] == "last":
131 if current_slideshow:
132 current_slideshow.last()
133 await notify_state()
134 elif data["action"] == "black":
135 if current_slideshow:
136 if current_slideshow.visible() == 3:
137 current_slideshow.normal()
138 else:
139 current_slideshow.black()
140 await notify_state()
141 elif data["action"] == "white":
142 if current_slideshow:
143 if current_slideshow.visible() == 4:
144 current_slideshow.normal()
145 else:
146 current_slideshow.white()
147 await notify_state()
148 elif data["action"] == "goto":
149 if current_slideshow:
150 current_slideshow.goto(int(data["value"]))
151 await notify_state()
152 elif data["action"] == "refresh":
153 if current_slideshow:
154 current_slideshow.export_current_next()
155 current_slideshow.refresh()
156 await notify_state()
157 else:
158 logging.error("unsupported event: {}", data)
159 finally:
160 await unregister(websocket)
161
162def run_ws():
163 pythoncom.CoInitializeEx(pythoncom.COINIT_MULTITHREADED)
164 asyncio.set_event_loop(asyncio.new_event_loop())
165 start_server = websockets.serve(ws_handle, "0.0.0.0", 5678)
166 print("Initialised websocket server")
167 asyncio.get_event_loop().run_until_complete(start_server)
168 print("Running websocket server until complete")
169 asyncio.get_event_loop().run_forever()
170
171def start_server():
172 #STATE["current"] = current_slide()
173 http_daemon = threading.Thread(name="http_daemon", target=run_http)
174 http_daemon.setDaemon(True)
175 http_daemon.start()
176 print("Started HTTP server")
177
178 #run_ws()
179
180 ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)
181 ws_daemon.setDaemon(True)
182 ws_daemon.start()
183 print("Started websocket server")
184
185 #try:
186 # ws_daemon.start()
187 # http_daemon.start()
188 #except (KeyboardInterrupt, SystemExit):
189 # cleanup_stop_thread()
190 # sys.exit()
191
192class Slideshow:
193 def __init__(self, instance):
194 self.instance = instance
195 if self.instance is None:
196 raise ValueError("PPT instance cannot be None")
197
198 if self.instance.SlideShowWindows.Count == 0:
199 raise ValueError("PPT instance has no slideshow windows")
200 self.view = self.instance.SlideShowWindows[0].View
201
202 if self.instance.ActivePresentation is None:
203 raise ValueError("PPT instance has no active presentation")
204 self.presentation = self.instance.ActivePresentation
205
206 self.export_all()
207
208 def refresh(self):
209 if self.instance is None:
210 raise ValueError("PPT instance cannot be None")
211
212 #if self.instance.SlideShowWindows.Count == 0:
213 # raise ValueError("PPT instance has no slideshow windows")
214 self.view = self.instance.SlideShowWindows[0].View
215
216 if self.instance.ActivePresentation is None:
217 raise ValueError("PPT instance has no active presentation")
218
219 def total_slides(self):
220 return len(self.presentation.Slides)
221
222 def current_slide(self):
223 return self.view.CurrentShowPosition
224
225 def visible(self):
226 return self.view.State
227
228 def prev(self):
229 self.refresh()
230 self.view.Previous()
231
232 def next(self):
233 self.refresh()
234 self.view.Next()
235 self.export_current_next()
236
237 def first(self):
238 self.refresh()
239 self.view.First()
240 self.export_current_next()
241
242 def last(self):
243 self.refresh()
244 self.view.Last()
245 self.export_current_next()
246
247 def goto(self, slide):
248 self.refresh()
249 if slide <= self.total_slides():
250 self.view.GotoSlide(slide)
251 else:
252 self.last()
253 self.next()
254 self.export_current_next()
255
256 def black(self):
257 self.refresh()
258 self.view.State = 3
259
260 def white(self):
261 self.refresh()
262 self.view.State = 4
263
264 def normal(self):
265 self.refresh()
266 self.view.State = 1
267
268 def name(self):
269 return self.presentation.Name
270
271 def export_current_next(self):
272 self.export(self.current_slide())
273 self.export(self.current_slide() + 1)
274
275 def export(self, slide):
276 destination = CACHEDIR + "\\" + self.name() + "\\" + str(slide) + ".jpg"
277 os.makedirs(os.path.dirname(destination), exist_ok=True)
278 if not os.path.exists(destination) or time.time() - os.path.getmtime(destination) > CACHE_TIMEOUT:
279 if slide <= self.total_slides():
280 attempts = 0
281 while attempts < 3:
282 try:
283 self.presentation.Slides(slide).Export(destination, "JPG")
284 time.sleep(0.5)
285 break
286 except:
287 pass
288 attempts += 1
289 elif slide == self.total_slides() + 1:
290 shutil.copyfileobj(open(os.path.dirname(os.path.realpath(__file__)) + r'''\black.jpg''', 'rb'), open(destination, 'wb'))
291 else:
292 pass
293
294 def export_all(self):
295 for i in range(1, self.total_slides()):
296 self.export(i)
297
298def get_ppt_instance():
299 instance = win32com.client.Dispatch('Powerpoint.Application')
300 if instance is None or instance.SlideShowWindows.Count == 0:
301 return None
302 return instance
303
304def get_current_slideshow():
305 print(str(current_slideshow))
306 return current_slideshow
307
308
309if __name__ == "__main__":
310
311 start_server()
312
313 while True:
314 # Check if PowerPoint is running
315 instance = get_ppt_instance()
316 try:
317 current_slideshow = Slideshow(instance)
318 STATE["connected"] = 1
319 STATE["current"] = current_slideshow.current_slide()
320 STATE["total"] = current_slideshow.total_slides()
321 print("Connected to PowerPoint instance " + str(get_current_slideshow()))
322 break
323 except ValueError as e:
324 current_slideshow = None
325 pass
326 time.sleep(1)