diff options
author | Diego Roversi <diegor@tiscali.it> | 2019-09-08 18:12:27 +0200 |
---|---|---|
committer | Diego Roversi <diegor@tiscali.it> | 2019-09-08 18:12:27 +0200 |
commit | 1d9925c287b318ec21343e2682b51ab6a36ae8db (patch) | |
tree | 17d1c0ac21eea6f291146520afa8381db4586fb4 /display |
initial commit from cvs 1.6.2
Diffstat (limited to 'display')
-rw-r--r-- | display/.cvsignore | 4 | ||||
-rw-r--r-- | display/Client.py | 170 | ||||
-rw-r--r-- | display/Makefile | 2 | ||||
-rw-r--r-- | display/__init__.py | 1 | ||||
-rw-r--r-- | display/caching.py | 261 | ||||
-rw-r--r-- | display/dpy_gtk.py | 204 | ||||
-rw-r--r-- | display/dpy_pygame.py | 245 | ||||
-rwxr-xr-x | display/dpy_windows.py | 23 | ||||
-rw-r--r-- | display/dpy_x.py | 40 | ||||
-rw-r--r-- | display/modes.py | 196 | ||||
-rw-r--r-- | display/music1.py | 33 | ||||
-rw-r--r-- | display/pclient.py | 860 | ||||
-rw-r--r-- | display/playback.py | 203 | ||||
-rw-r--r-- | display/puremixer.py | 123 | ||||
-rw-r--r-- | display/pythonxlibintf.py | 202 | ||||
-rwxr-xr-x | display/setup.py | 16 | ||||
-rw-r--r-- | display/snd_linux.py | 148 | ||||
-rw-r--r-- | display/snd_off.py | 5 | ||||
-rw-r--r-- | display/snd_pygame.py | 82 | ||||
-rw-r--r-- | display/snd_windows.py | 93 | ||||
-rw-r--r-- | display/windows/.cvsignore | 3 | ||||
-rwxr-xr-x | display/windows/wingame.c | 867 | ||||
-rwxr-xr-x | display/windows/wingame.def | 2 | ||||
-rwxr-xr-x | display/windows/wingame.dsp | 111 | ||||
-rwxr-xr-x | display/windows/wingame.dsw | 29 | ||||
-rw-r--r-- | display/xshm.c | 1202 | ||||
-rwxr-xr-x | display/xshm.so | bin | 0 -> 112912 bytes |
27 files changed, 5125 insertions, 0 deletions
diff --git a/display/.cvsignore b/display/.cvsignore new file mode 100644 index 0000000..687980a --- /dev/null +++ b/display/.cvsignore @@ -0,0 +1,4 @@ +*.py[co] +build +*.so +*.pyd diff --git a/display/Client.py b/display/Client.py new file mode 100644 index 0000000..3d1330d --- /dev/null +++ b/display/Client.py @@ -0,0 +1,170 @@ +#! /usr/bin/env python + +# __________ +import os, sys +if __name__ == '__main__': + LOCALDIR = sys.argv[0] +else: + LOCALDIR = __file__ +try: + LOCALDIR = os.readlink(LOCALDIR) +except: + pass +LOCALDIR = os.path.dirname(os.path.abspath(LOCALDIR)) +# ---------- + +sys.path.insert(0, os.path.dirname(LOCALDIR)) +sys.path.insert(0, LOCALDIR) +import common +import pclient +import modes + + +UdpLookForServer = [ + '127.0.0.1', + '<broadcast>', + ] + +def parse_cmdline(argv): + # parse command-line + def usage(): + print >> sys.stderr, 'usage:' + print >> sys.stderr, ' python Client.py [-d#] [-s#] [extra options] [host[:port]]' + print >> sys.stderr + print >> sys.stderr, 'options:' + print >> sys.stderr, ' host search for a game on the given machine' + print >> sys.stderr, ' host:port connect to the given game server' + print >> sys.stderr, ' (default search for any local server)' + print >> sys.stderr, ' -d# --display=# graphic driver (see below)' + print >> sys.stderr, ' -s# --sound=# sound driver (see below)' + print >> sys.stderr, ' --music=no disable background music' + print >> sys.stderr, ' -h --help display this text' + print >> sys.stderr, ' -m --metaserver connect with the help of the metaserver' + print >> sys.stderr, ' (list servers with Client.py -m)' + print >> sys.stderr, ' -t --tcp for slow or proxy connections' + print >> sys.stderr, ' -u --udp for fast direct connections' + print >> sys.stderr, ' (default is to autodetect tcp or udp)' + print >> sys.stderr, ' --port UDP=# or #:# fixed inbound udp port or host:port' + print >> sys.stderr, ' --port TCP=# fixed inbound tcp port (-m only)' + print >> sys.stderr + print >> sys.stderr, 'graphic drivers:' + for info in modes.graphicmodeslist(): + info.printline(sys.stderr) + print >> sys.stderr + print >> sys.stderr, 'sound drivers:' + for info in modes.soundmodeslist(): + info.printline(sys.stderr) + print >> sys.stderr + sys.exit(2) + + shortopts = 'd:s:htum' + longopts = ['display=', 'sound=', 'music=', 'help', 'tcp', 'udp', + 'cfg=', 'metaserver', 'port='] + for info in modes.graphicmodeslist() + modes.soundmodeslist(): + short, long = info.getformaloptions() + shortopts += short + longopts += long + try: + from getopt import gnu_getopt as getopt + except ImportError: + from getopt import getopt + from getopt import error + try: + opts, args = getopt(argv, shortopts, longopts) + except error, e: + print >> sys.stderr, 'Client.py: %s' % str(e) + print >> sys.stderr + usage() + + metaserver = 0 + driver = sound = None + extraopts = {} + for key, value in opts: + if key in ('-d', '--display'): + driver = value + elif key in ('-s', '--sound'): + sound = value + elif key in ('-t', '--tcp'): + extraopts['udp_over_tcp'] = 1 + elif key in ('-u', '--udp'): + extraopts['udp_over_tcp'] = 0 + elif key in ('-m', '--metaserver'): + metaserver = 1 + elif key == '--port': + import common.msgstruct + try: + portname, value = value.split('=') + if portname == 'UDP': + portname = 'CLIENT' + elif portname == 'TCP': + portname = 'BACK' + except ValueError: + portname = 'CLIENT' + if portname == 'CLIENT' and ':' in value: + udphostname, value = value.split(':') + common.msgstruct.PORTS['sendudpto'] = udphostname + common.msgstruct.PORTS[portname] = int(value) + elif key == '--cfg': + extraopts['cfgfile'] = value + elif key in ('-h', '--help'): + usage() + else: + extraopts[key] = value + mode = driver, sound, extraopts + + if metaserver: + if len(args) == 0: + metalist() + sys.exit(0) + elif len(args) != 1 or ':' not in args[0]: + usage() + return metaconnect(args[0]), mode + + if args: + if len(args) > 1: + usage() + hosts = args[0].split(':') + if len(hosts) == 1: + host, = hosts + from common import hostchooser + server = hostchooser.pick([host] * 5) + elif len(hosts) == 2: + host, port = hosts + try: + port = int(port) + except ValueError: + usage() + server = host, port + else: + usage() + return directconnect(server), mode + + from common import hostchooser + server = hostchooser.pick(UdpLookForServer * 3) + return directconnect(server), mode + +def directconnect(sockaddr): + print "connecting to %s:%d..." % sockaddr + from socket import socket, AF_INET, SOCK_STREAM + s = socket(AF_INET, SOCK_STREAM) + s.connect(sockaddr) + return s, sockaddr + +def metaconnect(metaaddr): + from metaserver import metaclient + import common.msgstruct + port = common.msgstruct.PORTS.get('BACK') + s = metaclient.meta_connect(metaaddr, port) + sockaddr = s.getpeername() + return s, sockaddr + +def metalist(): + from metaserver import metaclient + metaclient.print_server_list() + +def main(): + (s, sockaddr), mode = parse_cmdline(sys.argv[1:]) + pclient.run(s, sockaddr, mode) + +if __name__ == '__main__': + main() diff --git a/display/Makefile b/display/Makefile new file mode 100644 index 0000000..214347a --- /dev/null +++ b/display/Makefile @@ -0,0 +1,2 @@ +xshm.so: xshm.c + python setup.py build_ext -i diff --git a/display/__init__.py b/display/__init__.py new file mode 100644 index 0000000..ea30561 --- /dev/null +++ b/display/__init__.py @@ -0,0 +1 @@ +#empty diff --git a/display/caching.py b/display/caching.py new file mode 100644 index 0000000..27d772c --- /dev/null +++ b/display/caching.py @@ -0,0 +1,261 @@ +from __future__ import generators +import os, md5, sys +#import common.debug + + +class FileCache: + MAX_FILES = 8 + + def __init__(self): + self.cache = {} + self.time = 0 + + def access(self, filename, position, writing=0): + if filename in self.cache: + time, mode, f = self.cache[filename] + if writing > mode: + f.close() + del self.cache[filename] + if filename not in self.cache: + if len(self.cache) >= FileCache.MAX_FILES: + (time, mode, f), k = min([(v,k) for (k,v) in self.cache.items()]) + f.close() + del self.cache[k] + try: + f = open(filename, ('rb', 'r+b')[writing]) + except (IOError, OSError): + if not writing: + raise + if not os.path.isdir(os.path.dirname(filename)): + os.mkdir(os.path.dirname(filename)) + f = open(filename, 'w+b') + mode = writing + self.time += 1 + self.cache[filename] = self.time, mode, f + f.seek(position) + return f + + +class MemoryBlock: + def __init__(self, data): + self.data = data + def overwrite(self, newdata): + self.data = newdata + def read(self): + return self.data + +class FileBlock: + def __init__(self, filename, position, length, readonly=1, complete=1): + self.filename = filename + self.position = position + self.length = length + self.readonly = readonly + self.complete = complete + def overwrite(self, newdata): + self.memorydata = newdata + if self.readonly: + print >> sys.stderr, "cannot overwrite file", self.filename + return + try: + f = Data.Cache.access(self.filename, self.position, writing=1) + f.write(newdata) + except (IOError, OSError): + print >> sys.stderr, "cache write error:", self.filename + return + self.complete = 1 + del self.memorydata + def read(self): + if self.complete: + f = Data.Cache.access(self.filename, self.position) + return f.read(self.length) + else: + return self.memorydata + + +class Data: + SafeChars = {} + for c in ".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789": + SafeChars[c] = c + Translate = ''.join([SafeChars.get(chr(c), '_') for c in range(256)]) + del c, SafeChars + Cache = FileCache() + + def __init__(self): + self.content = {} + self.backupfile = None + self.readonly = 0 + + clear = __init__ + + ### Public interface ### + + def store(self, position, data, filename=None, readonly=1): + """This class assumes that all accesses to block within the data + are done for disjoint intervals: no overlapping writes !""" + if self.content is not None: + try: + self.content[position].overwrite(data) + except KeyError: + if filename is None: + self.content[position] = MemoryBlock(data) + else: + self.content[position] = FileBlock(filename, position, + len(data), readonly) + if self.backupfile and not self.readonly: + try: + f = Data.Cache.access(self.backupfile, position, writing=1) + f.write(data) + f.flush() + except (IOError, OSError): + print >> sys.stderr, "cache write error:", self.backupfile + + def loadfrom(self, filename, position, length, checksum): + """Try to load data from the given filename, with the given + expected MD5 checksum. The filename must be Unix-style, and is + looked up both in the directory SOURCEDIR and with a mangled name + in the cache directory CACHEDIR.""" + directname = os.path.join(self.SOURCEDIR, *filename.split('/')) + mangledname = filename.translate(Data.Translate) + cachename = os.path.join(self.CACHEDIR, mangledname) + for name, readonly in ((directname, 1), (cachename, 0)): + try: + f = Data.Cache.access(name, position) + data = f.read(length) + except (IOError, OSError): + pass + else: + if len(data) == length and md5.new(data).digest() == checksum: + # correct data + self.store(position, data, name, readonly) + return 1 + if self.content is not None and not self.content.has_key(position): + self.content[position] = FileBlock(cachename, position, length, + readonly=0, complete=0) + elif self.readonly: + print >> sys.stderr, "Note: the music data has changed. You can get" + print >> sys.stderr, "the server's version by deleting", directname + return 1 # incorrect data, but ignored + return 0 + + def read(self): + """Return the data as built so far.""" + if self.content is not None: + items = self.content.items() + items.sort() + result = '' + for position, block in items: + if len(result) < position: + result += '\x00' * (position-len(result)) + data = block.read() + result = result[:position] + data + result[position+len(data):] + return result + else: + f = Data.Cache.access(self.backupfile, 0) + return f.read() + + def fopen(self): + if self.content is not None: + from cStringIO import StringIO + return StringIO(self.read()) + else: + return Data.Cache.access(self.backupfile, 0) + + def freezefilename(self, fileexthint='.wav'): + """Return the name of a file from which the data can be read. If all + the current data comes from the same file, it is assumed to be exactly + the file that we want.""" + if not self.backupfile: + files = {} + for position, block in self.content.items(): + if not isinstance(block, FileBlock): + break + if block.complete: + files[block.filename] = block + else: + if len(files) == 1: + self.backupfile, block = files.items()[0] + self.readonly = block.readonly + if not self.backupfile: + self.backupfile = mktemp(fileexthint) + f = Data.Cache.access(self.backupfile, 0, writing=1) + for position, block in self.content.items(): + f.seek(position) + f.write(block.read()) + f.flush() + #print 'freezefilename ->', self.backupfile + #print ' readonly =', self.readonly + self.content = None + return self.backupfile + +# ____________________________________________________________ +# Temporary files management +# from the 'py' lib, mostly written by hpk + +def try_remove_dir(udir): + try: + for name in os.listdir(udir): + try: + os.unlink(os.path.join(udir, name)) + except: + pass + os.rmdir(udir) + except: + pass + +def make_numbered_dir(prefix='tmp-bub-n-bros-', rootdir=None, keep=0, + lock_timeout = 172800): # two days + """ return unique directory with a number greater than the current + maximum one. The number is assumed to start directly after prefix. + Directories with a number less than (maxnum-keep) will be removed. + """ + import atexit, tempfile + if rootdir is None: + rootdir = tempfile.gettempdir() + + def parse_num(bn): + """ parse the number out of a path (if it matches the prefix) """ + if bn.startswith(prefix): + try: + return int(bn[len(prefix):]) + except ValueError: + pass + + # compute the maximum number currently in use with the + # prefix + maxnum = -1 + for basename in os.listdir(rootdir): + num = parse_num(basename) + if num is not None: + maxnum = max(maxnum, num) + + # make the new directory + udir = os.path.join(rootdir, prefix + str(maxnum+1)) + os.mkdir(udir) + + # try to remove the directory at process exit + atexit.register(try_remove_dir, udir) + + # prune old directories + for basename in os.listdir(rootdir): + num = parse_num(basename) + if num is not None and num <= (maxnum - keep): + d1 = os.path.join(rootdir, basename) + try: + t1 = os.stat(d1).st_mtime + t2 = os.stat(udir).st_mtime + if abs(t2-t1) < lock_timeout: + continue # skip directories still recently used + except: + pass + try_remove_dir(d1) + return udir + +def enumtempfiles(): + tempdir = make_numbered_dir() + i = 0 + while True: + yield os.path.join(tempdir, 'b%d' % i) + i += 1 + +def mktemp(fileext, gen = enumtempfiles()): + return gen.next() + fileext diff --git a/display/dpy_gtk.py b/display/dpy_gtk.py new file mode 100644 index 0000000..7b73516 --- /dev/null +++ b/display/dpy_gtk.py @@ -0,0 +1,204 @@ + + ################################################ +## GTK-based implementation of xshm ## +################################################ + +import os, sys, math +from modes import KeyPressed, KeyReleased +import caching + +def import_trickery(): + global gtk, gdk + argv = sys.argv[:] + del sys.argv[1:] + import gtk + from gtk import gdk + sys.argv[:] = argv +import_trickery() + + +class Display: + + def __init__(self, width, height, title, zoom="100"): + if zoom.endswith('%'): + zoom = zoom[:-1] + scale = float(zoom) / 100.0 + iscale = int(scale+0.001) + if abs(scale - iscale) < 0.002: + scale = iscale + self.scale = scale + + self.width = int(width * scale) + self.height = int(height * scale) + self.tempppmfile = caching.mktemp('.ppm') + + # create a top level window + w = self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + w.connect("destroy", lambda w: sys.exit()) + w.connect("key-press-event", self.key_press_event) + w.connect("key-release-event", self.key_release_event) + w.connect("motion-notify-event", self.motion_notify_event) + w.connect("button-press-event", self.button_press_event) + w.add_events(gdk.KEY_PRESS_MASK | + gdk.POINTER_MOTION_MASK | + gdk.BUTTON_PRESS_MASK) + w.resize(self.width, self.height) + w.set_title(title) + w.show() + + self.offscreen = gtk.create_pixmap(w.window, self.width, self.height) + self.gc = gdk.gc_new(w.window) + self.gc.set_rgb_fg_color(gdk.color_parse('#000000')) + + self.events_key = [] + self.events_mouse = [] + self.event_motion = None + + pixel = "\x00\x00\x80" + hole = "\x01\x01\x01" + pb = self.pixmap(32, 32, ((pixel+hole)*16 + (hole+pixel)*16) * 16, 0x010101) + self.taskbkgnd = self.renderpixbuf(pb) + + def taskbar(self, (x, y, w, h)): + scale = self.scale + x2 = x+w + y2 = y+h + x, y, x2, y2 = int(x*scale), int(y*scale), int(x2*scale), int(y2*scale) + pixmap, gc, ignored = self.taskbkgnd + for j in range(y, y2, 32): + for i in range(x, x2, 32): + gc.set_clip_origin(i, j) + self.offscreen.draw_drawable(gc, pixmap, 0, 0, + i, j, x2-i, y2-j) + + def pixmap(self, w, h, data, colorkey=-1): + filename = self.tempppmfile + f = open(filename, 'wb') + print >> f, 'P6' + print >> f, w, h + print >> f, 255 + f.write(data) + f.close() + pb = gdk.pixbuf_new_from_file(filename) + if colorkey >= 0: + pb = pb.add_alpha(1, chr(colorkey >> 16), + chr((colorkey >> 8) & 0xFF), + chr(colorkey & 0xFF)) + if self.scale == 1: + return self.renderpixbuf((pb,)) + else: + return (pb,) + + def renderpixbuf(self, input): + if len(input) == 3: + return input + pb, = input + pixmap, mask = pb.render_pixmap_and_mask() + if mask is not None: + gc = gdk.gc_new(self.window.window) + gc.set_clip_mask(mask) + return (pixmap, gc, mask) + else: + return (pixmap, self.gc, None) + + def getopticon(self, input, (x, y, w, h), ignored_alpha=255): + if len(input) == 3: + return None + pb, = input + scale = self.scale + newpb = gdk.Pixbuf("rgb", 1, 8, w, h) + newpb.fill(0) + pb.copy_area(x, y, w, h, newpb, 0, 0) + newpb = newpb.scale_simple(int(w*scale), int(h*scale), + gdk.INTERP_HYPER) + if newpb is None: + return None, None, None + else: + return self.renderpixbuf((newpb,)) + + def getppm(self, (x, y, w, h), int=int, ceil=math.ceil): + scale = self.scale + if isinstance(scale, int): + x *= scale + y *= scale + w *= scale + h *= scale + else: + w = int(ceil((x+w)*scale)) + h = int(ceil((y+h)*scale)) + x = int(x*scale) + y = int(y*scale) + w -= x + h -= y + bkgnd = gtk.create_pixmap(self.window.window, w, h) + bkgnd.draw_drawable(self.gc, self.offscreen, x, y, 0, 0, w, h) + return bkgnd, self.gc, None + + def putppm(self, x, y, (pixmap, gc, ignored), rect=None, int=int): + if pixmap is None: + return + scale = self.scale + if rect is None: + srcx = srcy = 0 + w = h = 4095 + else: + srcx, srcy, w, h = rect + x = int(x*scale) + y = int(y*scale) + if gc is not self.gc: + gc.set_clip_origin(x-srcx, y-srcy) + self.offscreen.draw_drawable(gc, pixmap, srcx, srcy, x, y, w, h) + + def flip(self): + self.window.window.draw_drawable(self.gc, self.offscreen, + 0, 0, 0, 0, self.width, self.height) + gdk.flush() + self.events_poll() + + def close(self): + self.window.destroy() + + def clear(self): + self.offscreen.draw_rectangle(self.gc, 1, + 0, 0, self.width, self.height) + + def events_poll(self): + while gtk.events_pending(): + gtk.main_iteration() + + def key_press_event(self, window, event): + self.events_key.append((event.keyval, KeyPressed)) + + def key_release_event(self, window, event): + self.events_key.append((event.keyval, KeyReleased)) + + def motion_notify_event(self, window, event): + self.event_motion = (int(event.x/self.scale), int(event.y/self.scale)) + + def button_press_event(self, window, event): + self.events_mouse.append((int(event.x/self.scale), int(event.y/self.scale))) + + def keyevents(self): + self.events_poll() + result = self.events_key + self.events_key = [] + return result + + def pointermotion(self): + result = self.event_motion + self.event_motion = None + return result + + def mouseevents(self): + self.events_poll() + result = self.events_mouse + self.events_mouse = [] + return result + + def selectlist(self): + return [] + + +def htmloptionstext(nameval): + return 'Scale image by <%s size=5>%%' % ( + nameval('text', 'zoom', default='100')) diff --git a/display/dpy_pygame.py b/display/dpy_pygame.py new file mode 100644 index 0000000..acfd200 --- /dev/null +++ b/display/dpy_pygame.py @@ -0,0 +1,245 @@ + + ################################################ +## pygame-based implementation of xshm ## +################################################ + +import os +import pygame +from pygame.locals import * +from modes import KeyPressed, KeyReleased + + +class Display: + musthidemouse = 0 + mousevisible = 1 + + def __init__(self, width, height, title, transparency='yes', + fullscreen='no', zoom='100%', smooth='yes', + smoothfast='yes'): + self.use_transparency = not transparency.startswith('n') + self.use_fullscreen = fullscreen.startswith('y') + if zoom.endswith('%'): + zoom = zoom[:-1] + scale = float(zoom) / 100.0 + iscale = int(scale+0.001) + if abs(scale - iscale) < 0.002: + scale = iscale + self.scale = scale + self.smooth = smooth.startswith('y') + self.smoothfast = smoothfast.startswith('y') + + # Initialize pygame + pygame.init() + + # Set the display mode + winstyle = HWSURFACE + if self.use_fullscreen: + winstyle |= FULLSCREEN + bestdepth = pygame.display.mode_ok((int(width * self.scale), + int(height * self.scale)), + winstyle, 32) + self.screen = pygame.display.set_mode((int(width * self.scale), + int(height * self.scale)), + winstyle, bestdepth) + self.offscreen = pygame.Surface((width, height)) + #decorate the game window + pygame.display.set_caption(title) + #pygame.mouse.set_visible(0) + self.tbcache = None, None + self.events_key = [] + self.events_mouse = [] + self.prevposition = None + EVENT_HANDLERS[KEYDOWN] = self.keydown_handler + EVENT_HANDLERS[KEYUP] = self.keyup_handler + EVENT_HANDLERS[MOUSEBUTTONDOWN] = self.mousebuttondown_handler + + def keydown_handler(self, e): + if e.key == K_ESCAPE and self.use_fullscreen: + raise SystemExit # ESC to exit the game if full-screen + self.showmouse(not self.musthidemouse) + self.events_key.append((e.key, KeyPressed)) + del self.events_key[:-16] + + def keyup_handler(self, e): + self.events_key.append((e.key, KeyReleased)) + del self.events_key[:-16] + + def mousebuttondown_handler(self, e): + self.showmouse(1) + self.events_mouse.append(self.fixpos(e.pos)) + del self.events_mouse[:-8] + + def pixmap(self, w, h, data, colorkey=-1): + img = pygame.image.fromstring(data, (w, h), "RGB") + if colorkey >= 0: + r = colorkey & 0xFF + g = (colorkey >> 8) & 0xFF + b = (colorkey >> 16) & 0xFF + img.set_colorkey([r, g, b]) + return img # not optimized -- must use getopticon() + + def getopticon(self, pixmap, rect, alpha=255): + if not self.use_transparency: + alpha = 255 + img = pixmap.subsurface(rect) + colorkey = pixmap.get_colorkey() + if alpha == 255 and not colorkey: + return img.convert(self.offscreen) + else: + if colorkey: + img.set_colorkey(colorkey, RLEACCEL) + if alpha < 255: + img.set_alpha(alpha, RLEACCEL) + img = img.convert_alpha(self.offscreen) + img.set_alpha(255, RLEACCEL) + return img + +## def vflipppm(self, img): +## w, h = img.get_size() +## colorkey = img.get_colorkey() +## data = pygame.image.tostring(img, "RGB", 1) +## flipimg = pygame.image.fromstring(data, (w, h), "RGB") +## flipimg.set_colorkey(colorkey, RLEACCEL) +## return flipimg, h + + def getppm(self, rect): + bkgnd = pygame.Surface(rect[2:]) + bkgnd.blit(self.offscreen, (0, 0), rect) + return bkgnd + + def putppm(self, x, y, bitmap, rect=None): + if rect: + self.offscreen.blit(bitmap, (x, y), rect) + else: + self.offscreen.blit(bitmap, (x, y)) + + def flip(self): + offscreen = self.offscreen + if self.scale != 1: + w, h = offscreen.get_size() + w = int(w * self.scale) + h = int(h * self.scale) + if self.scale == 2 and self.smoothfast: + offscreen = pygame.transform.scale2x(offscreen) + elif self.smooth: + offscreen = pygame.transform.smoothscale(offscreen, (w, h)) + else: + offscreen = pygame.transform.scale(offscreen, (w, h)) + self.screen.blit(offscreen, (0, 0)) + pygame.display.flip() + events_dispatch() + + def close(self): + self.showmouse(1) + pygame.display.quit() + + def clear(self): + self.offscreen.fill([0,0,0,]) + + def fixpos(self, (x, y)): + if self.scale != 1: + x = int(x / self.scale) + y = int(y / self.scale) + return (x, y) + + def events_poll(self): + while 1: + e = pygame.event.poll() + if e.type == NOEVENT: + break + elif e.type == KEYDOWN: + self.events_key.append((e.key, KeyPressed)) + del self.events_key[:-16] + elif e.type == KEYUP: + self.events_key.append((e.key, KeyReleased)) + del self.events_key[:-16] + elif e.type == MOUSEBUTTONDOWN: + self.events_mouse.append(self.fixpos(e.pos)) + del self.events_mouse[:-8] + elif e.type == ENDMUSICEVENT: + self.next_music() + elif e.type == QUIT: + raise SystemExit + + def keyevents(self): + events_dispatch() + events = self.events_key + self.events_key = [] + return events + + def pointermotion(self): + position = pygame.mouse.get_pos() + if position != self.prevposition: + self.showmouse(1) + self.prevposition = position + return self.fixpos(position) + else: + return None + + def mouseevents(self): + events_dispatch() + events = self.events_mouse + self.events_mouse = [] + return events + + def selectlist(self): + return [] + + def taskbar(self, (x, y, w, h)): + tbs, tbh = self.tbcache + if tbh != h: + tbs = pygame.Surface((32, h)).convert_alpha(self.offscreen) + alpha_f = 256.0 / h + for j in range(h): + tbs.fill((128, 128, 255, int(j*alpha_f)), + (0, j, 32, 1)) + self.tbcache = tbs, h + for i in range(x, x+w, 32): + dw = x+w-i + if dw < 32: + self.offscreen.blit(tbs, (i, y), (0, 0, dw, h)) + else: + self.offscreen.blit(tbs, (i, y)) + + def settaskbar(self, tb_visible): + self.showmouse(1) + self.musthidemouse = not tb_visible # and self.use_fullscreen + + def showmouse(self, v): + if v != self.mousevisible: + self.mousevisible = v + pygame.mouse.set_visible(v) + + +def quit_handler(e): + raise SystemExit + +EVENT_HANDLERS = { + QUIT: quit_handler, + } + +def events_dispatch(handlers = EVENT_HANDLERS): + while 1: + e = pygame.event.poll() + if e.type == NOEVENT: + break + elif handlers.has_key(e.type): + handlers[e.type](e) + + +def htmloptionstext(nameval): + return ''' +<%s> Full Screen (Esc key to exit)</input><%s><br> +<%s> Draw slightly transparent bubbles</input><%s><br> +Scale image by <%s size=5>%%<br> +<%s> Smoothed scaled image</input><%s><br> +<%s> Semi-smoothed scaled image (for 200%% only)</input><%s><br> +''' % (nameval("checkbox", "fullscreen", "yes", default="no"), + nameval("hidden", "fullscreen", "no"), + nameval("checkbox", "transparency", "yes", default="yes"), + nameval("hidden", "transparency", "no"), + nameval("text", "zoom", default="100"), + nameval("checkbox", "smooth", "yes", default="yes"), + nameval("hidden", "smooth", "no"), + nameval("checkbox", "smoothfast", "yes", default="no"), + nameval("hidden", "smoothfast", "no")) diff --git a/display/dpy_windows.py b/display/dpy_windows.py new file mode 100755 index 0000000..b01dfcf --- /dev/null +++ b/display/dpy_windows.py @@ -0,0 +1,23 @@ +import sys +import wingame +from modes import BaseDisplay +from cStringIO import StringIO + + +class Display(BaseDisplay): + + def __init__(self, width, height, title): + self.xdpy = xdpy = wingame.Display(width, height) + xdpy.settitle(title) + self.pixmap = xdpy.pixmap + self.getppm = xdpy.getppm + self.putppm = xdpy.putppm + self.close = xdpy.close + self.clear = xdpy.clear + self.flip = xdpy.flip + self.keyevents = xdpy.keyevents + self.mouseevents = xdpy.mouseevents + self.pointermotion = xdpy.pointermotion + + def selectlist(self): + return [] diff --git a/display/dpy_x.py b/display/dpy_x.py new file mode 100644 index 0000000..ffca90f --- /dev/null +++ b/display/dpy_x.py @@ -0,0 +1,40 @@ +import sys +import xshm +from modes import BaseDisplay +from cStringIO import StringIO + + +class Display(BaseDisplay): + + def __init__(self, width, height, title, shm='yes'): + use_shm = not shm.startswith('n') + self.xdpy = xdpy = xshm.Display(width, height, use_shm) + self.pixmap = xdpy.pixmap + self.getppm = xdpy.getppm + self.putppm = xdpy.putppm + self.overlayppm = xdpy.overlayppm + self.close = xdpy.close + self.clear = xdpy.clear + self.flip = xdpy.flip + self.keyevents = xdpy.keyevents + self.mouseevents = xdpy.mouseevents + self.pointermotion = xdpy.pointermotion + if use_shm and not xdpy.shmmode(): + print >> sys.stderr, \ + "Note: cannot use SHM extension (%dx%d), display will be slow." % \ + (width, height) + + def selectlist(self): + if hasattr(self.xdpy, 'fd'): + from socket import fromfd, AF_INET, SOCK_STREAM + return [fromfd(self.xdpy.fd(), AF_INET, SOCK_STREAM)] + else: + return [] + + +def htmloptionstext(nameval): + return ''' +<%s> Use the shared memory extension</input><%s><br> +<font size=-1>Note: Disable it for remote connections or old X servers</font> +''' % (nameval("checkbox", "shm", "yes", default="yes"), + nameval("hidden", "shm", "no")) diff --git a/display/modes.py b/display/modes.py new file mode 100644 index 0000000..ad0c1c0 --- /dev/null +++ b/display/modes.py @@ -0,0 +1,196 @@ +import sys + +KeyPressed = 2 +KeyReleased = 3 + + +class BaseDisplay: + __taskbkgnd = None + + def taskbar(self, (x, y, w, h)): + if self.__taskbkgnd is None: + pixel = "\x00\x00\x80" + hole = "\x01\x01\x01" + self.__taskbkgnd = self.pixmap(32, 32, + ((pixel+hole)*16 + (hole+pixel)*16) * 16, 0x010101) + for j in range(y, y+h, 32): + for i in range(x, x+w, 32): + self.putppm(i, j, self.__taskbkgnd, + (0, 0, x+w-i, y+h-j)) + + +class Mode: + low_priority = 0 + + def __init__(self, name, descr, extraoptsdescr, + options={}, url=None): + self.name = name + self.descr = descr + self.extraoptsdescr = extraoptsdescr + self.options = options.copy() + self.url = url + + def getmodule(self): + return __import__(self.prefix + self.name.lower(), globals(), + locals(), ['available']) + + def imperror(self): + try: + return self.__imperror + except AttributeError: + try: + module = self.getmodule() + except ImportError: + result = 'not installed' + else: + result = hasattr(module, 'imperror') and module.imperror() + self.__imperror = result + return result + + def unique_id(self): + return self.prefix + self.name + + def printline(self, f): + err = self.imperror() + if err: + state = ' [%s]' % err + else: + state = '' + print >> f, ' %-8s %s%s' % (self.name, self.descr, state) + if self.url: + print >> f, ' %s' % self.url + for line in self.extraoptsdescr: + print >> f, ' %s' % line + + def getformaloptions(self): + return '', [c+'=' for c in self.options.keys()] + + def setoptions(self, options): + for key in self.options.keys(): + if options.has_key('--'+key): + self.options[key] = options['--'+key] + + def currentdriver(self): + lst = self.options.items() + lst.sort() + lst = ['--%s=%s' % keyvalue for keyvalue in lst] + return ' '.join([self.name] + lst) + + def htmloptionstext(self, *args): + if self.imperror(): + return None + module = self.getmodule() + return (hasattr(module, 'htmloptionstext') and + module.htmloptionstext(*args)) + + +class GraphicMode(Mode): + prefix = 'dpy_' + + +class SoundMode(Mode): + prefix = 'snd_' + + +def graphicmodeslist(): + return [ + GraphicMode('X', 'XWindow (Linux/Unix)', + ['--shm=yes use the Shared Memory extension (default)', + '--shm=no disable it (for remote connections or old X servers)', + ], + {'shm': 'yes'}), + GraphicMode('windows', 'MS Windows', []), + GraphicMode('pygame', 'PyGame library (all platforms)', + ['--fullscreen=yes go full screen (Esc key to exit)', + '--transparency=yes slightly transparent bubbles (default)', + '--transparency=no disable it (a bit faster)', + '--zoom=xxx% scale image by xxx %', + '--smooth smoothed scaled image', + '--smoothfast semi-smoothed, for 200% scale only'], + {'transparency': 'yes', 'fullscreen': 'no', + 'zoom': '100', 'smooth': 'yes', 'smoothfast': 'no'}, + url='http://www.pygame.org'), + GraphicMode('gtk', 'PyGTK (Gnome)', + ['--zoom=xxx% scale image by xxx %'], + {'zoom': '100'}, + url='http://www.pygtk.org/'), + ] + +def soundmodeslist(): + return [ + SoundMode('pygame', 'PyGame library mixer (all platforms)', + [], url='http://www.pygame.org'), + SoundMode('linux', 'audio mixer for Linux', + ['--freq=# mixer frequency (default 44100)', + '--fmt=# data format (default S16_NE, --fmt=list for a list)'], + {'freq': '44100', 'fmt': 'S16_NE'}), + SoundMode('windows', 'audio mixer for Windows', + ['--freq=# mixer frequency (default 44100)', + '--bits=# bits per sample (8 or default 16)'], + {'freq': '44100', 'bits': '16'}), + SoundMode('off', 'no sounds', []), + ] + +def findmode(name, lst): + if name is None: + # find the first installed mode + last_chance = None + for info in lst: + err = info.imperror() + if err: + continue + if info.low_priority: + if last_chance is None: + last_chance = info + else: + return info + if last_chance is not None: + return last_chance + raise KeyError, 'no driver available!' + else: + # find mode by name + for info in lst: + if info.name.upper() == name.upper(): + err = info.imperror() + if err: + raise KeyError, '%s: %s' % (info.name, err) + return info + raise KeyError, '%s: no such driver' % name + +def findmode_err(*args): + try: + return findmode(*args) + except KeyError, e: + print >> sys.stderr, str(e) + sys.exit(1) + +def open_dpy(mode, width, height, title): + driver, sound, extraopts = mode + ginfo = findmode_err(driver, graphicmodeslist()) + ginfo.setoptions(extraopts) + dpy = ginfo.getmodule().Display(width, height, title, **ginfo.options) + print 'graphics driver:', ginfo.currentdriver() + return dpy + +def open_snd(mode): + driver, sound, extraopts = mode + sinfo = findmode_err(sound, soundmodeslist()) + sinfo.setoptions(extraopts) + snd = sinfo.getmodule().Sound(**sinfo.options) + if snd.has_sound: + sinfo.options['music'] = 'yes' + sinfo.setoptions(extraopts) + if (sinfo.options['music'].startswith('n') or + sinfo.options['music'] == 'off'): + snd.has_music = 0 + print 'sound driver:', sinfo.currentdriver() + return snd + else: + return None + + +def musichtmloptiontext(nameval): + return '''<font size=-1> +<%s> Background music</input><%s> +</font>''' % (nameval("checkbox", "music", "yes", default="yes", mangling=0), + nameval("hidden", "music", "no", mangling=0)) diff --git a/display/music1.py b/display/music1.py new file mode 100644 index 0000000..b5c0c6d --- /dev/null +++ b/display/music1.py @@ -0,0 +1,33 @@ +class Music: + def __init__(self, filename): + self.filename = filename + self.w = None + self.sampledata = '' + def openchannel(self): + if self.w is not None: + self.w.close() + import wave + self.w = w = wave.open(open(self.filename, 'rb'), 'r') + self.w_params = (w.getnchannels(), + w.getsampwidth(), + w.getframerate()) + chan, width, freq = self.w_params + self.dataleft = w.getnframes() * (chan*width) + self.sampledata = '' + def decode(self, mixer, bytecount): + result = self.sampledata + if not result and self.dataleft > 0: + # decode and convert some more data + chan, width, freq = self.w_params + #framecount = bytecount / (chan*width) + inputdata = self.w.readframes(bytecount) #(framecount) + self.dataleft -= len(inputdata) + result = mixer.resample(inputdata, + freq = freq, + bits = width * 8, + signed = width > 1, + channels = chan, + byteorder = 'little') + #print len(result) + self.sampledata = result[bytecount:] + return result[:bytecount] diff --git a/display/pclient.py b/display/pclient.py new file mode 100644 index 0000000..c6546ce --- /dev/null +++ b/display/pclient.py @@ -0,0 +1,860 @@ +#! /usr/bin/env python + +import sys, os +from socket import * +from select import select +import struct, zlib +import time +from common.msgstruct import * +from common.pixmap import decodepixmap +from common import hostchooser +import modes +from modes import KeyPressed, KeyReleased +import caching + +#import psyco; psyco.full() + +# switch to udp_over_tcp if the udp socket didn't receive at least 60% of +# the packets sent by the server +UDP_EXPECTED_RATIO = 0.60 + + +def loadpixmap(dpy, data, colorkey=None): + w, h, data = decodepixmap(data) + if colorkey is None: + colorkey = -1 + elif colorkey < 0: + r, g, b = struct.unpack("BBB", self.data[:3]) + colorkey = b | (g<<8) | (r<<16) + return dpy.pixmap(w, h, data, colorkey) + +class Icon: + alpha = 255 + def __init__(self, playfield): + self.playfield = playfield + self.size = 0, 0 + def __getattr__(self, attr): + if attr == 'pixmap': + self.pixmap = self.playfield.getpixmap(self.bmpcode) + if hasattr(self.playfield.dpy, 'getopticon'): + ico = self.playfield.dpy.getopticon( + self.pixmap, self.originalrect, self.alpha) + if ico is not None: + self.pixmap = ico + self.rect = None + return self.pixmap + elif attr in ('bmpcode', 'rect'): + raise KeyError, attr + elif attr == 'originalrect': + self.originalrect = self.rect + return self.originalrect + raise AttributeError, attr + def clear(self): + if self.__dict__.has_key('pixmap'): + del self.pixmap + +class DataChunk(caching.Data): + SOURCEDIR = os.path.abspath(os.path.join(os.path.dirname(caching.__file__), + os.pardir)) + CACHEDIR = os.path.join(SOURCEDIR, 'cache') + TOTAL = 0 + + def __init__(self, fileid): + caching.Data.__init__(self) + self.fileid = fileid + self.pending = [] + self.progresshook = None + + def server_md5(self, playfield, filename, position, length, checksum): + if not self.loadfrom(filename, position, length, checksum): + self.pending.append((0, position)) + playfield.s.sendall(message(CMSG_DATA_REQUEST, self.fileid, + position, length)) + + def server_patch(self, position, data, lendata): + #print 'server_patch', self.fileid, position, len(data) + prev = DataChunk.TOTAL >> 10 + DataChunk.TOTAL += lendata + total = DataChunk.TOTAL >> 10 + if total != prev: + print "downloaded %dkb of data from server" % total + self.store(position, data) + try: + self.pending.remove((0, position)) + except ValueError: + pass + else: + while self.pending and self.pending[0][0]: + callback = self.pending[0][1] + del self.pending[0] + callback(self) + + def when_ready(self, callback): + if self.pending: + self.pending.append((1, callback)) + else: + callback(self) + + +class Playfield: + TASKBAR_HEIGHT = 48 + + def __init__(self, s, sockaddr): + self.s = s + self.sockaddr = sockaddr + try: + self.s.setsockopt(SOL_IP, IP_TOS, 0x10) # IPTOS_LOWDELAY + except error, e: + print >> sys.stderr, "Cannot set IPTOS_LOWDELAY:", str(e) + try: + self.s.setsockopt(SOL_TCP, TCP_NODELAY, 1) + except error, e: + print >> sys.stderr, "Cannot set TCP_NODELAY:", str(e) + + initialbuf = "" + while 1: + t = self.s.recv(200) + if not t and not hasattr(self.s, 'RECV_CAN_RETURN_EMPTY'): + raise error, "connexion closed" + initialbuf += t + if len(initialbuf) >= len(MSG_WELCOME): + head = initialbuf[:len(MSG_WELCOME)] + tail = initialbuf[len(MSG_WELCOME):] + if head != MSG_WELCOME: + raise error, "connected to something not a game server" + if '\n' in tail: + break + n = tail.index('\n') + line2 = tail[:n] + self.initialbuf = tail[n+1:] + + self.gameident = line2.strip() +## self.datapath = None +## if self.gameident.endswith(']'): +## i = self.gameident.rfind('[') +## if i >= 0: +## self.gameident, self.datapath = (self.gameident[:i].strip(), +## self.gameident[i+1:-1]) + print "connected to %r." % self.gameident + self.s.sendall(message(CMSG_PROTO_VERSION, 3)) + + def setup(self, mode, udp_over_tcp): + self.playing = {} # 0, 1, or 'l' for local + self.keys = {} + self.keycodes = {} + self.last_key_event = (None, None) + self.dpy = None + self.snd = None + self.pixmaps = {} # {bmpcode: dpy_pixmap} + self.bitmaps = {} # {bmpcode: (fileid_or_data, colorkey)} + self.icons = {} + self.sounds = {} + self.currentmusic = None + self.fileids = {} + self.sprites = [] + self.playingsounds = {} + self.playericons = {} + self.screenmode = mode + self.initlevel = 0 + if mode[-1].has_key('udp_over_tcp'): + udp_over_tcp = mode[-1]['udp_over_tcp'] + self.trackcfgmtime = None + if mode[-1].has_key('cfgfile'): + self.trackcfgfile = mode[-1]['cfgfile'] + else: + self.trackcfgfile = os.path.join(DataChunk.SOURCEDIR, + 'http2', 'config.txt') + self.udpsock = None + self.udpsock_low = None + self.udpsock2 = None + self.accepted_broadcast = 0 + self.tcpbytecounter = 0 + self.udpbytecounter = 0 + if udp_over_tcp == 1: + self.start_udp_over_tcp() + else: + self.pending_udp_data = None + if udp_over_tcp == 'auto': + self.udpsock_low = 0 + self.dyndecompress = [[None, None, None, None] for i in range(8)] + self.dynnorepeat = None + + def run(self, mode, udp_over_tcp='auto'): + self.setup(mode, udp_over_tcp) + try: + self.mainloop() + finally: + if self.dpy: + self.dpy.close() + try: + self.s.close() + except: + pass + + def mainloop(self): + pss = hostchooser.serverside_ping() + self.initial_iwtd = [self.s, pss] + self.iwtd = self.initial_iwtd[:] + self.animdelay = 0.0 + inbuf = self.process_inbuf(self.initialbuf) + self.initialbuf = "" + errors = 0 + while 1: + if self.dpy: + self.processkeys() + iwtd, owtd, ewtd = select(self.iwtd, [], [], self.animdelay) + self.animdelay = 0.5 + if self.dpy: + self.processkeys() + if self.s in iwtd: + inputdata = self.s.recv(0x6000) + self.tcpbytecounter += len(inputdata) + inbuf += inputdata + inbuf = self.process_inbuf(inbuf) + if self.dpy: + if self.udpsock in iwtd: + udpdata1 = None + while self.udpsock in iwtd: + try: + udpdata = self.udpsock.recv(65535) + except error, e: + print >> sys.stderr, e + errors += 1 + if errors > 10: + raise + break + self.udpbytecounter += len(udpdata) + if len(udpdata) > 3 and '\x80' <= udpdata[0] < '\x90': + udpdata = self.dynamic_decompress(udpdata) + if udpdata is not None: + udpdata1 = udpdata + iwtd, owtd, ewtd = select(self.iwtd, [], [], 0) + if udpdata1 is not None: + self.update_sprites(udpdata1) + if self.udpsock2 in iwtd: + while self.udpsock2 in iwtd: + udpdata = self.udpsock2.recv(65535) + self.udpbytecounter += len(udpdata) + if udpdata == BROADCAST_MESSAGE: + if not self.accepted_broadcast: + self.s.sendall(message(CMSG_UDP_PORT, '*')) + self.accepted_broadcast = 1 + #self.udpsock_low = None + udpdata = '' + iwtd, owtd, ewtd = select(self.iwtd, [], [], 0) + if udpdata and self.accepted_broadcast: + self.update_sprites(udpdata) + if self.pending_udp_data: + self.update_sprites(self.pending_udp_data) + self.pending_udp_data = '' + erasetb = self.taskbarmode and self.draw_taskbar() + d = self.dpy.flip() + if d: + self.animdelay = min(self.animdelay, d) + if self.snd: + d = self.snd.flop() + if d: + self.animdelay = min(self.animdelay, d) + if erasetb: + self.erase_taskbar(erasetb) + if pss in iwtd: + hostchooser.answer_ping(pss, self.gameident, self.sockaddr) + + def process_inbuf(self, inbuf): + while inbuf: + values, inbuf = decodemessage(inbuf) + if not values: + break # incomplete message + fn = Playfield.MESSAGES.get(values[0], self.msg_unknown) + fn(self, *values[1:]) + return inbuf + + def dynamic_decompress(self, udpdata): + # Format of a UDP version 3 packet: + # header byte: 0x80 - 0x87 packet from thread 0 - 7 + # or 0x88 - 0x8F reset packet from thread 0 - 7 + # previous frame in same thread (1 byte) + # frame number (1 byte) + thread = self.dyndecompress[ord(udpdata[0]) & 7] + # thread==[decompress, lastframenumber, recompressed, lastframedata] + prevframe = udpdata[1] + thisframe = udpdata[2] + #print '---' + #for t in self.dyndecompress: + # print repr(t)[:120] + #print + #print `udpdata[:3]` + + if udpdata[0] >= '\x88': + # reset + d = zlib.decompressobj().decompress + if prevframe != thisframe: # if not global sync point + # sync point from a previous frame + # find all threads with the same prevframe + threads = [t for t in self.dyndecompress if prevframe == t[1]] + if not threads: + return None # lost + # find a thread with already-recompressed data + for t in threads: + if t[2]: + data = t[3] + break + else: + # recompress and cache the prevframe data + t = threads[0] + data = t[3] + co = zlib.compressobj(6) + data = co.compress(data) + co.flush(zlib.Z_SYNC_FLUSH) + t[2] = 1 + t[3] = data + d(data) # use it to initialize the state of the decompressobj + #print d + thread[0] = d + elif prevframe != thread[1]: + #print 'lost' + return None # lost + else: + d = thread[0] + # go forward in thread + try: + framedata = d(udpdata[3:]) + #print d + thread[1] = thisframe + thread[2] = 0 + thread[3] = framedata + if thisframe == self.dynnorepeat: + return None + self.dynnorepeat = thisframe + return framedata + except zlib.error: + #print 'crash' + return None + + def geticon(self, icocode): + try: + return self.icons[icocode] + except KeyError: + ico = self.icons[icocode] = Icon(self) + return ico + + def getpixmap(self, bmpcode): + try: + return self.pixmaps[bmpcode] + except KeyError: + data, colorkey = self.bitmaps[bmpcode] + if type(data) is type(''): + data = zlib.decompress(data) + else: + if data.pending: + raise KeyError + data = data.read() + pixmap = loadpixmap(self.dpy, data, colorkey) + self.pixmaps[bmpcode] = pixmap + return pixmap + + def update_sprites(self, udpdata): + sprites = self.sprites + unpack = struct.unpack + + currentsounds = {} + base = 0 + while udpdata[base+4:base+6] == '\xFF\xFF': + key, lvol, rvol = struct.unpack("!hBB", udpdata[base:base+4]) + try: + snd = self.sounds[key] + except KeyError: + pass # ignore sounds with bad code (probably not defined yet) + else: + n = self.playingsounds.get(key) + if n: + currentsounds[key] = n-1 + elif self.snd: + self.snd.play(snd, + lvol / 255.0, + rvol / 255.0) + currentsounds[key] = 4 + base += 6 + self.playingsounds = currentsounds + + for j in range(len(sprites)): + if sprites[j][0] != udpdata[base:base+6]: + removes = sprites[j:] + del sprites[j:] + removes.reverse() + eraser = self.dpy.putppm + for reserved, eraseargs in removes: + eraser(*eraseargs) + break + base += 6 + #print "%d sprites redrawn" % (len(udpdata)/6-j) + try: + overlayer = self.dpy.overlayppm + except AttributeError: + getter = self.dpy.getppm + setter = self.dpy.putppm + for j in range(base, len(udpdata)-5, 6): + info = udpdata[j:j+6] + x, y, icocode = unpack("!hhh", info[:6]) + try: + ico = self.icons[icocode] + sprites.append((info, (x, y, getter((x, y) + ico.size)))) + setter(x, y, ico.pixmap, ico.rect) + except KeyError: + #print "bad ico code", icocode + pass # ignore sprites with bad ico (probably not defined yet) + else: + for j in range(base, len(udpdata)-5, 6): + info = udpdata[j:j+6] + x, y, icocode = unpack("!hhh", info[:6]) + try: + ico = self.icons[icocode] + overlay = overlayer(x, y, ico.pixmap, ico.rect, ico.alpha) + sprites.append((info, overlay)) + except KeyError: + #print "bad ico code", icocode + pass # ignore sprites with bad ico (probably not defined yet) + + t0, n = self.painttimes + n = n + 1 + if n == 50: + t = time.time() + t, t0 = t-t0, t + if t: + print "%.2f images per second, %.1f kbytes per second" % ( + float(n)/t, + float(self.tcpbytecounter+self.udpbytecounter)/1024/t) + self.tcpbytecounter = -self.udpbytecounter + n = 0 + self.painttimes = t0, n + + def get_taskbar(self): + y0 = self.height - self.TASKBAR_HEIGHT + iconlist = [] + f = 1.5 * time.time() + f = f-int(f) + pi = self.playericons.items() + pi.sort() + xpos = 0 + for id, ico in pi: + if self.playing.get(id) != 'l': + w, h = ico.size + xpos += int(w * 5 / 3) + if not self.playing.get(id): + y = self.height - h + if self.keydefinition and id == self.keydefinition[0]: + num, icons = self.keys[self.nextkeyname()] + ico = icons[int(f*len(icons))-1] + y = y0 + int((self.TASKBAR_HEIGHT-ico.size[1])/2) + self.animdelay = 0.04 + iconlist.append((xpos-w, y, ico, id)) + pi.reverse() + f = f * (1.0-f) * 4.0 + xpos = self.width + for id, ico in pi: + if self.playing.get(id) == 'l': + w, h = ico.size + xpos -= int(w * 5 / 3) + dy = self.TASKBAR_HEIGHT - h - 1 + y = self.height - h - int(dy*f) + iconlist.append((xpos, y, ico, id)) + self.animdelay = 0.04 + return y0, iconlist + + def clic_taskbar(self, (cx,cy)): + y0, icons = self.get_taskbar() + if cy >= y0: + for x, y, ico, id in icons: + if x <= cx < x+ico.size[0]: + return id + return None + + def draw_taskbar(self): + y0, icons = self.get_taskbar() + rect = (0, y0, self.width, self.TASKBAR_HEIGHT) + bkgnd = self.dpy.getppm(rect) + self.dpy.taskbar(rect) + for x, y, ico, id in icons: + try: + self.dpy.putppm(x, y, ico.pixmap, ico.rect) + except KeyError: + pass + return y0, bkgnd + + def erase_taskbar(self, (y0, bkgnd)): + self.dpy.putppm(0, y0, bkgnd) + + def nextkeyname(self): + pid, df = self.keydefinition + undef = [(num, keyname) for keyname, (num, icons) in self.keys.items() + if not df.has_key(keyname) and icons] + if undef: + num, keyname = min(undef) + return keyname + else: + return None + + def startplaying(self): + args = () + if hasattr(self.s, 'udp_over_udp_mixer'): + # for SocketOverUdp: reuse the UDP address + port = self.s.getsockname()[1] + self.udpsock_low = None + self.s.udp_over_udp_decoder = self.udp_over_udp_decoder + self.start_udp_over_tcp() + elif self.pending_udp_data is not None: + port = MSG_INLINE_FRAME + else: + if '*udpsock*' in PORTS: + self.udpsock, (host, port) = PORTS['*udpsock*'] + args = (host,) + else: + self.udpsock = socket(AF_INET, SOCK_DGRAM) + self.udpsock.bind(('', PORTS.get('CLIENT', INADDR_ANY))) + host, port = self.udpsock.getsockname() + # Send a dummy UDP message to the server. Some NATs will + # then let through the UDP messages from the server. + self.udpsock.sendto('.', self.s.getpeername()) + self.iwtd.append(self.udpsock) + self.initial_iwtd.append(self.udpsock) + if 'sendudpto' in PORTS: + args = (PORTS['sendudpto'],) + outbound = [] + outbound.append(message(CMSG_UDP_PORT, port, *args)) + if self.snd and self.snd.has_music: + outbound.append(message(CMSG_ENABLE_MUSIC, 1)) + outbound.append(message(CMSG_PING)) + self.s.sendall(''.join(outbound)) + + def start_udp_over_tcp(self): + self.pending_udp_data = '' + self.udp_over_tcp_decompress = zlib.decompressobj().decompress + self.udpsock_low = None + for name in ('udpsock', 'udpsock2'): + sock = getattr(self, name) + if sock is not None: + try: + self.iwtd.remove(sock) + except ValueError: + pass + try: + self.initial_iwtd.remove(sock) + except ValueError: + pass + sock.close() + setattr(self, name, None) + + def udp_over_udp_decoder(self, udpdata): + if len(udpdata) > 3 and '\x80' <= udpdata[0] < '\x90': + data = self.dynamic_decompress(udpdata) + if data: + self.pending_udp_data = data + + def processkeys(self): + keyevents = self.dpy.keyevents() + if keyevents: + now = time.time() + pending = {} + for keysym, event in keyevents: + pending[keysym] = event + for keysym, event in pending.items(): + code = self.keycodes.get((keysym, event)) + if code and self.playing.get(code[0]) == 'l': + if (code == self.last_key_event[0] and + now - self.last_key_event[1] < 0.77): + continue # don't send too much events for auto-repeat + self.last_key_event = code, now + self.s.sendall(code[1]) + elif self.keydefinition: + self.define_key(keysym) + pointermotion = self.dpy.pointermotion() + if pointermotion: + x, y = pointermotion + self.settaskbar(y >= self.height - 2*self.TASKBAR_HEIGHT) + mouseevents = self.dpy.mouseevents() + if mouseevents: + self.settaskbar(1) + self.keydefinition = None + for clic in mouseevents: + clic_id = self.clic_taskbar(clic) + if clic_id is not None: + if self.playing.get(clic_id) == 'l': + self.s.sendall(message(CMSG_REMOVE_PLAYER, clic_id)) + else: + self.keydefinition = clic_id, {} + if self.taskbartimeout is not None and time.time() > self.taskbartimeout: + self.settaskbar(0) + + def settaskbar(self, nmode): + self.taskbartimeout = None + if self.taskbarfree: + self.taskbarmode = (nmode or + 'l' not in self.playing.values() or + (self.keydefinition is not None)) + if nmode: + self.taskbartimeout = time.time() + 5.0 + if hasattr(self.dpy, 'settaskbar'): + self.dpy.settaskbar(self.taskbarmode) + + def define_key(self, keysym): + clic_id, df = self.keydefinition + if keysym in df.values(): + return + df[self.nextkeyname()] = keysym + if self.nextkeyname() is not None: + return + self.keydefinition = None + self.s.sendall(message(CMSG_ADD_PLAYER, clic_id)) + for keyname, (num, icons) in self.keys.items(): + if keyname[:1] == '-': + event = KeyReleased + keyname = keyname[1:] + else: + event = KeyPressed + if df.has_key(keyname): + keysym = df[keyname] + self.keycodes[keysym, event] = \ + clic_id, message(CMSG_KEY, clic_id, num) + + def msg_unknown(self, *rest): + print >> sys.stderr, "?" + + def msg_player_join(self, id, local, *rest): + if local: + self.playing[id] = 'l' + self.settaskbar(0) + self.checkcfgfile(1) + else: + self.playing[id] = 1 + + def msg_player_kill(self, id, *rest): + self.playing[id] = 0 + for key, (pid, msg) in self.keycodes.items(): + if pid == id: + del self.keycodes[key] + + def msg_broadcast_port(self, port): + if self.pending_udp_data is not None: + return + if self.udpsock2 is not None: + try: + self.iwtd.remove(self.udpsock2) + except ValueError: + pass + try: + self.initial_iwtd.remove(self.udpsock2) + except ValueError: + pass + self.udpsock2.close() + self.udpsock2 = None + self.accepted_broadcast = 0 + try: + self.udpsock2 = socket(AF_INET, SOCK_DGRAM) + self.udpsock2.bind(('', port)) + self.udpsock2.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + except error, e: + print "Cannot listen on the broadcast port %d" % port, str(e) + self.udpsock2 = None + else: + self.iwtd.append(self.udpsock2) + self.initial_iwtd.append(self.udpsock2) + + def msg_def_playfield(self, width, height, backcolor=None, + gameident=None, *rest): + #if self.snd is not None: + # self.snd.close() + if self.dpy is not None: + # clear all pixmaps + for ico in self.icons.values(): + ico.clear() + self.pixmaps.clear() + self.dpy.close() + del self.sprites[:] + self.width = width + self.height = height + if gameident: + self.gameident = gameident + self.dpy = modes.open_dpy(self.screenmode, width, height, self.gameident) + self.snd = self.snd or modes.open_snd(self.screenmode) + if self.snd: + self.s.sendall(message(CMSG_ENABLE_SOUND)) + self.iwtd = self.dpy.selectlist() + self.initial_iwtd + self.dpy.clear() # backcolor is ignored + self.painttimes = (time.time(), 0) + self.s.sendall(message(CMSG_PING)) + self.taskbarmode = 0 + self.taskbarfree = 0 + self.taskbartimeout = None + self.keydefinition = None + + def msg_def_key(self, name, num, *icons): + self.keys[name] = num, [self.geticon(ico) for ico in icons] + + def msg_def_icon(self, bmpcode, icocode, x, y, w, h, alpha=255, *rest): +## if h<0: +## try: +## bitmap, height = self.flippedbitmaps[bmpcode] +## except KeyError: +## bitmap, height = self.dpy.vflipppm(self.bitmaps[bmpcode]) +## self.flippedbitmaps[bmpcode] = bitmap, height +## y = height - y +## h = - h +## else: + ico = self.geticon(icocode) + ico.bmpcode = bmpcode + ico.rect = x, y, w, h + ico.size = w, h + if alpha < 255: + ico.alpha = alpha + + def msg_def_bitmap(self, bmpcode, data, colorkey=None, *rest): + if type(data) is not type(''): + data = self.fileids[data] + self.bitmaps[bmpcode] = data, colorkey + + def msg_def_sample(self, smpcode, data, *rest): + def ready(f, self=self, smpcode=smpcode): + if self.snd: + self.sounds[smpcode] = self.snd.sound(f) + f.clear() + if type(data) is type(''): + data = zlib.decompress(data) + f = DataChunk(None) + f.store(0, data) + ready(f) + else: + f = self.fileids[data] + f.when_ready(ready) + + def msg_patch_file(self, fileid, position, data, lendata=None, *rest): + if self.fileids.has_key(fileid): + f = self.fileids[fileid] + else: + f = self.fileids[fileid] = DataChunk(fileid) + f.server_patch(position, data, lendata or len(data)) + + def msg_zpatch_file(self, fileid, position, data, *rest): + data1 = zlib.decompress(data) + self.msg_patch_file(fileid, position, data1, len(data), *rest) + + def msg_md5_file(self, fileid, filename, position, length, checksum, *rest): + if self.fileids.has_key(fileid): + f = self.fileids[fileid] + else: + f = self.fileids[fileid] = DataChunk(fileid) + f.server_md5(self, filename, position, length, checksum) + + def msg_play_music(self, loop_from, *codes): + codes = [self.fileids[c] for c in codes] + self.currentmusic = loop_from, codes, list(codes) + self.activate_music() + + def activate_music(self, f=None): + loop_from, codes, checkcodes = self.currentmusic + if checkcodes: + checkcodes.pop().when_ready(self.activate_music) + elif self.snd: + self.snd.play_musics(codes, loop_from) + + def msg_fadeout(self, time, *rest): + if self.snd: + self.snd.fadeout(time) + + def msg_player_icon(self, pid, icocode, *rest): + self.playericons[pid] = self.geticon(icocode) + + def checkcfgfile(self, force=0): + if self.trackcfgfile: + try: + st = os.stat(self.trackcfgfile) + except OSError: + pass + else: + if force or (st.st_mtime != self.trackcfgmtime): + self.trackcfgmtime = st.st_mtime + try: + f = open(self.trackcfgfile, 'r') + data = f.read().strip() + f.close() + d = eval(data or '{}', {}, {}) + except: + print >> sys.stderr, 'Invalid config file format' + else: + d = d.get(gethostname(), {}) + namemsg = '' + for id, local in self.playing.items(): + keyid = 'player%d' % id + if local == 'l' and d.has_key(keyid): + namemsg = namemsg + message( + CMSG_PLAYER_NAME, id, d[keyid]) + if namemsg: + self.s.sendall(namemsg) + + def msg_ping(self, *rest): + self.s.sendall(message(CMSG_PONG, *rest)) + self.checkcfgfile() + if rest and self.udpsock_low is not None: + udpkbytes = rest[0] + if not udpkbytes: + return + #inp = self.udpbytecounter / (udpkbytes*1024.0) + #print "(%d%% packet loss)" % int(100*(1.0-inp)) + if (udpkbytes<<10) * UDP_EXPECTED_RATIO > self.udpbytecounter: + # too many packets were dropped (including, maybe, all of them) + self.udpsock_low += 1 + if self.udpsock_low >= 3 and self.initlevel >= 1: + # third time now -- that's too much + print "Note: routing UDP traffic over TCP", + inp = self.udpbytecounter / (udpkbytes*1024.0) + print "(%d%% packet loss)" % int(100*(1.0-inp)) + self.start_udp_over_tcp() + self.s.sendall(message(CMSG_UDP_PORT, MSG_INLINE_FRAME)) + else: + # enough packets received + self.udpsock_low = 0 + + def msg_pong(self, *rest): + if self.initlevel == 0: + self.startplaying() + self.initlevel = 1 + elif self.initlevel == 1: + if self.snd and self.snd.has_music: + self.s.sendall(message(CMSG_ENABLE_MUSIC, 2)) + self.initlevel = 2 + if not self.taskbarfree and not self.taskbarmode: + self.taskbarfree = 1 + self.settaskbar(1) + + def msg_inline_frame(self, data, *rest): + if self.pending_udp_data is not None: + self.pending_udp_data = self.udp_over_tcp_decompress(data) + + MESSAGES = { + MSG_BROADCAST_PORT:msg_broadcast_port, + MSG_DEF_PLAYFIELD: msg_def_playfield, + MSG_DEF_KEY : msg_def_key, + MSG_DEF_ICON : msg_def_icon, + MSG_DEF_BITMAP : msg_def_bitmap, + MSG_DEF_SAMPLE : msg_def_sample, + MSG_PLAY_MUSIC : msg_play_music, + MSG_FADEOUT : msg_fadeout, + MSG_PLAYER_JOIN : msg_player_join, + MSG_PLAYER_KILL : msg_player_kill, + MSG_PLAYER_ICON : msg_player_icon, + MSG_PING : msg_ping, + MSG_PONG : msg_pong, + MSG_INLINE_FRAME : msg_inline_frame, + MSG_PATCH_FILE : msg_patch_file, + MSG_ZPATCH_FILE : msg_zpatch_file, + MSG_MD5_FILE : msg_md5_file, +## MSG_LOAD_PREFIX : msg_load_prefix, + } + + +def run(s, sockaddr, *args, **kw): + try: + import psyco + except ImportError: + pass + else: + psyco.bind(Playfield.update_sprites) + Playfield(s, sockaddr).run(*args, **kw) diff --git a/display/playback.py b/display/playback.py new file mode 100644 index 0000000..f0e698b --- /dev/null +++ b/display/playback.py @@ -0,0 +1,203 @@ +#! /usr/bin/env python + +import sys, os, gzip +from socket import * +from select import select +import cStringIO, struct, zlib +import time +sys.path.insert(0, os.pardir) +from common.msgstruct import * +from common import hostchooser +import modes +from modes import KeyPressed, KeyReleased + +#import psyco; psyco.full() + +SOURCEDIR = os.pardir + + +def loadpixmap(dpy, data, colorkey=None): + f = cStringIO.StringIO(data) + sig = f.readline().strip() + assert sig == "P6" + while 1: + line = f.readline().strip() + if not line.startswith('#'): + break + wh = line.split() + w, h = map(int, wh) + sig = f.readline().strip() + assert sig == "255" + data = f.read() + f.close() + if colorkey is None: + colorkey = -1 + elif colorkey < 0: + r, g, b = struct.unpack("BBB", self.data[:3]) + colorkey = b | (g<<8) | (r<<16) + return dpy.pixmap(w, h, data, colorkey) + +class Icon: + def __init__(self, bitmap, (x, y, w, h), alpha): + self.rect = x, y, w, h + self.size = w, h + self.bitmap = bitmap + self.alpha = alpha + + +class Playback: + gameident = 'Playback' + + def __init__(self, filename, mode=('x', 'off', {})): + f = gzip.open(filename, 'rb') + self.screenmode = mode + self.width = None + self.deffiles = {} + self.defbitmaps = {} + self.deficons = {} + self.icons = {} + self.frames = [] + inbuf = '' + while 1: + values, inbuf = decodemessage(inbuf) + if not values: + # incomplete message + data = f.read(8192) + if not data: + break + inbuf += data + else: + #print values[0], + fn = Playback.MESSAGES.get(values[0], self.msg_unknown) + fn(self, *values[1:]) + print '%d frames in file.' % len(self.frames) + f.close() + assert self.width, "no playfield definition found in file" + + self.dpy = modes.open_dpy(self.screenmode, + self.width, self.height, self.gameident) + self.dpy.clear() # backcolor is ignored + self.sprites = [] + self.buildicons() + self.go(0) + + def buildicons(self): + bitmaps = {} + for bmpcode, (data, colorkey) in self.defbitmaps.items(): + if isinstance(data, str): + data = zlib.decompress(data) + else: + data = self.deffiles[data] + bitmaps[bmpcode] = loadpixmap(self.dpy, data, colorkey) + for icocode, (bmpcode, rect, alpha) in self.deficons.items(): + self.icons[icocode] = Icon(bitmaps[bmpcode], rect, alpha) + + def go(self, n): + self.n = n + self.update_sprites(self.frames[n]) + self.dpy.flip() + + def save(self, filename=None): + "shm only!" + w, h, data, reserved = self.dpy.getppm((0, 0, self.width, self.height)) + f = open(filename or ('frame%d.ppm' % self.n), 'wb') + print >> f, 'P6' + print >> f, w, h + print >> f, 255 + for i in range(0, len(data), 4): + f.write(data[i+2]+data[i+1]+data[i]) + f.close() + + def update_sprites(self, udpdata): + sprites = self.sprites + unpack = struct.unpack + base = 0 + for j in range(len(sprites)): + if sprites[j][0] != udpdata[base:base+6]: + removes = sprites[j:] + del sprites[j:] + removes.reverse() + eraser = self.dpy.putppm + for reserved, eraseargs in removes: + eraser(*eraseargs) + break + base += 6 + try: + overlayer = self.dpy.overlayppm + except AttributeError: + getter = self.dpy.getppm + setter = self.dpy.putppm + #print "%d sprites redrawn" % (len(udpdata)/6-j) + for j in range(base, len(udpdata)-5, 6): + info = udpdata[j:j+6] + x, y, icocode = unpack("!hhh", info[:6]) + try: + ico = self.icons[icocode] + sprites.append((info, (x, y, getter((x, y) + ico.size)))) + setter(x, y, ico.bitmap, ico.rect) + except KeyError: + #print "bad ico code", icocode + pass # ignore sprites with bad ico (probably not defined yet) + else: + for j in range(base, len(udpdata)-5, 6): + info = udpdata[j:j+6] + x, y, icocode = unpack("!hhh", info[:6]) + try: + ico = self.icons[icocode] + overlay = overlayer(x, y, ico.bitmap, ico.rect, ico.alpha) + sprites.append((info, overlay)) + except KeyError: + #print "bad ico code", icocode + pass # ignore sprites with bad ico (probably not defined yet) + + def msg_unknown(self, *rest): + pass + + def msg_patch_file(self, fileid, position, data, lendata=None, *rest): + try: + s = self.deffiles[fileid] + except KeyError: + s = '' + if len(s) < position: + s += '\x00' * (position-len(s)) + s = s[:position] + data + s[position+len(s):] + self.deffiles[fileid] = s + + def msg_zpatch_file(self, fileid, position, data, *rest): + data1 = zlib.decompress(data) + self.msg_patch_file(fileid, position, data1, len(data), *rest) + + def msg_md5_file(self, fileid, filename, position, length, checksum, *rest): + fn = os.path.join(SOURCEDIR, filename) + f = open(fn, 'rb') + f.seek(position) + data = f.read(length) + f.close() + assert len(data) == length + self.msg_patch_file(fileid, position, data) + + def msg_def_playfield(self, width, height, *rest): + self.width, self.height = width, height + + def msg_def_icon(self, bmpcode, icocode, x, y, w, h, alpha=255, *rest): + self.deficons[icocode] = bmpcode, (x, y, w, h), alpha + + def msg_def_bitmap(self, bmpcode, data, colorkey=None, *rest): + self.defbitmaps[bmpcode] = data, colorkey + + def msg_recorded(self, data): + self.frames.append(data) + + MESSAGES = { + MSG_PATCH_FILE : msg_patch_file, + MSG_ZPATCH_FILE : msg_zpatch_file, + MSG_MD5_FILE : msg_md5_file, + MSG_DEF_PLAYFIELD: msg_def_playfield, + MSG_DEF_ICON : msg_def_icon, + MSG_DEF_BITMAP : msg_def_bitmap, + MSG_RECORDED : msg_recorded, + } + + +if __name__ == '__main__' and len(sys.argv) > 1: + p = Playback(sys.argv[1]) diff --git a/display/puremixer.py b/display/puremixer.py new file mode 100644 index 0000000..d0b567a --- /dev/null +++ b/display/puremixer.py @@ -0,0 +1,123 @@ +import sys, audioop + + +class PureMixer: + # + # An audio mixer in Python based on audioop + # + # Note that opening the audio device itself is outside the scope of + # this module. Anything else could also be done with the mixed data, + # e.g. stored on disk, for all this module knows. + + def __init__(self, freq=44100, bits=8, signed=0, + channels=1, byteorder=None): + """Open the mixer and set its parameters.""" + self.freq = freq + self.bytes = bits/8 + self.signed = signed + self.channels = channels + self.byteorder = byteorder or sys.byteorder + self.parameters = (freq, self.bytes, signed, channels, self.byteorder) + self.bytespersample = channels*self.bytes + self.queue = '\x00' * self.bytes + + def resample(self, data, freq=44100, bits=8, signed=0, + channels=1, byteorder=None): + "Convert a sample to the mixer's own format." + bytes = bits/8 + byteorder = byteorder or sys.byteorder + if (freq, bytes, signed, channels, byteorder) == self.parameters: + return data + # convert to native endianness + if byteorder != sys.byteorder: + data = byteswap(data, bytes) + byteorder = sys.byteorder + # convert unsigned -> signed for the next operations + if not signed: + data = audioop.bias(data, bytes, -(1<<(bytes*8-1))) + signed = 1 + # convert stereo -> mono + while channels > self.channels: + assert channels % 2 == 0 + data = audioop.tomono(data, bytes, 0.5, 0.5) + channels /= 2 + # resample to self.freq + if freq != self.freq: + data, ignored = audioop.ratecv(data, bytes, channels, + freq, self.freq, None) + freq = self.freq + # convert between 8bits and 16bits + if bytes != self.bytes: + data = audioop.lin2lin(data, bytes, self.bytes) + bytes = self.bytes + # convert mono -> stereo + while channels < self.channels: + data = audioop.tostereo(data, bytes, 1.0, 1.0) + channels *= 2 + # convert signed -> unsigned + if not self.signed: + data = audioop.bias(data, bytes, 1<<(bytes*8-1)) + signed = 0 + # convert to mixer endianness + if byteorder != self.byteorder: + data = byteswap(data, bytes) + byteorder = self.byteorder + # done + if (freq, bytes, signed, channels, byteorder) != self.parameters: + raise ValueError, 'sound sample conversion failed' + return data + + def wavesample(self, file): + "Read a sample from a .wav file (or file-like object)." + import wave + w = wave.open(file, 'r') + return self.resample(w.readframes(w.getnframes()), + freq = w.getframerate(), + bits = w.getsampwidth() * 8, + signed = w.getsampwidth() > 1, + channels = w.getnchannels(), + byteorder = 'little') + + def mix(self, mixer_channels, bufsize): + """Mix the next batch buffer. + Each object in the mixer_channels list must be a file-like object + with a 'read(size)' method.""" + data = '' + already_seen = {} + channels = mixer_channels[:] + channels.reverse() + for c in channels: + if already_seen.has_key(c): + data1 = '' + else: + data1 = c.read(bufsize) + already_seen[c] = 1 + if data1: + l = min(len(data), len(data1)) + data = (audioop.add(data[:l], data1[:l], 1) + + (data1[l:] or data[l:])) + else: + try: + mixer_channels.remove(c) + except ValueError: + pass + data += self.queue * ((bufsize - len(data)) / self.bytes) + self.queue = data[-self.bytes:] + return data + + +def byteswap(data, byte): + if byte == 1: + return + if byte == 2: + typecode = 'h' + elif byte == 4: + typecode = 'i' + else: + raise ValueError, 'cannot convert endianness for samples of %d bytes' % byte + import array + a = array.array(typecode, data) + if a.itemsize != byte: + raise ValueError, 'endianness convertion failed' + a.byteswap() + return a.tostring() diff --git a/display/pythonxlibintf.py b/display/pythonxlibintf.py new file mode 100644 index 0000000..5e32715 --- /dev/null +++ b/display/pythonxlibintf.py @@ -0,0 +1,202 @@ + + ################################################ +## pygame-based implementation of xshm ## +################################################ + +import os, sys +from Xlib import X, display + + +# -*-*- SLOOWWW -*-*- +import psyco; psyco.full() + + +class Display: + + def __init__(self, width, height, title): + self.dpy = display.Display() + self.default_scr = self.dpy.screen() + self.root = self.default_scr.root + self.width = width + self.height = height + self.depth = self.default_scr.root_depth + + self.backpixmap = self.root.create_pixmap(width, height, self.depth) + self.win = self.root.create_window( + 0, 0, width, height, 0, self.depth, + override_redirec = 0, + background_pixel = self.default_scr.black_pixel, + backing_store = X.NotUseful, + ) + self.win.map() + + self.gc = self.win.create_gc() + self.gc_and = self.win.create_gc() + self.gc_or = self.win.create_gc() + + self.gc.change(foreground = self.default_scr.black_pixel) + self.gc_and.change(function = X.GXand) + self.gc_or .change(function = X.GXor) + + self.selectinput = 0 + self.keyev = [] + self.mouseev = [] + self.motionev = None + self.dpy.flush() + + pixel = "\x00\x00\x80" + hole = "\x01\x01\x01" + self.taskbkgnd = self.pixmap(32, 32, + ((pixel+hole)*16 + (hole+pixel)*16) * 16, + 0x010101) + + def pixmap(self, w, h, data, colorkey=-1): + print >> sys.stderr, '.', + extent = w*h + depth = self.depth + if depth >= 24: + bitmap_pad = 32 + else: + bitmap_pad = 16 + scanline = ((w+bitmap_pad-1) & ~(bitmap_pad-1)) / 8; + if colorkey >= 0: + key = (chr(colorkey >> 16) + + chr((colorkey>>8) & 0xFF) + + chr(colorkey & 0xFF)) + else: + key = None + if depth == 15: + p_size = 5, 5, 5 + elif depth == 16: + p_size = 5, 6, 5 + elif depth == 24 or depth == 32: + p_size = 8, 8, 8 + else: + raise ValueError, 'unsupported screen depth %d' % depth + + imgdata = [] + maskdata = [] + + for color in range(3): + plane = 128 + while plane >= (1<<(8-p_size[color])): + src = 0 + for y in range(h): + imgline = 0L + maskline = 0L + shifter = 1L + for x in range(w): + if data[src:src+3] == key: + # transparent + maskline |= shifter + elif ord(data[src+color]) & plane: + imgline |= shifter + shifter <<= 1 + src += 3 + imgdata.append(long2string(imgline, scanline)) + maskdata.append(long2string(maskline, scanline)) + plane /= 2 + + imgdata = ''.join(imgdata) + if colorkey >= 0: + maskdata = ''.join(maskdata) + mask = self.win.create_pixmap(w, h, depth) + mask.put_image(self.gc, 0, 0, w, h, X.XYPixmap, depth, 0, maskdata) + else: + mask = None + imgdata = ''.join(imgdata) + image = self.win.create_pixmap(w, h, depth) + image.put_image(self.gc, 0, 0, w, h, X.XYPixmap, depth, 0, imgdata) + image.mask = mask + image.size = w, h + return image + + def getppm(self, (x, y, w, h), bkgnd=None): + if bkgnd is None: + bkgnd = self.win.create_pixmap(w, h, self.depth) + bkgnd.mask = None + bkgnd.size = w, h + bkgnd.copy_area(self.gc, self.backpixmap, x, y, w, h, 0, 0) + return bkgnd + + def putppm(self, x, y, image, rect=None): + if rect: + x1, y1, w1, h1 = rect + else: + x1 = y1 = 0 + w1, h1 = image.size + if image.mask is None: + self.backpixmap.copy_area(self.gc, image, x1, y1, w1, h1, x, y) + else: + self.backpixmap.copy_area(self.gc_and, image.mask, + x1, y1, w1, h1, x, y) + self.backpixmap.copy_area(self.gc_or, image, + x1, y1, w1, h1, x, y) + + def flip(self): + self.win.copy_area(self.gc, self.backpixmap, + 0, 0, self.width, self.height, 0, 0) + self.dpy.flush() + self.readXevents() + + def close(self): + self.dpy.close() + + def clear(self): + self.backpixmap.fill_rectangle(self.gc, 0, 0, self.width, self.height) + + def readXevents(self): + n = self.dpy.pending_events() + if n: + for i in range(n): + event = self.dpy.next_event() + if event.type == X.KeyPress or event.type == X.KeyRelease: + self.keyev.append((event.detail, event.type)) + elif event.type == X.ButtonPress: + self.mouseev.append((event.event_x, event.event_y)) + elif event.type == X.MotionNotify: + self.motionev = event.event_x, event.event_y + elif event.type == X.DestroyNotify: + raise SystemExit + self.readXevents() + + def enable_event(self, mask): + self.selectinput |= mask + self.win.change_attributes(event_mask=self.selectinput) + + def keyevents(self): + if not (self.selectinput & X.KeyReleaseMask): + self.enable_event(X.KeyPressMask | X.KeyReleaseMask) + self.readXevents() + result = self.keyev + self.keyev = [] + return result + + def mouseevents(self): + if not (self.selectinput & X.ButtonPressMask): + self.enable_event(X.ButtonPressMask) + result = self.mouseev + self.mouseev = [] + return result + + def pointermotion(self): + result = self.motionev + self.motionev = None + return result + + def has_sound(self): + return 0 + + def selectlist(self): + from socket import fromfd, AF_INET, SOCK_STREAM + return [fromfd(self.dpy.fileno(), AF_INET, SOCK_STREAM)] + + def taskbar(self, (x, y, w, h)): + for j in range(y, y+h, 32): + for i in range(x, x+w, 32): + self.putppm(i, j, self.taskbkgnd, + (0, 0, x+w-i, y+h-j)) + + +def long2string(bits, strlen): + return ''.join([chr((bits>>n)&0xFF) for n in range(0, 8*strlen, 8)]) diff --git a/display/setup.py b/display/setup.py new file mode 100755 index 0000000..4776e79 --- /dev/null +++ b/display/setup.py @@ -0,0 +1,16 @@ +#! /usr/bin/env python + +from distutils.core import setup +from distutils.extension import Extension + +setup ( name="xshm", + version="0.2", + description="X window system Shared Memory extension", + author="Armin & Odie", + author_email="arigo@tunes.org", + ext_modules=[Extension(name = 'xshm', + sources = ['xshm.c'], + include_dirs = ['/usr/X11R6/include'], + library_dirs = ['/usr/X11R6/lib'], + libraries = ['X11', 'Xext'])] + ) diff --git a/display/snd_linux.py b/display/snd_linux.py new file mode 100644 index 0000000..dae82e0 --- /dev/null +++ b/display/snd_linux.py @@ -0,0 +1,148 @@ +import sys +from cStringIO import StringIO +import puremixer +from music1 import Music + + +class Sound: + # Mono only + has_sound = has_music = 0 # until initialized + + BUFFERTIME = 0.09 + FLOPTIME = 0.07 + + Formats = [ + ('U8', 8, 0, None), + ('S8', 8, 1, None), + ('S16_NE', 16, 1, None), + ('S16_LE', 16, 1, 'little'), + ('S16_BE', 16, 1, 'big'), + ('U16_LE', 16, 0, 'little'), + ('U16_BE', 16, 0, 'big'), + ] + + def __init__(self, freq=44100, fmt='S16_NE'): + self.f = None + self.freq = int(freq) + self.format = fmt.upper() + self.params = p = {} + for name, p['bits'], p['signed'], p['byteorder'] in self.Formats: + if name == self.format: + break + else: + print >> sys.stderr, 'available sound formats:' + for name, bits, signed, byteorder in self.Formats: + print >> sys.stderr, ' %-8s %s' % (name, nicefmttext( + bits, signed, byteorder)) + sys.exit(2) + + import linuxaudiodev + try: + f = linuxaudiodev.open('w') + f.setparameters(self.freq, p['bits'], 1, + getattr(linuxaudiodev, 'AFMT_' + self.format)) + except Exception, e: + print >> sys.stderr, "sound disabled: %s: %s" % ( + e.__class__.__name__, e) + return + self.f = f + self.mixer = mixer = puremixer.PureMixer(**p) + buffertime = self.BUFFERTIME + self.bufsize = int(mixer.bytespersample*mixer.freq*buffertime + + 255.5) & ~ 255 + if self.bufsize > f.bufsize(): + self.bufsize = f.bufsize() + buffertime = self.bufsize / float(freq) + self.buffertime = buffertime + self.mixer_channels = [] + self.mixer_accum = {} + self.has_sound = 1 + self.has_music = 1 + + def close(self): + self.f.close() + self.f = None + + def sound(self, f): + return self.mixer.wavesample(f.fopen()) + + def flop(self): + self.mixer_accum = {} + if self.f is None: + return + for i in range(3): + bufsize = self.bufsize - self.f.obufcount() + if bufsize <= 0: + break + self.f.write(self.mixer.mix(self.mixer_channels, bufsize)) + #cnt = getattr(self, 'CNT', 0) + #import time + #print cnt, time.time() + #self.CNT = cnt+1 + return self.FLOPTIME + + def play(self, sound, lvolume, rvolume): + # volume ignored + if sound not in self.mixer_accum: + self.mixer_channels.append(StringIO(sound)) + self.mixer_accum[sound] = 1 + + def play_musics(self, musics, loop_from): + self.cmusics = musics, loop_from, -1 + if self.mixer_channels[:1] != [self]: + self.mixer_channels.insert(0, self) + + def read(self, size): + "Provide some more data to self.mixer.poll()." + musics, loop_from, c = self.cmusics + if c < 0: + data = '' + else: + data = musics[c].mixed.decode(self.mixer, size) + if not data: + c += 1 + if c >= len(musics): # end + c = loop_from + if c >= len(musics): + return '' + self.cmusics = musics, loop_from, c + try: + mixed = musics[c].mixed + except AttributeError: + mixed = musics[c].mixed = Music(musics[c].freezefilename()) + mixed.openchannel() + data = mixed.decode(self.mixer, size) + if 0 < len(data) < size: + data += self.read(size - len(data)) + return data + + def fadeout(self, millisec): + self.cmusics = [], 0, -1 + + +def imperror(): + try: + import linuxaudiodev + except ImportError: + if sys.platform.startswith('linux'): + return 'linuxaudiodev module not installed' + else: + return 'only available on Linux' + +def nicefmttext(bits, signed, byteorder): + s = '%s %d bits' % (signed and 'signed' or 'unsigned', bits) + if byteorder: + s += ' %s endian' % byteorder + return s + +def htmloptionstext(nameval): + import modes + l = ['<font size=-1>Sampling <%s>' % nameval('select', 'fmt')] + for name, bits, signed, byteorder in Sound.Formats: + l.append('<'+nameval('option', 'fmt', name, default='S16_NE')+'>'+ + nicefmttext(bits, signed, byteorder)) + l+= ['</select> rate ', + '<%s size=5>Hz</font>' % nameval('text', 'freq', default='44100'), + '<br>', + modes.musichtmloptiontext(nameval)] + return '\n'.join(l) diff --git a/display/snd_off.py b/display/snd_off.py new file mode 100644 index 0000000..9d2e640 --- /dev/null +++ b/display/snd_off.py @@ -0,0 +1,5 @@ +class Sound: + has_sound = 0 # no sound + has_music = 0 # no music + def __init__(self): + pass diff --git a/display/snd_pygame.py b/display/snd_pygame.py new file mode 100644 index 0000000..e1a1455 --- /dev/null +++ b/display/snd_pygame.py @@ -0,0 +1,82 @@ +import sys +from modes import musichtmloptiontext as htmloptionstext +from pygame.locals import * +import pygame.mixer + +if pygame.mixer is None: + raise ImportError + + +#ENDMUSICEVENT = USEREVENT + + +class Sound: + has_sound = has_music = 0 # until initialized + + def __init__(self): + try: + pygame.mixer.init() + except pygame.error, e: + print >> sys.stderr, "sound disabled: %s" % str(e) + else: + self.has_sound = 1 + try: + from pygame.mixer import music + except ImportError: + pass + else: + self.has_music = music is not None + self.cmusics = None + + def close(self): + try: + pygame.mixer.stop() + except pygame.error: + pass + if self.has_music: + try: + pygame.mixer.music.stop() + except pygame.error: + pass + + def sound(self, f): + return pygame.mixer.Sound(f.freezefilename()) + + def flop(self): + # the events are not processed if pygame is not also the display, + # so ENDMUSICEVENT will not arrive -- poll for end of music + if self.cmusics and not pygame.mixer.music.get_busy(): + self.next_music() + + def play(self, sound, lvolume, rvolume): + channel = pygame.mixer.find_channel(1) + channel.stop() + try: + channel.set_volume(lvolume, rvolume) + except TypeError: + channel.set_volume(0.5 * (lvolume+rvolume)) + channel.play(sound) + + def play_musics(self, musics, loop_from): + #dpy_pygame.EVENT_HANDLERS[ENDMUSICEVENT] = self.next_music + #pygame.mixer.music.set_endevent(ENDMUSICEVENT) + self.cmusics = musics, loop_from, 0 + self.next_music() + + def next_music(self, e=None): + if self.cmusics: + musics, loop_from, c = self.cmusics + if c >= len(musics): # end + c = loop_from + if c >= len(musics): + pygame.mixer.music.stop() + self.cmusics = None + return + pygame.mixer.music.load(musics[c].freezefilename()) + pygame.mixer.music.play() + self.cmusics = musics, loop_from, c+1 + + def fadeout(self, millisec): + #print "fadeout:", millisec + pygame.mixer.music.fadeout(millisec) + self.cmusics = None diff --git a/display/snd_windows.py b/display/snd_windows.py new file mode 100644 index 0000000..3aea3bd --- /dev/null +++ b/display/snd_windows.py @@ -0,0 +1,93 @@ +import sys +from cStringIO import StringIO +import puremixer +import wingame +from music1 import Music + + +class Sound: + # Mono only + has_sound = has_music = 0 # until initialized + + BUFFERTIME = 0.09 + FLOPTIME = 0.07 + + def __init__(self, freq=44100, bits=16): + self.freq = int(freq) + self.bits = int(bits) + self.bufsize = (int(self.BUFFERTIME*self.freq*self.bits/8) + 64) & ~63 + + try: + self.audio = wingame.Audio(1, self.freq, self.bits, self.bufsize) + except Exception, e: + print >> sys.stderr, "sound disabled: %s: %s" % ( + e.__class__.__name__, e) + return + self.mixer = puremixer.PureMixer(self.freq, self.bits, self.bits==16, + byteorder='little') + self.mixer_channels = [] + self.mixer_accum = {} + self.has_sound = 1 + self.has_music = 1 + + def stop(self): + self.audio.close() + + def sound(self, f): + return self.mixer.wavesample(f.fopen()) + + def flop(self): + self.mixer_accum = {} + while self.audio.ready(): + self.audio.write(self.mixer.mix(self.mixer_channels, self.bufsize)) + return self.FLOPTIME + + def play(self, sound, lvolume, rvolume): + # volume ignored + if sound not in self.mixer_accum: + self.mixer_channels.append(StringIO(sound)) + self.mixer_accum[sound] = 1 + + def play_musics(self, musics, loop_from): + self.cmusics = musics, loop_from, -1 + self.mixer_channels.insert(0, self) + + def read(self, size): + "Provide some more data to self.mixer.poll()." + musics, loop_from, c = self.cmusics + if c < 0: + data = '' + else: + data = musics[c].mixed.decode(self.mixer, size) + if not data: + c += 1 + if c >= len(musics): # end + c = loop_from + if c >= len(musics): + return '' + self.cmusics = musics, loop_from, c + try: + mixed = musics[c].mixed + except AttributeError: + mixed = musics[c].mixed = Music(musics[c].freezefilename()) + mixed.openchannel() + data = mixed.decode(self.mixer, size) + if 0 < len(data) < size: + data += self.read(size - len(data)) + return data + + def fadeout(self, millisec): + self.cmusics = [], 0, -1 + + +def htmloptionstext(nameval): + import modes + l = ['<font size=-1>Sampling <%s>' % nameval('select', 'bits')] + for bits in (8, 16): + l.append('<'+nameval('option', 'bits', str(bits), default='16')+'>'+ + '%d bits' % bits) + l+= ['</select> rate ', + '<%s size=5>Hz</font>' % nameval('text', 'freq', default='44100'), + '<br>', + modes.musichtmloptiontext(nameval)] + return '\n'.join(l) diff --git a/display/windows/.cvsignore b/display/windows/.cvsignore new file mode 100644 index 0000000..0e01180 --- /dev/null +++ b/display/windows/.cvsignore @@ -0,0 +1,3 @@ +*.ncb +*.opt +*.plg diff --git a/display/windows/wingame.c b/display/windows/wingame.c new file mode 100755 index 0000000..644e496 --- /dev/null +++ b/display/windows/wingame.c @@ -0,0 +1,867 @@ +#include <Python.h>
+#include <windows.h>
+#include <mmsystem.h> +
+
+ /************************** DISPLAY PART ***************************/
+
+typedef struct {
+ BITMAPINFOHEADER bmiHeader;
+ union {
+ DWORD bmiMask[3];
+ short bmiIndices[255];
+ };
+} screenbmp_t;
+
+typedef struct {
+ PyObject_HEAD
+ HWND win;
+ int width, height, bpp;
+ HDC dc;
+ HPALETTE hpal, hprevpal;
+ screenbmp_t screenbmpinfo;
+ unsigned char* screenbmpdata;
+ unsigned char* screenfirstline;
+ int screenscanline; /* < 0 ! */
+ PyObject* keyevents;
+ PyObject* mouseevents;
+ PyObject* motionevent;
+} DisplayObject;
+
+#define DisplayObject_Check(v) ((v)->ob_type == &Display_Type)
+staticforward PyTypeObject Display_Type;
+
+
+static void flush(DisplayObject* self)
+{
+ /*GdiFlush();*/
+}
+
+static void release_window_data(DisplayObject* self)
+{
+ if (self->hprevpal)
+ {
+ SelectPalette(self->dc, self->hprevpal, FALSE);
+ self->hprevpal = (HPALETTE) NULL;
+ }
+ if (self->hpal)
+ {
+ DeleteObject(self->hpal);
+ self->hpal = (HPALETTE) NULL;
+ }
+ if (self->dc && self->win)
+ {
+ ReleaseDC(self->win, self->dc);
+ self->dc = (HDC) NULL;
+ }
+}
+
+static void display_close(DisplayObject* self)
+{
+ release_window_data(self);
+ if (self->win)
+ {
+ SetWindowLong(self->win, 0, 0);
+ DestroyWindow(self->win);
+ self->win = (HWND) NULL;
+ }
+}
+
+static LRESULT CALLBACK display_proc(HWND hwnd, UINT uMsg,
+ WPARAM wParam, LPARAM lParam)
+{
+ DisplayObject* self;
+ switch (uMsg) {
+
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ self = (DisplayObject*) GetWindowLong(hwnd, 0);
+ if (self)
+ {
+ PyObject* v;
+ int etype;
+ if (self->keyevents == NULL)
+ {
+ self->keyevents = PyList_New(0);
+ if (self->keyevents == NULL)
+ break;
+ }
+ etype = (uMsg == WM_KEYDOWN) ? 2 : 3;
+ v = Py_BuildValue("ii", (int) wParam, etype);
+ if (v == NULL)
+ break;
+ PyList_Append(self->keyevents, v);
+ Py_DECREF(v);
+ }
+ break;
+
+ case WM_LBUTTONDOWN:
+ self = (DisplayObject*) GetWindowLong(hwnd, 0);
+ if (self)
+ {
+ PyObject* v;
+ if (self->mouseevents == NULL)
+ {
+ self->mouseevents = PyList_New(0);
+ if (self->mouseevents == NULL)
+ break;
+ }
+ v = Py_BuildValue("ii", LOWORD(lParam), HIWORD(lParam));
+ if (v == NULL)
+ break;
+ PyList_Append(self->mouseevents, v);
+ Py_DECREF(v);
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ self = (DisplayObject*) GetWindowLong(hwnd, 0);
+ if (self)
+ {
+ Py_XDECREF(self->motionevent);
+ self->motionevent = Py_BuildValue("ii", LOWORD(lParam), HIWORD(lParam));
+ }
+ break;
+
+ case WM_DESTROY:
+ self = (DisplayObject*) GetWindowLong(hwnd, 0);
+ if (self)
+ {
+ release_window_data(self);
+ self->win = (HWND) NULL;
+ }
+ break;
+
+ default:
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+
+ return 0;
+}
+
+static PyObject* new_display(PyObject* dummy, PyObject* args)
+{
+ char* CLASSNAME = "winxshm";
+ WNDCLASS wcls;
+ DisplayObject* self;
+ int width, height, bytes, bpp, use_shm=0;
+ if (!PyArg_ParseTuple(args, "ii|i", &width, &height, &use_shm))
+ return NULL;
+
+ self = PyObject_New(DisplayObject, &Display_Type);
+ if (self == NULL)
+ return NULL;
+
+ memset(&self->win, 0, ((char*)(self+1)) - ((char*)(&self->win)));
+ self->width = width;
+ self->height = height;
+
+ /* set window class */
+ memset(&wcls, 0, sizeof(wcls));
+ wcls.style = CS_BYTEALIGNCLIENT;
+ wcls.lpfnWndProc = &display_proc;
+ wcls.cbWndExtra = sizeof(DisplayObject*);
+ //wcls.hInstance = HINSTANCE;
+ wcls.hCursor = LoadCursor(0, IDC_ARROW);
+ wcls.lpszClassName = CLASSNAME;
+ RegisterClass(&wcls);
+
+ /* Create the window */
+ self->win = CreateWindowEx(0, CLASSNAME, NULL,
+ WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
+ WS_MINIMIZEBOX | WS_VISIBLE,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ width + 2*GetSystemMetrics(SM_CXFIXEDFRAME),
+ height + 2*GetSystemMetrics(SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CYCAPTION),
+ (HWND) NULL, (HMENU) NULL,
+ /*HINSTANCE*/ 0, (LPVOID) NULL);
+ if (self->win == (HWND) NULL) goto err2;
+ SetWindowLong(self->win, 0, (long) self);
+
+ /* Create DC */
+ self->dc = GetDC(self->win);
+ if (!self->dc) goto err2;
+ self->bpp = bpp = GetDeviceCaps(self->dc, BITSPIXEL);
+ if (bpp == 8)
+ {
+ struct {
+ WORD palVersion;
+ WORD palNumEntries;
+ PALETTEENTRY palPalEntry[255];
+ } pal;
+ pal.palNumEntries = GetSystemPaletteEntries(self->dc, 0, 255, pal.palPalEntry);
+ if (pal.palNumEntries != 0)
+ {
+ int i;
+ pal.palVersion = 0x300;
+ self->hpal = CreatePalette((LOGPALETTE*)(&pal));
+ self->screenbmpinfo.bmiHeader.biClrUsed = pal.palNumEntries;
+ self->hprevpal = SelectPalette(self->dc, self->hpal, FALSE);
+ for (i=0; i<pal.palNumEntries; i++) {
+ self->screenbmpinfo.bmiIndices[i] = i;
+ }
+ }
+ }
+ if (bpp != 15 && bpp != 16 && bpp != 24 && bpp != 32 && !self->hpal)
+ {
+ bpp = 24; /* default */
+ fprintf(stderr, "WARNING: a hi/true color screen mode of 15, 16, 24 or 32 bits per pixels\n");
+ fprintf(stderr, " is highly recommended !\n");
+ }
+
+ /* Allocate screen bitmaps */
+ bytes = (bpp+7)/8; /* per pixel */
+ bytes = (bytes*width+3)&~3; /* per scan line */
+ self->screenscanline = -bytes;
+ bytes = bytes*height; /* for the whole screen */
+ self->screenbmpdata = PyMem_Malloc(bytes);
+ self->screenfirstline = self->screenbmpdata + bytes + self->screenscanline;
+ if (self->screenbmpdata == NULL)
+ {
+ PyErr_NoMemory();
+ goto err2;
+ }
+ self->screenbmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ self->screenbmpinfo.bmiHeader.biWidth = self->width;
+ self->screenbmpinfo.bmiHeader.biHeight = self->height;
+ self->screenbmpinfo.bmiHeader.biPlanes = 1;
+ self->screenbmpinfo.bmiHeader.biBitCount = (bpp+7)&~7;
+ if (bpp == 16)
+ {
+ self->screenbmpinfo.bmiHeader.biCompression = BI_BITFIELDS;
+ self->screenbmpinfo.bmiMask[0] = 0xF800;
+ self->screenbmpinfo.bmiMask[1] = 0x07E0;
+ self->screenbmpinfo.bmiMask[2] = 0x001F;
+ }
+
+ flush(self);
+ return (PyObject*) self;
+
+ err2:
+ display_close(self);
+ Py_DECREF(self);
+ if (!PyErr_Occurred())
+ PyErr_SetString(PyExc_IOError, "cannot open window");
+ return NULL;
+}
+
+static void display_dealloc(DisplayObject* self)
+{
+ display_close(self);
+ Py_XDECREF(self->keyevents);
+ Py_XDECREF(self->mouseevents);
+ Py_XDECREF(self->motionevent);
+ PyMem_Free(self->screenbmpdata);
+ PyObject_Del(self);
+}
+
+static PyObject* display_close1(DisplayObject* self, PyObject* args)
+{
+ display_close(self);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static int checkopen(DisplayObject* self)
+{
+ if (self->win)
+ return 1;
+ //PyErr_SetString(PyExc_IOError, "the window was closed");
+ PyErr_SetString(PyExc_SystemExit, "window closed.");
+ return 0;
+}
+
+static PyObject* display_clear1(DisplayObject* self, PyObject* args)
+{
+ if (!checkopen(self))
+ return NULL;
+ memset(self->screenbmpdata, 0, (-self->screenscanline) * self->height);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static void pack_pixel(DisplayObject* self, unsigned char *data, int r, int g, int b,
+ int depth)
+{
+ unsigned short pixel;
+ switch( depth )
+ {
+ case 8:
+ data[0] = GetNearestPaletteIndex(self->hpal, (b<<16) | (g<<8) | r);
+ break;
+ case 15:
+ pixel = ((r<<7) & 0x7c00) | ((g<<2) & 0x03e0) | ((b>>3) & 0x001f);
+ data[0] = (pixel) & 0xff;
+ data[1] = (pixel>>8) & 0xff;
+ break;
+ case 16:
+ /* assumes 5,6,5 model. */
+ pixel = ((r<<8) & 0xf800) | ((g<<3) & 0x07e0) | ((b>>3) & 0x001f);
+ data[0] = (pixel) & 0xff;
+ data[1] = (pixel>>8) & 0xff;
+ break;
+ case 24:
+ if( 1 )
+ {
+ data[0] = b;
+ data[1] = g;
+ data[2] = r;
+ break;
+ }
+ case 32:
+ *((long *)data) = (r<<16) | (g<<8) | b;
+ break;
+ }
+}
+
+static PyObject* display_pixmap1(DisplayObject* self, PyObject* args)
+{
+ int w,h;
+ int length;
+ unsigned char* input = NULL;
+ long keycol = -1;
+
+ if (!checkopen(self))
+ return NULL;
+ if (!PyArg_ParseTuple(args, "ii|s#l", &w, &h, &input, &length, &keycol))
+ return NULL;
+
+ if (1) /* SHM */
+ {
+ int x, y;
+ unsigned char *dst;
+ int size, bytes_per_pixel;
+ long packed_keycol = keycol;
+ PyObject* result;
+ PyObject* str;
+
+ if (input == NULL )
+ {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ bytes_per_pixel = self->screenbmpinfo.bmiHeader.biBitCount/8;
+ size = bytes_per_pixel*w*h;
+
+ if( 3*w*h != length )
+ {
+ PyErr_SetString(PyExc_TypeError, "bad string length");
+ return NULL;
+ }
+ /* Create a new string and fill it with the correctly packed image */
+ str = PyString_FromStringAndSize(NULL, size);
+ if (!str)
+ return NULL;
+ if (keycol >= 0)
+ switch( self->bpp )
+ {
+ case 8:
+ packed_keycol = 0xFF;
+ break;
+ case 15:
+ packed_keycol = (1 << 10) | (1 << 5) | 1;
+ break;
+ case 16:
+ packed_keycol = (1 << 11) | (1 << 5) | 1;
+ break;
+ default:
+ packed_keycol = keycol;
+ break;
+ }
+ result = Py_BuildValue("iiOl", w, h, str, packed_keycol);
+ Py_DECREF(str); /* one ref left in 'result' */
+ if (!result)
+ return NULL;
+ dst = (unsigned char*) PyString_AS_STRING(str);
+ memset(dst,0,size);
+
+ for( y=0; y<h; y++ )
+ for( x=0; x<w; x++, input+=3, dst += bytes_per_pixel )
+ {
+ int r = input[0];
+ int g = input[1];
+ int b = input[2];
+ if( ((r<<16)|(g<<8)|b) == keycol )
+ for( b=0; b<bytes_per_pixel; b++ )
+ dst[b] = ((unsigned char *)&packed_keycol)[b];
+ else
+ pack_pixel(self, dst, r, g, b, self->bpp);
+ }
+ return result;
+ }
+}
+
+static PyObject* display_putppm1(DisplayObject* self, PyObject* args)
+{
+ if (!checkopen(self))
+ return NULL;
+
+ if (1) /* SHM */
+ {
+ int x,y,w,h,scanline;
+ int clipx=0, clipy=0, clipw=65536, cliph=65536;
+ unsigned char* src;
+ int length;
+ long keycol;
+ int bytes_per_pixel = self->screenbmpinfo.bmiHeader.biBitCount/8;
+ unsigned char* data = self->screenfirstline;
+ if (!PyArg_ParseTuple(args, "ii(iis#l)|(iiii)",
+ &x, &y, &w, &h, &src, &length, &keycol,
+ &clipx, &clipy, &clipw, &cliph) || !data)
+ return NULL;
+
+ scanline = bytes_per_pixel*w;
+ if (scanline*h != length)
+ {
+ PyErr_SetString(PyExc_TypeError, "bad string length");
+ return NULL;
+ }
+ x -= clipx;
+ y -= clipy;
+ clipx += x;
+ clipy += y;
+ clipw += clipx;
+ cliph += clipy;
+ if (clipx<0) clipx=0;
+ if (clipy<0) clipy=0;
+ if (clipw>self->width) clipw=self->width;
+ if (cliph>self->height) cliph=self->height;
+ if (x<clipx) { src+=(clipx-x)*bytes_per_pixel; w+=x-clipx; x=clipx; }
+ if (y<clipy) { src+=(clipy-y)*scanline; h+=y-clipy; y=clipy; }
+ if (x+w > clipw) w = clipw-x;
+ if (y+h > cliph) h = cliph-y;
+ data += bytes_per_pixel*x+y*self->screenscanline;
+ while (h>0)
+ {
+ int i;
+ int b;
+ unsigned char* src0 = src;
+ unsigned char* data0 = data;
+ if (keycol < 0)
+ for (i=0; i<w; i++)
+ for (b=0; b<bytes_per_pixel; b++)
+ *data++ = *src++;
+ else
+ {
+ unsigned char *keycol_bytes = (unsigned char *)&keycol;
+ for (i=0; i<w; i++)
+ {
+ int transparent = 1;
+ for( b=0; b<bytes_per_pixel; b++ )
+ transparent = transparent && (keycol_bytes[b] == src[b]);
+
+ if (!transparent)
+ for( b=0; b<bytes_per_pixel; b++ )
+ *data++ = *src++;
+ else
+ {
+ data += bytes_per_pixel;
+ src += bytes_per_pixel;
+ }
+ }
+ }
+ src = src0 + scanline;
+ data = data0 + self->screenscanline;
+ h--;
+ }
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject* display_getppm1(DisplayObject* self, PyObject* args)
+{
+ if (!checkopen(self))
+ return NULL;
+
+ if (1) /* SHM */
+ {
+ int x,y,w,h,scanline;
+ int clipx=0, clipy=0, clipw=self->width, cliph=self->height;
+ unsigned char* dst;
+ int length;
+ PyObject* ignored;
+ PyObject* result;
+ PyObject* str;
+ int bytes_per_pixel = self->screenbmpinfo.bmiHeader.biBitCount/8;
+ unsigned char* data = self->screenfirstline;
+ if (!PyArg_ParseTuple(args, "(iiii)|O", &x, &y, &w, &h,
+ &ignored) || !data)
+ return NULL;
+
+ scanline = bytes_per_pixel*w;
+ length = scanline*h;
+ str = PyString_FromStringAndSize(NULL, length);
+ if (!str)
+ return NULL;
+ result = Py_BuildValue("iiOl", w, h, str, -1);
+ Py_DECREF(str); /* one ref left in 'result' */
+ if (!result)
+ return NULL;
+ dst = (unsigned char*) PyString_AS_STRING(str);
+
+ if (x<clipx) { dst+=(clipx-x)*bytes_per_pixel; w+=x-clipx; x=clipx; }
+ if (y<clipy) { dst+=(clipy-y)*scanline; h+=y-clipy; y=clipy; }
+ if (x+w > clipw) w = clipw-x;
+ if (y+h > cliph) h = cliph-y;
+ data += bytes_per_pixel*x+y*self->screenscanline;
+ while (h>0)
+ {
+ int i;
+ int b;
+ unsigned char* dst0 = dst;
+ unsigned char* data0 = data;
+ for (i=0; i<w; i++)
+ {
+ for( b=0; b<bytes_per_pixel; b++ )
+ *dst++ = *data++;
+ }
+ dst = dst0 + scanline;
+ data = data0 + self->screenscanline;
+ h--;
+ }
+ return result;
+ }
+}
+
+static int readXevents(DisplayObject* self)
+{
+ MSG Msg;
+ while (PeekMessage(&Msg, (HWND) NULL, 0, 0, PM_REMOVE))
+ {
+ DispatchMessage(&Msg);
+ if (PyErr_Occurred())
+ return 0;
+ }
+ return checkopen(self);
+}
+
+#define ENABLE_EVENTS(mask) do { } while (0) /* nothing */
+
+static PyObject* display_keyevents1(DisplayObject* self, PyObject* args)
+{
+ PyObject* result;
+ ENABLE_EVENTS(KeyPressMask|KeyReleaseMask);
+ if (!readXevents(self))
+ return NULL;
+ result = self->keyevents;
+ if (result == NULL)
+ result = PyList_New(0);
+ else
+ self->keyevents = NULL;
+ return result;
+}
+
+static PyObject* display_mouseevents1(DisplayObject* self, PyObject* args)
+{
+ PyObject* result;
+ ENABLE_EVENTS(ButtonPressMask);
+ result = self->mouseevents;
+ if (result == NULL)
+ result = PyList_New(0);
+ else
+ self->mouseevents = NULL;
+ return result;
+}
+
+static PyObject* display_pointermotion1(DisplayObject* self, PyObject* args)
+{
+ PyObject* result;
+ ENABLE_EVENTS(PointerMotionMask);
+ result = self->motionevent;
+ if (result == NULL)
+ {
+ Py_INCREF(Py_None);
+ result = Py_None;
+ }
+ else
+ self->motionevent = NULL;
+ return result;
+}
+
+static PyObject* display_flip1(DisplayObject* self, PyObject* args)
+{
+ int r;
+ if (!checkopen(self))
+ return NULL;
+
+ if (self->hpal)
+ RealizePalette(self->dc);
+
+ r = SetDIBitsToDevice(self->dc, 0, 0, self->width, self->height, 0, 0,
+ 0, self->height, self->screenbmpdata, (BITMAPINFO*)(&self->screenbmpinfo),
+ DIB_PAL_COLORS);
+ if (!r)
+ {
+ PyErr_SetString(PyExc_IOError, "SetDIBitsToDevice failed");
+ return NULL;
+ }
+
+ flush(self);
+ if (!readXevents(self))
+ return NULL;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject* display_shmmode(DisplayObject* self, PyObject *args)
+{
+ return PyInt_FromLong(0);
+}
+
+static PyObject* display_settitle(DisplayObject* self, PyObject* args)
+{
+ char* title;
+ if (!checkopen(self))
+ return NULL;
+ if (!PyArg_ParseTuple(args, "s", &title))
+ return NULL;
+ SetWindowText(self->win, title);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyMethodDef display_methods[] = {
+ {"close", (PyCFunction)display_close1, METH_VARARGS, NULL},
+ {"flip", (PyCFunction)display_flip1, METH_VARARGS, NULL},
+ {"clear", (PyCFunction)display_clear1, METH_VARARGS, NULL},
+ {"pixmap", (PyCFunction)display_pixmap1, METH_VARARGS, NULL},
+ {"putppm", (PyCFunction)display_putppm1, METH_VARARGS, NULL},
+ {"getppm", (PyCFunction)display_getppm1, METH_VARARGS, NULL},
+ {"keyevents",(PyCFunction)display_keyevents1,METH_VARARGS, NULL},
+ {"mouseevents",(PyCFunction)display_mouseevents1,METH_VARARGS,NULL},
+ {"pointermotion",(PyCFunction)display_pointermotion1,METH_VARARGS,NULL},
+ {"shmmode", (PyCFunction)display_shmmode, METH_VARARGS, NULL},
+ {"settitle", (PyCFunction)display_settitle, METH_VARARGS, NULL},
+ {NULL, NULL} /* sentinel */
+};
+
+static PyObject* display_getattr(DisplayObject* self, char* name)
+{
+ return Py_FindMethod(display_methods, (PyObject*)self, name);
+}
+
+
+statichere PyTypeObject Display_Type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /*ob_size*/
+ "Display", /*tp_name*/
+ sizeof(DisplayObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)display_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ (getattrfunc)display_getattr, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+};
+
+
+ /************************** AUDIO PART ***************************/
+
+#define NUM_WAVE_HDR 2
+
+typedef struct {
+ PyObject_HEAD
+ HWAVEOUT waveOut;
+ HANDLE doneEvent;
+ WAVEHDR waveHdr[NUM_WAVE_HDR];
+ int waveHdrCount, waveHdrNext;
+} AudioObject;
+
+#define AudioObject_Check(v) ((v)->ob_type == &Audio_Type)
+staticforward PyTypeObject Audio_Type;
+
+
+static PyObject* new_audio(PyObject* dummy, PyObject* args)
+{
+ WAVEFORMATEX wf;
+ int channels, freq, bits, err, bufsize;
+ AudioObject* self;
+ if (!PyArg_ParseTuple(args, "iiii", &channels, &freq, &bits, &bufsize))
+ return NULL;
+
+ self = PyObject_New(AudioObject, &Audio_Type);
+ if (self == NULL)
+ return NULL;
+
+ self->waveHdrCount = 0;
+ self->waveHdrNext = 0;
+ self->waveOut = 0;
+ self->doneEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
+
+ memset(&wf, 0, sizeof(wf));
+ wf.wFormatTag = WAVE_FORMAT_PCM;
+ wf.nChannels = channels;
+ wf.nSamplesPerSec = freq;
+ wf.wBitsPerSample = bits;
+ wf.nBlockAlign = wf.nChannels * wf.wBitsPerSample / 8;
+ wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
+ err = waveOutOpen(&self->waveOut, WAVE_MAPPER, &wf, (DWORD) self->doneEvent, 0, CALLBACK_EVENT);
+ if (err != MMSYSERR_NOERROR || self->doneEvent == NULL) {
+ Py_DECREF(self);
+ PyErr_SetString(PyExc_IOError, "cannot open audio device");
+ return NULL;
+ }
+
+ while (self->waveHdrCount < NUM_WAVE_HDR) {
+ WAVEHDR* wh = &self->waveHdr[self->waveHdrCount];
+ wh->lpData = PyMem_Malloc(bufsize);
+ wh->dwBufferLength = bufsize;
+ wh->dwFlags = 0;
+ if (wh->lpData == NULL ||
+ waveOutPrepareHeader(self->waveOut, wh, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
+ Py_DECREF(self);
+ return NULL;
+ }
+ wh->dwFlags |= WHDR_DONE;
+ self->waveHdrCount++;
+ }
+ return (PyObject*) self;
+}
+
+static void audio_close(AudioObject* self)
+{
+ if (self->waveOut != 0) {
+ waveOutReset(self->waveOut);
+ while (self->waveHdrCount > 0) {
+ WAVEHDR* wh = &self->waveHdr[--self->waveHdrCount];
+ waveOutUnprepareHeader(self->waveOut, wh, sizeof(WAVEHDR));
+ PyMem_Free(wh->lpData);
+ }
+ waveOutClose(self->waveOut);
+ self->waveOut = 0;
+ }
+}
+
+static void audio_dealloc(AudioObject* self)
+{
+ audio_close(self);
+ PyObject_Del(self);
+}
+
+static PyObject* audio_wait1(AudioObject* self, PyObject* args)
+{
+ float delay = -1.0;
+ if (!PyArg_ParseTuple(args, "|f", &delay))
+ return NULL;
+ if (self->waveHdrNext >= self->waveHdrCount) {
+ PyErr_SetString(PyExc_IOError, "audio device not ready");
+ return NULL;
+ }
+ Py_BEGIN_ALLOW_THREADS
+ while (!(self->waveHdr[self->waveHdrNext].dwFlags & WHDR_DONE)) {
+ WaitForSingleObject(self->doneEvent, delay<0.0?INFINITE:(DWORD)(delay*1000.0));
+ }
+ Py_END_ALLOW_THREADS
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject* audio_ready1(AudioObject* self, PyObject* args)
+{
+ if (!PyArg_ParseTuple(args, ""))
+ return NULL;
+ return PyInt_FromLong(self->waveHdrNext < self->waveHdrCount &&
+ (self->waveHdr[self->waveHdrNext].dwFlags & WHDR_DONE));
+}
+
+static PyObject* audio_write1(AudioObject* self, PyObject* args)
+{
+ WAVEHDR* wh;
+ char* buffer;
+ int bufsize;
+ if (!PyArg_ParseTuple(args, "s#", &buffer, &bufsize))
+ return NULL;
+ if (self->waveHdrNext >= self->waveHdrCount) {
+ PyErr_SetString(PyExc_IOError, "audio device not ready");
+ return NULL;
+ }
+ wh = &self->waveHdr[self->waveHdrNext];
+ if (!(wh->dwFlags & WHDR_DONE)) {
+ PyErr_SetString(PyExc_IOError, "audio device would block");
+ return NULL;
+ }
+ if ((DWORD) bufsize != wh->dwBufferLength) {
+ PyErr_SetString(PyExc_ValueError, "bufsize mismatch");
+ return NULL;
+ }
+ wh->dwFlags &= ~WHDR_DONE;
+ memcpy(wh->lpData, buffer, bufsize);
+ if (waveOutWrite(self->waveOut, wh, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
+ PyErr_SetString(PyExc_IOError, "audio device write error");
+ return NULL;
+ }
+ self->waveHdrNext++;
+ if (self->waveHdrNext >= self->waveHdrCount)
+ self->waveHdrNext = 0;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject* audio_close1(AudioObject* self, PyObject* args)
+{
+ if (!PyArg_ParseTuple(args, ""))
+ return NULL;
+ audio_close(self);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+
+static PyMethodDef audio_methods[] = {
+ {"ready", (PyCFunction)audio_ready1, METH_VARARGS, NULL},
+ {"wait", (PyCFunction)audio_wait1, METH_VARARGS, NULL},
+ {"write", (PyCFunction)audio_write1, METH_VARARGS, NULL},
+ {"close", (PyCFunction)audio_close1, METH_VARARGS, NULL},
+ {NULL, NULL} /* sentinel */
+};
+
+static PyObject* audio_getattr(AudioObject* self, char* name)
+{
+ return Py_FindMethod(audio_methods, (PyObject*)self, name);
+}
+
+
+statichere PyTypeObject Audio_Type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /*ob_size*/
+ "Audio", /*tp_name*/
+ sizeof(AudioObject), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)audio_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ (getattrfunc)audio_getattr, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+};
+ + +static PyMethodDef WinMethods[] = { + {"Display", new_display, METH_VARARGS},
+ {"Audio", new_audio, METH_VARARGS},
+ {NULL, NULL} /* Sentinel */ + }; + +void initwingame(void) +{
+ Display_Type.ob_type = &PyType_Type;
+ Audio_Type.ob_type = &PyType_Type;
+ Py_InitModule("wingame", WinMethods); +} diff --git a/display/windows/wingame.def b/display/windows/wingame.def new file mode 100755 index 0000000..de59dda --- /dev/null +++ b/display/windows/wingame.def @@ -0,0 +1,2 @@ +EXPORTS
+ initwingame
diff --git a/display/windows/wingame.dsp b/display/windows/wingame.dsp new file mode 100755 index 0000000..ce1db26 --- /dev/null +++ b/display/windows/wingame.dsp @@ -0,0 +1,111 @@ +# Microsoft Developer Studio Project File - Name="wingame" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** NICHT BEARBEITEN **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=wingame - Win32 Debug
+!MESSAGE Dies ist kein gültiges Makefile. Zum Erstellen dieses Projekts mit NMAKE
+!MESSAGE verwenden Sie den Befehl "Makefile exportieren" und führen Sie den Befehl
+!MESSAGE
+!MESSAGE NMAKE /f "wingame.mak".
+!MESSAGE
+!MESSAGE Sie können beim Ausführen von NMAKE eine Konfiguration angeben
+!MESSAGE durch Definieren des Makros CFG in der Befehlszeile. Zum Beispiel:
+!MESSAGE
+!MESSAGE NMAKE /f "wingame.mak" CFG="wingame - Win32 Debug"
+!MESSAGE
+!MESSAGE Für die Konfiguration stehen zur Auswahl:
+!MESSAGE
+!MESSAGE "wingame - Win32 Release" (basierend auf "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "wingame - Win32 Debug" (basierend auf "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "wingame - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WINGAME_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "\home\python222\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WINGAME_EXPORTS" /U "_DEBUG" /U "DEBUG" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x100c /d "NDEBUG"
+# ADD RSC /l 0x100c /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /machine:I386 /out:"../wingame.pyd" /libpath:"\home\python23\libs" /libpath:"\home\python222\libs"
+
+!ELSEIF "$(CFG)" == "wingame - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WINGAME_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "\home\python222\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WINGAME_EXPORTS" /U "_DEBUG" /U "DEBUG" /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x100c /d "_DEBUG"
+# ADD RSC /l 0x100c /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /debug /machine:I386 /out:"../wingame.pyd" /pdbtype:sept /libpath:"\home\python222\libs"
+
+!ENDIF
+
+# Begin Target
+
+# Name "wingame - Win32 Release"
+# Name "wingame - Win32 Debug"
+# Begin Group "Quellcodedateien"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\wingame.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\wingame.def
+# End Source File
+# End Group
+# Begin Group "Header-Dateien"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Ressourcendateien"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/display/windows/wingame.dsw b/display/windows/wingame.dsw new file mode 100755 index 0000000..06f07c9 --- /dev/null +++ b/display/windows/wingame.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNUNG: DIESE ARBEITSBEREICHSDATEI DARF NICHT BEARBEITET ODER GELÖSCHT WERDEN!
+
+###############################################################################
+
+Project: "wingame"=.\wingame.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/display/xshm.c b/display/xshm.c new file mode 100644 index 0000000..31ea861 --- /dev/null +++ b/display/xshm.c @@ -0,0 +1,1202 @@ +#include <Python.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/XShm.h> + +typedef struct { + XImage* m_shm_image; + XShmSegmentInfo m_shminfo; + int m_width, m_height; +} XImage_Shm; + +typedef struct { + PyObject_HEAD + Display* dpy; + int default_scr; + Window root, win; + int width, height; + XVisualInfo visual_info; + GC gc, gc_and, gc_or; + XImage_Shm plane; + Pixmap backpixmap; + int shmmode; + int selectinput; + PyObject* keyevents; + PyObject* mouseevents; + PyObject* motionevent; +} DisplayObject; + +typedef struct { + PyObject_HEAD + DisplayObject* dpy; + int width, height; + Pixmap mask; + Pixmap handle; +} XPixmapObject; + + +#define DisplayObject_Check(v) ((v)->ob_type == &Display_Type) +staticforward PyTypeObject Display_Type; +staticforward PyTypeObject XPixmap_Type; + + +static void pixmap_dealloc(XPixmapObject* pm) +{ + if (pm->dpy->dpy) + { + if (pm->mask != (Pixmap) -1) + XFreePixmap(pm->dpy->dpy, pm->mask); + XFreePixmap(pm->dpy->dpy, pm->handle); + } + Py_DECREF(pm->dpy); + PyObject_Del(pm); +} + +static XPixmapObject* new_pixmap(DisplayObject* self, int w, int h, int withmask) +{ + XPixmapObject* pm = PyObject_New(XPixmapObject, &XPixmap_Type); + if (pm != NULL) + { + Py_INCREF(self); + pm->dpy = self; + pm->width = w; + pm->height = h; + pm->handle = XCreatePixmap(self->dpy, self->win, w, h, + self->visual_info.depth); + if (withmask) + pm->mask = XCreatePixmap(self->dpy, self->win, w, h, + self->visual_info.depth); + else + pm->mask = (Pixmap) -1; + } + return pm; +} + + +static void flush(DisplayObject* self) +{ + XSync(self->dpy, False); +} + +static int create_shm_image(DisplayObject* self, XImage_Shm* img, + int width, int height) +{ + int image_size = 4*width*height; + + if (XShmQueryExtension(self->dpy) == False) + /* does we have the extension at all? */ + return 0; + + img->m_shm_image = XShmCreateImage( + self->dpy, + self->visual_info.visual, + self->visual_info.depth, + ZPixmap, + NULL, + &img->m_shminfo, + width, + height); + if (img->m_shm_image == NULL) + return 0; + img->m_width = width; + img->m_height = height; + + /* Create shared memory segment: */ + img->m_shminfo.shmid = shmget(IPC_PRIVATE, image_size, IPC_CREAT|0777); + if (img->m_shminfo.shmid < 0) + return 0; + + /* Get memory address to segment: */ + img->m_shminfo.shmaddr = (char *) shmat(img->m_shminfo.shmid, 0, 0); + + /* Mark the segment as destroyable (it will be destroyed when this + process terminates) */ + shmctl(img->m_shminfo.shmid, IPC_RMID, NULL); + + /* Tell XServer that it may only read from it and attach to display: */ + img->m_shminfo.readOnly = True; + XShmAttach (self->dpy, &img->m_shminfo); + + /* Fill the XImage struct: */ + img->m_shm_image->data = img->m_shminfo.shmaddr; + return 1; +} + +static PyObject* new_display(PyObject* dummy, PyObject* args) +{ + DisplayObject* self; + XSetWindowAttributes attr; + int width, height, use_shm=1; + if (!PyArg_ParseTuple(args, "ii|i", &width, &height, &use_shm)) + return NULL; + + self = PyObject_New(DisplayObject, &Display_Type); + if (self == NULL) + return NULL; + + self->dpy = XOpenDisplay(NULL); + if (self->dpy == NULL) goto err; + self->default_scr = DefaultScreen(self->dpy); + self->root = RootWindow(self->dpy, self->default_scr); + self->width = width; + self->height = height; + + if (!XMatchVisualInfo(self->dpy, self->default_scr, + DefaultDepth(self->dpy,self->default_scr), TrueColor, + &self->visual_info)) goto err2; + + /* set window attributes */ + memset(&attr, 0, sizeof(attr)); + attr.override_redirect = False; + attr.background_pixel = BlackPixel(self->dpy, self->default_scr); + attr.backing_store = NotUseful; + + /* Create the window */ + self->win = XCreateWindow( + self->dpy, + self->root, + 0, + 0, + width, + height, + 0, + CopyFromParent, + CopyFromParent, + self->visual_info.visual, + CWOverrideRedirect | CWBackPixel | CWBackingStore, + &attr); + if (self->win == (Window) -1) goto err2; + + XMapRaised(self->dpy, self->win); + + self->shmmode = use_shm && + create_shm_image(self, &self->plane, width, height); + + self->gc = XCreateGC(self->dpy, self->win, 0, 0); + if (!self->shmmode) + { + self->backpixmap = XCreatePixmap(self->dpy, self->root, + width, height, self->visual_info.depth); + if (self->backpixmap == (Pixmap) -1) goto err2; + + self->gc_and = XCreateGC(self->dpy, self->win, 0, 0); + self->gc_or = XCreateGC(self->dpy, self->win, 0, 0); + XSetForeground(self->dpy, self->gc, attr.background_pixel); + XSetFunction(self->dpy, self->gc_and, GXand); + XSetFunction(self->dpy, self->gc_or, GXor); + } + + self->selectinput = 0; + self->keyevents = NULL; + self->mouseevents = NULL; + self->motionevent = NULL; + + flush(self); + return (PyObject*) self; + + err2: + XCloseDisplay(self->dpy); + err: + Py_DECREF(self); + PyErr_SetString(PyExc_IOError, "cannot open X11 display"); + return NULL; +} + +static void display_close(DisplayObject* self) +{ + if (self->dpy) + { + XCloseDisplay(self->dpy); + self->dpy = NULL; + } +} + +static void display_dealloc(DisplayObject* self) +{ + display_close(self); + Py_XDECREF(self->keyevents); + Py_XDECREF(self->mouseevents); + Py_XDECREF(self->motionevent); + PyObject_Del(self); +} + +static PyObject* display_close1(DisplayObject* self, PyObject* args) +{ + display_close(self); + Py_INCREF(Py_None); + return Py_None; +} + +static int checkopen(DisplayObject* self) +{ + if (self->dpy) + return 1; + PyErr_SetString(PyExc_IOError, "X11 connexion already closed"); + return 0; +} + +static unsigned char* get_dpy_data(DisplayObject* self) +{ + unsigned char* result; + if (!checkopen(self)) + return NULL; + result = (unsigned char*)(self->plane.m_shminfo.shmaddr); + if (!result) + PyErr_SetString(PyExc_IOError, "X11 SHM failed"); + return result; +} + +static PyObject* display_clear1(DisplayObject* self, PyObject* args) +{ + if (self->shmmode) + { + unsigned char* data = get_dpy_data(self); + if (data == NULL) + return NULL; + memset(data, 0, + ( self->plane.m_shm_image->bits_per_pixel/8 + *self->width*self->height ) ); + } + else + { + if (!checkopen(self)) + return NULL; + XFillRectangle(self->dpy, self->backpixmap, self->gc, + 0, 0, self->width, self->height); + } + Py_INCREF(Py_None); + return Py_None; +} + +inline void pack_pixel(unsigned char *data, int r, int g, int b, + int depth, int bytes_per_pixel) +{ + unsigned short pixel = 0; + switch( depth ) + { + /* No True color below 15 bits per pixel */ + case 15: + pixel = ((r<<7) & 0x7c00) | ((g<<2) & 0x03e0) | ((b>>3) & 0x001f); + data[0] = (pixel) & 0xff; + data[1] = (pixel>>8) & 0xff; + break; + case 16: + /* assumes 5,6,5 model. */ + pixel = ((r<<8) & 0xf800) | ((g<<3) & 0x07e0) | ((b>>3) & 0x001f); + data[0] = (pixel) & 0xff; + data[1] = (pixel>>8) & 0xff; + break; + case 24: + if( bytes_per_pixel == 3 ) + { + data[0] = b; + data[1] = g; + data[2] = r; + break; + } + /* else it's on 32 bits. Drop into depth of 32. */ + case 32: + *((long *)data) = (r<<16) | (g<<8) | b; + break; + } +} + +static PyObject* display_pixmap1(DisplayObject* self, PyObject* args) +{ + int w,h; + unsigned char* input = NULL; + int length; + long keycol = -1; + + if (!checkopen(self)) + return NULL; + if (!PyArg_ParseTuple(args, "ii|s#l", &w, &h, &input, &length, &keycol)) + return NULL; + + if (self->shmmode) + { + int x, y; + int bytes_per_pixel = self->plane.m_shm_image->bits_per_pixel/8; + int countblocks, countpixels; + PyObject* result; + PyObject* strblocks; + PyObject* strpixels; + unsigned int* pblocks; + unsigned char* ppixels; + unsigned char* input1; + + if (input == NULL) + { + Py_INCREF(Py_None); + return Py_None; + } + if (3*w*h != length) + { + PyErr_SetString(PyExc_ValueError, "bad string length"); + return NULL; + } + + /* Convert the image to our internal format. + See display_putppm1() for a description of the format. + */ + + countblocks = 0; + countpixels = 0; + input1 = input; + for (y=0; y<h; y++) + { + int opaque = 0; + for (x=0; x<w; x++) + { + unsigned int r = input1[0]; + unsigned int g = input1[1]; + unsigned int b = input1[2]; + input1 += 3; + if (((r<<16)|(g<<8)|b) == keycol) + opaque = 0; + else + { + if (!opaque) + { + countblocks++; /* start a new block */ + opaque = 1; + } + countpixels++; + } + } + countblocks++; /* end-of-line marker block */ + } + + /* allocate memory */ + strblocks = PyString_FromStringAndSize(NULL, + countblocks*sizeof(int)); + if (strblocks == NULL) + return NULL; + strpixels = PyString_FromStringAndSize(NULL, + countpixels*bytes_per_pixel); + if (strpixels == NULL) + { + Py_DECREF(strblocks); + return NULL; + } + + /* write data */ + pblocks = (unsigned int*) PyString_AS_STRING(strblocks); + ppixels = (unsigned char*) PyString_AS_STRING(strpixels); + for (y=0; y<h; y++) + { + int opaque = 0; + for (x=0; x<w; x++) + { + unsigned int r = input[0]; + unsigned int g = input[1]; + unsigned int b = input[2]; + input += 3; + if (((r<<16)|(g<<8)|b) == keycol) + opaque = 0; + else + { + if (!opaque) + { + *pblocks++ = x*bytes_per_pixel; /* start a new block */ + opaque = 1; + } + pblocks[-1] += bytes_per_pixel<<16; /* add pixel to block */ + pack_pixel(ppixels, r, g, b, + self->visual_info.depth, bytes_per_pixel); + ppixels += bytes_per_pixel; + } + } + *pblocks++ = 0; /* end-of-line marker block */ + } + + result = Py_BuildValue("iiOO", w, h, strblocks, strpixels); + Py_DECREF(strblocks); + Py_DECREF(strpixels); + return result; + } + else + { + XImage* image; + long extent; + unsigned char* data = NULL; + unsigned char* maskdata = NULL; + int scanline, bitmap_pad; + XPixmapObject* pm; + + pm = new_pixmap(self, w, h, keycol>=0); + if (pm == NULL) + return NULL; + + if (input == NULL) + return (PyObject*) pm; /* uninitialized pixmap */ + + extent = w*h; + if (3*extent != length) + { + PyErr_SetString(PyExc_ValueError, "bad string length"); + goto err; + } + + bitmap_pad = self->visual_info.depth >= 24 ? 32 : 16; + scanline = ((w+bitmap_pad-1) & ~(bitmap_pad-1)) / 8; + /*while (scanline&3) scanline++;*/ + data = malloc(self->visual_info.depth*scanline*h); + if (data == NULL) + { + PyErr_NoMemory(); + goto err; + } + memset(data, 0, self->visual_info.depth*scanline*h); + maskdata = malloc(self->visual_info.depth*scanline*h); + if (maskdata == NULL) + { + PyErr_NoMemory(); + goto err; + } + memset(maskdata, 0, self->visual_info.depth*scanline*h); + + { + int key_r = keycol>>16; + unsigned char key_g = keycol>>8; + unsigned char key_b = keycol>>0; + unsigned char* target = data; + unsigned char* masktarget = maskdata; + int plane, color; + + unsigned int p_size[3]; + switch( self->visual_info.depth ) + { + case 15: + p_size[0] = p_size[1] = p_size[2] = 5; + break; + case 16: + p_size[0] = p_size[2] = 5; + p_size[1] = 6; + break; + case 24: + case 32: + p_size[0] = p_size[1] = p_size[2] = 8; + break; + } + + for (color=0; color<3; color++) + for (plane=128; plane>=(1<<(8-p_size[color])); plane/=2) + { + unsigned char* src = input; + int x, y; + for (y=0; y<h; y++, target+=scanline, masktarget+=scanline) + for (x=0; x<w; x++, src+=3) + { + if (src[0] == key_r && src[1] == key_g && src[2] == key_b) + { + /* transparent */ + masktarget[x/8] |= (1<<(x&7)); + } + else + if (src[color] & plane) + target[x/8] |= (1<<(x&7)); + } + } + } + + if (keycol < 0) + free(maskdata); + else + { + image = XCreateImage(self->dpy, self->visual_info.visual, + self->visual_info.depth, XYPixmap, 0, + maskdata, w, h, + bitmap_pad, scanline); + if (image == NULL || image == (XImage*) -1) + { + PyErr_SetString(PyExc_IOError, "XCreateImage failed (2)"); + goto err; + } + image->byte_order = LSBFirst; + image->bitmap_bit_order = LSBFirst; + maskdata = NULL; + XPutImage(self->dpy, pm->mask, self->gc, image, 0, 0, 0, 0, w, h); + XDestroyImage(image); + } + + image = XCreateImage(self->dpy, self->visual_info.visual, + self->visual_info.depth, XYPixmap, 0, + data, w, h, + bitmap_pad, scanline); + if (image == NULL || image == (XImage*) -1) + { + PyErr_SetString(PyExc_IOError, "XCreateImage failed"); + goto err; + } + image->byte_order = LSBFirst; + image->bitmap_bit_order = LSBFirst; + data = NULL; + XPutImage(self->dpy, pm->handle, self->gc, image, 0, 0, 0, 0, w, h); + XDestroyImage(image); + + return (PyObject*) pm; + + err: + free(maskdata); + free(data); + Py_DECREF(pm); + return NULL; + } +} + +static PyObject* display_get(DisplayObject* self, int x, int y, int w, int h) +{ + if (self->shmmode) + { + int clipx=0, clipy=0, clipw=self->width, cliph=self->height; + int original_w, original_h; + int firstline=0, firstcol=0; + unsigned int bytes_per_pixel = self->plane.m_shm_image->bits_per_pixel/8; + unsigned char* data = get_dpy_data(self); + if (!data) + return NULL; + + original_w = w; + original_h = h; + if (x<clipx) { firstcol=clipx-x; w+=x-clipx; x=clipx; } + if (y<clipy) { firstline=clipy-y; h+=y-clipy; y=clipy; } + if (x+w > clipw) w = clipw-x; + if (y+h > cliph) h = cliph-y; + + { + int countblocks = original_h + ((w>0 && h>0) ? h : 0); + /* end blocks + real blocks */ + int countpixels = (w>0 && h>0) ? w * h : 0; + PyObject* result; + PyObject* strblocks; + PyObject* strpixels; + unsigned int* pblocks; + unsigned char* ppixels; + int wbytes = w * bytes_per_pixel; + int block = (firstcol * bytes_per_pixel) | (wbytes << 16); + int data_scanline = bytes_per_pixel*self->width; + + /* allocate memory */ + strblocks = PyString_FromStringAndSize(NULL, + countblocks*sizeof(int)); + if (strblocks == NULL) + return NULL; + strpixels = PyString_FromStringAndSize(NULL, + countpixels*bytes_per_pixel); + if (strpixels == NULL) + { + Py_DECREF(strblocks); + return NULL; + } + + /* write data */ + pblocks = (unsigned int*) PyString_AS_STRING(strblocks); + ppixels = (unsigned char*) PyString_AS_STRING(strpixels); + data += bytes_per_pixel*(x+y*self->width); + for (y=0; y<original_h; y++) + { + if (y >= firstline && y < firstline+h && w > 0) + { + *pblocks++ = block; + memcpy(ppixels, data, wbytes); + ppixels += wbytes; + data += data_scanline; + } + *pblocks++ = 0; + } + + result = Py_BuildValue("iiOO", original_w, original_h, + strblocks, strpixels); + Py_DECREF(strblocks); + Py_DECREF(strpixels); + return result; + } + } + else + { + XPixmapObject* pm = new_pixmap(self, w, h, 0); + if (pm != NULL) + XCopyArea(self->dpy, self->backpixmap, pm->handle, self->gc, + x, y, w, h, 0, 0); + return (PyObject*) pm; + } +} + +static PyObject* save_background(DisplayObject* self, int x, int y, + int w, int h, int save_bkgnd) +{ + if (save_bkgnd) + { + PyObject* pm = display_get(self, x, y, w, h); + PyObject* result; + if (pm == NULL) + return NULL; + result = Py_BuildValue("iiO", x, y, pm); + Py_DECREF(pm); + return result; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +#define ALPHAFACTOR 2 +#define ALPHABLEND(maximum, x, y) ((maximum-y)*x/(maximum*ALPHAFACTOR) + y) + +static void memcpy_alpha_32(unsigned int* dst, unsigned int* src, int count) +{ + int i; + for (i=0; i<count/4; i++) + { + int x = dst[i]; + int y = src[i]; + + int xr = x >> 16; + int xg = x & 0xff00; + int xb = x & 0xff; + + int yr = y >> 16; + int yg = y & 0xff00; + int yb = y & 0xff; + + int zr = ALPHABLEND(0xff, xr, yr); + int zg = ALPHABLEND(0xff00, xg, yg); + int zb = ALPHABLEND(0xff, xb, yb); + + dst[i] = (zr << 16) | (zg & 0xff00) | zb; + } +} + +static void memcpy_alpha_24(unsigned char* dst, unsigned char* src, int count) +{ + int i; + for (i=0; i<count; i++) + { + int x = dst[i]; + int y = src[i]; + dst[i] = ALPHABLEND(255, x, y); + } +} + +static void memcpy_alpha_15(unsigned short* dst, unsigned short* src, int count) +{ + int i; + for (i=0; i<count/2; i++) + { + unsigned short x = dst[i]; + unsigned short y = src[i]; + + int xr = x >> 10; + int xg = x & 0x03e0; + int xb = x & 0x001f; + + int yr = y >> 10; + int yg = y & 0x03e0; + int yb = y & 0x001f; + + int zr = ALPHABLEND(31, xr, yr); + int zg = ALPHABLEND(0x3e0, xg, yg); + int zb = ALPHABLEND(31, xb, yb); + + dst[i] = (zr << 10) | (zg & 0x03e0) | zb; + } +} + +static void memcpy_alpha_16(unsigned short* dst, unsigned short* src, int count) +{ + int i; + for (i=0; i<count/2; i++) + { + unsigned short x = dst[i]; + unsigned short y = src[i]; + + int xr = x >> 11; + int xg = x & 0x07e0; + int xb = x & 0x001f; + + int yr = y >> 11; + int yg = y & 0x07e0; + int yb = y & 0x001f; + + int zr = ALPHABLEND(31, xr, yr); + int zg = ALPHABLEND(0x7e0, xg, yg); + int zb = ALPHABLEND(31, xb, yb); + + dst[i] = (zr << 11) | (zg & 0x07e0) | zb; + } +} + +typedef void (*memcpy_alpha_fn) (unsigned char*, unsigned char*, int); + +static PyObject* display_overlay(DisplayObject* self, PyObject* args, + int save_bkgnd) +{ + PyObject* result; + + if (self->shmmode) + { + int x,y,w,h, original_x, original_y, original_w, original_h; + int data_scanline; + int clipx=0, clipy=0, clipw=65536, cliph=65536, alpha=255; + unsigned int* src; + unsigned char* srcdata; + unsigned char* original_srcdata; + int length1, length2, firstline=0, firstcol=0; + unsigned int bytes_per_pixel = self->plane.m_shm_image->bits_per_pixel/8; + memcpy_alpha_fn memcpy_alpha; + unsigned char* data = get_dpy_data(self); + if (!PyArg_ParseTuple(args, "ii(iis#s#)|(iiii)i", + &x, &y, &w, &h, &src, &length1, &srcdata, &length2, + &clipx, &clipy, &clipw, &cliph, &alpha) || !data) + return NULL; + + original_x = x; + original_y = y; + original_w = w; + original_h = h; + original_srcdata = srcdata; + x -= clipx; + y -= clipy; + clipx += x; + clipy += y; + clipw += clipx; + cliph += clipy; + if (clipx<0) clipx=0; + if (clipy<0) clipy=0; + if (clipw>self->width) clipw=self->width; + if (cliph>self->height) cliph=self->height; + if (x<clipx) { firstcol = clipx-x; w+=x-clipx; x=clipx; } + if (y<clipy) { firstline = clipy-y; h+=y-clipy; y=clipy; } + if (x+w > clipw) w = clipw-x; + if (y+h > cliph) h = cliph-y; + if (w > 0 && h > 0) + { + int dstoffset, blocksize; + unsigned int block; + data += bytes_per_pixel*(x+y*self->width); + data_scanline = bytes_per_pixel*self->width; + + memcpy_alpha = (memcpy_alpha_fn) memcpy; + if (alpha < 255) + switch (self->visual_info.depth) { + case 15: memcpy_alpha = (memcpy_alpha_fn) memcpy_alpha_15; break; + case 16: memcpy_alpha = (memcpy_alpha_fn) memcpy_alpha_16; break; + case 24: memcpy_alpha = (memcpy_alpha_fn) memcpy_alpha_24; break; + case 32: memcpy_alpha = (memcpy_alpha_fn) memcpy_alpha_32; break; + } + + /* 'structure' points to a sequence of int-sized blocks with the + following meaning: + + n & 0xFFFF -- byte offset within the line + n >> 16 -- number of opaque bytes to copy there + + n == 0 means end of line. + */ + + /* read and ignore 'firstline' complete lines */ + while (firstline--) + { + while ((block = *src++) != 0) + { + blocksize = block >> 16; + srcdata += blocksize; + } + } + + if (w == original_w) + { + if (!save_bkgnd) + { + /* common fast case: copy the whole width of the image */ + do + { + while ((block = *src++) != 0) + { + dstoffset = block & 0xFFFF; + blocksize = block >> 16; + memcpy(data + dstoffset, srcdata, blocksize); + srcdata += blocksize; + } + data += data_scanline; + } + while (--h); + result = Py_None; + Py_INCREF(result); + } + else + { + /* copy and save the background */ + PyObject* cliprect; + PyObject* strblocks; + PyObject* strpixels; + unsigned char* ppixels; + + strpixels = PyString_FromStringAndSize(NULL, length2); + if (strpixels == NULL) + return NULL; + ppixels = (unsigned char*) PyString_AS_STRING(strpixels); + ppixels += srcdata - original_srcdata; + + do + { + while ((block = *src++) != 0) + { + dstoffset = block & 0xFFFF; + blocksize = block >> 16; + memcpy(ppixels, data + dstoffset, blocksize); + ppixels += blocksize; + memcpy_alpha(data + dstoffset, srcdata, blocksize); + srcdata += blocksize; + } + data += data_scanline; + } + while (--h); + + strblocks = PyTuple_GET_ITEM(PyTuple_GET_ITEM(args, 2), 2); + if (PyTuple_GET_SIZE(args) > 3) + { + cliprect = PyTuple_GET_ITEM(args, 3); + result = Py_BuildValue("ii(iiOO)O", + original_x, + original_y, + original_w, + original_h, + strblocks, + strpixels, + cliprect); + } + else + { + result = Py_BuildValue("ii(iiOO)", + original_x, + original_y, + original_w, + original_h, + strblocks, + strpixels); + } + Py_DECREF(strpixels); + } + } + else + { + /* byte offsets within a line */ + unsigned char* blocksrc; + int skip, lastcol; + + result = save_background(self, x, y, w, h, save_bkgnd); + + lastcol = (firstcol + w) * bytes_per_pixel; + firstcol *= bytes_per_pixel; + + /* slow case: only copy a portion of the width of the image */ + data -= firstcol; + do + { + while ((block = *src++) != 0) + { + dstoffset = block & 0xFFFF; + blocksize = block >> 16; + blocksrc = srcdata; + srcdata += blocksize; + skip = firstcol - dstoffset; + if (skip < 0) + skip = 0; + if (blocksize > lastcol - dstoffset) + blocksize = lastcol - dstoffset; + if (blocksize > skip) + memcpy_alpha(data + dstoffset + skip, blocksrc + skip, + blocksize - skip); + } + data += data_scanline; + } + while (--h); + } + } + else + { + result = args; + Py_INCREF(result); + } + } + else + { + int x,y, x1=0,y1=0,w1=-1,h1=-1,alpha; + XPixmapObject* pm; + + if (!checkopen(self)) + return NULL; + if (!PyArg_ParseTuple(args, "iiO!|(iiii)i", &x, &y, &XPixmap_Type, &pm, + &x1, &y1, &w1, &h1, &alpha)) + return NULL; + + if (w1 < 0) + w1 = pm->width; + if (h1 < 0) + h1 = pm->height; + + result = save_background(self, x, y, w1, h1, save_bkgnd); + + if (pm->mask == (Pixmap) -1) + { + XCopyArea(self->dpy, pm->handle, self->backpixmap, self->gc, + x1, y1, w1, h1, x, y); + } + else + { + XCopyArea(self->dpy, pm->mask, self->backpixmap, self->gc_and, + x1, y1, w1, h1, x, y); + XCopyArea(self->dpy, pm->handle, self->backpixmap, self->gc_or, + x1, y1, w1, h1, x, y); + } + } + return result; +} + +static PyObject* display_putppm1(DisplayObject* self, PyObject* args) +{ + return display_overlay(self, args, 0); +} + +static PyObject* display_overlayppm1(DisplayObject* self, PyObject* args) +{ + return display_overlay(self, args, 1); +} + +static PyObject* display_getppm1(DisplayObject* self, PyObject* args) +{ + int x, y, w, h; + if (!checkopen(self)) + return NULL; + if (!PyArg_ParseTuple(args, "(iiii)", &x, &y, &w, &h)) + return NULL; + return display_get(self, x, y, w, h); +} + +static int readXevents(DisplayObject* self) +{ + while (XEventsQueued(self->dpy, QueuedAfterReading) > 0) + { + XEvent e; + XNextEvent(self->dpy, &e); + switch (e.type) { + case KeyPress: + case KeyRelease: + { + KeySym sym; + PyObject* v; + int err; + if (self->keyevents == NULL) + { + self->keyevents = PyList_New(0); + if (self->keyevents == NULL) + return 0; + } + sym = XLookupKeysym(&e.xkey,0); + v = Py_BuildValue("ii", sym, e.type); + if (v == NULL) + return 0; + err = PyList_Append(self->keyevents, v); + Py_DECREF(v); + if (err) + return 0; + break; + } + case ButtonPress: + { + PyObject* v; + int err; + if (self->mouseevents == NULL) + { + self->mouseevents = PyList_New(0); + if (self->mouseevents == NULL) + return 0; + } + v = Py_BuildValue("ii", e.xbutton.x, e.xbutton.y); + if (v == NULL) + return 0; + err = PyList_Append(self->mouseevents, v); + Py_DECREF(v); + if (err) + return 0; + break; + } + case MotionNotify: + { + Py_XDECREF(self->motionevent); + self->motionevent = Py_BuildValue("ii", e.xmotion.x, e.xmotion.y); + if (self->motionevent == NULL) + return 0; + break; + } + } + } + return 1; +} + +#define ENABLE_EVENTS(mask) do { \ + if (!(self->selectinput & (mask))) \ + { \ + self->selectinput |= (mask); \ + XSelectInput(self->dpy, self->win, self->selectinput); \ + } \ +} while (0) + +static PyObject* display_keyevents1(DisplayObject* self, PyObject* args) +{ + PyObject* result; + ENABLE_EVENTS(KeyPressMask|KeyReleaseMask); + if (!readXevents(self)) + return NULL; + result = self->keyevents; + if (result == NULL) + result = PyList_New(0); + else + self->keyevents = NULL; + return result; +} + +static PyObject* display_mouseevents1(DisplayObject* self, PyObject* args) +{ + PyObject* result; + ENABLE_EVENTS(ButtonPressMask); + result = self->mouseevents; + if (result == NULL) + result = PyList_New(0); + else + self->mouseevents = NULL; + return result; +} + +static PyObject* display_pointermotion1(DisplayObject* self, PyObject* args) +{ + PyObject* result; + ENABLE_EVENTS(PointerMotionMask); + result = self->motionevent; + if (result == NULL) + { + Py_INCREF(Py_None); + result = Py_None; + } + else + self->motionevent = NULL; + return result; +} + +static PyObject* display_flip1(DisplayObject* self, PyObject* args) +{ + if (!checkopen(self)) + return NULL; + + if (self->shmmode) + { + XShmPutImage(self->dpy, self->win, self->gc, + self->plane.m_shm_image, + 0, 0, 0, 0, + self->plane.m_width, + self->plane.m_height, + False); + } + else + { + XCopyArea(self->dpy, self->backpixmap, self->win, self->gc, + 0, 0, self->width, self->height, 0, 0); + } + flush(self); + if (!readXevents(self)) + return NULL; + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* display_fd1(DisplayObject* self, PyObject *args) +{ + return PyInt_FromLong(ConnectionNumber(self->dpy)); +} + +static PyObject* display_shmmode(DisplayObject* self, PyObject *args) +{ + return PyInt_FromLong(self->shmmode); +} + +static PyMethodDef display_methods[] = { + {"close", (PyCFunction)display_close1, METH_VARARGS, NULL}, + {"flip", (PyCFunction)display_flip1, METH_VARARGS, NULL}, + {"clear", (PyCFunction)display_clear1, METH_VARARGS, NULL}, + {"pixmap", (PyCFunction)display_pixmap1, METH_VARARGS, NULL}, + {"putppm", (PyCFunction)display_putppm1, METH_VARARGS, NULL}, + {"getppm", (PyCFunction)display_getppm1, METH_VARARGS, NULL}, + {"overlayppm",(PyCFunction)display_overlayppm1, METH_VARARGS, NULL}, + {"keyevents",(PyCFunction)display_keyevents1,METH_VARARGS, NULL}, + {"mouseevents",(PyCFunction)display_mouseevents1,METH_VARARGS,NULL}, + {"pointermotion",(PyCFunction)display_pointermotion1,METH_VARARGS,NULL}, + {"fd", (PyCFunction)display_fd1, METH_VARARGS, NULL}, + {"shmmode", (PyCFunction)display_shmmode, METH_VARARGS, NULL}, + {NULL, NULL} /* sentinel */ +}; + +static PyObject* display_getattr(DisplayObject* self, char* name) +{ + return Py_FindMethod(display_methods, (PyObject*)self, name); +} + + +statichere PyTypeObject Display_Type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Display", /*tp_name*/ + sizeof(DisplayObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)display_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc)display_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ +}; + +statichere PyTypeObject XPixmap_Type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Pixmap", /*tp_name*/ + sizeof(XPixmapObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)pixmap_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ +}; + + +static PyMethodDef ShmMethods[] = { + {"Display", new_display, METH_VARARGS}, + {NULL, NULL} /* Sentinel */ + }; + +void initxshm(void) +{ + Display_Type.ob_type = &PyType_Type; + XPixmap_Type.ob_type = &PyType_Type; + Py_InitModule("xshm", ShmMethods); +} diff --git a/display/xshm.so b/display/xshm.so Binary files differnew file mode 100755 index 0000000..2296a67 --- /dev/null +++ b/display/xshm.so |