Subject: FVWM: Fvwm.py -- A Python module for Fvwm modules!

From: "Barry A. Warsaw" <[email-masked]>
Date: Fri, 9 Aug 1996 17:46:03 -0400

Here is my new Fvwm.py for writing Fvwm modules in Python.  It was
inspired by Jonathan Kelley's fvwmmod.py for Fvwm version 1.  I
haven't done a lot with it, so I'd really appreciate feedback.

Find out more, and get my Snap.py sample module, by checking out this
URL:

    <http://www.python.org/~bwarsaw/pyware/>

Note that these pages use frames and I *think* it should support
non-frame browsers, but if that doesn't work, I'd like to know that
too!

Enjoy,
-Barry

-------------------- snip snip --------------------
#! /bin/env python

"""
FVWM version 2 module interface framework for Python.

Much thanks go to fvwmmod.py, a module written by Jonathan Kelley
<[email-masked]> for FVWM version 1.  I've done some
restructuring, enhancements, and upgrading to FVWM version 2, and to
provide a bit more framework for module authors.

"""

__version__ = '0.1'

import struct
import os
import sys
import string
import regex
from types import *

def capitalize(s):
    if len(s) < 1: return s
    return string.upper(s[0]) + string.lower(s[1:])


# useful contants from fvwm/module.h -- not all are duplicated here

SynchronizationError = 'SynchronizationError'
PipeReadError = 'PipeReadError'

HEADER_SIZE = 4
DATASIZE = struct.calcsize('l')
START = 0xffffffff
WINDOW_NONE = 0

Contexts = {'C_NO_CONTEXT': 0,
	    'C_WINDOW':     1,
	    'C_TITLE':      2,
	    'C_ICON':       4,
	    'C_ROOT':       8,
	    'C_FRAME':     16,
	    'C_SIDEBAR':   32,
	    'C_L1':        64,
	    'C_L2':       128,
	    'C_L3':       256,
	    'C_L4':       512,
	    'C_L5':      1024,
	    'C_R1':      2048,
	    'C_R2':      4096,
	    'C_R3':      8192,
	    'C_R4':     16384,
	    'C_R5':     32768,
	    }
for k, v in Contexts.items()[:]:
    Contexts[v] = k

Types = {'M_NEW_PAGE':            1,
	 'M_NEW_DESK':         1<<1,
	 'M_ADD_WINDOW':       1<<2,
	 'M_RAISE_WINDOW':     1<<3,
	 'M_LOWER_WINDOW':     1<<4,
	 'M_CONFIGURE_WINDOW': 1<<5,
	 'M_FOCUS_CHANGE':     1<<6,
	 'M_DESTROY_WINDOW':   1<<7,
	 'M_ICONIFY':          1<<8,
	 'M_DEICONIFY':        1<<9,
	 'M_WINDOW_NAME':      1<<10,
	 'M_ICON_NAME':        1<<11,
	 'M_RES_CLASS':        1<<12,
	 'M_RES_NAME':         1<<13,
	 'M_END_WINDOWLIST':   1<<14,
	 'M_ICON_LOCATION':    1<<15,
	 'M_MAP':              1<<16,
	 'M_ERROR':            1<<17,
	 'M_CONFIG_INFO':      1<<18,
	 'M_END_CONFIG_INFO':  1<<19,
	 'M_ICON_FILE':        1<<20,
	 'M_DEFAULTICON':      1<<21,
	 'M_STRING':           1<<22,
	 }
for k, v in Types.items()[:]:
    Types[v] = k


class PacketReader:
    """Reads a packet from the given file-like object.

    Public functions: read_packet(fp)
    """
    def read_packet(self, fp):
	"""Reads a packet from `fp' a file-like object.

	Returns an instance of a class derived from Packet.  Raises
	PipeReadError if a valid packet could not be read.  Returns
	None if no corresponding Packet-derived class could be
	instantiated.

	"""
	data = fp.read(DATASIZE * 2)
	try:
	    start, typenum = struct.unpack('2l', data)
	except struct.error:
	    raise PipeReadError
	if start <> START:
	    raise SynchronizationError
	try:
	    msgtype = Types[typenum]
	except KeyError:
	    print 'unknown FVWM message type:', typenum, '(ignored)'
	    return None
	classname = string.joinfields(
	    map(capitalize, string.splitfields(msgtype, '_')[1:]) + ['Packet'],
	    '')
	# create an instance of the class named by the packet type
	try:
	    return getattr(PacketReader, classname)(typenum, fp)
	except AttributeError:
	    print 'no packet class named:', classname
	    return None

    class __Packet:
	def __init__(self, typenum, fp):
	    # start of packet and type have already been read
	    data = fp.read(DATASIZE * 2)
	    self.pktlen, self.timestamp = struct.unpack('2l', data)
	    self.msgtype = typenum
	    self.pkthandler = string.joinfields(
		map(capitalize, string.splitfields(Types[typenum], '_')[1:]),
		'')
    
	def _read(self, fp, *attrs):
	    attrcnt = len(attrs)
	    fmt = '%dl' % attrcnt
	    data = fp.read(DATASIZE * attrcnt)
	    attrvals = struct.unpack(fmt, data)
	    i = 0
	    for a in attrs:
		setattr(self, a, data[i])
		i = i+1
    
    class NewPagePacket(__Packet):
	def __init__(self, type, fp):
	    PacketReader.__Packet.__init__(self, type, fp)
	    self._read(fp, 'x', 'y', 'desk', 'max_x', 'max_y')
    
    class NewDeskPacket(__Packet):
	def __init__(self, type, fp):
	    PacketReader.__Packet.__init__(self, type, fp)
	    self._read(fp, 'desk')
    
    class AddWindowPacket(__Packet):
	def __init__(self, type, fp):
	    PacketReader.__Packet.__init__(self, type, fp)
	    self._read(fp, 'top_id', 'frame_id', 'db_entry', 'x', 'y',
		       'width', 'height', 'desk', 'flags',
		       'title_height', 'border_width',
		       'base_width', 'base_height',
		       'resize_width_incr', 'resize_height_incr',
		       'min_width', 'min_height',
		       'max_width', 'max_height',
		       'icon_label_id', 'icon_pixmap_id',
		       'gravity',
		       'text_color', 'border_color')
    
    class ConfigureWindowPacket(AddWindowPacket):
	pass
    
    class LowerWindowPacket(__Packet):
	def __init__(self, type, fp):
	    PacketReader.__Packet.__init__(self, type, fp)
	    self._read(fp, 'top_id', 'frame_id', 'db_entry')
    
    class RaiseWindowPacket(LowerWindowPacket):
	pass
    
    class DestroyPacket(LowerWindowPacket):
	pass
    
    class FocusChangePacket(LowerWindowPacket):
	def __init__(self, type, fp):
	    PacketReader.LowerWindowPacket.__init__(self, type, fp)
	    self._read(fp, 'text_color', 'border_color')
    
    class IconifyPacket(LowerWindowPacket):
	def __init__(self, type, fp):
	    PacketReader.LowerWindowPacket.__init__(self, type, fp)
	    self._read(fp, 'x', 'y', 'width', 'height')
    
    class IconLocationPacket(IconifyPacket):
	pass
    
    class DeiconifyPacket(LowerWindowPacket):
	pass
    
    class MapPacket(LowerWindowPacket):
	pass
    
    class __VariableLenStringPacket:
	"""Can only be used for multiple inheritance with Packet"""
	def __init__(self, fp, extraskip=3):
	    fieldlen = self.pktlen - HEADER_SIZE - extraskip
	    data = fp.read(DATASIZE * fieldlen)
	    index = string.find(data, '\000')
	    if index >= 0:
		data = data[:index]
	    self.data = string.strip(data)
    
    class WindowNamePacket(LowerWindowPacket, __VariableLenStringPacket):
	def __init__(self, type, fp):
	    PacketReader.LowerWindowPacket.__init__(self, type, fp)
	    PacketReader.__VariableLenStringPacket.__init__(self, fp)
	    self.name = self.data
    
    class IconNamePacket(WindowNamePacket):
	pass
    
    class ResClassPacket(WindowNamePacket):
	pass
    
    class ResNamePacket(WindowNamePacket):
	pass
    
    class EndWindowlistPacket(__Packet):
	pass
    
    class StringPacket(__Packet, __VariableLenStringPacket):
	def __init__(self, type, fp):
	    PacketReader.__Packet.__init__(self, type, fp)
	    dummy = fp.read(DATASIZE * 3)       # three zero fields
	    PacketReader.__VariableLenStringPacket.__init__(self, fp)

    class ErrorPacket(StringPacket):
	def __init__(self, type, fp):
	    PacketReader.StringPacket.__init__(self, type, fp)
	    self.errmsg = self.data
    
    class ConfigInfoPacket(StringPacket):
	pass
    
    class EndConfigInfoPacket(__Packet):
	pass

    class IconFilePacket(WindowNamePacket):
	pass

    class DefaulticonPacket(StringPacket):
	pass


class FvwmModule:
    def __init__(self, argv):
	if len(argv) < 6:
	    raise UsageError, \
		  'Usage: %s ofd ifd configfile appcontext wincontext' % \
		  argv[0]
	try:
	    self.__tofvwm   = os.fdopen(string.atoi(argv[1]), 'wb')
	    self.__fromfvwm = os.fdopen(string.atoi(argv[2]), 'rb')
	except ValueError:
	    raise UsageError, \
		  'Usage: %s ofd ifd configfile appcontext wincontext' % \
		  argv[0]
	self.configfile = argv[3]
	self.appcontext = argv[4]
	self.wincontext = argv[5]
	self.args = argv[6:][:]
	self.done = 0
	self.__inmainloop = 0
	self.__pktreader = PacketReader()
	# initialize dispatch table
	self.__dispatch = {}

    def __close(self):
	self.__tofvwm.close()
	self.__fromfvwm.close()

    def __del__(self):
	self.__close()

    def __do_dispatch(self):
	"""Dispatch on the read packet type.

	Any register()'d callbacks take precedence over instance methods.

	"""
	try:
	    pkt = self.__pktreader.read_packet(self.__fromfvwm)
	except PipeReadError:
	    self.__close()
	    sys.exit(1)
	# dispatch
	if pkt:
	    try:
		self.__dispatch[pkt.pkthandler](self, pkt)
	    except KeyError:
		if hasattr(self, pkt.pkthandler):
		    method = getattr(self, pkt.pkthandler)
		else:
		    method = self.unhandled_packet
		method(pkt)

    #
    # alternative callback mechanism (used before method invocation)
    #
    def register(self, pktname, callback):
	"""Register a callback for a packet type.

	PKTNAME is the name of the packet to dispatch on.  Packet
	names are strings as described in the Fvwm Module Interface
	documentation.  PKTNAME can also be the numeric equivalent of
	the packet type.

	CALLBACK is a method that takes two arguments.  The first
	argument is the FvwmModule instance receiving the packet, and
	the second argument is the packet instance.

	Callbacks are not nestable.  If a callback has been previously
	registered for the packet, the old callback is returned.

	"""

	if type(pktname) == IntType:
	    pktname = Types[pktname]
	# raise KeyError if it is an invalid packet name
	Types[pktname]
	pkthandler = string.joinfields(
	    map(capitalize, string.splitfields(pktname, '_')[1:]),
	    '')
	try:
	    cb = self.__dispatch[pkthandler]
	except KeyError:
	    cb = None
	self.__dispatch[pkthandler] = callback
	return cb

    def unregister(self, pktname):
	"""Unregister any callbacks for the named packet.

	Any registered callback is returned.

	"""
	if type(pktname) == IntType:
	    pktname = Types[pktname]
	# raise KeyError if it is an invalid packet name
	Types[pktname]
	pkthandler = string.joinfields(
	    map(capitalize, string.splitfields(pktname, '_')[1:]),
	    '')
	try:
	    cb = self.__dispatch[pkthandler]
	    del self.__dispatch[pkthandler]
	except KeyError:
	    cb = None
	return cb

    #
    # typical usage
    #
    def mainloop(self):
	self.__inmainloop = 1
	while not self.done:
	    self.__do_dispatch()
	self.__inmainloop = 0

    def send(self, command, window=WINDOW_NONE, cont=1):
	if command:
	    self.__tofvwm.write(struct.pack('l', window))
	    self.__tofvwm.write(struct.pack('i', len(command)))
	    self.__tofvwm.write(command)
	    self.__tofvwm.write(struct.pack('i', cont))
	    self.__tofvwm.flush()

    def set_mask(self, mask=None):
	if mask is None:
	    mask = 0
	    for name, flag in Types.items():
		if type(name) <> StringType:
		    continue
		methodname = string.joinfields(
		    map(capitalize, string.splitfields(name, '_')[1:]),
		    '')
		if hasattr(self, methodname) or \
		   self.__dispatch.has_key(methodname):
		    mask = mask | flag
	self.send('Set_Mask ' + `mask`)

    def unhandled_packet(self, pkt):
	pass

    #
    # useful shortcuts
    #
    def get_configinfo(self):
	"""Returns Fvwm module configuration line object."""
	info = []
	def collect_cb(self, pkt, info=info):
	    info.append(string.strip(pkt.data))

	def end_cb(self, pkt, inmainloop=self.__inmainloop):
	    if inmainloop:
		raise 'BogusNonlocalExit'
	    else:
		self.done = 1

	oldcb_1 = self.register('M_CONFIG_INFO', collect_cb)
	oldcb_2 = self.register('M_END_CONFIG_INFO', end_cb)

	self.send('Send_ConfigInfo')
	try:
	    self.mainloop()
	except 'BogusNonlocalExit':
	    pass

	if oldcb_1: self.register('M_CONFIG_INFO', oldcb_1)
	else: self.unregister('M_CONFIG_INFO')
	if oldcb_2: self.register('M_END_CONFIG_INFO', oldcb_2)
	else: self.unregister('M_CONFIG_INFO')

	return ConfigInfo(info)


class ConfigInfo:
    """Class encapsulating the FVWM configuration lines.

    Public methods:
        get_iconpath()    -- returns the value of IconPath variable
	get_pixmappath()  -- returns the value of PixmapPath variable
	get_clicktime()   -- returns the value of ClickTime variable

	get_infolines(re) -- returns all lines.  If optional RE is given, only
	                     lines that start with `*<RE>' are
	                     returned (the initial star should not be
	                     included).

    """

    def __init__(self, lines):
	self.__lines = lines
	self.__iconpath = None
	self.__pixmappath = None
	self.__clicktime = None

    def __get_predefineds(self, varname):
	for line in self.__lines:
	    parts = string.split(line)
	    if string.lower(parts[0]) == varname:
		return parts[1]

    def get_iconpath(self):
	if not self.__iconpath:
	    self.__iconpath = self.__get_predefineds('iconpath')
	return self.__iconpath

    def get_pixmappath(self):
	if not self.__pixmappath:
	    self.__pixmappath = self.__get_predefineds('pixmappath')
	return self.__pixmappath

    def get_clicktime(self):
	if not self.__clicktime:
	    self.__clicktime = self.__get_predefineds('clicktime')
	return self.__clicktime

    def get_infolines(self, re=None):
	pred = None
	if re:
	    cre = regex.compile('\*' + re)
	    pred = lambda l, cre=cre: cre.match(l) >= 0
	return filter(pred, self.__lines)


# testing

if __name__ == '__main__':
    def printlist(list):
	for item in list:
	    print "`%s'" % item

    m = FvwmModule(sys.argv)
    info = m.get_configinfo()
    print 'IconPath   ==', info.get_iconpath()
    print 'PixmapPath ==', info.get_pixmappath()
    print 'ClickTime  ==', info.get_clicktime()
    print '1 =========='
    printlist(info.get_infolines())
    print '2 =========='
    printlist(info.get_infolines('FvwmIdent'))
--
Visit the official FVWM web page at <URL:http://www.hpc.uh.edu/fvwm/>.
To unsubscribe from the list, send "unsubscribe fvwm" in the body of a
message to [email-masked].
To report problems, send mail to [email-masked].