Skip to content

Commit

Permalink
Launcher robustness
Browse files Browse the repository at this point in the history
* Detect if engine is not found and report the problem (fixes #18)
* Detect first run on Windows and pop up a warning about the firewall dialog
* PEP8-style spaces instead of tabs
  • Loading branch information
matatk committed Oct 12, 2014
1 parent 687ca97 commit 2008988
Showing 1 changed file with 196 additions and 165 deletions.
361 changes: 196 additions & 165 deletions audioquake/AudioQuake.py
Original file line number Diff line number Diff line change
@@ -1,181 +1,212 @@
import sys, os # chdir hack
import platform
import sys # chdir
import os # chdir, stamp file
import platform # conditional stuff
import subprocess # launching the game
import threading # engine TTS wrapper
import threading # engine TTS wrapper

from GUI import Window, Button, application
from GUI import Window, Button, Alerts, application

import pyttsx


class EngineWrapper(threading.Thread):
def __init__(self, command_line, speaker):
threading.Thread.__init__(self)
self._command_line = command_line
self._speaker = speaker

def run(self):
# The docs imply this shouldn't be necessary but it seems to be...
if platform.system() is 'Windows':
self._command_line = ' '.join(self._command_line)
# Buffering may be necessary for Windows; seems not to affect Mac
proc = subprocess.Popen(
self._command_line, bufsize=1, stdout=subprocess.PIPE)

while True:
retcode = proc.poll()
line = proc.stdout.readline().rstrip()

length = len(line)
if length > 0:
# Some messages are high priority, others are critical.
# These need to be spoken instead of anything else queued up.
if length is 1 or line[0] is '!':
print 'STOPPING'
self._speaker.stop()

print 'SAYING', line
self._speaker.say(line)
else:
# Blank line occurs after all the initialisation spewage
print 'BLANK'
self._speaker.stop()

print 'TICK'
self._speaker.iterate()

if retcode is not None:
break

print 'Game thread done.'
def __init__(self, command_line, speaker):
threading.Thread.__init__(self)
self._command_line = command_line
self._speaker = speaker

def run(self):
try:
# The docs imply this shouldn't be necessary but it seems to be...
if platform.system() is 'Windows':
self._command_line = ' '.join(self._command_line)

# Buffering may be necessary for Windows; seems not to affect Mac
print 'Running:', self._command_line
proc = subprocess.Popen(
self._command_line, bufsize=1, stdout=subprocess.PIPE)

while True:
retcode = proc.poll()
line = proc.stdout.readline().rstrip()

length = len(line)
if length > 0:
# Some messages are high priority, others are critical.
# These need to be spoken instead of anything else queued up.
if length is 1 or line[0] is '!':
print 'STOPPING'
self._speaker.stop()

print 'SAYING', line
self._speaker.say(line)
else:
# Blank line occurs after all the initialisation spewage
print 'BLANK'
self._speaker.stop()

print 'TICK'
self._speaker.iterate()

if retcode is not None:
break
except:
pass

print 'Game thread done.'


class GameController(object):
_engine = None
_opts_default = ("-window", "+set sensitivity 0")
_opts_tutorial = ("+coop 0", "+deathmatch 0", "+map agtut01")

def __init__(self):
if platform.system() == 'Windows':
self._engine = 'zquake-gl.exe'
else: # assume Mac for now (may also work on Linux)
self._engine = './zquake-glsdl'

self._engine_wrapper = None
self._tts = pyttsx.init()
self._tts.startLoop(False)

def _running(self):
if not self._engine_wrapper or not self._engine_wrapper.is_alive():
print 'Game running? No'
return False
elif self._engine_wrapper.is_alive():
print 'Game running? Yes'
return True

def _launch_core(self, command_line):
if self._running(): return
# Saying something is necessary on 'doze; probably inits COM
self._tts.say(' ')
self._tts.iterate()
self._engine_wrapper = EngineWrapper(command_line, self._tts)
self._engine_wrapper.start()

def launch_default(self):
self._launch_core((self._engine,) + self._opts_default)

def launch_tutorial(self):
# FIXME the tutorial options are ignored on Windows
self._launch_core((self._engine,) + self._opts_default + self._opts_tutorial)

def quit(self, application):
if self._running(): return
print 'Closing TTS...'
self._tts.endLoop()
print 'Quitting...'
application.quit_cmd()
print 'Quat!'
_engine = None
_opts_default = ("-window", "+set sensitivity 0")
_opts_tutorial = ("+coop 0", "+deathmatch 0", "+map agtut01")

def __init__(self):
if platform.system() == 'Windows':
self._engine = 'zquake-gl.exe'
else: # assume Mac for now (may also work on Linux)
self._engine = './zquake-glsdl'

self._engine_wrapper = None
self._tts = pyttsx.init()
self._tts.startLoop(False)

def _running(self):
if not self._engine_wrapper or not self._engine_wrapper.is_alive():
print 'Game running? No'
return False
elif self._engine_wrapper.is_alive():
print 'Game running? Yes'
return True

def _launch_core(self, command_line):
if self._running():
return
else:
if os.path.exists(self._engine):
# Saying something is necessary on 'doze; probably inits COM
self._tts.say(' ')
self._tts.iterate()
self._engine_wrapper = EngineWrapper(command_line, self._tts)
self._engine_wrapper.start()
return True
else:
return False

def launch_default(self):
return self._launch_core((self._engine,) + self._opts_default)

def launch_tutorial(self):
# FIXME the tutorial options are ignored on Windows
return self._launch_core((self._engine,) + self._opts_default + self._opts_tutorial)

def quit(self, application):
if self._running():
return
else:
print 'Closing TTS...'
self._tts.endLoop()
print 'Quitting...'
application.quit_cmd()
print 'Quat!'


class LauncherSingletonWindow(Window):
_text_viewer_command = None

def __init__(self, application, *args, **kwargs):
if platform.system() == 'Windows':
self._text_viewer_command = ('cmd', '/c', 'start', 'wordpad')
else: # assume Mac for now (won't work on Linux)
self._text_viewer_command = ('open', '-a', 'TextEdit')

super(LauncherSingletonWindow, self).__init__(
title = "Launcher",
resizable = False,
zoomable = False,
*args,
**kwargs)

self._application = application
self._game_controller = GameController()

self.auto_position = False
self.position = (200, 250)
self.size = (140, 170)
self.resizable = 0
self.zoomable = 0

self.add(Button(
position = (10, 10),
size = (120, 25),
title = "Play Quake",
action = self._btn_default
))

self.add(Button(
position = (10, 40),
size = (120, 25),
title = "Play Tutorial",
action = self._btn_tutorial
))

self.add(Button(
position = (10, 70),
size = (120, 25),
title = "README",
action = self._btn_readme
))

self.add(Button(
position = (10, 100),
size = (120, 25),
title = "Licence",
action = self._btn_licence
))

self.add(Button(
position = (10, 140),
size = (120, 25),
title = "Quit Launcher",
action = self.close_cmd
))

def close_cmd(self):
self._game_controller.quit(self._application)

def _btn_default(self):
self._game_controller.launch_default()

def _btn_tutorial(self):
self._game_controller.launch_tutorial()

def _btn_readme(self):
subprocess.call(self._text_viewer_command + ('README.md',))

def _btn_licence(self):
subprocess.call(self._text_viewer_command + ('LICENCE.md',))
_text_viewer_command = None

def __init__(self, application, *args, **kwargs):
if platform.system() == 'Windows':
self._text_viewer_command = ('cmd', '/c', 'start', 'wordpad')
else: # assume Mac for now (won't work on Linux)
self._text_viewer_command = ('open', '-a', 'TextEdit')

super(LauncherSingletonWindow, self).__init__(
title = "Launcher",
resizable = False,
zoomable = False,
*args,
**kwargs)

self._application = application
self._game_controller = GameController()

self.auto_position = False
self.position = (200, 250)
self.size = (140, 170)
self.resizable = 0
self.zoomable = 0

self.add(Button(
position = (10, 10),
size = (120, 25),
title = "Play Quake",
action = self._btn_default
))

self.add(Button(
position = (10, 40),
size = (120, 25),
title = "Play Tutorial",
action = self._btn_tutorial
))

self.add(Button(
position = (10, 70),
size = (120, 25),
title = "README",
action = self._btn_readme
))

self.add(Button(
position = (10, 100),
size = (120, 25),
title = "Licence",
action = self._btn_licence
))

self.add(Button(
position = (10, 140),
size = (120, 25),
title = "Quit Launcher",
action = self.close_cmd
))

def close_cmd(self):
self._game_controller.quit(self._application)

def _launch_button_core(self, method):
self._first_time_check()
result = method()
if not result:
self._launch_problem()

def _btn_default(self):
self._launch_button_core(self._game_controller.launch_default)

def _btn_tutorial(self):
self._launch_button_core(self._game_controller.launch_tutorial)

def _btn_readme(self):
subprocess.call(self._text_viewer_command + ('README.md',))

def _btn_licence(self):
subprocess.call(self._text_viewer_command + ('LICENCE.md',))

def _first_time_check(self):
if platform.system() == 'Windows':
stamp_file_name = 'not-first-run'
prompt = 'When the Quake engine first runs, Windows may ask you to allow it through the firewall; this will be done in a secure window that pops up above the Quake engine, which you will need an Assistive Technology (such as Narrator) to access.'
if not os.path.exists(stamp_file_name):
Alerts.alert('caution', prompt)
open(stamp_file_name, 'a').close()

def _launch_problem(self):
Alerts.alert('stop', 'There was a problem launching the game; it is likely the Quake engine could not be found.')


if __name__ == '__main__':
os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
app = application()
launcher = LauncherSingletonWindow(app)
launcher.show()
app.run()
os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
app = application()
launcher = LauncherSingletonWindow(app)
launcher.show()
app.run()

0 comments on commit 2008988

Please sign in to comment.