initial import
--HG-- branch : toybocks
This commit is contained in:
commit
053f435a0b
10 changed files with 946 additions and 0 deletions
238
backend.py
Executable file
238
backend.py
Executable file
|
@ -0,0 +1,238 @@
|
|||
import dblayer
|
||||
from dblayer import db, cur
|
||||
import subprocess, shlex
|
||||
import os
|
||||
from zipfile import ZipFile
|
||||
from threading import Thread
|
||||
from ringbuffer import SplitRingBuffer
|
||||
import time
|
||||
|
||||
class EmulatorHandlerThread(Thread):
|
||||
def __init__(self, game, proc):
|
||||
Thread.__init__(self)
|
||||
self.buffer = SplitRingBuffer(1024*1024, 512*1024)
|
||||
self.game = game
|
||||
self.proc = proc
|
||||
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
data = self.proc.stdout.read(4096)
|
||||
self.proc.poll()
|
||||
if not data and self.proc.returncode != None:
|
||||
break
|
||||
self.buffer.write(data)
|
||||
time.sleep(0.4)
|
||||
|
||||
|
||||
emulator_table = {}
|
||||
gamesystem_table = {}
|
||||
playlist_list = []
|
||||
playlist_table = {}
|
||||
|
||||
class GamesystemInfo(object):
|
||||
def __init__(self, id, name, fileext):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.fileexts = fileext.split(';')
|
||||
self.emu_id = None
|
||||
|
||||
class EmulatorInfo(object):
|
||||
def __init__(self, id, name, path, options):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.path = path
|
||||
self.options = options
|
||||
self.system_id = None
|
||||
|
||||
def start_game(self, game):
|
||||
args = shlex.split(self.options)
|
||||
emu = subprocess.Popen([self.path] + args + [game.path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
return emu
|
||||
|
||||
class GameInfo(object):
|
||||
def __init__(self, id, name, path, emu, syst):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.path = path
|
||||
self.emu_id = emu
|
||||
self.system_id = syst
|
||||
|
||||
class AllGames_Playlist(object):
|
||||
def __init__(self):
|
||||
self.id = None
|
||||
self.name = '(All Games)'
|
||||
|
||||
def lookup(self):
|
||||
pass
|
||||
|
||||
def gamelist(self):
|
||||
cur.execute("select game.id, game.name, game.path, game.emu_id, game.gamesystem_id from game order by game.name")
|
||||
|
||||
gl = []
|
||||
|
||||
for r in cur:
|
||||
i, n, p, e, s = r
|
||||
gl.append(GameInfo(i, n, p, e, s))
|
||||
|
||||
return gl
|
||||
|
||||
class Playlist(object):
|
||||
def __init__(self, id=None, name=None):
|
||||
if id == None and name == None:
|
||||
raise ValueError, "Either name or id must be specified"
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.gamelist_cache = None
|
||||
|
||||
def lookup(self):
|
||||
if self.name != None:
|
||||
cur.execute("select id from playlist where name = ?", [self.name])
|
||||
else:
|
||||
cur.execute("select name from playlist where id = ?", [self.id])
|
||||
|
||||
rs = cur.fetchall()
|
||||
if len(rs) > 1:
|
||||
raise ValueError, "Non unique name for playlist"
|
||||
else:
|
||||
raise ValueError, "Playlist not found"
|
||||
|
||||
if self.name == None:
|
||||
self.name = rs[0][0]
|
||||
else:
|
||||
self.id = rs[0][0]
|
||||
|
||||
def gamelist(self):
|
||||
if self.gamelist_cache != None:
|
||||
return self.gamelist_cache
|
||||
|
||||
cur.execute("select game.id, game.name, game.path, game.emu_id, game.gamesystem_id from game, playlist_game where playlist_game.game_id = game.id and playlist_game.playlist_id = ? order by game.name", [self.id])
|
||||
|
||||
gl = []
|
||||
|
||||
for r in cur:
|
||||
i, n, p, e, s = r
|
||||
gl.append(GameInfo(i, n, p, e, s))
|
||||
|
||||
# enabling this could cause huge memory usage, but make things faster
|
||||
#self.gamelist_cache = gl
|
||||
return gl
|
||||
|
||||
def load_playlists():
|
||||
global playlist_list
|
||||
dblayer.connect()
|
||||
cur.execute("select id, name from playlist order by name")
|
||||
pl = [AllGames_Playlist()]
|
||||
playlist_table[-1] = pl[0]
|
||||
for r in cur:
|
||||
id, name = r
|
||||
p = Playlist(id, name)
|
||||
pl.append(p)
|
||||
playlist_table[id] = p
|
||||
|
||||
playlist_list = pl
|
||||
|
||||
def load_emulators():
|
||||
dblayer.connect()
|
||||
cur.execute("select id, name, path, options from emulator")
|
||||
for r in cur:
|
||||
id, name, path, options = r
|
||||
ei = EmulatorInfo(id, name, path, options)
|
||||
emulator_table[id] = ei
|
||||
|
||||
cur.execute("select id, name, fileext from gamesystem")
|
||||
for r in cur:
|
||||
id, name, fileext = r
|
||||
gsi = GamesystemInfo(id, name, fileext)
|
||||
gamesystem_table[id] = gsi
|
||||
|
||||
cur.execute("select gamesystem_id, emulator_id from gamesystem_emulator")
|
||||
for r in cur:
|
||||
gsid, eid = r
|
||||
gamesystem_table[gsid].emu_id = eid
|
||||
emulator_table[eid].system_id = gsid
|
||||
|
||||
def find_emu(emu):
|
||||
if not os.environ.has_key('PATH'):
|
||||
paths = ['.']
|
||||
else:
|
||||
paths = os.environ['PATH'].split(os.pathsep) + ['.']
|
||||
|
||||
for path in paths:
|
||||
if os.path.exists(os.path.join(path, emu)):
|
||||
return os.path.abspath(os.path.join(path, emu))
|
||||
return None
|
||||
|
||||
def initialize_emulator_row(emulator, gamesystem):
|
||||
cur.execute("insert into emulator (name, path, options) values (?, ?, ?)", [emulator.name, emulator.path, emulator.options])
|
||||
emulator.id = cur.lastrowid
|
||||
cur.execute("insert into gamesystem (name, fileext) values (?, ?)", [gamesystem.name, ';'.join(gamesystem.fileexts)])
|
||||
gamesystem.id = cur.lastrowid
|
||||
cur.execute("insert into gamesystem_emulator (gamesystem_id, emulator_id) values (?, ?)", [gamesystem.id, emulator.id])
|
||||
|
||||
def initialize_emulator_table():
|
||||
emulist = [(('SNES','.SMC'),'snes9x','snes9x-sdl','snes9x-x11','snes9x-gtk','zsnes'),(('NES','.NES'),'fceu'),(('Genesis','.BIN'),'dgen'),(('MAME','**MAME**'),'xmame')]
|
||||
for emutype in emulist:
|
||||
system, fileext = emutype[0]
|
||||
for emu in emutype[1:]:
|
||||
emupath = find_emu(emu)
|
||||
if emupath:
|
||||
initialize_emulator_row(EmulatorInfo(None, emu, emupath, ''), GamesystemInfo(None, system, fileext))
|
||||
|
||||
def scan_zip(path):
|
||||
zf = ZipFile(path, "r")
|
||||
zfl = zf.namelist()
|
||||
zf.close()
|
||||
if len(zfl) > 1:
|
||||
return "**MAME**"
|
||||
elif zfl:
|
||||
return os.path.splitext(zfl[0])[1].upper()
|
||||
else:
|
||||
return None
|
||||
|
||||
def autoload_data(path, playlist, system=None, emulator=None):
|
||||
fileext_map = {}
|
||||
if system == None:
|
||||
for gsi in gamesystem_table.values():
|
||||
for fe in gsi.fileexts:
|
||||
fileext_map[fe.upper()] = gsi
|
||||
print fileext_map
|
||||
for base, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
fn, fe = os.path.splitext(file)
|
||||
feu = fe.upper()
|
||||
|
||||
if feu == '.ZIP':
|
||||
feu = scan_zip(os.path.join(path, base, file))
|
||||
if not feu:
|
||||
continue
|
||||
autosystem = None
|
||||
if feu in fileext_map:
|
||||
autosystem = fileext_map[feu]
|
||||
if system != None:
|
||||
autosystem = system
|
||||
if autosystem != None:
|
||||
"We have a valid game, we know what system it's for, and we will use that system's default emulator"
|
||||
emuid = None
|
||||
if emulator != None:
|
||||
emuid = emulator.id
|
||||
sysid = None
|
||||
if autosystem != None:
|
||||
sysid = autosystem.id
|
||||
cur.execute("insert into game (name, path, emu_id, gamesystem_id) values (?, ?, ?, ?)", [fn, os.path.join(path, base, file), emuid, sysid])
|
||||
gameid = cur.lastrowid
|
||||
cur.execute("insert into playlist_game (game_id, playlist_id) values (?, ?)", [gameid, playlist.id])
|
||||
|
||||
|
||||
def get_game(idnum):
|
||||
cur.execute("select game.id, game.name, game.path, game.emu_id, game.gamesystem_id from game where game.id = ? order by game.name", [idnum])
|
||||
|
||||
rs = cur.fetchall()
|
||||
if len(rs) > 1:
|
||||
raise ValueError, "Non unique id for game, multiple games matched"
|
||||
elif len(rs) == 0:
|
||||
raise ValueError, "Game not found"
|
||||
i, n, p, e, s = rs[0]
|
||||
gi = GameInfo(i, n, p, e, s)
|
||||
|
||||
return gi
|
25
bootstrap_autoload.py
Executable file
25
bootstrap_autoload.py
Executable file
|
@ -0,0 +1,25 @@
|
|||
import backend
|
||||
import dblayer
|
||||
import os
|
||||
|
||||
if os.path.exists('games.db'):
|
||||
os.remove('games.db')
|
||||
dblayer.connect()
|
||||
dblayer.create()
|
||||
backend.initialize_emulator_table()
|
||||
backend.load_emulators()
|
||||
|
||||
|
||||
base = '/mnt/gamez/Games/Emulators/ROMs/'
|
||||
|
||||
for d in ('SNES', 'NES', 'Genesis'):
|
||||
system = None
|
||||
for gs in backend.gamesystem_table.values():
|
||||
if gs.name == d:
|
||||
system = gs
|
||||
backend.cur.execute("insert into playlist (name) values (?)", [d])
|
||||
path = os.path.join(base, d + ' ROMs')
|
||||
assert os.path.exists(path)
|
||||
|
||||
backend.autoload_data(path, backend.Playlist(backend.cur.lastrowid, d))
|
||||
backend.db.commit()
|
5
config.py
Executable file
5
config.py
Executable file
|
@ -0,0 +1,5 @@
|
|||
from utils import program_path
|
||||
from inifile import inifile
|
||||
|
||||
config = inifile('settings.ini', path=[program_path()])
|
||||
config.read()
|
38
dblayer.py
Executable file
38
dblayer.py
Executable file
|
@ -0,0 +1,38 @@
|
|||
import sqlite3
|
||||
|
||||
class ObjectProxy(object):
|
||||
def __init__(self):
|
||||
self._realobj = None
|
||||
def __setattr__(self, name, value):
|
||||
if name in ("_realobj",) or hasattr(self, name):
|
||||
object.__setattr__(self, name, value)
|
||||
else:
|
||||
self._realobj.__setattr__(name, value)
|
||||
def __getattribute__(self, name):
|
||||
try:
|
||||
return object.__getattribute__(self, name)
|
||||
except:
|
||||
return self._realobj.__getattribute__(name)
|
||||
def __repr__(self):
|
||||
return """<Proxy for "%s">""" % (repr(self._realobj),)
|
||||
|
||||
def __iter__(self):
|
||||
return self._realobj.__iter__()
|
||||
|
||||
db = ObjectProxy()
|
||||
cur = ObjectProxy()
|
||||
|
||||
def connect():
|
||||
if db._realobj != None:
|
||||
return
|
||||
db._realobj = sqlite3.connect("games.db")
|
||||
cur._realobj = db.cursor()
|
||||
|
||||
def create():
|
||||
cur.execute("""create table playlist (id integer primary key autoincrement, name text)""")
|
||||
cur.execute("""create table emulator (id integer primary key autoincrement, name text, path text, options text)""")
|
||||
cur.execute("""create table gamesystem (id integer primary key autoincrement, name text, fileext text)""")
|
||||
cur.execute("""create table gamesystem_emulator (id integer primary key autoincrement, emulator_id integer, gamesystem_id integer, foreign key (emulator_id) references emulator(id), foreign key (gamesystem_id) references gamesystem(id))""")
|
||||
cur.execute("""create table game (id integer primary key autoincrement, name text, path text, emu_id integer, gamesystem_id integer, foreign key (emu_id) references emulator(id), foreign key (gamesystem_id) references gamesystem(id))""")
|
||||
cur.execute("""create table playlist_game (id integer primary key autoincrement, game_id integer, playlist_id integer, foreign key (game_id) references game(id), foreign key (playlist_id) references playlist(id))""")
|
||||
|
267
inifile.py
Executable file
267
inifile.py
Executable file
|
@ -0,0 +1,267 @@
|
|||
# ini reader/writer
|
||||
__doc__ = """
|
||||
reads and writes config files using the microsoft windows 'ini' format.
|
||||
(author's note: I don't think there IS any official 'ini' format. Some
|
||||
artistic licence may have been used)
|
||||
|
||||
Provides class 'inifile', which is a dict-like object that can be read
|
||||
from a file or written out to a file (or both!)
|
||||
"""
|
||||
|
||||
__license__ = """
|
||||
Copyright (c) 2006-2007 Bradley Lawrence <bradley@eltanin.net>
|
||||
|
||||
This is licensed under the Modified BSD License, as follows:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
|
||||
import os.path
|
||||
from utils import program_path
|
||||
|
||||
def sanitize_for_subst(key):
|
||||
"""
|
||||
replaces open and close brackets with tokens, to ensure the brackets
|
||||
do not interfere with the %(dict_key)s string formatting syntax.
|
||||
|
||||
'=' is used as a token delimiter because we know an equals can never
|
||||
appear in a keyname, since it is interpreted by this reader as the
|
||||
end of the key and the start of the value.
|
||||
"""
|
||||
return key.replace('(', '=o=').replace(')', '=c=')
|
||||
|
||||
|
||||
|
||||
|
||||
class inisubsec(object):
|
||||
def __init__(self, name, owner):
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
self.data = {}
|
||||
self.verbatim_hdr = "[%(sec_title)s]\n"
|
||||
self.verbatim = ["%(=new_data=)s"]
|
||||
self.verbatim_ftr = "\n"
|
||||
self.verbatim_fields = []
|
||||
self.new_fields = []
|
||||
self.initializing = True
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.data[key.lower()]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if not self.data.has_key(key.lower()):
|
||||
if self.initializing:
|
||||
self.verbatim_fields += [key]
|
||||
else:
|
||||
self.new_fields += [key]
|
||||
self.data[key.lower()] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.data[key.lower()]
|
||||
|
||||
def __iter__(self):
|
||||
return self.data.__iter__()
|
||||
def items(self):
|
||||
return self.data.items()
|
||||
|
||||
|
||||
|
||||
class inifile(object):
|
||||
def __init__(self, fname, path=None):
|
||||
self.data = {}
|
||||
sep = os.path.sep
|
||||
|
||||
if not path:
|
||||
fs = os.path.split(fname)
|
||||
self.fname = os.path.split(fname)[1]
|
||||
if not fs[0]:
|
||||
self.path = (program_path() + sep, '')
|
||||
else:
|
||||
self.path = (fs[0] + sep,)
|
||||
else:
|
||||
if type(path) == str:
|
||||
self.path = (path,)
|
||||
else:
|
||||
self.path = []
|
||||
for p in path:
|
||||
if p and (p[-1] != sep):
|
||||
p += sep
|
||||
self.path += [p]
|
||||
self.fname = fname
|
||||
|
||||
self.last_opened_file = None
|
||||
self.cur_section = None
|
||||
self.activepath = None
|
||||
self.verbatim_hdr = []
|
||||
self.section_order = []
|
||||
|
||||
def set_section(self, section):
|
||||
self.cur_section = section.lower()
|
||||
|
||||
def unset_section(self):
|
||||
self.cur_section = None
|
||||
|
||||
def get_current_file(self):
|
||||
return
|
||||
def read(self):
|
||||
fd = None
|
||||
for p in self.path:
|
||||
if os.path.exists(p + self.fname):
|
||||
self.activepath = p
|
||||
fd = file(p + self.fname, 'r')
|
||||
break
|
||||
if not fd:
|
||||
return False
|
||||
|
||||
act_section = 'default'
|
||||
section_init = False
|
||||
for line in fd:
|
||||
verbatimline = line
|
||||
keyvalue_line = False
|
||||
line = line.lstrip()
|
||||
if not line:
|
||||
# blank line, ignore
|
||||
pass
|
||||
elif line[0] == ';':
|
||||
# comment! ignore
|
||||
pass
|
||||
elif line[0] == '[':
|
||||
# section header
|
||||
line = line.rstrip()
|
||||
if line[-1] == ']':
|
||||
act_section = line[1:-1]
|
||||
section_init = True
|
||||
if not self.data.has_key(act_section.lower()):
|
||||
"add specified section"
|
||||
actsec = inisubsec(act_section, self)
|
||||
self.data[act_section.lower()] = actsec
|
||||
actsec.verbatim_hdr = verbatimline
|
||||
actsec.verbatim_ftr = ""
|
||||
verbatimline = None
|
||||
self.section_order += [act_section]
|
||||
else:
|
||||
# garbage line, don't understand it
|
||||
pass
|
||||
else:
|
||||
peq = line.find('=')
|
||||
if peq == -1:
|
||||
# garbage line, don't understand it
|
||||
pass
|
||||
else:
|
||||
k = line[:peq]
|
||||
v = line[peq+1:].rstrip('\n\r')
|
||||
if not self.data.has_key(act_section.lower()):
|
||||
"add default section"
|
||||
assert not section_init
|
||||
|
||||
actsec = inisubsec('default', self)
|
||||
self.data[act_section.lower()] = actsec
|
||||
|
||||
|
||||
self.data[act_section.lower()][k] = v
|
||||
verbatimline = "".join([k, '=%(', sanitize_for_subst(k.lower()), ')s\n'])
|
||||
keyvalue_line = True
|
||||
|
||||
if verbatimline:
|
||||
if section_init or keyvalue_line:
|
||||
"keyvalue lines always go under the default section, regardless of initialized state"
|
||||
actsec = self.data[act_section.lower()]
|
||||
actsec.verbatim += [verbatimline]
|
||||
else:
|
||||
self.verbatim_hdr += [verbatimline]
|
||||
|
||||
|
||||
if section_init:
|
||||
actsec = self.data[act_section.lower()]
|
||||
actsec.verbatim += ["\n"]
|
||||
else:
|
||||
self.verbatim_hdr += ["\n"]
|
||||
|
||||
if self.data.has_key('default'):
|
||||
"has a default section. make sure it's added to the section_order"
|
||||
try:
|
||||
self.section_order.index('default')
|
||||
except:
|
||||
self.section_order += ['default']
|
||||
|
||||
for secdict in self.data.values():
|
||||
secdict.initializing = False
|
||||
|
||||
def write(self):
|
||||
p = self.path[0]
|
||||
if self.activepath:
|
||||
p = self.activepath
|
||||
|
||||
fd = file(p + self.fname, 'w')
|
||||
first = True
|
||||
|
||||
fd.write("".join(self.verbatim_hdr))
|
||||
|
||||
for sec in self.section_order:
|
||||
secdict = self.data[sec.lower()]
|
||||
|
||||
new_lines = []
|
||||
for k in secdict.new_fields:
|
||||
new_lines += ["%s=%s\n" % (k, secdict[k])]
|
||||
new_lines = "".join(new_lines)
|
||||
verbatim_dict = {'=new_data=': new_lines}
|
||||
for k in secdict.verbatim_fields:
|
||||
verbatim_dict[sanitize_for_subst(k.lower())] = secdict[k]
|
||||
|
||||
fd.write(secdict.verbatim_hdr % {'sec_title': secdict.name})
|
||||
fd.write("".join(secdict.verbatim) % verbatim_dict)
|
||||
fd.write(secdict.verbatim_ftr)
|
||||
|
||||
def add_section(self, key):
|
||||
if not self.data.has_key(key):
|
||||
self.data[key.lower()] = inisubsec(key, self)
|
||||
self.section_order += [key]
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
if type(key) != str:
|
||||
raise TypeError, "Ini files can only contain string keys"
|
||||
|
||||
if self.cur_section != None:
|
||||
if self.data[self.cur_section].has_key(key.lower()):
|
||||
return self.data[self.cur_section][key.lower()]
|
||||
else:
|
||||
raise KeyError, "Section '%s' does not contain a key named '%s'" % (self.cur_section, key)
|
||||
else:
|
||||
#if not self.data.has_key(key.lower()):
|
||||
# self.data[key.lower()] = inisubsec(key, self)
|
||||
return self.data[key.lower()]
|
||||
def __setitem__(self, key, value):
|
||||
if self.cur_section != None:
|
||||
if not self.data.has_key(self.cur_section):
|
||||
raise KeyError, "Section '%s' does not exist" % (self.cur_section)
|
||||
self.data[self.cur_section][key.lower()] = value
|
||||
else:
|
||||
raise ValueError, "Cannot set the value of a section header"
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.data[key.lower()]
|
||||
|
||||
def __iter__(self):
|
||||
return self.data.__iter__()
|
||||
|
||||
def items(self):
|
||||
return self.data.items()
|
||||
|
88
mainwnd.py
Executable file
88
mainwnd.py
Executable file
|
@ -0,0 +1,88 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: iso-8859-15 -*-
|
||||
# generated by wxGlade 0.6.3 on Sun Feb 21 18:24:20 2010
|
||||
|
||||
import wx
|
||||
|
||||
# begin wxGlade: extracode
|
||||
# end wxGlade
|
||||
|
||||
|
||||
|
||||
class MainWindow(wx.Frame):
|
||||
def __init__(self, *args, **kwds):
|
||||
# begin wxGlade: MainWindow.__init__
|
||||
kwds["style"] = wx.DEFAULT_FRAME_STYLE
|
||||
wx.Frame.__init__(self, *args, **kwds)
|
||||
self.splitter = wx.SplitterWindow(self, -1, style=wx.SP_3D|wx.SP_BORDER)
|
||||
self.playlists = customListCtrl(self.splitter, -1, style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.SUNKEN_BORDER)
|
||||
self.games = customListCtrl(self.splitter, -1, style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VIRTUAL|wx.LC_NO_HEADER|wx.SUNKEN_BORDER)
|
||||
|
||||
self.__set_properties()
|
||||
self.__do_layout()
|
||||
# end wxGlade
|
||||
|
||||
def __set_properties(self):
|
||||
# begin wxGlade: MainWindow.__set_properties
|
||||
self.SetTitle("frame_2")
|
||||
self.SetSize((723, 569))
|
||||
self.SetMenuBar(MainMenu())
|
||||
self.playlists.SetBackgroundColour(wx.Colour(20, 20, 20))
|
||||
self.playlists.SetForegroundColour(wx.Colour(192, 192, 192))
|
||||
self.playlists.SetFont(wx.Font(16, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
|
||||
self.playlists.SetFocus()
|
||||
self.games.SetBackgroundColour(wx.Colour(20, 20, 20))
|
||||
self.games.SetForegroundColour(wx.Colour(192, 192, 192))
|
||||
self.games.SetFont(wx.Font(16, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
|
||||
# end wxGlade
|
||||
|
||||
def __do_layout(self):
|
||||
# begin wxGlade: MainWindow.__do_layout
|
||||
sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.splitter.SplitVertically(self.playlists, self.games, 250)
|
||||
sizer.Add(self.splitter, 1, wx.EXPAND, 0)
|
||||
self.SetSizer(sizer)
|
||||
self.Layout()
|
||||
# end wxGlade
|
||||
|
||||
# end of class MainWindow
|
||||
|
||||
|
||||
class MainMenu(wx.MenuBar):
|
||||
def __init__(self, *args, **kwds):
|
||||
# begin wxGlade: MainMenu.__init__
|
||||
wx.MenuBar.__init__(self, *args, **kwds)
|
||||
wxglade_tmp_menu = wx.Menu()
|
||||
wxglade_tmp_menu.Append(wx.NewId(), "E&xit", "", wx.ITEM_NORMAL)
|
||||
self.Append(wxglade_tmp_menu, "&File")
|
||||
|
||||
self.__set_properties()
|
||||
self.__do_layout()
|
||||
# end wxGlade
|
||||
|
||||
def __set_properties(self):
|
||||
# begin wxGlade: MainMenu.__set_properties
|
||||
pass
|
||||
# end wxGlade
|
||||
|
||||
def __do_layout(self):
|
||||
# begin wxGlade: MainMenu.__do_layout
|
||||
pass
|
||||
# end wxGlade
|
||||
|
||||
# end of class MainMenu
|
||||
|
||||
|
||||
class MyApp(wx.App):
|
||||
def OnInit(self):
|
||||
wx.InitAllImageHandlers()
|
||||
mainwnd = MainWindow(None, -1, "")
|
||||
self.SetTopWindow(mainwnd)
|
||||
mainwnd.Show()
|
||||
return 1
|
||||
|
||||
# end of class MyApp
|
||||
|
||||
if __name__ == "__main__":
|
||||
Toybocks = MyApp(0)
|
||||
Toybocks.MainLoop()
|
137
ringbuffer.py
Executable file
137
ringbuffer.py
Executable file
|
@ -0,0 +1,137 @@
|
|||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
|
||||
|
||||
class RingBuffer(object):
|
||||
def __init__(self, size):
|
||||
self.size = size
|
||||
self.buffer = StringIO()
|
||||
self.buffer_pos = 0
|
||||
self.read_pos = 0
|
||||
self.bytes_written = 0
|
||||
self.first_read = True
|
||||
self.fullclass = RingBufferFull
|
||||
|
||||
def write(self, data):
|
||||
if self.buffer_pos + len(data) >= self.size:
|
||||
self.__class__ = self.fullclass
|
||||
split = self.size - self.buffer_pos
|
||||
self.buffer.write(data[:split])
|
||||
self.buffer.seek(0, 0)
|
||||
self.buffer_pos = 0
|
||||
self.bytes_written += split
|
||||
self.write(data[split:])
|
||||
else:
|
||||
self.buffer.write(data)
|
||||
self.buffer_pos += len(data)
|
||||
self.bytes_written += len(data)
|
||||
|
||||
def read(self, bytes=0):
|
||||
self.buffer.seek(self.read_pos, 0)
|
||||
rb = self.buffer.read(bytes)
|
||||
self.read_pos = self.buffer.tell()
|
||||
self.buffer.seek(self.buffer_pos, 0)
|
||||
return rb
|
||||
|
||||
|
||||
class SplitRingBuffer(RingBuffer):
|
||||
def __init__(self, size, split):
|
||||
RingBuffer.__init__(self, size)
|
||||
self.fullclass = SplitRingBufferFull
|
||||
self.splitpos = split
|
||||
self.read_pos = split
|
||||
|
||||
def read_head():
|
||||
self.buffer.seek(0, 0)
|
||||
rb = self.buffer.read()
|
||||
self.buffer.seek(self.buffer_pos, 0)
|
||||
return (True, rb)
|
||||
|
||||
|
||||
class RingBufferFull(object):
|
||||
def __init__(self, size):
|
||||
raise NotImplementedError, "You should not create this class manually, use RingBuffer() instead"
|
||||
|
||||
def overflow_buffer():
|
||||
self.buffer_pos = 0
|
||||
self.seek_to_start()
|
||||
|
||||
def seek_to_start():
|
||||
self.buffer.seek(0, 0)
|
||||
|
||||
|
||||
def write(self, data):
|
||||
di = 0
|
||||
ld = len(data)
|
||||
while (ld - di) + self.buffer_pos >= self.size:
|
||||
self.buffer.write(data[di:di + (self.size - self.buffer_pos)])
|
||||
if self.read_pos != None and self.read_pos > self.buffer_pos:
|
||||
# our read pos has been overwritten, we've lost our place
|
||||
self.read_pos = None
|
||||
self.overflow_buffer()
|
||||
di += (self.size - self.buffer_pos)
|
||||
self.buffer.write(data[di:])
|
||||
if self.read_pos != None and self.buffer_pos <= self.read_pos and (self.buffer_pos + ld - di) > self.read_pos:
|
||||
self.read_pos = None
|
||||
self.buffer_pos += ld - di
|
||||
self.bytes_written += ld
|
||||
|
||||
def read(self, bytes=0):
|
||||
pos = self.read_pos
|
||||
fullread = False
|
||||
if pos == None:
|
||||
pos = self.buffer_pos
|
||||
fullread = True
|
||||
|
||||
if pos == self.buffer_pos and fullread:
|
||||
maxlen = self.size
|
||||
elif pos == self.buffer_pos and not fullread:
|
||||
maxlen = 0
|
||||
else:
|
||||
maxlen = self.buffer_pos - pos
|
||||
if maxlen < 0:
|
||||
maxlen += self.size
|
||||
self.buffer.seek(pos, 0)
|
||||
if bytes > 0 and maxlen > bytes:
|
||||
maxlen = bytes
|
||||
|
||||
if maxlen == 0:
|
||||
return ''
|
||||
|
||||
split = self.size - pos
|
||||
if split >= maxlen:
|
||||
self.buffer.seek(pos, 0)
|
||||
rb = self.buffer.read(maxlen)
|
||||
self.read_pos = self.buffer.tell()
|
||||
self.buffer.seek(self.buffer_pos, 0)
|
||||
else:
|
||||
self.buffer.seek(pos, 0)
|
||||
rb = self.buffer.read(split)
|
||||
self.seek_to_start()
|
||||
rb += self.buffer.read(maxlen - split)
|
||||
self.read_pos = self.buffer.tell()
|
||||
self.buffer.seek(self.buffer_pos, 0)
|
||||
|
||||
return rb
|
||||
|
||||
|
||||
class SplitRingBufferFull(RingBufferFull):
|
||||
def read(self, bytes=0):
|
||||
pass
|
||||
|
||||
def overflow_buffer():
|
||||
self.buffer_pos = self.split_pos
|
||||
|
||||
def seek_to_start():
|
||||
self.buffer.seek(self.split_pos, 0)
|
||||
|
||||
def read_head():
|
||||
self.buffer.seek(0, 0)
|
||||
rb = self.buffer.read(self.split_pos)
|
||||
self.buffer.seek(self.buffer_pos, 0)
|
||||
return (False, rb)
|
||||
|
||||
|
2
settings.ini
Executable file
2
settings.ini
Executable file
|
@ -0,0 +1,2 @@
|
|||
[Paths]
|
||||
Roms=S:/Games/Emulators/ROMs/SNES:S:/Games/Emulators/ROMs/SNES
|
138
toybocks.py
Executable file
138
toybocks.py
Executable file
|
@ -0,0 +1,138 @@
|
|||
import mainwnd
|
||||
import wx
|
||||
import backend
|
||||
|
||||
class customListCtrl(wx.ListCtrl):
|
||||
def GetItemBackgroundColour(self, item):
|
||||
return None
|
||||
|
||||
def OnGetItemText(self, item, col):
|
||||
return self.gamelist[item].name
|
||||
|
||||
def GetItemData(self, item):
|
||||
if hasattr(self, "gamelist"):
|
||||
return self.gamelist[item].id
|
||||
else:
|
||||
return wx.ListCtrl.GetItemData(self, item)
|
||||
|
||||
mainwnd.customListCtrl = customListCtrl
|
||||
|
||||
class ToybocksMainWindow(mainwnd.MainWindow):
|
||||
def __init__(self, *args, **kwds):
|
||||
mainwnd.MainWindow.__init__(self, *args, **kwds)
|
||||
|
||||
self.emulator_thread = None
|
||||
|
||||
|
||||
self.playlists.InsertColumn(0, "Selection")
|
||||
# self.playlists.InsertStringItem(0, "(All Games)")
|
||||
# self.playlists.InsertStringItem(1, "(None)")
|
||||
self.games.InsertColumn(0, "Game")
|
||||
self.games.SetColumnWidth(0, 1800)
|
||||
# self.games.InsertStringItem(0, "Game One")
|
||||
# self.games.InsertStringItem(1, "Testing 123")
|
||||
# self.games.InsertStringItem(2, "Entry Test")
|
||||
# self.games.InsertStringItem(3, "Listing Items")
|
||||
# self.games.InsertStringItem(4, "Game 789")
|
||||
|
||||
backend.load_emulators()
|
||||
backend.load_playlists()
|
||||
pl = backend.playlist_list
|
||||
c = 0
|
||||
for p in pl:
|
||||
self.playlists.InsertStringItem(c, p.name)
|
||||
if p.id != None:
|
||||
self.playlists.SetItemData(c, int(p.id))
|
||||
else:
|
||||
self.playlists.SetItemData(c, -1)
|
||||
c += 1
|
||||
|
||||
p = pl[0]
|
||||
|
||||
self.games.playlist = p
|
||||
self.games.gamelist = p.gamelist()
|
||||
self.games.SetItemCount(len(self.games.gamelist))
|
||||
self.games.RefreshItems(0, len(self.games.gamelist))
|
||||
self.playlists.SetColumnWidth(0, 200)
|
||||
self.playlists.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||
|
||||
self.playlists.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnChangePlaylist)
|
||||
self.playlists.Bind(wx.EVT_LIST_KEY_DOWN, self.OnKeyUp)
|
||||
#wx.EVT_LIST_KEY_DOWN(self.playlists, self.OnKeyUp)
|
||||
self.playlists.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
|
||||
self.games.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
|
||||
|
||||
TIMER_ID = 150
|
||||
self.timer = wx.Timer(self, TIMER_ID)
|
||||
self.Bind(wx.EVT_TIMER, self.OnTimer)
|
||||
|
||||
def OnChangePlaylist(self, event):
|
||||
name, id, item = self.GetSelItem(self.playlists)
|
||||
p = backend.playlist_table[id]
|
||||
self.games.playlist = p
|
||||
self.games.gamelist = p.gamelist()
|
||||
self.games.SetItemCount(len(self.games.gamelist))
|
||||
self.games.RefreshItems(0, len(self.games.gamelist))
|
||||
#self.games.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||
|
||||
|
||||
def OnTimer(self, event):
|
||||
ep = self.emulator_thread
|
||||
if ep and ep != True:
|
||||
if not ep.is_alive():
|
||||
self.timer.Stop()
|
||||
ep.buffer.write("\nEmulator exited with return code %d\n" % (ep.proc.returncode,))
|
||||
self.emulator_thread = None
|
||||
item = self.games.FindItemData(-1, ep.game.id)
|
||||
self.games.SetItemBackgroundColour(item, self.games.GetBackgroundColour())
|
||||
|
||||
def OnKeyUp(self, event):
|
||||
key = event.GetKeyCode()
|
||||
|
||||
if key == wx.WXK_RETURN:
|
||||
self.OnStartGame(None)
|
||||
elif key == wx.WXK_LEFT:
|
||||
self.playlists.SetFocus()
|
||||
elif key == wx.WXK_RIGHT:
|
||||
self.games.SetFocus()
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
def GetSelItem(self, box):
|
||||
item = box.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED)
|
||||
if item == -1:
|
||||
return None
|
||||
else:
|
||||
return (box.GetItemText(item), box.GetItemData(item), item)
|
||||
|
||||
def OnStartGame(self, event):
|
||||
if self.emulator_thread:
|
||||
return
|
||||
self.emulator_thread = True
|
||||
name, id, item = self.GetSelItem(self.games)
|
||||
print name, id, item
|
||||
game = backend.get_game(id)
|
||||
emuid = game.emu_id
|
||||
if emuid == None:
|
||||
emuid = backend.gamesystem_table[game.system_id].emu_id
|
||||
emu = backend.emulator_table[emuid]
|
||||
proc = emu.start_game(game)
|
||||
self.emulator_thread = backend.EmulatorHandlerThread(game, proc)
|
||||
self.emulator_thread.start()
|
||||
self.timer.Start(1000)
|
||||
self.games.SetItemBackgroundColour(item, wx.Colour(196,0,0))
|
||||
|
||||
class ToybocksApp(wx.App):
|
||||
def OnInit(self):
|
||||
wx.InitAllImageHandlers()
|
||||
mainwnd = ToybocksMainWindow(None, -1, "")
|
||||
self.SetTopWindow(mainwnd)
|
||||
mainwnd.Show()
|
||||
state = wx.LIST_STATE_FOCUSED|wx.LIST_STATE_SELECTED
|
||||
mainwnd.playlists.SetItemState(0, state, state)
|
||||
mainwnd.games.SetItemState(0, state, state)
|
||||
mainwnd.playlists.SetFocus()
|
||||
return 1
|
||||
|
||||
app = ToybocksApp(0)
|
||||
app.MainLoop()
|
8
utils.py
Executable file
8
utils.py
Executable file
|
@ -0,0 +1,8 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
def program_path():
|
||||
p = os.path.split(sys.argv[0])[0]
|
||||
p = os.path.abspath(p)
|
||||
p = os.path.realpath(p)
|
||||
return p
|
Loading…
Add table
Reference in a new issue