-import sys\r
-sys.coinit_flags= 0\r
-import win32com.client\r
-import pywintypes\r
-import os\r
-import shutil\r
-import http_server_39 as server\r
-#import http.server as server\r
-import socketserver\r
-import threading\r
-import asyncio\r
-import websockets\r
-import logging, json\r
-import urllib\r
-import posixpath\r
-import time\r
-import pythoncom\r
-\r
-logging.basicConfig()\r
-\r
-global current_slideshow\r
-current_slideshow = None\r
-CACHEDIR = r'''C:\Windows\Temp\ppt-cache'''\r
-CACHE_TIMEOUT = 2*60*60\r
-\r
-class Handler(server.SimpleHTTPRequestHandler):\r
- def __init__(self, *args, **kwargs):\r
- super().__init__(*args, directory=os.path.dirname(os.path.realpath(__file__)))\r
- \r
- def translate_path(self, path):\r
- """Translate a /-separated PATH to the local filename syntax.\r
-\r
- Components that mean special things to the local file system\r
- (e.g. drive or directory names) are ignored. (XXX They should\r
- probably be diagnosed.)\r
-\r
- """\r
- # abandon query parameters\r
- path = path.split('?',1)[0]\r
- path = path.split('#',1)[0]\r
- # Don't forget explicit trailing slash when normalizing. Issue17324\r
- trailing_slash = path.rstrip().endswith('/')\r
- try:\r
- path = urllib.parse.unquote(path, errors='surrogatepass')\r
- except UnicodeDecodeError:\r
- path = urllib.parse.unquote(path)\r
- path = posixpath.normpath(path)\r
- words = path.split('/')\r
- words = list(filter(None, words))\r
- if len(words) > 0 and words[0] == "cache":\r
- if current_slideshow:\r
- path = CACHEDIR + "\\" + current_slideshow.name()\r
- words.pop(0)\r
- else:\r
- path = self.directory\r
- for word in words:\r
- if os.path.dirname(word) or word in (os.curdir, os.pardir):\r
- # Ignore components that are not a simple file/directory name\r
- continue\r
- path = os.path.join(path, word)\r
- if trailing_slash:\r
- path += '/'\r
- print(path)\r
- return path\r
-\r
-\r
-def run_http():\r
- http_server = server.HTTPServer(("", 80), Handler)\r
- http_server.serve_forever()\r
-\r
-STATE = {"connected": 0, "current": 0, "total": 0, "visible": 0}\r
-USERS = set()\r
-\r
-\r
-def state_event():\r
- return json.dumps({"type": "state", **STATE})\r
-\r
-\r
-def users_event():\r
- return json.dumps({"type": "users", "count": len(USERS)})\r
-\r
-\r
-async def notify_state():\r
- global current_slideshow\r
- if current_slideshow:\r
- STATE["current"] = current_slideshow.current_slide()\r
- STATE["total"] = current_slideshow.total_slides()\r
- STATE["visible"] = current_slideshow.visible()\r
- if USERS: # asyncio.wait doesn't accept an empty list\r
- message = state_event()\r
- await asyncio.wait([user.send(message) for user in USERS])\r
-\r
-\r
-async def notify_users():\r
- if USERS: # asyncio.wait doesn't accept an empty list\r
- message = users_event()\r
- await asyncio.wait([user.send(message) for user in USERS])\r
-\r
-\r
-async def register(websocket):\r
- USERS.add(websocket)\r
- await notify_users()\r
-\r
-\r
-async def unregister(websocket):\r
- USERS.remove(websocket)\r
- await notify_users()\r
-\r
-\r
-async def ws_handle(websocket, path):\r
- global current_slideshow\r
- # register(websocket) sends user_event() to websocket\r
- await register(websocket)\r
- try:\r
- await websocket.send(state_event())\r
- async for message in websocket:\r
- data = json.loads(message)\r
- if data["action"] == "prev":\r
- if current_slideshow:\r
- current_slideshow.prev()\r
- await notify_state()\r
- elif data["action"] == "next":\r
- if current_slideshow:\r
- current_slideshow.next()\r
- await notify_state()\r
- elif data["action"] == "first":\r
- if current_slideshow:\r
- current_slideshow.first()\r
- await notify_state()\r
- elif data["action"] == "last":\r
- if current_slideshow:\r
- current_slideshow.last()\r
- await notify_state()\r
- elif data["action"] == "black":\r
- if current_slideshow:\r
- if current_slideshow.visible() == 3:\r
- current_slideshow.normal()\r
- else:\r
- current_slideshow.black()\r
- await notify_state()\r
- elif data["action"] == "white":\r
- if current_slideshow:\r
- if current_slideshow.visible() == 4:\r
- current_slideshow.normal()\r
- else:\r
- current_slideshow.white()\r
- await notify_state()\r
- elif data["action"] == "goto":\r
- if current_slideshow:\r
- current_slideshow.goto(int(data["value"]))\r
- await notify_state()\r
- elif data["action"] == "refresh":\r
- if current_slideshow:\r
- current_slideshow.export_current_next()\r
- current_slideshow.refresh()\r
- await notify_state()\r
- else:\r
- logging.error("unsupported event: {}", data)\r
- finally:\r
- await unregister(websocket)\r
-\r
-def run_ws():\r
- pythoncom.CoInitializeEx(pythoncom.COINIT_MULTITHREADED)\r
- asyncio.set_event_loop(asyncio.new_event_loop())\r
- start_server = websockets.serve(ws_handle, "0.0.0.0", 5678)\r
- print("Initialised websocket server")\r
- asyncio.get_event_loop().run_until_complete(start_server)\r
- print("Running websocket server until complete") \r
- asyncio.get_event_loop().run_forever()\r
-\r
-def start_server():\r
- #STATE["current"] = current_slide()\r
- http_daemon = threading.Thread(name="http_daemon", target=run_http)\r
- http_daemon.setDaemon(True)\r
- http_daemon.start()\r
- print("Started HTTP server")\r
-\r
- #run_ws()\r
- \r
- ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)\r
- ws_daemon.setDaemon(True)\r
- ws_daemon.start()\r
- print("Started websocket server")\r
-\r
- #try:\r
- # ws_daemon.start()\r
- # http_daemon.start()\r
- #except (KeyboardInterrupt, SystemExit):\r
- # cleanup_stop_thread()\r
- # sys.exit()\r
-\r
-class Slideshow:\r
- def __init__(self, instance):\r
- self.instance = instance\r
- if self.instance is None:\r
- raise ValueError("PPT instance cannot be None")\r
-\r
- if self.instance.SlideShowWindows.Count == 0:\r
- raise ValueError("PPT instance has no slideshow windows")\r
- self.view = self.instance.SlideShowWindows[0].View\r
-\r
- if self.instance.ActivePresentation is None:\r
- raise ValueError("PPT instance has no active presentation")\r
- self.presentation = self.instance.ActivePresentation\r
- \r
- self.export_all()\r
-\r
- def refresh(self):\r
- if self.instance is None:\r
- raise ValueError("PPT instance cannot be None")\r
-\r
- #if self.instance.SlideShowWindows.Count == 0:\r
- # raise ValueError("PPT instance has no slideshow windows")\r
- self.view = self.instance.SlideShowWindows[0].View\r
-\r
- if self.instance.ActivePresentation is None:\r
- raise ValueError("PPT instance has no active presentation")\r
-\r
- def total_slides(self):\r
- return len(self.presentation.Slides)\r
-\r
- def current_slide(self):\r
- return self.view.CurrentShowPosition\r
-\r
- def visible(self):\r
- return self.view.State\r
-\r
- def prev(self):\r
- self.refresh()\r
- self.view.Previous()\r
-\r
- def next(self):\r
- self.refresh()\r
- self.view.Next()\r
- self.export_current_next()\r
-\r
- def first(self):\r
- self.refresh()\r
- self.view.First()\r
- self.export_current_next()\r
- \r
- def last(self):\r
- self.refresh()\r
- self.view.Last()\r
- self.export_current_next()\r
-\r
- def goto(self, slide):\r
- self.refresh()\r
- if slide <= self.total_slides():\r
- self.view.GotoSlide(slide)\r
- else:\r
- self.last()\r
- self.next()\r
- self.export_current_next()\r
-\r
- def black(self):\r
- self.refresh()\r
- self.view.State = 3\r
-\r
- def white(self):\r
- self.refresh()\r
- self.view.State = 4\r
-\r
- def normal(self):\r
- self.refresh()\r
- self.view.State = 1\r
-\r
- def name(self):\r
- return self.presentation.Name\r
-\r
- def export_current_next(self):\r
- self.export(self.current_slide())\r
- self.export(self.current_slide() + 1)\r
-\r
- def export(self, slide):\r
- destination = CACHEDIR + "\\" + self.name() + "\\" + str(slide) + ".jpg"\r
- os.makedirs(os.path.dirname(destination), exist_ok=True)\r
- if not os.path.exists(destination) or time.time() - os.path.getmtime(destination) > CACHE_TIMEOUT:\r
- if slide <= self.total_slides():\r
- attempts = 0\r
- while attempts < 3:\r
- try:\r
- self.presentation.Slides(slide).Export(destination, "JPG")\r
- time.sleep(0.5)\r
- break\r
- except:\r
- pass\r
- attempts += 1\r
- elif slide == self.total_slides() + 1:\r
- shutil.copyfileobj(open(os.path.dirname(os.path.realpath(__file__)) + r'''\black.jpg''', 'rb'), open(destination, 'wb'))\r
- else:\r
- pass\r
-\r
- def export_all(self):\r
- for i in range(1, self.total_slides()):\r
- self.export(i)\r
-\r
-def get_ppt_instance():\r
- instance = win32com.client.Dispatch('Powerpoint.Application')\r
- if instance is None or instance.SlideShowWindows.Count == 0:\r
- return None\r
- return instance\r
-\r
-def get_current_slideshow():\r
- print(str(current_slideshow))\r
- return current_slideshow\r
-\r
-\r
-if __name__ == "__main__":\r
-\r
- start_server()\r
- \r
- while True:\r
- # Check if PowerPoint is running\r
- instance = get_ppt_instance()\r
- try:\r
- current_slideshow = Slideshow(instance)\r
- STATE["connected"] = 1\r
- STATE["current"] = current_slideshow.current_slide()\r
- STATE["total"] = current_slideshow.total_slides()\r
- print("Connected to PowerPoint instance " + str(get_current_slideshow()))\r
- break\r
- except ValueError as e:\r
- current_slideshow = None\r
- pass\r
- time.sleep(1)\r