bugfixing, obs interface
authorAndrew Lorimer <andrew@lorimer.id.au>
Sat, 1 May 2021 07:11:33 +0000 (17:11 +1000)
committerAndrew Lorimer <andrew@lorimer.id.au>
Sat, 1 May 2021 07:11:33 +0000 (17:11 +1000)
icons/ppt.ico [new file with mode: 0755]
icons/ppt.png [new file with mode: 0755]
index.html
obs_ppt.py [deleted file]
ppt-control.js
ppt_control.py
ppt_control_obs.py [new file with mode: 0755]
settings.js
diff --git a/icons/ppt.ico b/icons/ppt.ico
new file mode 100755 (executable)
index 0000000..e3fb47b
Binary files /dev/null and b/icons/ppt.ico differ
diff --git a/icons/ppt.png b/icons/ppt.png
new file mode 100755 (executable)
index 0000000..841b67b
Binary files /dev/null and b/icons/ppt.png differ
index 5a0cec626fb85dda280d5abd531eaabedcc784c9..b19415f6cf566d9a9daf24b5246e6057de7f02dc 100755 (executable)
@@ -3,6 +3,7 @@
        <head>\r
        <link href="style.css" rel="stylesheet" type="text/css" media="all" />\r
        <script src="settings.js"></script>\r
+        <meta name="viewport" content="width=device-width, initial-scale=1" />\r
         <title>ppt-control</title>\r
     </head>\r
     <body onload="initSettings();">\r
@@ -28,7 +29,7 @@
                                <img class="icon" id="prev" src="icons/left.svg" />\r
                                <img class="icon" id="next" src="icons/right.svg" />\r
                                <img class="icon" id="last" src="icons/last.svg" />\r
-                                       <span id="count"><span id="slide_label">Current: </span><input type="text" id="current"></input>/<span id="total">?</span></span>\r
+                                <span id="count"><span id="slide_label">Current: </span><input type="text" id="current"></input>/<span id="total">?</span></span>\r
                                <button id="black">Black</button>\r
                                <button id="white">White</button>\r
                        </p>\r
@@ -41,6 +42,7 @@
                </div>\r
         </div>\r
 \r
+               <img id="preload_img" style="display: none;" />\r
        <script src="ppt-control.js"></script>\r
 \r
     </body>\r
diff --git a/obs_ppt.py b/obs_ppt.py
deleted file mode 100755 (executable)
index a4e0c39..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-# -*- coding: utf-8 -*-\r
-\r
-import obspython as obs\r
-# pip install pywin32\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 functools\r
-\r
-powerpoint = None\r
-hotkey_id_frst = None\r
-hotkey_id_prev = None\r
-hotkey_id_next = None\r
-hotkey_id_last = None\r
-hotkey_id_black = None\r
-\r
-HOTKEY_NAME_FRST = 'powerpoint_slides.first'\r
-HOTKEY_NAME_PREV = 'powerpoint_slides.previous'\r
-HOTKEY_NAME_NEXT = 'powerpoint_slides.next'\r
-HOTKEY_NAME_LAST = 'powerpoint_slides.last'\r
-HOTKEY_NAME_BLACK = 'powerpoint_slides.black'\r
-HOTKEY_NAME_WHITE = 'powerpoint_slides.white'\r
-\r
-HOTKEY_DESC_FRST = 'First PowerPoint slide'\r
-HOTKEY_DESC_PREV = 'Previous PowerPoint slide'\r
-HOTKEY_DESC_NEXT = 'Next PowerPoint slide'\r
-HOTKEY_DESC_LAST = 'Last PowerPoint slide'\r
-HOTKEY_DESC_BLACK = 'Black PowerPoint slide'\r
-HOTKEY_DESC_WHITE = 'White PowerPoint slide'\r
-\r
-class Handler(server.CGIHTTPRequestHandler):\r
-    def __init__(self, *args, **kwargs):\r
-        super().__init__(*args, directory=os.path.dirname(os.path.realpath(__file__)))\r
-\r
-def run_http():\r
-    httpd = server.HTTPServer(("", 8000), Handler)\r
-    httpd.serve_forever()\r
-    \r
-\r
-\r
-\r
-# ------------------------------------------------------------\r
-# global functions for script plugins\r
-\r
-def script_load(settings):\r
-    global hotkey_id_frst\r
-    global hotkey_id_prev\r
-    global hotkey_id_next\r
-    global hotkey_id_last\r
-    global hotkey_id_black\r
-    global hotkey_id_white\r
-\r
-    hotkey_id_frst = register_and_load_hotkey(settings, HOTKEY_NAME_FRST, HOTKEY_DESC_FRST, slideshow_view_first)\r
-    hotkey_id_prev = register_and_load_hotkey(settings, HOTKEY_NAME_PREV, HOTKEY_DESC_PREV, slideshow_view_previous)\r
-    hotkey_id_next = register_and_load_hotkey(settings, HOTKEY_NAME_NEXT, HOTKEY_DESC_NEXT, slideshow_view_next)\r
-    hotkey_id_last = register_and_load_hotkey(settings, HOTKEY_NAME_LAST, HOTKEY_DESC_LAST, slideshow_view_last)\r
-    hotkey_id_black = register_and_load_hotkey(settings, HOTKEY_NAME_BLACK, HOTKEY_DESC_BLACK, slideshow_view_black)\r
-    hotkey_id_white = register_and_load_hotkey(settings, HOTKEY_NAME_WHITE, HOTKEY_DESC_WHITE, slideshow_view_white)\r
-\r
-    daemon = threading.Thread(name="daemon_server", target=run_http)\r
-    daemon.setDaemon(True)\r
-    daemon.start()\r
-\r
-def script_unload():\r
-    obs.obs_hotkey_unregister(slideshow_view_first)\r
-    obs.obs_hotkey_unregister(slideshow_view_previous)\r
-    obs.obs_hotkey_unregister(slideshow_view_next)\r
-    obs.obs_hotkey_unregister(slideshow_view_last)\r
-    obs.obs_hotkey_unregister(slideshow_view_black)\r
-    obs.obs_hotkey_unregister(slideshow_view_white)\r
-\r
-def script_save(settings):\r
-    save_hotkey(settings, HOTKEY_NAME_FRST, hotkey_id_frst)\r
-    save_hotkey(settings, HOTKEY_NAME_PREV, hotkey_id_prev)\r
-    save_hotkey(settings, HOTKEY_NAME_NEXT, hotkey_id_next)\r
-    save_hotkey(settings, HOTKEY_NAME_LAST, hotkey_id_last)\r
-    save_hotkey(settings, HOTKEY_NAME_BLACK, hotkey_id_black)\r
-    save_hotkey(settings, HOTKEY_NAME_WHITE, hotkey_id_white)\r
-\r
-def script_description():\r
-    return 'Navigate Powerpoint Slides.'\r
-\r
-def script_defaults(settings):\r
-    obs.obs_data_set_default_string(settings, 'cache', r'''C:\Windows\Temp''')\r
-\r
-def script_properties():\r
-    props = obs.obs_properties_create()\r
-\r
-    obs.obs_properties_add_path(props, "cache", "Slide cache: ", obs.OBS_PATH_DIRECTORY, "*.jpg", r'''C:\Windows\Temp''')\r
-    return props\r
-\r
-def script_update(settings):\r
-    global cache\r
-    cache = obs.obs_data_get_string(settings, "cache").replace("/", "\\")\r
-\r
-def register_and_load_hotkey(settings, name, description, callback):\r
-    hotkey_id = obs.obs_hotkey_register_frontend(name, description, callback)\r
-    hotkey_save_array = obs.obs_data_get_array(settings, name)\r
-    obs.obs_hotkey_load(hotkey_id, hotkey_save_array)\r
-    obs.obs_data_array_release(hotkey_save_array)\r
-\r
-    return hotkey_id\r
-\r
-def save_hotkey(settings, name, hotkey_id):\r
-    hotkey_save_array = obs.obs_hotkey_save(hotkey_id)\r
-    obs.obs_data_set_array(settings, name, hotkey_save_array)\r
-    obs.obs_data_array_release(hotkey_save_array)\r
-\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 export_next(slide):\r
-    global cache\r
-    ssp = get_activepresentation()\r
-    if ssp:\r
-        if slide < len(ssp.Slides):\r
-            ssp.Slides(slide + 1).Export(cache + r'''\slide0.jpg''', "JPG")\r
-            attempts = 0\r
-            while attempts < 3:\r
-                try:\r
-                    os.replace(cache + r'''\slide0.jpg''', cache + r'''\slide.jpg''')\r
-                except:\r
-                    pass\r
-                attempts += 1\r
-        else:\r
-            shutil.copyfileobj(open(os.path.dirname(os.path.realpath(__file__)) + r'''\blank.jpg''', 'rb'), open(cache + r'''\slide.jpg''', 'wb'))\r
-\r
-def slideshow_view_first(pressed):\r
-    if pressed:\r
-        ssv = get_slideshow_view()\r
-        if ssv:\r
-            ssv.First()\r
-            ssv.State = 1\r
-\r
-def slideshow_view_previous(pressed):\r
-    if pressed:\r
-        ssv = get_slideshow_view()\r
-        if ssv:\r
-            ssv.Previous()\r
-            ssv.State = 1\r
-            export_next(ssv.CurrentShowPosition)\r
-\r
-def slideshow_view_next(pressed):\r
-    if pressed:\r
-        ssv = get_slideshow_view()\r
-        if ssv:\r
-            ssv.Next()\r
-            ssv.State = 1\r
-            export_next(ssv.CurrentShowPosition)\r
-            \r
-\r
-def slideshow_view_last(pressed):\r
-    if pressed:\r
-        ssv = get_slideshow_view()\r
-        if ssv:\r
-            ssv.Last()\r
-            ssv.State = 1\r
-\r
-def slideshow_view_black(pressed):\r
-    if pressed:\r
-        ssv = get_slideshow_view()\r
-        if ssv:\r
-            if ssv.State == 3 or ssv.State == 4:\r
-                ssv.State = 1\r
-            else:\r
-                ssv.State = 3\r
-\r
-def slideshow_view_white(pressed):\r
-    if pressed:\r
-        ssv = get_slideshow_view()\r
-        if ssv:\r
-            if ssv.State == 4 or ssv.State == 3:\r
-                ssv.State = 1\r
-            else:\r
-                ssv.State = 4\r
index 7e4c3122bb26e4f1ada13075782dea4c51e44abb..e1b88419f46cf24c84fbb0a0e923fb54c6c107c6 100644 (file)
@@ -1,4 +1,6 @@
+var DEFAULT_TITLE = "ppt-control"
 var preloaded = false;
+var preload = [];
 
 function imageRefresh(id) {
     img = document.getElementById(id);
@@ -108,6 +110,11 @@ function sync_next() {
 }
 show_next.onclick = sync_next;
 
+function sync_shortcuts() {
+  saveSettings();
+}
+shortcuts.onclick = sync_shortcuts;
+
 function set_control_width() {
        var width = window.innerWidth
        || document.documentElement.clientWidth
@@ -154,10 +161,27 @@ document.addEventListener('keydown', function (e) {
        }
 });
 
+function sleep(ms) {
+      return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+function disconnect() {
+       document.title = DEFAULT_TITLE;
+    current_img.src = "/black.jpg";
+    next_img.src = "/black.jpg";
+    users.textContent = "Connection to PowerPoint failed";
+}
+
 websocket.onmessage = function (event) {
+       console.log("Received data");
     data = JSON.parse(event.data);
     switch (data.type) {
         case 'state':
+            if (data.connected == "0" || data.connected == 0) {
+                               console.log("Disconnected");
+               disconnect();
+               break;
+            }
             var d = new Date;
             switch (data.visible) {
                 case 3:
@@ -194,14 +218,13 @@ websocket.onmessage = function (event) {
             console.error(
                 "unsupported event", data);
     }
-       if (!preloaded) {
-               var i = 0
-               var preload = [];
+       if (preloaded == false && ! isNaN(total.textContent)) {
+               image = document.getElementById("preload_img");
                for (let i=1; i<=Number(total.textContent); i++) {
-                       image = new Image();
                        image.src = "/cache/" + i + ".jpg";
                        preload.push(image);
-                       console.log("Preloaded image " + i);
+               console.log("Preloaded " + total.textContent);
+                       //sleep(0.5)
                }
                preloaded = true;
        }
@@ -211,6 +234,7 @@ websocket.onmessage = function (event) {
 var interval = setInterval(refresh, 5000);
 
 function refresh() {
+       console.log("Refreshing")
     websocket.send(JSON.stringify({action: 'refresh'}));
 }
 
index 75cf1b0a54264e6323c49c11c04ec3315ae810f4..2e0cc3d41debb9351674cbb9551e2cdaa63f3a87 100755 (executable)
@@ -15,9 +15,15 @@ import urllib
 import posixpath\r
 import time\r
 import pythoncom\r
+import pystray\r
+import tkinter as tk\r
+from tkinter import ttk\r
+from PIL import Image, ImageDraw\r
 \r
 logging.basicConfig()\r
 \r
+global STATE\r
+global STATE_DEFAULT\r
 global current_slideshow\r
 current_slideshow = None\r
 CACHEDIR = r'''C:\Windows\Temp\ppt-cache'''\r
@@ -50,6 +56,9 @@ class Handler(server.SimpleHTTPRequestHandler):
         if len(words) > 0 and words[0] == "cache":\r
             if current_slideshow:\r
                 path = CACHEDIR + "\\" + current_slideshow.name()\r
+            else:\r
+                path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "black.jpg") + '/'\r
+                return path\r
             words.pop(0)\r
         else:\r
             path = self.directory\r
@@ -67,11 +76,13 @@ def run_http():
     http_server = server.HTTPServer(("", 80), Handler)\r
     http_server.serve_forever()\r
 \r
-STATE = {"connected": 0, "current": 0, "total": 0, "visible": 0, "name": ""}\r
+STATE_DEFAULT = {"connected": 0, "current": 0, "total": 0, "visible": 0, "name": ""}\r
+STATE = STATE_DEFAULT\r
 USERS = set()\r
 \r
 \r
 def state_event():\r
+    print("Running state event")\r
     return json.dumps({"type": "state", **STATE})\r
 \r
 \r
@@ -80,12 +91,15 @@ def users_event():
 \r
 \r
 async def notify_state():\r
-    global current_slideshow\r
-    if current_slideshow:\r
+    print("Notifying state to " + str(len(USERS)) + " users")\r
+    global STATE\r
+    if current_slideshow and STATE["connected"] == 1:\r
         STATE["current"] = current_slideshow.current_slide()\r
         STATE["total"] = current_slideshow.total_slides()\r
         STATE["visible"] = current_slideshow.visible()\r
         STATE["name"] = current_slideshow.name()\r
+    else:\r
+        STATE = STATE_DEFAULT\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
@@ -108,6 +122,7 @@ async def unregister(websocket):
 \r
 \r
 async def ws_handle(websocket, path):\r
+    print("Received command")\r
     global current_slideshow\r
     # register(websocket) sends user_event() to websocket\r
     await register(websocket)\r
@@ -150,19 +165,22 @@ async def ws_handle(websocket, path):
                     current_slideshow.goto(int(data["value"]))\r
                 await notify_state()\r
             elif data["action"] == "refresh":\r
+                print("Received refresh command")\r
+                await notify_state()\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
+    # https://stackoverflow.com/questions/21141217/how-to-launch-win32-applications-in-separate-threads-in-python/22619084#22619084\r
+    # https://www.reddit.com/r/learnpython/comments/mwt4qi/pywintypescom_error_2147417842_the_application/\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
+    start_server = websockets.serve(ws_handle, "0.0.0.0", 5678, ping_interval=None)\r
     asyncio.get_event_loop().run_until_complete(start_server)\r
     asyncio.get_event_loop().run_forever()\r
 \r
@@ -200,71 +218,120 @@ class Slideshow:
         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 unload(self):\r
+        connect_ppt()\r
 \r
     def refresh(self):\r
-        if self.instance is None:\r
-            raise ValueError("PPT instance cannot be None")\r
+        try:\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
+            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
+            if self.instance.ActivePresentation is None:\r
+                raise ValueError("PPT instance has no  active presentation")\r
+        except:\r
+            self.unload()\r
 \r
     def total_slides(self):\r
-        return len(self.presentation.Slides)\r
+        try:\r
+            self.refresh()\r
+            return len(self.presentation.Slides)\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
 \r
     def current_slide(self):\r
-        return self.view.CurrentShowPosition\r
+        try:\r
+            self.refresh()\r
+            return self.view.CurrentShowPosition\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
 \r
     def visible(self):\r
-        return self.view.State\r
+        try:\r
+            self.refresh()\r
+            return self.view.State\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
 \r
     def prev(self):\r
-        self.refresh()\r
-        self.view.Previous()\r
+        try:\r
+            self.refresh()\r
+            self.view.Previous()\r
+            self.export_current_next()\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
 \r
     def next(self):\r
-        self.refresh()\r
-        self.view.Next()\r
-        self.export_current_next()\r
+        try:\r
+            self.refresh()\r
+            self.view.Next()\r
+            self.export_current_next()\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
 \r
     def first(self):\r
-        self.refresh()\r
-        self.view.First()\r
-        self.export_current_next()\r
+        try:\r
+            self.refresh()\r
+            self.view.First()\r
+            self.export_current_next()\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
                 \r
     def last(self):\r
-        self.refresh()\r
-        self.view.Last()\r
-        self.export_current_next()\r
+        try:\r
+            self.refresh()\r
+            self.view.Last()\r
+            self.export_current_next()\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\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
+        try:\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
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
 \r
     def black(self):\r
-        self.refresh()\r
-        self.view.State = 3\r
+        try:\r
+            self.refresh()\r
+            self.view.State = 3\r
+            self.export_current_next()\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
 \r
     def white(self):\r
-        self.refresh()\r
-        self.view.State = 4\r
+        try:\r
+            self.refresh()\r
+            self.view.State = 4\r
+            self.export_current_next()\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
 \r
     def normal(self):\r
-        self.refresh()\r
-        self.view.State = 1\r
+        try:\r
+            self.refresh()\r
+            self.view.State = 1\r
+            self.export_current_next()\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
 \r
     def name(self):\r
-        return self.presentation.Name\r
+        try:\r
+            self.refresh()\r
+            return self.presentation.Name\r
+        except ValueError or pywintypes.com_error:\r
+            self.unload()\r
+\r
 \r
     def export_current_next(self):\r
         self.export(self.current_slide())\r
@@ -279,7 +346,6 @@ class Slideshow:
                 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
@@ -302,22 +368,74 @@ def get_ppt_instance():
 def get_current_slideshow():\r
     return current_slideshow\r
 \r
-\r
-if __name__ == "__main__":\r
-\r
-    start_server()\r
-    \r
+def connect_ppt():\r
+    global STATE\r
+    if STATE["connected"] == 1:\r
+        print("Disconnected from PowerPoint instance")\r
+    STATE = STATE_DEFAULT\r
     while True:\r
-        # Check if PowerPoint is running\r
-        instance = get_ppt_instance()\r
         try:\r
+            instance = get_ppt_instance()\r
+            global current_slideshow\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")\r
+            current_slideshow.export_all()\r
             break\r
         except ValueError as e:\r
             current_slideshow = None\r
             pass\r
         time.sleep(1)\r
+\r
+def start(_=None):\r
+    #root = tk.Tk()\r
+    #root.iconphoto(False, tk.PhotoImage(file="icons/ppt.png"))\r
+    #root.geometry("250x150+300+300")\r
+    #app = Interface(root)\r
+    #interface_thread = threading.Thread(target=root.mainloop())\r
+    #interface_thread.setDaemon(True)\r
+    #interface_thread.start()\r
+    start_server()\r
+    connect_ppt()\r
+    \r
+\r
+def null_action():\r
+    pass\r
+\r
+class Interface(ttk.Frame):\r
+\r
+    def __init__(self, parent):\r
+        ttk.Frame.__init__(self, parent)\r
+\r
+        self.parent = parent\r
+\r
+        self.initUI()\r
+\r
+    def initUI(self):\r
+\r
+        self.parent.title("ppt-control")\r
+        self.style = ttk.Style()\r
+        #self.style.theme_use("default")\r
+\r
+        self.pack(fill=tk.BOTH, expand=1)\r
+\r
+        quitButton = ttk.Button(self, text="Close",\r
+            command=self.quit)\r
+        quitButton.place(x=50, y=50)\r
+        status_label = ttk.Label(self, text="PowerPoint status: not detected")\r
+        status_label.place(x=10,y=10)\r
+        \r
+        \r
+\r
+def show_icon():\r
+    menu = (pystray.MenuItem("Status", lambda: null_action(), enabled=False),\r
+            pystray.MenuItem("Restart", lambda: start()),\r
+            pystray.MenuItem("Settings", lambda: open_settings()))\r
+    icon = pystray.Icon("ppt-control", Image.open("icons/ppt.ico"), "ppt-control", menu)\r
+    icon.visible = True\r
+    icon.run(setup=start)\r
+\r
+if __name__ == "__main__":\r
+    show_icon()\r
diff --git a/ppt_control_obs.py b/ppt_control_obs.py
new file mode 100755 (executable)
index 0000000..d9ab70d
--- /dev/null
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-\r
+\r
+import obspython as obs\r
+import asyncio\r
+import websockets\r
+import threading\r
+\r
+hotkey_id_first = None\r
+hotkey_id_prev = None\r
+hotkey_id_next = None\r
+hotkey_id_last = None\r
+hotkey_id_black = None\r
+hotkey_id_white = None\r
+\r
+HOTKEY_NAME_FIRST = 'powerpoint_slides.first'\r
+HOTKEY_NAME_PREV = 'powerpoint_slides.previous'\r
+HOTKEY_NAME_NEXT = 'powerpoint_slides.next'\r
+HOTKEY_NAME_LAST = 'powerpoint_slides.last'\r
+HOTKEY_NAME_BLACK = 'powerpoint_slides.black'\r
+HOTKEY_NAME_WHITE = 'powerpoint_slides.white'\r
+\r
+HOTKEY_DESC_FIRST = 'First PowerPoint slide'\r
+HOTKEY_DESC_PREV = 'Previous PowerPoint slide'\r
+HOTKEY_DESC_NEXT = 'Next PowerPoint slide'\r
+HOTKEY_DESC_LAST = 'Last PowerPoint slide'\r
+HOTKEY_DESC_BLACK = 'Black PowerPoint slide'\r
+HOTKEY_DESC_WHITE = 'White PowerPoint slide'\r
+\r
+global cmd \r
+cmd = ""\r
+\r
+async def communicate():\r
+    global cmd\r
+    async with websockets.connect("ws://10.0.0.93:5678", ping_interval=None) as websocket:\r
+        while True:\r
+            if cmd:\r
+                await websocket.send("{\"action\": \"" + cmd + "\"}")\r
+                cmd = ""\r
+            await asyncio.sleep(0.05)\r
+\r
+def run_ws():\r
+    asyncio.set_event_loop(asyncio.new_event_loop())\r
+    asyncio.get_event_loop().run_until_complete(communicate())\r
+\r
+\r
+\r
+#------------------------------------------------------------\r
+# global functions for script plugins\r
+\r
+def script_load(settings):\r
+    global hotkey_id_first\r
+    global hotkey_id_prev\r
+    global hotkey_id_next\r
+    global hotkey_id_last\r
+    global hotkey_id_black\r
+    global hotkey_id_white\r
+\r
+    hotkey_id_first = register_and_load_hotkey(settings, HOTKEY_NAME_FIRST, HOTKEY_DESC_FIRST, first_slide)\r
+    hotkey_id_prev = register_and_load_hotkey(settings, HOTKEY_NAME_PREV, HOTKEY_DESC_PREV, prev_slide)\r
+    hotkey_id_next = register_and_load_hotkey(settings, HOTKEY_NAME_NEXT, HOTKEY_DESC_NEXT, next_slide)\r
+    hotkey_id_last = register_and_load_hotkey(settings, HOTKEY_NAME_LAST, HOTKEY_DESC_LAST, last_slide)\r
+    hotkey_id_black = register_and_load_hotkey(settings, HOTKEY_NAME_BLACK, HOTKEY_DESC_BLACK, black)\r
+    hotkey_id_white = register_and_load_hotkey(settings, HOTKEY_NAME_WHITE, HOTKEY_DESC_WHITE, white)\r
+    ws_daemon = threading.Thread(name="ws_daemon", target=run_ws)\r
+    ws_daemon.setDaemon(True)\r
+    ws_daemon.start()\r
+    print("Started websocket client")\r
+\r
+def script_unload():\r
+    obs.obs_hotkey_unregister(first_slide)\r
+    obs.obs_hotkey_unregister(prev_slide)\r
+    obs.obs_hotkey_unregister(next_slide)\r
+    obs.obs_hotkey_unregister(last_slide)\r
+    obs.obs_hotkey_unregister(black)\r
+    obs.obs_hotkey_unregister(white)\r
+\r
+def script_save(settings):\r
+    save_hotkey(settings, HOTKEY_NAME_FIRST, hotkey_id_first)\r
+    save_hotkey(settings, HOTKEY_NAME_PREV, hotkey_id_prev)\r
+    save_hotkey(settings, HOTKEY_NAME_NEXT, hotkey_id_next)\r
+    save_hotkey(settings, HOTKEY_NAME_LAST, hotkey_id_last)\r
+    save_hotkey(settings, HOTKEY_NAME_BLACK, hotkey_id_black)\r
+    save_hotkey(settings, HOTKEY_NAME_WHITE, hotkey_id_white)\r
+\r
+def script_description():\r
+    return "ppt-control client\nHotkeys for controlling PowerPoint slides using websockets"\r
+\r
+def script_defaults(settings):\r
+    obs.obs_data_set_default_int(settings, 'port', 5678)\r
+\r
+def script_properties():\r
+    props = obs.obs_properties_create()\r
+\r
+    obs.obs_properties_add_int(props, "port", "Websocket port: ", 0, 9999, 1)\r
+    return props\r
+\r
+def script_update(settings):\r
+    global port\r
+    port = obs.obs_data_get_int(settings, "port")\r
+\r
+def register_and_load_hotkey(settings, name, description, callback):\r
+    hotkey_id = obs.obs_hotkey_register_frontend(name, description, callback)\r
+    hotkey_save_array = obs.obs_data_get_array(settings, name)\r
+    obs.obs_hotkey_load(hotkey_id, hotkey_save_array)\r
+    obs.obs_data_array_release(hotkey_save_array)\r
+\r
+    return hotkey_id\r
+\r
+def save_hotkey(settings, name, hotkey_id):\r
+    hotkey_save_array = obs.obs_hotkey_save(hotkey_id)\r
+    obs.obs_data_set_array(settings, name, hotkey_save_array)\r
+    obs.obs_data_array_release(hotkey_save_array)\r
+\r
+#-------------------------------------\r
+\r
+def first_slide(pressed):\r
+    if pressed:\r
+        global cmd\r
+        cmd = "first"\r
+\r
+def prev_slide(pressed):\r
+    if pressed:\r
+        global cmd\r
+        cmd = "prev"\r
+\r
+def next_slide(pressed):\r
+    if pressed:\r
+        global cmd\r
+        cmd = "next"\r
+\r
+def last_slide(pressed):\r
+    if pressed:\r
+        global cmd\r
+        cmd = "last"\r
+\r
+def black(pressed):\r
+    if pressed:\r
+        global cmd\r
+        cmd = "black"\r
+\r
+def white(pressed):\r
+    if pressed:\r
+        global cmd\r
+        cmd = "white"\r
index eb94f8ef49af8fc44b19e8917cd88a6ae06bb4ad..901af0b5934e2536e5876fdfaf863126267b20b1 100644 (file)
@@ -25,16 +25,17 @@ function getCookie(cname) {
 }
 
 function saveSettings() {
+    console.log("Saving settings")
     settingsString = JSON.stringify({showcurrent: show_current.checked, shownext: show_next.checked, enable_shortcuts: shortcuts.checked});
     setCookie(COOKIENAME, settingsString, COOKIEEXP);
 }
 
 function initSettings() {
     if (getCookie(COOKIENAME) == 0) {
-               if (window.obssstudio) {
-                       shortcuts.checked = False;
-                       show_current.checked = False;
-               }
+        if (window.obssstudio) {
+                shortcuts.checked = False;
+                show_current.checked = False;
+        }
         saveSettings()
     } else {
         cookie = JSON.parse(getCookie(COOKIENAME));