systemtray integration
This commit is contained in:
parent
c1666978b2
commit
62f9daf16f
10 changed files with 683 additions and 0 deletions
245
trayicon/Open Media Library.py
Normal file
245
trayicon/Open Media Library.py
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
import win32api
|
||||||
|
import win32con
|
||||||
|
import win32gui_struct
|
||||||
|
|
||||||
|
try:
|
||||||
|
import winxpgui as win32gui
|
||||||
|
except ImportError:
|
||||||
|
import win32gui
|
||||||
|
|
||||||
|
import install
|
||||||
|
|
||||||
|
class OMLTrayIcon(object):
|
||||||
|
QUIT = 'QUIT'
|
||||||
|
SPECIAL_ACTIONS = [QUIT]
|
||||||
|
|
||||||
|
FIRST_ID = 1023
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
launch()
|
||||||
|
name = "Open Media Library"
|
||||||
|
default_menu_index = 1
|
||||||
|
self.icon = "ico/oml.ico"
|
||||||
|
self.hover_text = name
|
||||||
|
self.on_quit = quit
|
||||||
|
|
||||||
|
menu_options = (
|
||||||
|
('Launch', None, launch),
|
||||||
|
('Quit', None, self.QUIT)
|
||||||
|
)
|
||||||
|
self._next_action_id = self.FIRST_ID
|
||||||
|
self.menu_actions_by_id = set()
|
||||||
|
self.menu_options = self._add_ids_to_menu_options(list(menu_options))
|
||||||
|
self.menu_actions_by_id = dict(self.menu_actions_by_id)
|
||||||
|
del self._next_action_id
|
||||||
|
|
||||||
|
self.default_menu_index = (default_menu_index or 0)
|
||||||
|
self.window_class_name = name
|
||||||
|
|
||||||
|
message_map = {win32gui.RegisterWindowMessage("TaskbarCreated"): self.restart,
|
||||||
|
win32con.WM_DESTROY: self.destroy,
|
||||||
|
win32con.WM_COMMAND: self.command,
|
||||||
|
win32con.WM_USER+20 : self.notify,}
|
||||||
|
# Register the Window class.
|
||||||
|
window_class = win32gui.WNDCLASS()
|
||||||
|
hinst = window_class.hInstance = win32gui.GetModuleHandle(None)
|
||||||
|
window_class.lpszClassName = self.window_class_name
|
||||||
|
window_class.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW;
|
||||||
|
window_class.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
|
||||||
|
window_class.hbrBackground = win32con.COLOR_WINDOW
|
||||||
|
window_class.lpfnWndProc = message_map # could also specify a wndproc.
|
||||||
|
classAtom = win32gui.RegisterClass(window_class)
|
||||||
|
# Create the Window.
|
||||||
|
style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
|
||||||
|
self.hwnd = win32gui.CreateWindow(classAtom,
|
||||||
|
self.window_class_name,
|
||||||
|
style,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
win32con.CW_USEDEFAULT,
|
||||||
|
win32con.CW_USEDEFAULT,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
hinst,
|
||||||
|
None)
|
||||||
|
win32gui.UpdateWindow(self.hwnd)
|
||||||
|
self.notify_id = None
|
||||||
|
self.refresh_icon()
|
||||||
|
win32gui.PumpMessages()
|
||||||
|
|
||||||
|
def _add_ids_to_menu_options(self, menu_options):
|
||||||
|
result = []
|
||||||
|
for menu_option in menu_options:
|
||||||
|
option_text, option_icon, option_action = menu_option
|
||||||
|
if callable(option_action) or option_action in self.SPECIAL_ACTIONS:
|
||||||
|
self.menu_actions_by_id.add((self._next_action_id, option_action))
|
||||||
|
result.append(menu_option + (self._next_action_id,))
|
||||||
|
else:
|
||||||
|
print ('Unknown item', option_text, option_icon, option_action)
|
||||||
|
self._next_action_id += 1
|
||||||
|
return result
|
||||||
|
|
||||||
|
def refresh_icon(self):
|
||||||
|
# Try and find a custom icon
|
||||||
|
hinst = win32gui.GetModuleHandle(None)
|
||||||
|
if os.path.isfile(self.icon):
|
||||||
|
icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
|
||||||
|
hicon = win32gui.LoadImage(hinst,
|
||||||
|
self.icon,
|
||||||
|
win32con.IMAGE_ICON,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
icon_flags)
|
||||||
|
else:
|
||||||
|
print ("Can't find icon file - using default.")
|
||||||
|
hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
|
||||||
|
|
||||||
|
if self.notify_id: message = win32gui.NIM_MODIFY
|
||||||
|
else: message = win32gui.NIM_ADD
|
||||||
|
self.notify_id = (self.hwnd,
|
||||||
|
0,
|
||||||
|
win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP,
|
||||||
|
win32con.WM_USER+20,
|
||||||
|
hicon,
|
||||||
|
self.hover_text)
|
||||||
|
win32gui.Shell_NotifyIcon(message, self.notify_id)
|
||||||
|
|
||||||
|
def restart(self, hwnd, msg, wparam, lparam):
|
||||||
|
self.refresh_icon()
|
||||||
|
|
||||||
|
def destroy(self, hwnd, msg, wparam, lparam):
|
||||||
|
if self.on_quit: self.on_quit(self)
|
||||||
|
nid = (self.hwnd, 0)
|
||||||
|
win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid)
|
||||||
|
win32gui.PostQuitMessage(0) # Terminate the app.
|
||||||
|
|
||||||
|
def notify(self, hwnd, msg, wparam, lparam):
|
||||||
|
if lparam==win32con.WM_LBUTTONDBLCLK:
|
||||||
|
self.execute_menu_option(self.default_menu_index + self.FIRST_ID)
|
||||||
|
elif lparam==win32con.WM_RBUTTONUP:
|
||||||
|
self.show_menu()
|
||||||
|
elif lparam==win32con.WM_LBUTTONUP:
|
||||||
|
self.show_menu()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def show_menu(self):
|
||||||
|
menu = win32gui.CreatePopupMenu()
|
||||||
|
self.create_menu(menu, self.menu_options)
|
||||||
|
#win32gui.SetMenuDefaultItem(menu, 1000, 0)
|
||||||
|
|
||||||
|
pos = win32gui.GetCursorPos()
|
||||||
|
# See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/menus_0hdi.asp
|
||||||
|
win32gui.SetForegroundWindow(self.hwnd)
|
||||||
|
win32gui.TrackPopupMenu(menu,
|
||||||
|
win32con.TPM_LEFTALIGN,
|
||||||
|
pos[0],
|
||||||
|
pos[1],
|
||||||
|
0,
|
||||||
|
self.hwnd,
|
||||||
|
None)
|
||||||
|
win32gui.PostMessage(self.hwnd, win32con.WM_NULL, 0, 0)
|
||||||
|
|
||||||
|
def create_menu(self, menu, menu_options):
|
||||||
|
for option_text, option_icon, option_action, option_id in menu_options[::-1]:
|
||||||
|
if option_icon:
|
||||||
|
option_icon = self.prep_menu_icon(option_icon)
|
||||||
|
|
||||||
|
if option_id in self.menu_actions_by_id:
|
||||||
|
item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text,
|
||||||
|
hbmpItem=option_icon,
|
||||||
|
wID=option_id)
|
||||||
|
win32gui.InsertMenuItem(menu, 0, 1, item)
|
||||||
|
else:
|
||||||
|
submenu = win32gui.CreatePopupMenu()
|
||||||
|
self.create_menu(submenu, option_action)
|
||||||
|
item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text,
|
||||||
|
hbmpItem=option_icon,
|
||||||
|
hSubMenu=submenu)
|
||||||
|
win32gui.InsertMenuItem(menu, 0, 1, item)
|
||||||
|
|
||||||
|
def prep_menu_icon(self, icon):
|
||||||
|
# First load the icon.
|
||||||
|
ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON)
|
||||||
|
ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON)
|
||||||
|
hicon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, ico_x, ico_y, win32con.LR_LOADFROMFILE)
|
||||||
|
|
||||||
|
hdcBitmap = win32gui.CreateCompatibleDC(0)
|
||||||
|
hdcScreen = win32gui.GetDC(0)
|
||||||
|
hbm = win32gui.CreateCompatibleBitmap(hdcScreen, ico_x, ico_y)
|
||||||
|
hbmOld = win32gui.SelectObject(hdcBitmap, hbm)
|
||||||
|
# Fill the background.
|
||||||
|
brush = win32gui.GetSysColorBrush(win32con.COLOR_MENU)
|
||||||
|
win32gui.FillRect(hdcBitmap, (0, 0, 16, 16), brush)
|
||||||
|
# unclear if brush needs to be feed. Best clue I can find is:
|
||||||
|
# "GetSysColorBrush returns a cached brush instead of allocating a new
|
||||||
|
# one." - implies no DeleteObject
|
||||||
|
# draw the icon
|
||||||
|
win32gui.DrawIconEx(hdcBitmap, 0, 0, hicon, ico_x, ico_y, 0, 0, win32con.DI_NORMAL)
|
||||||
|
win32gui.SelectObject(hdcBitmap, hbmOld)
|
||||||
|
win32gui.DeleteDC(hdcBitmap)
|
||||||
|
|
||||||
|
return hbm
|
||||||
|
|
||||||
|
def command(self, hwnd, msg, wparam, lparam):
|
||||||
|
id = win32gui.LOWORD(wparam)
|
||||||
|
self.execute_menu_option(id)
|
||||||
|
|
||||||
|
def execute_menu_option(self, id):
|
||||||
|
menu_action = self.menu_actions_by_id[id]
|
||||||
|
if menu_action == self.QUIT:
|
||||||
|
win32gui.DestroyWindow(self.hwnd)
|
||||||
|
else:
|
||||||
|
menu_action(self)
|
||||||
|
|
||||||
|
def check_pid(pid):
|
||||||
|
try:
|
||||||
|
with open(pid) as fd:
|
||||||
|
pid = int(fd.read())
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
os.kill(pid, 0)
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def launch(sysTrayIcon=None):
|
||||||
|
base = os.path.join(os.getenv('APPDATA'), 'Open Media Library')
|
||||||
|
pid = os.path.join(base, 'data', 'openmedialibrary.pid')
|
||||||
|
if os.path.exists(pid) and check_pid(pid):
|
||||||
|
webbrowser.open_new_tab(os.path.join(base, 'openmedialibrary', 'static', 'html', 'load.html'))
|
||||||
|
elif os.path.exists(base):
|
||||||
|
python = os.path.join(base, 'platform_win32', 'pythonw.exe')
|
||||||
|
oml = os.path.join(base, 'openmedialibrary')
|
||||||
|
subprocess.Popen([python, 'oml', 'server', pid], cwd=oml, start_new_session=True)
|
||||||
|
webbrowser.open_new_tab(os.path.join(base, 'openmedialibrary', 'static', 'html', 'load.html'))
|
||||||
|
else:
|
||||||
|
install.run()
|
||||||
|
|
||||||
|
def quit(sysTrayIcon):
|
||||||
|
base = os.path.join(os.getenv('APPDATA'), 'Open Media Library')
|
||||||
|
pid = os.path.join(base, 'data', 'openmedialibrary.pid')
|
||||||
|
if os.path.exists(pid):
|
||||||
|
with open(pid) as fd:
|
||||||
|
data = fd.read().strip()
|
||||||
|
try:
|
||||||
|
os.kill(int(data), signal.SIGTERM)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
base = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
base = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
base = os.path.abspath(base)
|
||||||
|
os.chdir(base)
|
||||||
|
OMLTrayIcon()
|
10
trayicon/README.md
Normal file
10
trayicon/README.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Open Media Library Windows System Tray Integration
|
||||||
|
|
||||||
|
To build a new version you need pywin32
|
||||||
|
http://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/pywin32-220.win32-py3.5.exe/download
|
||||||
|
and cx_Freeze from hg
|
||||||
|
hg clone https://bitbucket.org/anthony_tuininga/cx_freeze
|
||||||
|
|
||||||
|
# Build
|
||||||
|
python setup.py bdist_msi
|
||||||
|
|
BIN
trayicon/ico/oml.ico
Normal file
BIN
trayicon/ico/oml.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 361 KiB |
11
trayicon/index.html
Normal file
11
trayicon/index.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Open Media Library</title>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<link href="/png/oml.png" rel="icon" type="image/png">
|
||||||
|
<script src="/js/install.js" type="text/javascript"></script>
|
||||||
|
<meta name="google" value="notranslate"/>
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
155
trayicon/install.py
Normal file
155
trayicon/install.py
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
from __future__ import division, with_statement
|
||||||
|
|
||||||
|
from contextlib import closing
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import tarfile
|
||||||
|
from urllib.request import urlopen
|
||||||
|
import http.server
|
||||||
|
import socketserver
|
||||||
|
from threading import Thread
|
||||||
|
import subprocess
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
|
||||||
|
PORT = 9841
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
static_dir = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
static_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
static_dir = os.path.abspath(static_dir)
|
||||||
|
|
||||||
|
def makedirs(dirname):
|
||||||
|
if not os.path.exists(dirname):
|
||||||
|
os.makedirs(dirname)
|
||||||
|
|
||||||
|
def get_platform():
|
||||||
|
name = sys.platform
|
||||||
|
if name.startswith('darwin'):
|
||||||
|
name = 'darwin64'
|
||||||
|
elif name.startswith('linux'):
|
||||||
|
import platform
|
||||||
|
if platform.architecture()[0] == '64bit':
|
||||||
|
name = 'linux64'
|
||||||
|
else:
|
||||||
|
name = 'linux32'
|
||||||
|
return name
|
||||||
|
|
||||||
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
|
def do_OPTIONS(self):
|
||||||
|
self.send_response(200, 'OK')
|
||||||
|
self.send_header('Allow', 'GET, POST, OPTIONS')
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.send_header('Access-Control-Allow-Headers', 'X-Requested-With')
|
||||||
|
self.send_header('Content-Length', '0')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
|
||||||
|
if self.path == '/status':
|
||||||
|
content = json.dumps(self.server.install.status).encode()
|
||||||
|
self.send_response(200, 'OK')
|
||||||
|
else:
|
||||||
|
path = os.path.join(static_dir, 'index.html' if self.path == '/' else self.path[1:])
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path, 'rb') as fd:
|
||||||
|
content = fd.read()
|
||||||
|
self.send_response(200, 'OK')
|
||||||
|
content_type = {
|
||||||
|
'html': 'text/html',
|
||||||
|
'png': 'image/png',
|
||||||
|
'svg': 'image/svg+xml',
|
||||||
|
'txt': 'text/plain',
|
||||||
|
}.get(path.split('.')[-1], 'txt')
|
||||||
|
self.send_header('Content-Type', content_type)
|
||||||
|
else:
|
||||||
|
self.send_response(404, 'not found')
|
||||||
|
content = b'404 not found'
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
self.send_header('Content-Length', str(len(content)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(content)
|
||||||
|
|
||||||
|
class Install(Thread):
|
||||||
|
|
||||||
|
release_url = "http://downloads.openmedialibrary.com/release.json"
|
||||||
|
status = {
|
||||||
|
'step': 'Downloading...'
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, target, httpd):
|
||||||
|
target = os.path.normpath(os.path.join(os.path.abspath(target)))
|
||||||
|
self.target = target
|
||||||
|
self.httpd = httpd
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
webbrowser.open('http://127.0.0.1:%s'%PORT)
|
||||||
|
target = self.target
|
||||||
|
makedirs(target)
|
||||||
|
os.chdir(target)
|
||||||
|
self.status["step"] = 'Downloading...'
|
||||||
|
release = self.get_release()
|
||||||
|
self.status["release"] = release
|
||||||
|
self.status["progress"] = 0
|
||||||
|
platform = get_platform()
|
||||||
|
for module in sorted(release['modules']):
|
||||||
|
if release['modules'][module].get('platform', platform) == platform:
|
||||||
|
package_tar = release['modules'][module]['name']
|
||||||
|
url = self.release_url.replace('release.json', package_tar)
|
||||||
|
self.download(url, package_tar)
|
||||||
|
self.status["step"] = 'Installing...'
|
||||||
|
for module in sorted(release['modules']):
|
||||||
|
if release['modules'][module].get('platform', platform) == platform:
|
||||||
|
package_tar = release['modules'][module]['name']
|
||||||
|
tar = tarfile.open(package_tar)
|
||||||
|
tar.extractall()
|
||||||
|
tar.close()
|
||||||
|
os.unlink(package_tar)
|
||||||
|
makedirs('data')
|
||||||
|
with open('data/release.json', 'w') as fd:
|
||||||
|
json.dump(release, fd, indent=2)
|
||||||
|
self.status = {"relaunch": True}
|
||||||
|
open_oml(target)
|
||||||
|
time.sleep(5)
|
||||||
|
self.httpd.shutdown()
|
||||||
|
|
||||||
|
def download(self, url, filename):
|
||||||
|
dirname = os.path.dirname(filename)
|
||||||
|
if dirname:
|
||||||
|
makedirs(dirname)
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
with closing(urlopen(url)) as u:
|
||||||
|
size = int(u.headers.get('content-length', 0))
|
||||||
|
self.status["size"] = size
|
||||||
|
available = 0
|
||||||
|
data = u.read(4096)
|
||||||
|
while data:
|
||||||
|
if size:
|
||||||
|
available += len(data)
|
||||||
|
f.write(data)
|
||||||
|
data = u.read(4096)
|
||||||
|
|
||||||
|
def get_release(self):
|
||||||
|
with closing(urlopen(self.release_url)) as u:
|
||||||
|
data = json.loads(u.read().decode())
|
||||||
|
return data
|
||||||
|
|
||||||
|
class Server(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||||
|
allow_reuse_address = True
|
||||||
|
|
||||||
|
def open_oml(base):
|
||||||
|
python = os.path.join(base, 'platform_win32', 'pythonw.exe')
|
||||||
|
pid = os.path.join(base, 'data', 'openmedialibrary.pid')
|
||||||
|
oml = os.path.join(base, 'openmedialibrary')
|
||||||
|
subprocess.Popen([python, 'oml', 'server', pid], cwd=oml, start_new_session=True)
|
||||||
|
|
||||||
|
def run(target):
|
||||||
|
httpd = Server(("", PORT), Handler)
|
||||||
|
install = Install(target, httpd)
|
||||||
|
httpd.install = install
|
||||||
|
httpd.serve_forever()
|
148
trayicon/js/install.js
Normal file
148
trayicon/js/install.js
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
loadImages(function(images) {
|
||||||
|
loadScreen(images);
|
||||||
|
initUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
function initUpdate(browserSupported) {
|
||||||
|
window.update = {};
|
||||||
|
update.status = document.createElement('div');
|
||||||
|
update.status.className = 'OxElement';
|
||||||
|
update.status.style.position = 'absolute';
|
||||||
|
update.status.style.left = '16px';
|
||||||
|
update.status.style.top = '336px';
|
||||||
|
update.status.style.right = 0;
|
||||||
|
update.status.style.bottom = 0;
|
||||||
|
update.status.style.width = '512px';
|
||||||
|
update.status.style.height = '16px';
|
||||||
|
update.status.style.margin = 'auto';
|
||||||
|
update.status.style.textAlign = 'center';
|
||||||
|
update.status.style.color = 'rgb(16, 16, 16)';
|
||||||
|
update.status.style.fontFamily = 'Lucida Grande, Segoe UI, DejaVu Sans, Lucida Sans Unicode, Helvetica, Arial, sans-serif';
|
||||||
|
update.status.style.fontSize = '11px';
|
||||||
|
document.querySelector('#loadingScreen').appendChild(update.status);
|
||||||
|
update.status.innerHTML = '';
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
var base = '//127.0.0.1:9842',
|
||||||
|
ws = new WebSocket('ws:' + base + '/ws');
|
||||||
|
ws.onopen = function(event) {
|
||||||
|
document.location.href = 'http:' + base;
|
||||||
|
};
|
||||||
|
ws.onerror = function(event) {
|
||||||
|
ws.close();
|
||||||
|
setTimeout(load, 500);
|
||||||
|
};
|
||||||
|
ws.onclose = function(event) {
|
||||||
|
setTimeout(load, 500);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadImages(callback) {
|
||||||
|
var images = {};
|
||||||
|
images.logo = document.createElement('img');
|
||||||
|
images.logo.onload = function() {
|
||||||
|
images.logo.style.position = 'absolute';
|
||||||
|
images.logo.style.left = 0;
|
||||||
|
images.logo.style.top = 0;
|
||||||
|
images.logo.style.right = 0;
|
||||||
|
images.logo.style.bottom = '96px';
|
||||||
|
images.logo.style.width = '256px';
|
||||||
|
images.logo.style.height = '256px';
|
||||||
|
images.logo.style.margin = 'auto';
|
||||||
|
images.logo.style.MozUserSelect = 'none';
|
||||||
|
images.logo.style.MSUserSelect = 'none';
|
||||||
|
images.logo.style.OUserSelect = 'none';
|
||||||
|
images.logo.style.WebkitUserSelect = 'none';
|
||||||
|
images.loadingIcon = document.createElement('img');
|
||||||
|
images.loadingIcon.setAttribute('id', 'loadingIcon');
|
||||||
|
images.loadingIcon.style.position = 'absolute';
|
||||||
|
images.loadingIcon.style.left = '16px';
|
||||||
|
images.loadingIcon.style.top = '256px'
|
||||||
|
images.loadingIcon.style.right = 0;
|
||||||
|
images.loadingIcon.style.bottom = 0;
|
||||||
|
images.loadingIcon.style.width = '32px';
|
||||||
|
images.loadingIcon.style.height = '32px';
|
||||||
|
images.loadingIcon.style.margin = 'auto';
|
||||||
|
images.loadingIcon.style.MozUserSelect = 'none';
|
||||||
|
images.loadingIcon.style.MSUserSelect = 'none';
|
||||||
|
images.loadingIcon.style.OUserSelect = 'none';
|
||||||
|
images.loadingIcon.style.WebkitUserSelect = 'none';
|
||||||
|
images.loadingIcon.src = '/svg/symbolLoading.svg';
|
||||||
|
callback(images);
|
||||||
|
};
|
||||||
|
images.logo.src = '/png/oml.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadScreen(images) {
|
||||||
|
var loadingScreen = document.createElement('div');
|
||||||
|
loadingScreen.setAttribute('id', 'loadingScreen');
|
||||||
|
loadingScreen.className = 'OxScreen';
|
||||||
|
loadingScreen.style.position = 'absolute';
|
||||||
|
loadingScreen.style.width = '100%';
|
||||||
|
loadingScreen.style.height = '100%';
|
||||||
|
loadingScreen.style.backgroundColor = 'rgb(224, 224, 224)';
|
||||||
|
loadingScreen.style.zIndex = '1002';
|
||||||
|
loadingScreen.appendChild(images.logo);
|
||||||
|
loadingScreen.appendChild(images.loadingIcon);
|
||||||
|
// FF3.6 document.body can be undefined here
|
||||||
|
window.onload = function() {
|
||||||
|
document.body.style.margin = 0;
|
||||||
|
document.body.appendChild(loadingScreen);
|
||||||
|
startAnimation();
|
||||||
|
};
|
||||||
|
// IE8 does not call onload if already loaded before set
|
||||||
|
document.body && window.onload();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function startAnimation() {
|
||||||
|
var css, deg = 0, loadingIcon = document.getElementById('loadingIcon'),
|
||||||
|
previousTime = +new Date();
|
||||||
|
var animationInterval = setInterval(function() {
|
||||||
|
var currentTime = +new Date(),
|
||||||
|
delta = (currentTime - previousTime) / 1000;
|
||||||
|
previousTime = currentTime;
|
||||||
|
deg = Math.round((deg + delta * 360) % 360 / 30) * 30;
|
||||||
|
css = 'rotate(' + deg + 'deg)';
|
||||||
|
loadingIcon.style.MozTransform = css;
|
||||||
|
loadingIcon.style.MSTransform = css;
|
||||||
|
loadingIcon.style.OTransform = css;
|
||||||
|
loadingIcon.style.WebkitTransform = css;
|
||||||
|
loadingIcon.style.transform = css;
|
||||||
|
}, 83);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatus() {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.onload = function() {
|
||||||
|
var response = JSON.parse(this.responseText);
|
||||||
|
if (response.step) {
|
||||||
|
var status = response.step;
|
||||||
|
if (response.progress) {
|
||||||
|
status = parseInt(response.progress * 100) + '% ' + status;
|
||||||
|
}
|
||||||
|
update.status.innerHTML = status;
|
||||||
|
setTimeout(updateStatus, 1000);
|
||||||
|
} else {
|
||||||
|
update.status.innerHTML = 'Relaunching...';
|
||||||
|
setTimeout(load, 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onerror = function() {
|
||||||
|
var status = update.status.innerHTML;
|
||||||
|
if (['Relaunching...', ''].indexOf(status) == -1) {
|
||||||
|
update.status.innerHTML = 'Installation failed';
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
xhr.open('get', '/status');
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
}());
|
BIN
trayicon/png/oml.png
Normal file
BIN
trayicon/png/oml.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
47
trayicon/setup.py
Normal file
47
trayicon/setup.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import sys
|
||||||
|
from cx_Freeze import setup, Executable
|
||||||
|
|
||||||
|
'''
|
||||||
|
to build run: python.exe setup.py bdist_msi
|
||||||
|
'''
|
||||||
|
|
||||||
|
build_exe_options = {
|
||||||
|
"packages": ["os"],
|
||||||
|
"excludes": ["tkinter"],
|
||||||
|
"include_msvcr": True,
|
||||||
|
"include_files": [
|
||||||
|
"ico",
|
||||||
|
"index.html",
|
||||||
|
"js",
|
||||||
|
"png",
|
||||||
|
"svg",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
bdist_msi_options = {
|
||||||
|
"upgrade_code": "{d2ff6dae-c817-11e5-bedb-08002781ab3d}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# GUI applications require a different base on Windows (the default is for a
|
||||||
|
# console application).
|
||||||
|
base = None
|
||||||
|
if sys.platform == "win32":
|
||||||
|
base = "Win32GUI"
|
||||||
|
|
||||||
|
oml = Executable(
|
||||||
|
"Open Media Library.py", base=base,
|
||||||
|
shortcutName="Open Media Library",
|
||||||
|
shortcutDir="ProgramMenuFolder",
|
||||||
|
icon="ico/oml.ico"
|
||||||
|
)
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name = "Open Media Library",
|
||||||
|
version = "0.1",
|
||||||
|
description = "share media collectoins",
|
||||||
|
options = {
|
||||||
|
"build_exe": build_exe_options,
|
||||||
|
"bdist_msi": bdist_msi_options
|
||||||
|
},
|
||||||
|
executables = [oml]
|
||||||
|
)
|
51
trayicon/svg/oml.svg
Normal file
51
trayicon/svg/oml.svg
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="600" width="600">
|
||||||
|
<g transform="translate(134 -64) rotate(-45 300 300)">
|
||||||
|
<g transform="translate(115.47005383792516 66.66666666666667)">
|
||||||
|
<path d="M 86.60254037844388,50.0 L 173.20508075688775,0 173.20508075688775,100 86.60254037844388,150.0 86.60254037844388,50.0" fill="rgb(140,131,114)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(230.94010767585033 133.33333333333334)">
|
||||||
|
<path d="M 0,0 L 86.60254037844388,-50.0 173.20508075688775,0 86.60254037844388,50.0 0,0" fill="rgb(156,163,142)"/>
|
||||||
|
<path d="M 86.60254037844388,50.0 L 173.20508075688775,0 173.20508075688775,100 86.60254037844388,150.0 86.60254037844388,50.0" fill="rgb(131,140,114)"/>
|
||||||
|
<path d="M 0,0 L 86.60254037844388,50.0 86.60254037844388,150.0 0,100 0,0" fill="rgb(105,112,91)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(346.4101615137755 200.0)">
|
||||||
|
<path d="M 0,0 L 86.60254037844388,-50.0 173.20508075688775,0 86.60254037844388,50.0 0,0" fill="rgb(142,163,142)"/>
|
||||||
|
<path d="M 86.60254037844388,50.0 L 173.20508075688775,0 173.20508075688775,100 86.60254037844388,150.0 86.60254037844388,50.0" fill="rgb(114,140,114)"/>
|
||||||
|
<path d="M 0,0 L 86.60254037844388,50.0 86.60254037844388,150.0 0,100 0,0" fill="rgb(91,112,91)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(230.94010767585033 266.6666666666667)">
|
||||||
|
<path d="M 0,0 L 86.60254037844388,-50.0 173.20508075688775,0 86.60254037844388,50.0 0,0" fill="rgb(142,163,156)"/>
|
||||||
|
<path d="M 86.60254037844388,50.0 L 173.20508075688775,0 173.20508075688775,100 86.60254037844388,150.0 86.60254037844388,50.0" fill="rgb(114,140,131)"/>
|
||||||
|
<path d="M 0,0 L 86.60254037844388,50.0 86.60254037844388,150.0 0,100 0,0" fill="rgb(91,112,105)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(115.47005383792516 333.33333333333337)">
|
||||||
|
<path d="M 0,0 L 86.60254037844388,-50.0 173.20508075688775,0 86.60254037844388,50.0 0,0" fill="rgb(142,156,163)"/>
|
||||||
|
<path d="M 86.60254037844388,50.0 L 173.20508075688775,0 173.20508075688775,100 86.60254037844388,150.0 86.60254037844388,50.0" fill="rgb(114,131,140)"/>
|
||||||
|
<path d="M 0,0 L 86.60254037844388,50.0 86.60254037844388,150.0 0,100 0,0" fill="rgb(91,105,112)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(0 400.0)">
|
||||||
|
<path d="M 0,0 L 86.60254037844388,-50.0 173.20508075688775,0 86.60254037844388,50.0 0,0" fill="rgb(142,142,163)"/>
|
||||||
|
<path d="M 86.60254037844388,50.0 L 173.20508075688775,0 173.20508075688775,100 86.60254037844388,150.0 86.60254037844388,50.0" fill="rgb(114,114,140)"/>
|
||||||
|
<path d="M 0,0 L 86.60254037844388,50.0 86.60254037844388,150.0 0,100 0,0" fill="rgb(91,91,112)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(0 266.6666666666667)">
|
||||||
|
<path d="M 0,0 L 86.60254037844388,-50.0 173.20508075688775,0 86.60254037844388,50.0 0,0" fill="rgb(156,142,163)"/>
|
||||||
|
<path d="M 86.60254037844388,50.0 L 173.20508075688775,0 173.20508075688775,100 86.60254037844388,150.0 86.60254037844388,50.0" fill="rgb(131,114,140)"/>
|
||||||
|
<path d="M 0,0 L 86.60254037844388,50.0 86.60254037844388,150.0 0,100 0,0" fill="rgb(105,91,112)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(0 133.33333333333334)">
|
||||||
|
<path d="M 0,0 L 86.60254037844388,-50.0 173.20508075688775,0 86.60254037844388,50.0 0,0" fill="rgb(163,142,156)"/>
|
||||||
|
<path d="M 86.60254037844388,50.0 L 173.20508075688775,0 173.20508075688775,100 86.60254037844388,150.0 86.60254037844388,50.0" fill="rgb(140,114,131)"/>
|
||||||
|
<path d="M 0,0 L 86.60254037844388,50.0 86.60254037844388,150.0 0,100 0,0" fill="rgb(112,91,105)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(0 0)">
|
||||||
|
<path d="M 0,0 L 86.60254037844388,-50.0 173.20508075688775,0 86.60254037844388,50.0 0,0" fill="rgb(163,142,142)"/>
|
||||||
|
<path d="M 86.60254037844388,50.0 L 173.20508075688775,0 173.20508075688775,100 86.60254037844388,150.0 86.60254037844388,50.0" fill="rgb(140,114,114)"/>
|
||||||
|
<path d="M 0,0 L 86.60254037844388,50.0 86.60254037844388,150.0 0,100 0,0" fill="rgb(112,91,91)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(115.47005383792516 66.66666666666667)">
|
||||||
|
<path d="M 0,0 L 86.60254037844388,-50.0 173.20508075688775,0 86.60254037844388,50.0 0,0" fill="rgb(163,156,142)"/>
|
||||||
|
<path d="M 0,0 L 86.60254037844388,50.0 86.60254037844388,150.0 0,100 0,0" fill="rgb(112,105,91)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.4 KiB |
16
trayicon/svg/symbolLoading.svg
Normal file
16
trayicon/svg/symbolLoading.svg
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256">
|
||||||
|
<g transform="translate(128, 128)" stroke="#808080" stroke-linecap="round" stroke-width="28">
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(0)" opacity="1"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(30)" opacity="0.083333"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(60)" opacity="0.166667"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(90)" opacity="0.25"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(120)" opacity="0.333333"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(150)" opacity="0.416667"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(180)" opacity="0.5"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(210)" opacity="0.583333"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(240)" opacity="0.666667"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(270)" opacity="0.75"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(300)" opacity="0.833333"/>
|
||||||
|
<line x1="0" y1="-114" x2="0" y2="-70" transform="rotate(330)" opacity="0.916667"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
Loading…
Reference in a new issue