commit 053f435a0b29bf866416caec8a1105f5be8e526b Author: cecilkorik Date: Fri Mar 26 03:50:24 2010 +0000 initial import --HG-- branch : toybocks diff --git a/backend.py b/backend.py new file mode 100755 index 0000000..6e4f376 --- /dev/null +++ b/backend.py @@ -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 diff --git a/bootstrap_autoload.py b/bootstrap_autoload.py new file mode 100755 index 0000000..286ce52 --- /dev/null +++ b/bootstrap_autoload.py @@ -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() diff --git a/config.py b/config.py new file mode 100755 index 0000000..1823695 --- /dev/null +++ b/config.py @@ -0,0 +1,5 @@ +from utils import program_path +from inifile import inifile + +config = inifile('settings.ini', path=[program_path()]) +config.read() \ No newline at end of file diff --git a/dblayer.py b/dblayer.py new file mode 100755 index 0000000..c517f0c --- /dev/null +++ b/dblayer.py @@ -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 """""" % (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))""") + diff --git a/inifile.py b/inifile.py new file mode 100755 index 0000000..5734152 --- /dev/null +++ b/inifile.py @@ -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 + +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() + diff --git a/mainwnd.py b/mainwnd.py new file mode 100755 index 0000000..3004b1d --- /dev/null +++ b/mainwnd.py @@ -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() diff --git a/ringbuffer.py b/ringbuffer.py new file mode 100755 index 0000000..0112cfc --- /dev/null +++ b/ringbuffer.py @@ -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) + + \ No newline at end of file diff --git a/settings.ini b/settings.ini new file mode 100755 index 0000000..0202b2f --- /dev/null +++ b/settings.ini @@ -0,0 +1,2 @@ +[Paths] +Roms=S:/Games/Emulators/ROMs/SNES:S:/Games/Emulators/ROMs/SNES \ No newline at end of file diff --git a/toybocks.py b/toybocks.py new file mode 100755 index 0000000..b668c1e --- /dev/null +++ b/toybocks.py @@ -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() diff --git a/utils.py b/utils.py new file mode 100755 index 0000000..276a85a --- /dev/null +++ b/utils.py @@ -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 \ No newline at end of file