From: Andrew Lorimer Date: Sun, 8 Aug 2021 06:51:29 +0000 (+1000) Subject: replace notify_state timer with PPT async events X-Git-Tag: v0.0.4~5 X-Git-Url: https://git.lorimer.id.au/ppt-control.git/diff_plain/94ddf1b0640beaddf4d36dc9ef5a882176dbce42 replace notify_state timer with PPT async events --- diff --git a/README.md b/README.md index d8b8f82..2d83314 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ will install all three components. You can then start the daemon by running `py -m ppt_control` -from a command prompt (note the underscore). There are a few steps to set the package up fully: +from a command prompt (note the underscore). On first run, Windows Defender will show a warning and attempt to block Python from starting the server. You can safely allow the program through the firewall. You can now start a PowerPoint slideshow and navigate to the server by IP address/hostname (`http://localhost` if on the same machine) and control the slideshow. + +There are a few steps to set the package up fully: ### Starting the daemon at bootup @@ -28,7 +30,7 @@ There are several ways to start a Python program at login. Here is one method: 1. Navigate to the directory containing the `pythonw` executable in Explorer (usually in `C:\Program Files\Python36` - run `python -c "import sys, print(sys.executable)"` to check) 2. Right click on `pythonw.exe` and click "Create shortcut" -3. A shortcut will be placed on the desktop. Go to the properties of this shortcut, and in the target field, append ` -m ppt_control` (after the quotes, including an initial space). You can also rename the shortcut if you like. +3. Windows will ask you whether to place a shortcut on the desktop - click yes. Go to the properties of this shortcut, and in the target field, append ` -m ppt_control` (after the quotes, including an initial space). You can also change the name and icon of the shortcut if you like. 4. Copy this shortcut into the Startup folder (`%AppData%\Microsoft\Windows\Start Menu\Programs\Startup`). To quickly navigate to this folder, open an Explorer window and type `startup` in the address bar. ### Using the HTTP interface in OBS diff --git a/ppt_control/config.py b/ppt_control/config.py index 7964cc9..e0416a4 100644 --- a/ppt_control/config.py +++ b/ppt_control/config.py @@ -6,10 +6,11 @@ prefs = None defaults = { 'Main': { - 'logging': 'info', + 'logging': 'debug', 'cache': r'''C:\Windows\Temp\ppt-cache''', 'cache_format': 'JPG', 'cache_timeout': 5*60, + 'cache_init': True, 'blackwhite': 'both' }, 'HTTP': { diff --git a/ppt_control/http_server_39.py b/ppt_control/http_server_39.py old mode 100755 new mode 100644 diff --git a/ppt_control/ppt_control.py b/ppt_control/ppt_control.py old mode 100755 new mode 100644 index 4218f5c..8502bcb --- a/ppt_control/ppt_control.py +++ b/ppt_control/ppt_control.py @@ -47,6 +47,7 @@ interface_root = None interface_thread = None CONFIG_FILE = r'''..\ppt-control.ini''' LOGFILE = r'''..\ppt-control.log''' +REFRESH_INTERVAL = 2 logger = None refresh_daemon = None status_label = None @@ -56,6 +57,7 @@ ws_daemon = None http_server = None reset_ppt_button = None icon = None +ws_stop_event = False class Handler(server.SimpleHTTPRequestHandler): @@ -125,6 +127,7 @@ def state_event(): def notify_state(): + logger.debug("Notifying state") global STATE if current_slideshow and STATE["connected"] == 1: try: @@ -166,37 +169,37 @@ async def ws_receive(websocket, path): if data["action"] == "prev": if current_slideshow: current_slideshow.prev() - notify_state() + #notify_state() elif data["action"] == "next": if current_slideshow: current_slideshow.next() - notify_state() + #notify_state() elif data["action"] == "first": if current_slideshow: current_slideshow.first() - notify_state() + #notify_state() elif data["action"] == "last": if current_slideshow: current_slideshow.last() - notify_state() + #notify_state() elif data["action"] == "black": if current_slideshow: if current_slideshow.visible() == 3: current_slideshow.normal() else: current_slideshow.black() - notify_state() + #notify_state() elif data["action"] == "white": if current_slideshow: if current_slideshow.visible() == 4: current_slideshow.normal() else: current_slideshow.white() - notify_state() + #notify_state() elif data["action"] == "goto": if current_slideshow: current_slideshow.goto(int(data["value"])) - notify_state() + #notify_state() else: logger.error("Received unnsupported event: {}", data) finally: @@ -244,6 +247,30 @@ def start_ws(): ws_daemon.start() logger.info("Started websocket server") +def restart_ws(): + global ws_daemon + global ws_stop_event + if ws_daemon and not ws_stop_event: + ws_stop_event = True + logger.debug("Stopped WebSocket server") + refresh_status() + #ws_daemon = None + time.sleep(2) + #start_ws() + refresh_status() + + +class ApplicationEvents: + def OnSlideShowNextSlide(self, *args): + notify_state() + logger.debug("Slide changed") + current_slideshow.export_current_next() + + def OnSlideShowPrevSlide(self, *args): + notify_state() + logger.debug("Slide changed") + current_slideshow.export_current_next() + class Slideshow: def __init__(self, instance, blackwhite): self.instance = instance @@ -252,15 +279,21 @@ class Slideshow: if self.instance.SlideShowWindows.Count == 0: raise ValueError("PPT instance has no slideshow windows") - self.view = self.instance.SlideShowWindows[0].View + self.view = self.instance.SlideShowWindows(1).View if self.instance.ActivePresentation is None: - raise ValueError("PPT instance has no active presentation") + raise ValueError("PPT instance has no active presentation") self.presentation = self.instance.ActivePresentation self.blackwhite = blackwhite - self.export_current_next() + if config.prefs["Main"]["cache_init"]: + self.export_all() + else: + self.export_current_next() + + events = win32com.client.WithEvents(win32com.client.GetActiveObject("Powerpoint.Application"), ApplicationEvents) + logger.debug("Dispatched events") def unload(self): connect_ppt() @@ -272,10 +305,10 @@ class Slideshow: if self.instance.SlideShowWindows.Count == 0: raise ValueError("PPT instance has no slideshow windows") - self.view = self.instance.SlideShowWindows[0].View + self.view = self.instance.SlideShowWindows(1).View if self.instance.ActivePresentation is None: - raise ValueError("PPT instance has no active presentation") + raise ValueError("PPT instance has no active presentation") except: self.unload() @@ -389,6 +422,7 @@ class Slideshow: def export(self, slide): destination = config.prefs["Main"]["cache"] + "\\" + self.name() + "\\" + str(slide) + ".jpg" + logger.debug("Exporting slide " + str(slide)) os.makedirs(os.path.dirname(destination), exist_ok=True) if not os.path.exists(destination) or time.time() - os.path.getmtime(destination) > config.prefs.getint("Main", "cache_timeout"): if slide <= self.total_slides(): @@ -409,7 +443,7 @@ class Slideshow: pass def export_all(self): - for i in range(1, self.total_slides()): + for i in range(1, self.total_slides() + 2): self.export(i) def get_ppt_instance(): @@ -423,13 +457,16 @@ def get_current_slideshow(): def refresh_interval(): while getattr(refresh_daemon, "do_run", True): + logger.debug("Refreshing general") + pythoncom.PumpWaitingMessages() current_slideshow.refresh() - notify_state() - refresh_status() - time.sleep(0.5) + if current_slideshow.visible != STATE["visible"]: + notify_state() + #refresh_status() + time.sleep(REFRESH_INTERVAL) def refresh_status(): - if interface_root is not None and interface_root.state == "normal": + if interface_root is not None: logger.debug("Refreshing UI") if status_label is not None: status_label.config(text="PowerPoint status: " + ("not " if not STATE["connected"] else "") + "connected") @@ -497,14 +534,15 @@ def on_closing(): def open_settings(_=None): global interface_root global interface_thread - interface_root = tk.Tk() - interface_root.protocol("WM_DELETE_WINDOW", on_closing) - interface_root.iconphoto(False, tk.PhotoImage(file=os.path.dirname(os.path.realpath(__file__)) + r'''\static\icons\ppt.png''')) - interface_root.geometry("600x300+300+300") - app = Interface(interface_root) - interface_thread = threading.Thread(target=interface_root.mainloop()) - interface_thread.setDaemon(True) - interface_thread.start() + if interface_root is None: + interface_root = tk.Tk() + interface_root.protocol("WM_DELETE_WINDOW", on_closing) + interface_root.iconphoto(False, tk.PhotoImage(file=os.path.dirname(os.path.realpath(__file__)) + r'''\static\icons\ppt.png''')) + interface_root.geometry("600x300+300+300") + app = Interface(interface_root) + interface_thread = threading.Thread(target=interface_root.mainloop()) + interface_thread.setDaemon(True) + interface_thread.start() def null_action(): pass @@ -546,7 +584,7 @@ class Interface(ttk.Frame): reset_http_button = ttk.Button(self, text="Restart", command=restart_http) reset_http_button.place(x=300, y=30) - #reset_ws_button = ttk.Button(self, text="Restart", command=null_action) + #reset_ws_button = ttk.Button(self, text="Restart", command=restart_ws) #reset_ws_button.place(x=300, y=50) status_label = ttk.Label(self) @@ -563,13 +601,18 @@ class Interface(ttk.Frame): def exit_action(icon): logger.debug("User requested shutdown") + if interface_root is not None: + try: + interface_root.destroy() + except: + pass icon.visible = False icon.stop() def refresh_menu(): icon.menu = (pystray.MenuItem("Status: " + "dis"*(not STATE["connected"]) + "connected", lambda: null_action(), enabled=False), pystray.MenuItem("Stop", lambda: exit_action(icon)), - pystray.MenuItem("Settings", lambda: open_settings(), enabled=False) + pystray.MenuItem("Settings", lambda: open_settings(), enabled=True) ) def show_icon(): diff --git a/ppt_control/ppt_control_obs.py b/ppt_control/ppt_control_obs.py old mode 100755 new mode 100644 diff --git a/ppt_control/static/black.jpg b/ppt_control/static/black.jpg old mode 100755 new mode 100644 diff --git a/ppt_control/static/icons/ppt.png b/ppt_control/static/icons/ppt.png old mode 100755 new mode 100644 diff --git a/ppt_control/static/index.html b/ppt_control/static/index.html old mode 100755 new mode 100644