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

From: Barry A. Warsaw <bwarsaw_at_anthem.cnri.reston.va.us>
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
<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

This archive was generated by hypermail 2.3.0 : Mon Aug 29 2016 - 19:37:59 BST