--- /dev/null
+<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="step-backward" class="svg-inline--fa fa-step-backward fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M64 468V44c0-6.6 5.4-12 12-12h48c6.6 0 12 5.4 12 12v176.4l195.5-181C352.1 22.3 384 36.6 384 64v384c0 27.4-31.9 41.7-52.5 24.6L136 292.7V468c0 6.6-5.4 12-12 12H76c-6.6 0-12-5.4-12-12z"></path></svg>
\ No newline at end of file
--- /dev/null
+<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="step-forward" class="svg-inline--fa fa-step-forward fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 44v424c0 6.6-5.4 12-12 12h-48c-6.6 0-12-5.4-12-12V291.6l-195.5 181C95.9 489.7 64 475.4 64 448V64c0-27.4 31.9-41.7 52.5-24.6L312 219.3V44c0-6.6 5.4-12 12-12h48c6.6 0 12 5.4 12 12z"></path></svg>
\ No newline at end of file
--- /dev/null
+<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-circle-left" class="svg-inline--fa fa-chevron-circle-left fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 504C119 504 8 393 8 256S119 8 256 8s248 111 248 248-111 248-248 248zM142.1 273l135.5 135.5c9.4 9.4 24.6 9.4 33.9 0l17-17c9.4-9.4 9.4-24.6 0-33.9L226.9 256l101.6-101.6c9.4-9.4 9.4-24.6 0-33.9l-17-17c-9.4-9.4-24.6-9.4-33.9 0L142.1 239c-9.4 9.4-9.4 24.6 0 34z"></path></svg>
\ No newline at end of file
--- /dev/null
+<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-circle-right" class="svg-inline--fa fa-chevron-circle-right fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8c137 0 248 111 248 248S393 504 256 504 8 393 8 256 119 8 256 8zm113.9 231L234.4 103.5c-9.4-9.4-24.6-9.4-33.9 0l-17 17c-9.4 9.4-9.4 24.6 0 33.9L285.1 256 183.5 357.6c-9.4 9.4-9.4 24.6 0 33.9l17 17c9.4 9.4 24.6 9.4 33.9 0L369.9 273c9.4-9.4 9.4-24.6 0-34z"></path></svg>
\ No newline at end of file
<title>ppt-control</title>\r
</head>\r
<body onload="initSettings();">\r
+\r
<div id="img_container">\r
+\r
<div id="current_div">\r
<h1>Current slide</h1>\r
<img id="current_img" src="/black.jpg" />\r
</div>\r
+\r
<div id="next_div">\r
<h1>Next slide</h1>\r
<img id="next_img" src="/black.jpg" />\r
</div>\r
+\r
</div>\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 id="controls_container_inner">\r
+ <p>\r
+ <img class="icon" id="first" src="icons/first.svg" />\r
+ <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
+ <button id="black">Black</button>\r
+ <button id="white">White</button>\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
+ <input type="checkbox" checked="true" id="shortcuts">Keyboard shortcuts</input>\r
+\r
+ <p class="users">Not connected</p>\r
+ </div>\r
</div>\r
\r
<script src="ppt-control.js"></script>\r
+++ /dev/null
-import sys\r
-sys.coinit_flags= 0\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 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
-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.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
-STATE = {"connected": 0, "current": 0, "total": 0, "visible": 0}\r
-USERS = set()\r
-\r
-\r
-def state_event():\r
- return json.dumps({"type": "state", **STATE})\r
-\r
-\r
-def users_event():\r
- return json.dumps({"type": "users", "count": len(USERS)})\r
-\r
-\r
-async def notify_state():\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 notify_users():\r
- if USERS: # asyncio.wait doesn't accept an empty list\r
- message = users_event()\r
- await asyncio.wait([user.send(message) for user in USERS])\r
-\r
-\r
-async def register(websocket):\r
- USERS.add(websocket)\r
- await notify_users()\r
-\r
-\r
-async def unregister(websocket):\r
- USERS.remove(websocket)\r
- await notify_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
- await websocket.send(state_event())\r
- async for message in websocket:\r
- data = json.loads(message)\r
- if data["action"] == "prev":\r
- if current_slideshow:\r
- current_slideshow.prev()\r
- await notify_state()\r
- elif data["action"] == "next":\r
- if current_slideshow:\r
- current_slideshow.next()\r
- await notify_state()\r
- elif data["action"] == "first":\r
- if current_slideshow:\r
- current_slideshow.first()\r
- await notify_state()\r
- elif data["action"] == "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
- finally:\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["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
- \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
- # http_daemon.start()\r
- #except (KeyboardInterrupt, SystemExit):\r
- # cleanup_stop_thread()\r
- # sys.exit()\r
-\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
- 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
- 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
+var preloaded = false;
+
function imageRefresh(id) {
img = document.getElementById(id);
var d = new Date;
var websocket = startWebsocket();
-//if (window.obssstudio) {
-//}
-
var prev = document.querySelector('#prev'),
next = document.querySelector('#next'),
first = document.querySelector('#first'),
current = document.querySelector('#current'),
total = document.querySelector('#total'),
users = document.querySelector('.users'),
- prev_img = document.querySelector('#prev_img'),
+ current_img = document.querySelector('#current_img'),
next_img = document.querySelector('#next_img'),
current_div = document.querySelector('#current_div'),
next_div = document.querySelector('#next_div'),
+ controls_container = document.querySelector('#controls_container'),
+ controls_container_inner = document.querySelector('#controls_container_inner'),
show_current = document.querySelector('#show_current'),
- show_next = document.querySelector('#show_next');
+ show_next = document.querySelector('#show_next'),
+ shortcuts = document.querySelector('#shortcuts');
prev.onclick = function (event) {
websocket.send(JSON.stringify({action: 'prev'}));
if (e.which == 13) this.blur();
});
+current_img.onclick = function (event) {
+ next.click()
+}
+
+next_img.onclick = function (event) {
+ next.click()
+}
+
+
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%";
+ slide_label.style.display = "inline";
+ next_div.style.width = "calc(100% - 20px)";
}
+ set_control_width();
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%";
+ current_div.style.width = "calc(100% - 20px)";
}
+ set_control_width();
saveSettings();
}
show_next.onclick = sync_next;
+function set_control_width() {
+ var width = window.innerWidth
+ || document.documentElement.clientWidth
+ || document.body.clientWidth;
+ if (show_current.checked && show_next.checked && width > 800) {
+ controls_container_inner.style.width = "70%"
+ } else {
+ controls_container_inner.style.width = "100%"
+ }
+}
+
+
+document.addEventListener('keydown', function (e) {
+ if (shortcuts.checked) {
+ switch (e.key) {
+ case "Left":
+ case "ArrowLeft":
+ case "Up":
+ case "ArrowUp":
+ case "k":
+ case "K":
+ prev.click();
+ break;
+ case " ":
+ case "Spacebar":
+ case "Enter":
+ case "Right":
+ case "ArrowRight":
+ case "Down":
+ case "ArrowDown":
+ case "j":
+ case "J":
+ next.click();
+ break;
+ case "b":
+ case "B":
+ black.click();
+ case "w":
+ case "W":
+ white.click();
+ default:
+ return
+ }
+ }
+});
+
websocket.onmessage = function (event) {
data = JSON.parse(event.data);
switch (data.type) {
current_img.src = "/white.jpg";
break;
default:
- current_img.src = "/cache/" + data.current + ".jpg?t=" + d.getTime();
+ //current_img.src = "/cache/" + data.current + ".jpg?t=" + d.getTime();
+ current_img.src = "/cache/" + data.current + ".jpg";
break;
}
if (data.current == data.total + 1) {
current.value = data.current;
}
total.textContent = data.total;
+ document.title = data.name;
break;
case 'users':
users.textContent = (
console.error(
"unsupported event", data);
}
+ if (!preloaded) {
+ var i = 0
+ var preload = [];
+ 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);
+ }
+ preloaded = true;
+ }
+
};
var interval = setInterval(refresh, 5000);
function refresh() {
websocket.send(JSON.stringify({action: 'refresh'}));
}
+
--- /dev/null
+import sys\r
+sys.coinit_flags= 0\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 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
+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.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
+ return path\r
+\r
+\r
+def run_http():\r
+ http_server = server.HTTPServer(("", 80), Handler)\r
+ http_server.serve_forever()\r
+\r
+STATE = {"connected": 0, "current": 0, "total": 0, "visible": 0, "name": ""}\r
+USERS = set()\r
+\r
+\r
+def state_event():\r
+ return json.dumps({"type": "state", **STATE})\r
+\r
+\r
+def users_event():\r
+ return json.dumps({"type": "users", "count": len(USERS)})\r
+\r
+\r
+async def notify_state():\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
+ STATE["name"] = current_slideshow.name()\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 notify_users():\r
+ if USERS: # asyncio.wait doesn't accept an empty list\r
+ message = users_event()\r
+ await asyncio.wait([user.send(message) for user in USERS])\r
+\r
+\r
+async def register(websocket):\r
+ USERS.add(websocket)\r
+ await notify_users()\r
+\r
+\r
+async def unregister(websocket):\r
+ USERS.remove(websocket)\r
+ await notify_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
+ await websocket.send(state_event())\r
+ async for message in websocket:\r
+ data = json.loads(message)\r
+ if data["action"] == "prev":\r
+ if current_slideshow:\r
+ current_slideshow.prev()\r
+ await notify_state()\r
+ elif data["action"] == "next":\r
+ if current_slideshow:\r
+ current_slideshow.next()\r
+ await notify_state()\r
+ elif data["action"] == "first":\r
+ if current_slideshow:\r
+ current_slideshow.first()\r
+ await notify_state()\r
+ elif data["action"] == "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
+ finally:\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
+ asyncio.get_event_loop().run_until_complete(start_server)\r
+ asyncio.get_event_loop().run_forever()\r
+\r
+def start_server():\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
+ \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
+ # http_daemon.start()\r
+ #except (KeyboardInterrupt, SystemExit):\r
+ # cleanup_stop_thread()\r
+ # sys.exit()\r
+\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
+ 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
+ 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
+ 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")\r
+ break\r
+ except ValueError as e:\r
+ current_slideshow = None\r
+ pass\r
+ time.sleep(1)\r
}
function saveSettings() {
- settingsString = JSON.stringify({showcurrent: show_current.checked, shownext: show_next.checked});
- console.log("Saving cookie " + settingsString);
+ settingsString = JSON.stringify({showcurrent: show_current.checked, shownext: show_next.checked, enable_shortcuts: shortcuts.checked});
setCookie(COOKIENAME, settingsString, COOKIEEXP);
}
function initSettings() {
- console.log("Retrieving cookie");
if (getCookie(COOKIENAME) == 0) {
- console.log("No cookie found - setting new cookie");
+ if (window.obssstudio) {
+ shortcuts.checked = False;
+ show_current.checked = False;
+ }
saveSettings()
} else {
cookie = JSON.parse(getCookie(COOKIENAME));
- console.log("Found cookie " + cookie);
show_current.checked = cookie.showcurrent;
show_next.checked = cookie.shownext;
+ shortcuts.checked = cookie.enable_shortcuts;
sync_current();
sync_next();
}
clear: both;
}
-::-webkit-scrollbar {
- //display: none;
-}
-
body {
background: #3a393a;
color: #efefef;
@media only screen and (max-width: 800px) {
#current_div, #next_div {
- width: 95% !important;
+ width: calc(100% - 20px) !important;
}
}
+
+.icon {
+ width: 50px;
+ filter: invert(88%) sepia(4%) saturate(15%) hue-rotate(18deg) brightness(92%) contrast(97%);
+}
+
+.icon:hover {
+ cursor: pointer;
+ filter: invert(100%) sepia(24%) saturate(1720%) hue-rotate(187deg) brightness(123%) contrast(87%);
+}
+
+.icon#first, .icon#last {
+ width: 20px;
+ margin-bottom: 10px;
+}
+
+button {
+ float: right;
+ margin: 0 10px 0 0;
+}
+
+input[type='checkbox'] {
+ font-size: 15px;
+}