Create attractive VASL scenarios, with loads of useful information embedded to assist with game play. https://vasl-templates.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
vasl-templates/loader/main.py

207 lines
7.4 KiB

""" Load the main vasl-templates program.
vasl-templates can be slow to start (especially on Windows), since it has to unpack the PyInstaller-generated EXE,
then startup Qt. We want to show a splash screen while all this happening, but we can't just put it in vasl-templates,
since it would only happen *after* all the slow stuff has finished :-/ So, we have this stub program that shows
a splash screen, launches the main vasl-templates program, and waits for it to finish starting up.
"""
import sys
import os
import subprocess
import threading
import itertools
import urllib.request
from urllib.error import URLError
import time
import configparser
# NOTE: It's important that this program start up quickly (otherwise it becomes pointless),
# so we use tkinter, instead of PyQt (and also avoid bundling a 2nd copy of PyQt :-/).
import tkinter
import tkinter.messagebox
if getattr( sys, "frozen", False ):
BASE_DIR = sys._MEIPASS #pylint: disable=no-member,protected-access
else:
BASE_DIR = os.path.abspath( os.path.dirname( __file__ ) )
STARTUP_TIMEOUT = 60 # how to long to wait for vasl-templates to start (seconds)
main_window = None
# ---------------------------------------------------------------------
def main( args ):
"""Load the main vasl-templates program."""
# initialize Tkinter
global main_window
main_window = tkinter.Tk()
main_window.option_add( "*Dialog.msg.font", "Helvetica 12" )
# load the app icon
# NOTE: This image file doesn't exist in source control, but is created dynamically from
# the main app icon by the freeze script, and inserted into the PyInstaller-generated executable.
# We do things this way so that we don't have to bundle Pillow into the release.
app_icon = tkinter.PhotoImage(
file = make_asset_path( "app-icon.png" )
)
# locate the main vasl-templates executable
fname = os.path.join( os.path.dirname( sys.executable ), "vasl-templates-main" )
if sys.platform == "win32":
fname += ".exe"
if not os.path.isfile( fname ):
show_error_msg( "Can't find the main vasl-templates program.", withdraw=True )
return -1
# launch the main vasl-templates program
try:
proc = subprocess.Popen( itertools.chain( [fname], args ) ) #pylint: disable=consider-using-with
except Exception as ex: #pylint: disable=broad-except
show_error_msg( "Can't start vasl-templates:\n\n{}".format( ex ), withdraw=True )
return -2
# get the webapp port number
port = 5010
fname = os.path.join( os.path.dirname( fname ), "config/app.cfg" )
if os.path.isfile( fname ):
config_parser = configparser.ConfigParser()
config_parser.optionxform = str # preserve case for the keys :-/
config_parser.read( fname )
args = dict( config_parser.items( "System" ) )
port = args.get( "FLASK_PORT_NO", port )
# create the splash window
create_window( app_icon )
# start a background thread to check on the main vasl-templates process
threading.Thread(
target = check_startup,
args = ( proc, port )
).start()
# run the main loop
main_window.mainloop()
return 0
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def create_window( app_icon ):
"""Create the splash window."""
# create the splash window
main_window.geometry( "275x64" )
main_window.title( "vasl-templates loader" )
main_window.overrideredirect( 1 ) # nb: "-type splash" doesn't work on Windows :-/
main_window.eval( "tk::PlaceWindow . center" )
main_window.wm_attributes( "-topmost", 1 )
main_window.tk.call( "wm", "iconphoto", main_window._w, app_icon ) #pylint: disable=protected-access
main_window.protocol( "WM_DELETE_WINDOW", lambda: None )
# add the app icon
label = tkinter.Label( main_window, image=app_icon )
label.grid( row=0, column=0, rowspan=2, padx=8, pady=8 )
# add the caption
label = tkinter.Label( main_window, text="Loading vasl-templates...", font=("Helvetica",12) )
label.grid( row=0, column=1, padx=5, pady=(8,0) )
# add the "loading" image (we have to animate it ourself :-/)
anim_label = tkinter.Label( main_window )
anim_label.grid( row=1, column=1, sticky=tkinter.N, padx=0, pady=0 )
fname = make_asset_path( "loading.gif" )
nframes = 13
frames = [
tkinter.PhotoImage( file=fname, format="gif -index {}".format( i ) )
for i in range(nframes)
]
frame_index = 0
def next_frame():
nonlocal frame_index
frame = frames[ frame_index ]
frame_index = ( frame_index + 1 ) % nframes
anim_label.configure( image=frame )
main_window.after( 75, next_frame )
main_window.after( 0, next_frame )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def check_startup( proc, port ):
"""Check the startup of the main vasl-templates process."""
def do_check():
# check if we've waited for too long
if time.time() - start_time > STARTUP_TIMEOUT:
# yup - give up
raise RuntimeError( "Couldn't start vasl-templates." )
# check if the main vasl-templates process has gone away
if proc.poll() is not None:
raise RuntimeError( "The vasl-templates program ended unexpectedly." )
# check if the webapp is responding
url = "http://localhost:{}/ping".format( port )
try:
with urllib.request.urlopen( url ) as resp:
_ = resp.read()
except URLError:
# no response - the webapp is probably still starting up
return False
except Exception as ex: #pylint: disable=broad-except
raise RuntimeError( "Couldn't communicate with vasl-templates:\n\n{}".format( ex ) ) from ex
# the main vasl-templates program has started up and is responsive - our job is done!
if sys.platform == "win32":
# FUDGE! There is a short amount of time between the webapp server starting and
# the main window appearing. We delay here for a bit, to try to synchronize
# our window fading out with the main vasl-templates window appearing.
time.sleep( 1 )
return True
def on_done( msg ):
if msg:
show_error_msg( msg, withdraw=True )
fade_out( main_window, main_window.quit )
# run the main loop
start_time = time.time()
while True:
try:
if do_check():
on_done( None )
break
except Exception as ex: #pylint: disable=broad-except
on_done( str(ex) )
return
time.sleep( 0.25 )
# ---------------------------------------------------------------------
def fade_out( target, on_done ):
"""Fade out the target window."""
alpha = target.attributes( "-alpha" )
if alpha > 0:
alpha -= 0.1
target.attributes( "-alpha", alpha )
target.after( 50, lambda: fade_out( target, on_done ) )
else:
on_done()
def make_asset_path( fname ):
"""Generate the path to an asset file."""
return os.path.join( BASE_DIR, "assets", fname )
def show_error_msg( error_msg, withdraw=False ):
"""Show an error dialog."""
if withdraw:
main_window.withdraw()
tkinter.messagebox.showinfo( "vasl-templates loader error", error_msg )
# ---------------------------------------------------------------------
if __name__ == "__main__":
sys.exit( main( sys.argv[1:] ) )