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
<jkelley_at_ugcs.caltech.edu> 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 majordomo_at_hpc.uh.edu.
To report problems, send mail to fvwm-owner_at_hpc.uh.edu.
Received on Fri Aug 09 1996 - 16:51:06 BST