#!/usr/bin/env python2.4 import py from lxml import etree command = 'xmame.SDL' switches = '-fullscreen' rompath = '/usr/share/games/xmame/roms' datapath = '/home/johnny/.xmamerunner' browserpath = '/usr/bin/mozilla' http_port = 10001 # if this is filled, only the games that have their name in this list will be # displayed list_override = [] class XMameRunner(object): """A graphical user interface for XMAME combined with a small database for roms and images, and a data collector """ def __init__(self): self.db = RomDB(rompath, datapath) def get_items(self): """return an list of dictionaries with item data""" xml = self.db.get_data() dom = etree.fromstring(xml) ret = [] for node in dom.xpath('//game'): name = node.attrib['name'] if list_override and name not in list_override: continue ret.append({ 'name': name, 'cloneof': node.attrib.get('cloneof', ''), 'description': node[0].text, 'year': node[1].text, 'manufacturer': node[2].text, }) return ret def run(self, name): """run a game name is the name of the game from the dict (key 'name'), which can be fed to xmame as a game name """ py.std.os.system('%s %s %s' % (command, switches, name)) class RomDB(object): def __init__(self, rompath, datapath): self.rompath = py.path.local(rompath) self.datapath = dp = py.path.local(datapath) if not dp.check(): dp.mkdir() elif not dp.check(dir=1): raise IOError, 'datapath is not a directory' def get_data(self): cache = self.datapath / '.mamerunner_cache' if cache.check(): # if cache is still valid, use it, else create new valid = True for p in self.rompath.listdir(): if p.basename.endswith('.zip'): print 'p:', p print 'mtime:', p.mtime() print 'cache mtime:', cache.mtime() if p.mtime() > cache.mtime(): data = self.gather_data() cache.write(data) break else: data = cache.read() else: data = self.gather_data() cache.write(data) return data def gather_data(self): print 'going to gather data, this can take a long time!' print 'first reading XML for all ROMs' outfp, infp, errfp = py.std.popen2.popen3('%s -listxml' % command) try: xml = outfp.read() finally: outfp.close() infp.close() errfp.close() print 'XML read, finding out which files are installed (slow!!)' tree = etree.fromstring(xml) outfp, infp, errfp = py.std.popen2.popen3('%s -verifyromsets' % command) usedgameels = [] try: for line in outfp.readlines(): line = line.strip() while ' ' in line: line = line.replace(' ', ' ') if line and line.split(' ')[1] == 'correct': print 'found:', line.split(' ')[0] usedgameels.append( tree.xpath('//game[@name="%s"]' % line.split(' ')[0])[0]) finally: outfp.close() infp.close() errfp.close() data = [''] for el in usedgameels: data.append(etree.tostring(el)) print 'done gathering data' return '%s\n' % '\t'.join(data) class CGIXMameRunner(object): """show the XMAMERunner menu in a browser boot a simple HTTP server and point a browser to it, a page with all the games is shown and the user can start one by clicking on its entry """ def __init__(self): self.runner = XMameRunner() self.quit = False def run(self): self.lock = py.std.thread.allocate_lock() this = self # make it available to the handler code class XMameHTTPRequestHandler( py.std.BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): """show the listing page, run a game, or quit""" path = self.path query = '' if '?' in path: path, query = path.split('?') if path == '/': self.show_list() elif path == '/run': self.run_game(query) elif path == '/images': self.serve_image(query) elif path == '/quit': self.quit() else: data = 'no such url, please visit the root' self.serve_data('text/html', data) def serve_data(self, content_type, data): self.wfile.write( ('HTTP/1.0 200 OK\r\n' 'Content-Type: %s; charset=UTF-8\r\n' 'Content-Length: %s\r\n' '\r\n') % (content_type, len(data))) self.wfile.write(data) def show_list(self): """show the list of games""" from templess.templess import template t = template(py.magic.autopath().dirpath() / 'templates/list.html') games = this.runner.get_items() games.sort(lambda a, b: cmp(a['name'], b['name'])) for item in games: item['link'] = '/run?%s' % item['name'] imgspath = py.path.local(datapath) / 'images' if (imgspath / ('%s.png' % item['name'])).check(): item['imgsrc'] = '/images?%s' % item['name'] item['showimage'] = True item['showlink'] = False elif (imgspath / ('%s.png' % item['cloneof'])).check(): item['imgsrc'] = '/images?%s' % item['cloneof'] item['showimage'] = True item['showlink'] = False else: item['showimage'] = False item['showlink'] = True html = t.render_to_string({ 'games': games, }) self.serve_data('text/html', html) def run_game(self, query): """run a game""" print 'going to run', query this.runner.run(query) def serve_image(self, query): img = py.path.local(datapath) / ('images/%s.png' % query) self.serve_data('image/png', img.read()) def quit(self): """quit the server and app""" this.quit = 1 from templess.templess import template t = template(py.magic.autopath().dirpath() / 'templates/quit.html') self.serve_data('text/html', t.render_to_string({})) thread = py.std.thread.start_new_thread(self.run_server, (XMameHTTPRequestHandler,)) self.start_browser() # wait for the lock to be set while not self.lock.locked(): py.std.time.sleep(0.1) # lock until the server has quit self.lock.acquire() def run_server(self, handler): server = py.std.BaseHTTPServer.HTTPServer(('', http_port), handler) self.lock.acquire() while 1: server.handle_request() if self.quit: print 'quitting' break self.lock.release() def start_browser(self): url = 'http://localhost:%s/' % http_port py.std.os.system('%s "%s" > /dev/null 2>&1 &' % (browserpath, url)) if __name__ == '__main__': r = CGIXMameRunner() r.run()