replace notify_state timer with PPT async events
[ppt-control.git] / ppt_control / ppt_control.py
old mode 100755 (executable)
new mode 100644 (file)
index 4218f5c..8502bcb
@@ -47,6 +47,7 @@ interface_root = None
 interface_thread = None\r
 CONFIG_FILE = r'''..\ppt-control.ini'''\r
 LOGFILE = r'''..\ppt-control.log'''\r
+REFRESH_INTERVAL = 2\r
 logger = None\r
 refresh_daemon = None\r
 status_label = None\r
@@ -56,6 +57,7 @@ ws_daemon = None
 http_server = None\r
 reset_ppt_button = None\r
 icon = None\r
+ws_stop_event = False\r
 \r
 \r
 class Handler(server.SimpleHTTPRequestHandler):\r
@@ -125,6 +127,7 @@ def state_event():
 \r
 \r
 def notify_state():\r
+    logger.debug("Notifying state")\r
     global STATE\r
     if current_slideshow and STATE["connected"] == 1:\r
         try:\r
@@ -166,37 +169,37 @@ async def ws_receive(websocket, path):
             if data["action"] == "prev":\r
                 if current_slideshow:\r
                     current_slideshow.prev()\r
-                notify_state()\r
+                #notify_state()\r
             elif data["action"] == "next":\r
                 if current_slideshow:\r
                     current_slideshow.next()\r
-                notify_state()\r
+                #notify_state()\r
             elif data["action"] == "first":\r
                 if current_slideshow:\r
                     current_slideshow.first()\r
-                notify_state()\r
+                #notify_state()\r
             elif data["action"] == "last":\r
                 if current_slideshow:\r
                     current_slideshow.last()\r
-                notify_state()\r
+                #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
-                notify_state()\r
+                #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
-                notify_state()\r
+                #notify_state()\r
             elif data["action"] == "goto":\r
                 if current_slideshow:\r
                     current_slideshow.goto(int(data["value"]))\r
-                notify_state()\r
+                #notify_state()\r
             else:\r
                 logger.error("Received unnsupported event: {}", data)\r
     finally:\r
@@ -244,6 +247,30 @@ def start_ws():
     ws_daemon.start()\r
     logger.info("Started websocket server")\r
 \r
+def restart_ws():\r
+    global ws_daemon\r
+    global ws_stop_event\r
+    if ws_daemon and not ws_stop_event:\r
+        ws_stop_event = True\r
+        logger.debug("Stopped WebSocket server")\r
+        refresh_status()\r
+        #ws_daemon = None\r
+        time.sleep(2)\r
+        #start_ws()\r
+        refresh_status()\r
+    \r
+\r
+class ApplicationEvents:\r
+    def OnSlideShowNextSlide(self, *args):\r
+        notify_state()\r
+        logger.debug("Slide changed")\r
+        current_slideshow.export_current_next()\r
+\r
+    def OnSlideShowPrevSlide(self, *args):\r
+        notify_state()\r
+        logger.debug("Slide changed")\r
+        current_slideshow.export_current_next()\r
+\r
 class Slideshow:\r
     def __init__(self, instance, blackwhite):\r
         self.instance = instance\r
@@ -252,15 +279,21 @@ class Slideshow:
 \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
+        self.view = self.instance.SlideShowWindows(1).View\r
 \r
         if self.instance.ActivePresentation is None:\r
-            raise ValueError("PPT instance has no  active presentation")\r
+            raise ValueError("PPT instance has no active presentation")\r
         self.presentation = self.instance.ActivePresentation\r
 \r
         self.blackwhite = blackwhite\r
 \r
-        self.export_current_next()\r
+        if config.prefs["Main"]["cache_init"]:\r
+            self.export_all()\r
+        else:\r
+            self.export_current_next()\r
+\r
+        events = win32com.client.WithEvents(win32com.client.GetActiveObject("Powerpoint.Application"), ApplicationEvents)\r
+        logger.debug("Dispatched events")\r
 \r
     def unload(self):\r
         connect_ppt()\r
@@ -272,10 +305,10 @@ class Slideshow:
 \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
+            self.view = self.instance.SlideShowWindows(1).View\r
 \r
             if self.instance.ActivePresentation is None:\r
-                raise ValueError("PPT instance has no  active presentation")\r
+                raise ValueError("PPT instance has no active presentation")\r
         except:\r
             self.unload()\r
 \r
@@ -389,6 +422,7 @@ class Slideshow:
 \r
     def export(self, slide):\r
         destination = config.prefs["Main"]["cache"] + "\\" + self.name() + "\\" + str(slide) + ".jpg"\r
+        logger.debug("Exporting slide " + str(slide))\r
         os.makedirs(os.path.dirname(destination), exist_ok=True)\r
         if not os.path.exists(destination) or time.time() - os.path.getmtime(destination) > config.prefs.getint("Main", "cache_timeout"):\r
             if slide <= self.total_slides():\r
@@ -409,7 +443,7 @@ class Slideshow:
                 pass\r
 \r
     def export_all(self):\r
-        for i in range(1, self.total_slides()):\r
+        for i in range(1, self.total_slides() + 2):\r
             self.export(i)\r
 \r
 def get_ppt_instance():\r
@@ -423,13 +457,16 @@ def get_current_slideshow():
 \r
 def refresh_interval():\r
     while getattr(refresh_daemon, "do_run", True):\r
+        logger.debug("Refreshing general")\r
+        pythoncom.PumpWaitingMessages()\r
         current_slideshow.refresh()\r
-        notify_state()\r
-        refresh_status()\r
-        time.sleep(0.5)\r
+        if current_slideshow.visible != STATE["visible"]:\r
+            notify_state()\r
+        #refresh_status()\r
+        time.sleep(REFRESH_INTERVAL)\r
 \r
 def refresh_status():\r
-    if interface_root is not None and interface_root.state == "normal":\r
+    if interface_root is not None:\r
         logger.debug("Refreshing UI")\r
         if status_label is not None:\r
             status_label.config(text="PowerPoint status: " + ("not " if not STATE["connected"] else "") +  "connected")\r
@@ -497,14 +534,15 @@ def on_closing():
 def open_settings(_=None):\r
     global interface_root\r
     global interface_thread\r
-    interface_root = tk.Tk()\r
-    interface_root.protocol("WM_DELETE_WINDOW", on_closing)\r
-    interface_root.iconphoto(False, tk.PhotoImage(file=os.path.dirname(os.path.realpath(__file__)) + r'''\static\icons\ppt.png'''))\r
-    interface_root.geometry("600x300+300+300")\r
-    app = Interface(interface_root)\r
-    interface_thread = threading.Thread(target=interface_root.mainloop())\r
-    interface_thread.setDaemon(True)\r
-    interface_thread.start()\r
+    if interface_root is None:\r
+        interface_root = tk.Tk()\r
+        interface_root.protocol("WM_DELETE_WINDOW", on_closing)\r
+        interface_root.iconphoto(False, tk.PhotoImage(file=os.path.dirname(os.path.realpath(__file__)) + r'''\static\icons\ppt.png'''))\r
+        interface_root.geometry("600x300+300+300")\r
+        app = Interface(interface_root)\r
+        interface_thread = threading.Thread(target=interface_root.mainloop())\r
+        interface_thread.setDaemon(True)\r
+        interface_thread.start()\r
 \r
 def null_action():\r
     pass\r
@@ -546,7 +584,7 @@ class Interface(ttk.Frame):
         reset_http_button = ttk.Button(self, text="Restart", command=restart_http)\r
         reset_http_button.place(x=300, y=30)\r
 \r
-        #reset_ws_button = ttk.Button(self, text="Restart", command=null_action)\r
+        #reset_ws_button = ttk.Button(self, text="Restart", command=restart_ws)\r
         #reset_ws_button.place(x=300, y=50)\r
 \r
         status_label = ttk.Label(self)\r
@@ -563,13 +601,18 @@ class Interface(ttk.Frame):
         \r
 def exit_action(icon):\r
     logger.debug("User requested shutdown")\r
+    if interface_root is not None:\r
+        try:\r
+            interface_root.destroy()\r
+        except:\r
+            pass\r
     icon.visible = False\r
     icon.stop()\r
 \r
 def refresh_menu():\r
     icon.menu = (pystray.MenuItem("Status: " + "dis"*(not STATE["connected"]) + "connected", lambda: null_action(), enabled=False),\r
             pystray.MenuItem("Stop", lambda: exit_action(icon)),\r
-            pystray.MenuItem("Settings", lambda: open_settings(), enabled=False)\r
+            pystray.MenuItem("Settings", lambda: open_settings(), enabled=True)\r
             )\r
 \r
 def show_icon():\r