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 return path
64
65
66def run_http():
67 http_server = server.HTTPServer(("", 80), Handler)
68 http_server.serve_forever()
69
70STATE = {"connected": 0, "current": 0, "total": 0, "visible": 0, "name": ""}
71USERS = set()
72
73
74def state_event():
75 return json.dumps({"type": "state", **STATE})
76
77
78def users_event():
79 return json.dumps({"type": "users", "count": len(USERS)})
80
81
82async def notify_state():
83 global current_slideshow
84 if current_slideshow:
85 STATE["current"] = current_slideshow.current_slide()
86 STATE["total"] = current_slideshow.total_slides()
87 STATE["visible"] = current_slideshow.visible()
88 STATE["name"] = current_slideshow.name()
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 asyncio.get_event_loop().run_until_complete(start_server)
167 asyncio.get_event_loop().run_forever()
168
169def start_server():
170 #STATE["current"] = current_slide()
171 http_daemon = threading.Thread(name="http_daemon", target=run_http)
172 http_daemon.setDaemon(True)
173 http_daemon.start()
174 print("Started HTTP server")
175
176 #run_ws()
177
178 ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)
179 ws_daemon.setDaemon(True)
180 ws_daemon.start()
181 print("Started websocket server")
182
183 #try:
184 # ws_daemon.start()
185 # http_daemon.start()
186 #except (KeyboardInterrupt, SystemExit):
187 # cleanup_stop_thread()
188 # sys.exit()
189
190class Slideshow:
191 def __init__(self, instance):
192 self.instance = instance
193 if self.instance is None:
194 raise ValueError("PPT instance cannot be None")
195
196 if self.instance.SlideShowWindows.Count == 0:
197 raise ValueError("PPT instance has no slideshow windows")
198 self.view = self.instance.SlideShowWindows[0].View
199
200 if self.instance.ActivePresentation is None:
201 raise ValueError("PPT instance has no active presentation")
202 self.presentation = self.instance.ActivePresentation
203
204 self.export_all()
205
206 def refresh(self):
207 if self.instance is None:
208 raise ValueError("PPT instance cannot be None")
209
210 #if self.instance.SlideShowWindows.Count == 0:
211 # raise ValueError("PPT instance has no slideshow windows")
212 self.view = self.instance.SlideShowWindows[0].View
213
214 if self.instance.ActivePresentation is None:
215 raise ValueError("PPT instance has no active presentation")
216
217 def total_slides(self):
218 return len(self.presentation.Slides)
219
220 def current_slide(self):
221 return self.view.CurrentShowPosition
222
223 def visible(self):
224 return self.view.State
225
226 def prev(self):
227 self.refresh()
228 self.view.Previous()
229
230 def next(self):
231 self.refresh()
232 self.view.Next()
233 self.export_current_next()
234
235 def first(self):
236 self.refresh()
237 self.view.First()
238 self.export_current_next()
239
240 def last(self):
241 self.refresh()
242 self.view.Last()
243 self.export_current_next()
244
245 def goto(self, slide):
246 self.refresh()
247 if slide <= self.total_slides():
248 self.view.GotoSlide(slide)
249 else:
250 self.last()
251 self.next()
252 self.export_current_next()
253
254 def black(self):
255 self.refresh()
256 self.view.State = 3
257
258 def white(self):
259 self.refresh()
260 self.view.State = 4
261
262 def normal(self):
263 self.refresh()
264 self.view.State = 1
265
266 def name(self):
267 return self.presentation.Name
268
269 def export_current_next(self):
270 self.export(self.current_slide())
271 self.export(self.current_slide() + 1)
272
273 def export(self, slide):
274 destination = CACHEDIR + "\\" + self.name() + "\\" + str(slide) + ".jpg"
275 os.makedirs(os.path.dirname(destination), exist_ok=True)
276 if not os.path.exists(destination) or time.time() - os.path.getmtime(destination) > CACHE_TIMEOUT:
277 if slide <= self.total_slides():
278 attempts = 0
279 while attempts < 3:
280 try:
281 self.presentation.Slides(slide).Export(destination, "JPG")
282 time.sleep(0.5)
283 break
284 except:
285 pass
286 attempts += 1
287 elif slide == self.total_slides() + 1:
288 shutil.copyfileobj(open(os.path.dirname(os.path.realpath(__file__)) + r'''\black.jpg''', 'rb'), open(destination, 'wb'))
289 else:
290 pass
291
292 def export_all(self):
293 for i in range(1, self.total_slides()):
294 self.export(i)
295
296def get_ppt_instance():
297 instance = win32com.client.Dispatch('Powerpoint.Application')
298 if instance is None or instance.SlideShowWindows.Count == 0:
299 return None
300 return instance
301
302def get_current_slideshow():
303 return current_slideshow
304
305
306if __name__ == "__main__":
307
308 start_server()
309
310 while True:
311 # Check if PowerPoint is running
312 instance = get_ppt_instance()
313 try:
314 current_slideshow = Slideshow(instance)
315 STATE["connected"] = 1
316 STATE["current"] = current_slideshow.current_slide()
317 STATE["total"] = current_slideshow.total_slides()
318 print("Connected to PowerPoint instance")
319 break
320 except ValueError as e:
321 current_slideshow = None
322 pass
323 time.sleep(1)