From 053f435a0b29bf866416caec8a1105f5be8e526b Mon Sep 17 00:00:00 2001 From: cecilkorik Date: Fri, 26 Mar 2010 03:50:24 +0000 Subject: [PATCH] initial import --HG-- branch : toybocks --- backend.py | 238 +++++++++++++++++++++++++++++++++++++ bootstrap_autoload.py | 25 ++++ config.py | 5 + dblayer.py | 38 ++++++ inifile.py | 267 ++++++++++++++++++++++++++++++++++++++++++ mainwnd.py | 88 ++++++++++++++ ringbuffer.py | 137 ++++++++++++++++++++++ settings.ini | 2 + toybocks.py | 138 ++++++++++++++++++++++ utils.py | 8 ++ 10 files changed, 946 insertions(+) create mode 100755 backend.py create mode 100755 bootstrap_autoload.py create mode 100755 config.py create mode 100755 dblayer.py create mode 100755 inifile.py create mode 100755 mainwnd.py create mode 100755 ringbuffer.py create mode 100755 settings.ini create mode 100755 toybocks.py create mode 100755 utils.py 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