fix threading bugs, add further HTML controls
authorAndrew Lorimer <andrew@lorimer.id.au>
Tue, 27 Apr 2021 09:02:25 +0000 (19:02 +1000)
committerAndrew Lorimer <andrew@lorimer.id.au>
Tue, 27 Apr 2021 09:02:25 +0000 (19:02 +1000)
black.jpg [new file with mode: 0755]
blank.jpg [deleted file]
index.html
obs_ppt_server.py
ppt-control.js [new file with mode: 0644]
settings.js [new file with mode: 0644]
style.css [new file with mode: 0644]
white.jpg [new file with mode: 0644]
diff --git a/black.jpg b/black.jpg
new file mode 100755 (executable)
index 0000000..7b05935
Binary files /dev/null and b/black.jpg differ
diff --git a/blank.jpg b/blank.jpg
deleted file mode 100755 (executable)
index 7b05935..0000000
Binary files a/blank.jpg and /dev/null differ
index d9522b9c923365fec2e58f7905cec2eb534480e0..a1a4c3ff1f419e85b2be66faf94c5b9b254a660e 100755 (executable)
@@ -1,97 +1,40 @@
 <!DOCTYPE html>\r
 <html>\r
 <!DOCTYPE html>\r
 <html>\r
-    <head>\r
-<style>\r
-img {\r
-    #width: 100%;\r
-    border: 3px solid #000;\r
-}\r
-h1 {\r
-    font-size: 20px;\r
-    font-weight: 300;\r
-    margin-bottom: 0;\r
-}\r
-#slide_label {\r
-    float: right;\r
-    font-size: 16px;\r
-}\r
-::-webkit-scrollbar { \r
-    display: none; \r
-}\r
-body {\r
-    background: #3a393a;\r
-    color: #efefef;\r
-    font-family: sans-serif;\r
-    #max-width: 500px;\r
-}\r
-</style>\r
-        <title>WebSocket demo</title>\r
+       <head>\r
+       <link href="style.css" rel="stylesheet" type="text/css" media="all" />\r
+       <script src="settings.js"></script>\r
+        <title>ppt-control</title>\r
     </head>\r
     </head>\r
-    <body>\r
-        <div>\r
-        <h1>Current slide</h1>\r
-        <img id="current_img" src="/current.jpg" />\r
+    <body onload="initSettings();">\r
+       <div id="img_container">\r
+               <div id="current_div">\r
+                       <h1>Current slide</h1>\r
+                       <img id="current_img" src="/black.jpg" />\r
+               </div>\r
+               <div id="next_div">\r
+                       <h1>Next slide</h1>\r
+                       <img id="next_img" src="/black.jpg" />\r
+               </div>\r
         </div>\r
         </div>\r
-        <div>\r
-        <h1>Next slide</h1>\r
-        <img id="next_img" src="/next.jpg" />\r
+\r
+               <div id="controls_container">\r
+               <p>\r
+                       <button id="prev">Prev</button>\r
+                       <button id="next">Next</button>\r
+                       <button id="first">First</button>\r
+                       <button id="last">Last</button>\r
+                       <button id="black">Black</button>\r
+                       <button id="white">White</button>\r
+                               <span id="count"><span id="slide_label">Current: </span><input type="text" id="current"></input>/<span id="total">?</span></span>\r
+               </p>\r
+\r
+               <input type="checkbox" checked="true" id="show_current">Show current slide</input>\r
+               <input type="checkbox" checked="true" id="show_next">Show next slide</input>\r
+\r
+               <p class="users">Not connected</p>\r
         </div>\r
         </div>\r
-        <p>\r
-        <button id="prev">Prev</button>\r
-        <button id="next">Next</button>\r
-        <button id="first">First</button>\r
-        <button id="last">Last</button>\r
-        <span id="slide_label">Current: <span id="slide"></span>\r
-        </p>\r
-        <div class="state">\r
-            <span class="users">?</span>\r
-        </div>\r
-        \r
-        <script>\r
-            function imageRefresh(id) {\r
-                img = document.getElementById(id);\r
-                var d = new Date;\r
-                var http = img.src;\r
-                if (http.indexOf("?t=") != -1) { http = http.split("?t=")[0]; } \r
-                img.src = http + '?t=' + d.getTime();\r
-            }\r
-            var prev = document.querySelector('#prev'),\r
-                next = document.querySelector('#next'),\r
-                first = document.querySelector('#first'),\r
-                last = document.querySelector('#last'),\r
-                slide = document.querySelector('#slide'),\r
-                users = document.querySelector('.users'),\r
-                websocket = new WebSocket("ws://" + window.location.host + ":5678/");\r
-            prev.onclick = function (event) {\r
-                websocket.send(JSON.stringify({action: 'prev'}));\r
-            }\r
-            next.onclick = function (event) {\r
-                websocket.send(JSON.stringify({action: 'next'}));\r
-            }\r
-            first.onclick = function (event) {\r
-                websocket.send(JSON.stringify({action: 'first'}));\r
-            }\r
-            last.onclick = function (event) {\r
-                websocket.send(JSON.stringify({action: 'last'}));\r
-            }\r
-            websocket.onmessage = function (event) {\r
-                data = JSON.parse(event.data);\r
-                switch (data.type) {\r
-                    case 'state':\r
-                        slide.textContent = data.value;\r
-                        imageRefresh("current_img");\r
-                        imageRefresh("next_img");\r
-                        break;\r
-                    case 'users':\r
-                        users.textContent = (\r
-                            data.count.toString() + " client" +\r
-                            (data.count == 1 ? "" : "s"));\r
-                        break;\r
-                    default:\r
-                        console.error(\r
-                            "unsupported event", data);\r
-                }\r
-            };\r
-        </script>\r
+\r
+       <script src="ppt-control.js"></script>\r
+\r
     </body>\r
     </body>\r
-</html>
\ No newline at end of file
+</html>\r
index 221179e9867ae8d58fedb60fae80783c38db6786..4172d9493d8ccf92fe26004e752a6efa2863ab7e 100755 (executable)
@@ -1,3 +1,5 @@
+import sys\r
+sys.coinit_flags= 0\r
 import win32com.client\r
 import pywintypes\r
 import os\r
 import win32com.client\r
 import pywintypes\r
 import os\r
@@ -9,26 +11,64 @@ import threading
 import asyncio\r
 import websockets\r
 import logging, json\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
 \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
 \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
     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
 \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
 USERS = set()\r
 \r
 \r
@@ -41,7 +81,11 @@ def users_event():
 \r
 \r
 async def notify_state():\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
     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
@@ -64,6 +108,7 @@ async def unregister(websocket):
 \r
 \r
 async def ws_handle(websocket, path):\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
     # register(websocket) sends user_event() to websocket\r
     await register(websocket)\r
     try:\r
@@ -71,16 +116,43 @@ async def ws_handle(websocket, path):
         async for message in websocket:\r
             data = json.loads(message)\r
             if data["action"] == "prev":\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
                 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
                 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
                 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 notify_state()\r
             else:\r
                 logging.error("unsupported event: {}", data)\r
@@ -88,20 +160,27 @@ async def ws_handle(websocket, path):
         await unregister(websocket)\r
 \r
 def run_ws():\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
     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
     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
     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
     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
 \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
 \r
     #try:\r
     #    ws_daemon.start()\r
@@ -110,103 +189,138 @@ def start_server():
     #    cleanup_stop_thread()\r
     #    sys.exit()\r
 \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
                 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
                     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
             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
 if __name__ == "__main__":\r
+\r
     start_server()\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
diff --git a/ppt-control.js b/ppt-control.js
new file mode 100644 (file)
index 0000000..7496f1b
--- /dev/null
@@ -0,0 +1,144 @@
+function imageRefresh(id) {
+    img = document.getElementById(id);
+    var d = new Date;
+    var http = img.src;
+    if (http.indexOf("?t=") != -1) { http = http.split("?t=")[0]; } 
+    img.src = http + '?t=' + d.getTime();
+}
+
+function startWebsocket() {
+    ws = new WebSocket("ws://" + window.location.host + ":5678/");
+    ws.onclose = function(){
+        //websocket = null;
+        setTimeout(function(){startWebsocket()}, 10000);
+    }
+    return ws;
+}
+
+var websocket = startWebsocket();
+
+//if (window.obssstudio) {
+//}
+
+var prev = document.querySelector('#prev'),
+    next = document.querySelector('#next'),
+    first = document.querySelector('#first'),
+    last = document.querySelector('#last'),
+    black = document.querySelector('#black'),
+    white = document.querySelector('#white'),
+    slide_label = document.querySelector('#slide_label'),
+    current = document.querySelector('#current'),
+    total = document.querySelector('#total'),
+    users = document.querySelector('.users'),
+    prev_img = document.querySelector('#prev_img'),
+    next_img = document.querySelector('#next_img'),
+    current_div = document.querySelector('#current_div'),
+    next_div = document.querySelector('#next_div'),
+    show_current = document.querySelector('#show_current'),
+    show_next = document.querySelector('#show_next');
+
+prev.onclick = function (event) {
+    websocket.send(JSON.stringify({action: 'prev'}));
+}
+
+next.onclick = function (event) {
+    websocket.send(JSON.stringify({action: 'next'}));
+}
+
+first.onclick = function (event) {
+    websocket.send(JSON.stringify({action: 'first'}));
+}
+
+last.onclick = function (event) {
+    websocket.send(JSON.stringify({action: 'last'}));
+}
+
+black.onclick = function (event) {
+    websocket.send(JSON.stringify({action: 'black'}));
+}
+
+white.onclick = function (event) {
+    websocket.send(JSON.stringify({action: 'white'}));
+}
+
+current.onblur = function (event) {
+    websocket.send(JSON.stringify({action: 'goto', value: current.value}));
+}
+
+current.addEventListener('keyup',function(e){
+    if (e.which == 13) this.blur();
+});
+
+function sync_current() {
+    console.log("State of current checkbox changed");
+    if (show_current.checked) {
+        current_div.style.display = "block";
+        slide_label.style.display = "none";
+        next_div.style.width = "25%";
+    } else {
+        current_div.style.display = "none";
+        slide_label.style.display = "block";
+        next_div.style.width = "95%";
+    }
+    saveSettings();
+}
+show_current.onclick = sync_current;
+
+function sync_next() {
+    console.log("State of next checkbox changed");
+    if (show_next.checked) {
+        next_div.style.display = "block";
+        current_div.style.width = "70%";
+    } else {
+        next_div.style.display = "none";
+        current_div.style.width = "95%";
+    }
+    saveSettings();
+}
+show_next.onclick = sync_next;
+
+websocket.onmessage = function (event) {
+    data = JSON.parse(event.data);
+    switch (data.type) {
+        case 'state':
+            var d = new Date;
+            switch (data.visible) {
+                case 3:
+                    current_img.src = "/black.jpg";
+                    break;
+                case 4:
+                    current_img.src = "/white.jpg";
+                    break;
+                default:
+                    current_img.src = "/cache/" + data.current + ".jpg?t=" + d.getTime();
+                    break;
+            }
+            if (data.current == data.total + 1) { 
+                //next_img.src = "/cache/" + (data.total + 1) + ".jpg?t=" + d.getTime();
+                next_img.src = "/cache/" + (data.total + 1) + ".jpg";
+            } else {
+                //next_img.src = "/cache/" + (data.current + 1) + ".jpg?t=" + d.getTime();
+                next_img.src = "/cache/" + (data.current + 1) + ".jpg";
+            }
+
+                       if (document.activeElement != current) {
+               current.value = data.current;
+            }
+            total.textContent = data.total;
+            break;
+        case 'users':
+            users.textContent = (
+                data.count.toString() + " client" +
+                (data.count == 1 ? "" : "s"));
+            break;
+        default:
+            console.error(
+                "unsupported event", data);
+    }
+};
+
+var interval = setInterval(refresh, 5000);
+
+function refresh() {
+    websocket.send(JSON.stringify({action: 'refresh'}));
+}
diff --git a/settings.js b/settings.js
new file mode 100644 (file)
index 0000000..2807801
--- /dev/null
@@ -0,0 +1,48 @@
+const COOKIENAME = "settings";
+const COOKIEEXP = 365;
+
+function setCookie(cname, cvalue, exdays) {
+    var d = new Date();
+    d.setTime(d.getTime() + (exdays*24*60*60*1000));
+    var expires = "expires="+ d.toUTCString();
+    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
+}
+
+function getCookie(cname) {
+    var name = cname + "=";
+    var decodedCookie = decodeURIComponent(document.cookie);
+    var ca = decodedCookie.split(';');
+    for(var i = 0; i <ca.length; i++) {
+        var c = ca[i];
+        while (c.charAt(0) == ' ') {
+            c = c.substring(1);
+        }
+        if (c.indexOf(name) == 0) {
+            return c.substring(name.length, c.length);
+        }
+    }
+    return 0;
+}
+
+function saveSettings() {
+    settingsString = JSON.stringify({showcurrent: show_current.checked, shownext: show_next.checked});
+    console.log("Saving cookie " + settingsString);
+    setCookie(COOKIENAME, settingsString, COOKIEEXP);
+}
+
+function initSettings() {
+    console.log("Retrieving cookie");
+    if (getCookie(COOKIENAME) == 0) {
+        console.log("No cookie found - setting new cookie");
+        saveSettings()
+    } else {
+        cookie = JSON.parse(getCookie(COOKIENAME));
+        console.log("Found cookie " + cookie);
+        show_current.checked = cookie.showcurrent;
+        show_next.checked = cookie.shownext;
+        sync_current();
+        sync_next();
+    }
+
+}
+
diff --git a/style.css b/style.css
new file mode 100644 (file)
index 0000000..5b2be56
--- /dev/null
+++ b/style.css
@@ -0,0 +1,57 @@
+#img_container, #controls_container {
+       width: 100%;
+       max-width: 1500px;
+       margin: auto;
+}
+
+img {
+       width: 100%;
+}
+
+#current_div {
+       float: left;
+       width: 70%;
+       margin: 10px;
+}
+
+#next_div {
+       width: 25%;
+       float: left;
+       margin: 10px;
+}
+
+h1 {
+    font-size: 20px;
+    font-weight: 300;
+    margin-bottom: 0;
+}
+
+p {
+       clear: both;
+}
+
+::-webkit-scrollbar { 
+    //display: none; 
+}
+
+body {
+    background: #3a393a;
+    color: #efefef;
+    font-family: sans-serif;
+    #max-width: 500px;
+}
+
+input {
+       width: 20px;
+       font-size: 17px;
+}
+
+#count {
+       float: right;
+}
+
+@media only screen and (max-width: 800px) {
+       #current_div, #next_div {
+               width: 95% !important;
+       }
+}
diff --git a/white.jpg b/white.jpg
new file mode 100644 (file)
index 0000000..d4a375a
Binary files /dev/null and b/white.jpg differ