+import sys\r
+sys.coinit_flags= 0\r
import win32com.client\r
import pywintypes\r
import os\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
-powerpoint = None\r
-cache = r'''C:\Windows\Temp'''\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.CGIHTTPRequestHandler):\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
-async def first(websocket, path):\r
- slideshow_view_first()\r
- await websocket.send(True)\r
-\r
-STATE = {"value": "?", "visible": "1"}\r
-\r
+STATE = {"connected": 0, "current": 0, "total": 0, "visible": 0}\r
USERS = set()\r
\r
\r
\r
\r
async def notify_state():\r
- STATE["value"] = str(current_slide()) + "/" + str(total_slides())\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 ws_handle(websocket, path):\r
+ global current_slideshow\r
# register(websocket) sends user_event() to websocket\r
await register(websocket)\r
try:\r
async for message in websocket:\r
data = json.loads(message)\r
if data["action"] == "prev":\r
- slideshow_view_previous()\r
+ if current_slideshow:\r
+ current_slideshow.prev()\r
await notify_state()\r
elif data["action"] == "next":\r
- slideshow_view_next()\r
+ if current_slideshow:\r
+ current_slideshow.next()\r
await notify_state()\r
elif data["action"] == "first":\r
- slideshow_view_first()\r
+ if current_slideshow:\r
+ current_slideshow.first()\r
await notify_state()\r
elif data["action"] == "last":\r
- slideshow_view_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
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["value"] = current_slide()\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
- #ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)\r
- #ws_daemon.setDaemon(True)\r
- #ws_daemon.start()\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
# cleanup_stop_thread()\r
# sys.exit()\r
\r
-\r
-def get_slideshow_view():\r
- global powerpoint\r
-\r
- if powerpoint is None:\r
- powerpoint = win32com.client.Dispatch('Powerpoint.Application')\r
-\r
- if powerpoint is None:\r
- return\r
-\r
- ssw = powerpoint.SlideShowWindows\r
- if ssw.Count == 0:\r
- return\r
-\r
- # https://docs.microsoft.com/en-us/office/vba/api/powerpoint.slideshowwindow.view\r
- ssv = ssw[0].View\r
-\r
- return ssv\r
-\r
-def get_activepresentation():\r
- global powerpoint\r
-\r
- if powerpoint is None:\r
- powerpoint = win32com.client.Dispatch('Powerpoint.Application')\r
-\r
- if powerpoint is None:\r
- return\r
-\r
- activepres = powerpoint.ActivePresentation\r
- return activepres\r
-\r
-def total_slides():\r
- ssp = get_activepresentation()\r
- if ssp:\r
- return len(ssp.Slides)\r
-\r
-def current_slide():\r
- ssv = get_slideshow_view()\r
- if ssv:\r
- return ssv.CurrentShowPosition\r
-\r
-def export(slide):\r
- global cache\r
- ssp = get_activepresentation()\r
- if ssp:\r
- for (slide, name) in [(slide, "current"), (slide+1, "next")]:\r
- if slide < len(ssp.Slides):\r
- ssp.Slides(slide).Export(os.path.dirname(os.path.realpath(__file__)) + r'''\\''' + name + r'''0.jpg''', "JPG")\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
- os.replace(os.path.dirname(os.path.realpath(__file__)) + r'''\\''' + name + r'''0.jpg''', os.path.dirname(os.path.realpath(__file__)) + r'''\\''' + name + '''.jpg''')\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
- shutil.copyfileobj(open(os.path.dirname(os.path.realpath(__file__)) + r'''\blank.jpg''', 'rb'), open(os.path.dirname(os.path.realpath(__file__)) + r'''\\''' + name + r'''next.jpg''', 'wb'))\r
-\r
-def slideshow_view_first():\r
- ssv = get_slideshow_view()\r
- if ssv:\r
- ssv.First()\r
- export(ssv.CurrentShowPosition)\r
-\r
-def slideshow_view_previous():\r
- ssv = get_slideshow_view()\r
- if ssv:\r
- ssv.Previous()\r
- export(ssv.CurrentShowPosition)\r
-\r
-def slideshow_view_next():\r
- ssv = get_slideshow_view()\r
- if ssv:\r
- ssv.Next()\r
- export(ssv.CurrentShowPosition)\r
- \r
-\r
-def slideshow_view_last():\r
- ssv = get_slideshow_view()\r
- if ssv:\r
- ssv.Last()\r
- export(ssv.CurrentShowPosition)\r
-\r
-def slideshow_view_black():\r
- ssv = get_slideshow_view()\r
- if ssv:\r
- ssv.State = 3\r
-\r
-def slideshow_view_white():\r
- ssv = get_slideshow_view()\r
- if ssv:\r
- ssv.State = 4\r
-\r
-def slideshow_view_normal():\r
- ssv = get_slideshow_view()\r
- if ssv:\r
- ssv.State = 1\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