Fernando Canizo <[EMAIL PROTECTED]> writes:

> "Release early and often".

Fair 'nuf; it's attached.

It requires python 2.4 because it uses the subproccess module.

#!/usr/bin/python2.4
########################################################################
#
# Time-stamp: <2006-06-07 13:53:47 Jeremy Hankins>
#
# Interface with wmii
#
# TODO:
#
#    - Add caching to wmii.proglist
#
########################################################################

from optparse import OptionParser
import os, sys, re, string, signal, subprocess, dircache, stat

def abort(reason, exitcode):
    program = os.path.basename(sys.argv[0])
    print >>sys.stderr, '%s: error: %s' % (program, reason)
    sys.exit(exitcode)

def warn(message):
    print >>sys.stderr, message

def timeout_handler(signum, frame):
    raise TimedOutError()

class TimedOutError(Exception):
    def __init__(self, value = "Timed Out"):
        self.value = value
    def __str__(self):
        return repr(self.value)

class wmiiError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return `self.value`

class wmii_event(subprocess.Popen):

    def read(self):
        return string.rstrip(self.stdout.readline())

    def close(self, timeout = 1):
        signal.signal(signal.SIGALRM, timeout_handler)
        signal.alarm(timeout)

        returncode = None
        try:
            self.stdout.close()
            returncode = self.wait()
        except TimedOutError, KeyboardInterrupt:
            warn("wmiir process %d killed." % self.pid)
            os.kill(self.pid, signal.SIGTERM)

        return(returncode)


class wmii:
    """ Class for objects which interact with a running wmii instance.
    """

    def event(self):
        """Return a wmii_event object from which events can be read.
        """
        return(wmii_event(("wmiir", "read", "/event"),
                          stdout = subprocess.PIPE))


    def menu(self, menu, timeout = None):
        """Sends menu (a list) to the wmiimenu command and returns the
        output.  By default there is no timeout, since wmiimenu is
        interactive.
        """
        if timeout:
            signal.signal(signal.SIGALRM, timeout_handler)
            signal.alarm(timeout)

        try:
            p = subprocess.Popen(("wmiimenu"),
                                 stdin = subprocess.PIPE,
                                 stdout = subprocess.PIPE)
            out = p.communicate("\n".join(menu + [""]))
        except TimedOutError:
            os.kill(p.pid, signal.SIGTERM)
            raise TimedOutError("Timed out calling wmiimenu.")

        if timeout:
            signal.alarm(0)

        if p.returncode == 1:
            return None
        elif p.returncode != 0:
            raise wmiiError("Error calling wmiimenu: %d" % p.returncode)

        return out[0]



    def read(self, file, timeout = 5):
        """Reads the contents of file using wmiir.  The read is done
        with a timeout, so this is not appropriate for reading from
        /event.
        """
        signal.signal(signal.SIGALRM, timeout_handler)
        signal.alarm(timeout)

        try:
            p = subprocess.Popen(("wmiir", "read", "%s" % file),
                                 stdout = subprocess.PIPE)
            data = map(lambda s: string.rstrip(s), p.stdout.readlines())
            returncode = p.wait()
        except TimedOutError:
            os.kill(p.pid, signal.SIGTERM)
            raise TimedOutError("Timed out reading from %s." % file)
        signal.alarm(0)

        if p.returncode != 0:
            raise wmiiError("Error reading %s." % file)

        return(data)


    def write(self, file, str, timeout = 5):
        """Writes str to file using wmiir.
        """
        signal.signal(signal.SIGALRM, timeout_handler)
        signal.alarm(timeout)

        try:
            p = subprocess.Popen(("wmiir", "write", "%s" % file),
                                 stdin = subprocess.PIPE)
            p.stdin.write(str)
            p.stdin.close()
            returncode = p.wait()
        except TimedOutError:
            os.kill(p.pid, signal.SIGTERM)
            raise TimedOutError("Timed out writing to %s." % file)
        signal.alarm(0)

        if returncode != 0:
            raise wmiiError("Error writing %s." % file)


    def ls(self, dir):
        """Returns a list of files in dir.
        """
        return map(lambda s: string.split(s)[-1], self.read(dir))


    def match_class(self, xclass_expr):
        """Returns a list of clients with matching classes.
        """
        exp = re.compile(xclass_expr)
        l = []
        for client in self.ls("/client"):
            xclass = self.read("/client/%s/class" % client)[0]
            if exp.search(xclass):
                l.append(client)
        return(l)


    def match_title(self, title_expr):
        """Returns a list of clients with matching titles.
        """
        exp = re.compile(title_expr)
        l = []
        for client in self.ls("/client"):
            title = self.read("/client/%s/name" % client)[0]
            if exp.search(title):
                l.append(client)
        return(l)


    def get_focus(self):
        """Returns the client with focus.
        """
        return self.read("/view/sel/sel/index")[0]


    def find_client(self, client):
        """Returns a list of (view, column, window, client) quads where
        client can be found.
        """
        numeric = re.compile(r"^\d+$")
        l = []
        for view in self.read("/tags"):
            for col in filter(lambda s: numeric.match(s), self.ls("/%s" % view)):
                for win in filter(lambda s: numeric.match(s), self.ls("/%s/%s" % (view, col))):
                    if client == self.read("/%s/%s/%s/index" % (view, col, win))[0]:
                        l.append((view, col, win, client))
                        break
                else:
                    # No need to continue looking for a match in this
                    # view if one has already been found, so continue if
                    # no match, otherwise break.
                    continue
                break
        return(l)


    def show_client(self, clients):
        """Give focus to a client in clients.  Priority is given to
        switching to a client other than the current one, if possible.
        If there are still multiple options preference is given to
        options in the current view.  After that an option is chosen
        essentially at random.
        """
        choices = []
        for c in clients:
            choices = choices + self.find_client(c)

        num = len(choices)
        path = None

        if num == 0:
            return(False)

        # Filter out the currently displayed client
        curclient = self.get_focus()
        choices = filter(lambda t: t[3] != curclient, choices)
        num = len(choices)

        if num == 0:
            # The only option is the current client, so this is a null-op.
            return(True)
        elif num == 1:
            path = choices[0]
        else:
            # Still multiple options; check for any in the current view.
            curview = self.read("/view/name")[0]
            choices_view = filter(lambda t: t[0] == curview, choices)
            num_view = len(choices_view)
            if num_view == 1:
                path = choices_view[0]
            else:
                if num_view > 1:
                    choices = choices_view
                    num = num_view
                path = choices[0]

        # Now that a path to a window has been chosen, switch to it.
        # Selecting in order from most to least specific will eliminate
        # the possibility that some other window will have focus
        # temporarily, and maximize responsiveness.
        # Select the window:
        self.write("/%s/%s/ctl" % (path[0], path[1]), "select %s" % path[2])
        # Select the column:
        self.write("/%s/ctl" % path[0], "select %s" % path[1])
        # Select the view:
        self.write("/ctl", "view %s" % path[0])

        return(True)


    def show_class(self, class_expr, cmd):
        """If a client with a matching class can be found, switch to
        it.  Otherwise, run cmd.
        """
        if not self.show_client(self.match_class(class_expr)):
            self.bg(cmd)


    def show_title(self, title_expr, cmd):
        """If a client with a matching title can be found, switch to
        it.  Otherwise, run cmd.
        """
        if not self.show_client(self.match_title(title_expr)):
            self.bg(cmd)


    def proglist(self, path = None):
        """ Generate a listing of executables in path (a list of
        directories).
        """
        # I really should figure out how to cache this.

        if not path:
            path = string.split(os.environ['PATH'], ':')

        l = []
        for d in path:
            if os.path.isdir(d):
                for f in dircache.listdir(d):
                    full = os.path.join(d, f)
                    try:
                        mode = os.stat(full)[stat.ST_MODE]
                        isexe = mode & 0111
                        isdir = stat.S_ISDIR(mode)
                        if isexe and not isdir:
                            l.append(f)
                    except OSError, e:
                        # Pass on file-not-found (broken link, probably),
                        # otherwise re-raise the error.
                        if e.errno != 2:
                            raise e

        # Now to sort the list & weed out dups:
        n = len(l)
        if n > 0:
            l.sort()
            last = l[0]
            lasti = i = 1
            while i < n:
                if l[i] != last:
                    l[lasti] = last = l[i]
                    lasti += 1
                i += 1

        return l[:lasti]


    def bg(self, cmd, path = None):
        """Run a command via a shell, backgrounded.  Optionally, set the
        PATH env variable to path first.
        """
        if path:
            environment = os.environ.copy()
            environment['PATH'] = path

            subprocess.call(cmd + " &", shell = True, env = environment)
        else:
            subprocess.call(cmd + " &", shell = True)


    def run_menu(self, menu, path = None):
        cmd = self.menu(menu)
        if cmd:
            self.bg(cmd, path)




if __name__ == '__main__':
    cli = OptionParser(usage='%prog [options]\nTest access to wmii',
                       version='%prog 0.1')
    options, args = cli.parse_args()

    wm = wmii()

    #print(repr(wm.read("/tags")))

    #wm.write("/event", "Write test successful.\n")

    #wm.run_menu(wm.proglist(os.environ["PATH"].split(":")))

    #wm.show_class(r"^Firefox-bin:", "firefox")

    #print "Menu: %s" % repr(wm.menu(["a", "b", "c"]))

    #events = wm.event()
    #print(repr(events.read()))
    #events.close()



#!/usr/bin/python2.4
########################################################################
#
# Time-stamp: <2006-06-07 13:53:24 Jeremy Hankins>
#
# TODO:
#
#    - mount wmiifs?
#
#    - Native monitors?
#
########################################################################

import modwmii, time, subprocess, os, sys

# {{{ Variables

key_mod   = 'Mod4'
key_up    = 'k'
key_down  = 'j'
key_left  = 'h'
key_right = 'l' 

font = '-misc-fixed-bold-r-normal--*-140-*-*-*-*-iso10646-1'

clr_dim_green = '#AACCAA #333333 #336633'
clr_act_green = '#AACCAA #111111 #336633'
clr_dim_blue  = '#AAAACC #333333 #333366'
clr_act_blue  = '#AAAACC #111111 #333366'
clr_dim_red   = '#CCAAAA #333333 #663333'
clr_act_red   = '#CCAAAA #111111 #663333'

clr_normal = clr_dim_green
clr_select = clr_act_red
clr_bar    = clr_act_blue

tag_rules = """/XMMS.*/ -> ~
/Gimp.*/ -> ~
/MPlayer.*/ -> ~
/wm.*/ -> ~
/qw.exe.*/ -> quicken
/Firefox-bin.*/ -> www
/.*/ -> !
/.*/ -> main
"""

wmiidir_priv = os.environ["HOME"] + "/.wmii-3"
wmiidir_glob = "/etc/X11/wmii-3"

# Order is important:
wmiidirs = (wmiidir_priv, wmiidir_glob)

# }}}
# {{{ Key bindings

# These strings are eval'd on Key events.  Would it make sense to compile them?

mod_keys = {
    key_left  : 'wm.write("/view/ctl", "select prev")',
    key_right : 'wm.write("/view/ctl", "select next")',
    key_up    : 'wm.write("/view/sel/ctl", "select prev")',
    key_down  : 'wm.write("/view/sel/ctl", "select next")',
    "space"   : 'wm.write("/view/ctl", "select toggle")',
    "d"       : 'wm.write("/view/sel/mode", "default")',
    "s"       : 'wm.write("/view/sel/mode", "stack")',
    "m"       : 'wm.write("/view/sel/mode", "max")',
    "a"       : 'wm.run_menu(wm.proglist(wmiidirs), path = "%s:%s:" % wmiidirs + os.environ["PATH"])',
    "r"       : 'wm.run_menu(wm.proglist(os.environ["PATH"].split(":")))',
    "g"       : 'wm.write("/ctl", "view %s" % wm.menu(wm.read("/tags")))',
    "t"       : 'wm.show_class(r"^Emacs:", "wmiisetsid emacs")',
    "w"       : 'wm.show_class(r"^Firefox-bin:", "wmiisetsid firefox")',
    "c"       : 'wm.show_title(r"^shell$", "wmiisetsid myterm -title shell")',
    "x"       : 'wm.bg("wmiisetsid xscreensaver-command -lock")',
    "Up"      : 'wm.bg("wmiisetsid amixer set Master 5%+ >/dev/null 2>&1")',
    "Down"    : 'wm.bg("wmiisetsid amixer set Master 5%- >/dev/null 2>&1")'
    }

mod_shift_keys = {
    key_left  : 'wm.write("/view/sel/sel/ctl", "sendto prev")',
    key_right : 'wm.write("/view/sel/sel/ctl", "sendto next")',
    key_up    : 'wm.write("/view/sel/sel/ctl", "swap up")',
    key_down  : 'wm.write("/view/sel/sel/ctl", "swap down")',
    "space"   : 'wm.write("/view/sel/sel/ctl", "sendto toggle")',
    "c"       : 'wm.write("/view/sel/sel/ctl", "kill")',
    "t"       : 'wm.write("/view/sel/sel/tags", wm.menu(wm.read("/tags")))'
    }

mod_ctrl_keys = {
    key_left  : 'wm.write("/view/sel/sel/ctl", "swap prev")',
    key_right : 'wm.write("/view/sel/sel/ctl", "swap next")',
    key_up    : 'wm.write("/view/sel/sel/ctl", "swap up")',
    key_down  : 'wm.write("/view/sel/sel/ctl", "swap down")'
    }

# }}}
# {{{ Utility functions

# Having the time on messages is sometimes useful for debugging...

def warn(message):
    """ Print a warning to stderr, with timestamp.
    """
    stamp = time.strftime("%T")
    print >>sys.stderr, "%s: %s" % (stamp, message)

# }}}
# {{{ Initialize

wm = modwmii.wmii()

sucess = False
while not sucess:
    try:
        wm.write("/event", "Start wmiirc\n")
        sucess = True
    except wmiiError:
        time.sleep(1)

events = wm.event()

# }}}
# {{{ WM settings

# Some subprocesses (e.g., status monitors & wmiimenu) use these:
os.environ['WMII_FONT'] = font
os.environ['WMII_NORMCOLORS'] = clr_normal
os.environ['WMII_SELCOLORS'] = clr_select
os.environ['WMII_BARCOLORS'] = clr_bar

os.environ['DIM_GREEN'] = clr_dim_green
os.environ['ACT_GREEN'] = clr_act_green
os.environ['DIM_BLUE'] = clr_dim_blue
os.environ['ACT_BLUE'] = clr_act_blue
os.environ['DIM_RED'] = clr_dim_red
os.environ['ACT_RED'] = clr_act_red

subprocess.call(("xsetroot", "-solid", "#333333"))

wm.write("/def/border", "2")
wm.write("/def/font", font)
wm.write("/def/selcolors", clr_select)
wm.write("/def/normcolors", clr_normal)
wm.write("/def/colmode", "stack")
wm.write("/def/colwidth", "0")
wm.write("/def/rules", tag_rules)

wm.write("/def/grabmod", key_mod)
wm.write("/def/keys",
         "\n".join(map(lambda s: "-".join([key_mod, s]),
                       mod_keys.keys()) +
                   map(lambda s: "-".join([key_mod, "Shift", s]),
                       mod_shift_keys.keys()) +
                   map(lambda s: "-".join([key_mod, "Control", s]),
                       mod_ctrl_keys.keys()) +
                   [""]))

# }}}
# {{{ Monitors

# These should be wait()'ed for -- they spawn zombies.

os.spawnlp(os.P_NOWAIT, "status")
#os.spawnlp(os.P_NOWAIT, "status_mail")
#os.spawnlp(os.P_NOWAIT, "status_dgs")
#os.spawnlp(os.P_NOWAIT, "status_acpi")

# }}}
# {{{ Event loop

while True:
    args = events.read().split()

    if args[0] == "Start" and args[1] == "wmiirc":
        events.close()
        sys.exit(0)

    elif args[0] == "BarClick":
        wm.write("/ctl" "view %s" % args[1])

    elif args[0] == "Key":
        args = args[1].split("-")

        if len(args) == 2:
            eval(mod_keys[args[1]])
        elif args[1] == "Shift":
            eval(mod_shift_keys[args[2]])
        elif args[1] == "Control":
            eval(mod_ctrl_keys[args[2]])


# }}}

# Local Variables:
# folded-file:t
# End:
-- 
Jeremy Hankins <[EMAIL PROTECTED]>
PGP fingerprint: 748F 4D16 538E 75D6 8333  9E10 D212 B5ED 37D0 0A03
_______________________________________________
[email protected] mailing list
http://wmii.de/cgi-bin/mailman/listinfo/wmii

Reply via email to