python-plwm-2.6a+20080530/0000755000175000017500000000000011020017212013425 5ustar stewstewpython-plwm-2.6a+20080530/ideas/0000755000175000017500000000000011020017212014512 5ustar stewstewpython-plwm-2.6a+20080530/ideas/views.txt0000644000175000017500000000331407410562562016436 0ustar stewstew* Generalized view handling ** Problem Views, or workspaces, are a general concept that should work no matter what window layout handling we use. Currently views only understands client window configuration, and that is all that is stored and restored when switching between views. When panes were added, there was for the first time more than one way of handling window layout. We can't combine panes and views, as the views would badly mess up the internal state of the panes. It would however be very nice to have views of panes, as we already have views of windows. ** Solution Generalize views, making it call a separate mixin to store and restore view layout. Then we could mixin WindowViewLayout or PanesViewLayout, or any future mixin, depending on what kind of window handling we use. (Compare with outline, where we can mixin XorOutline or WindowOutline depending on our preferences.) *** Alternative solution Views saves and restores client state. As noted, this can screw up other mixins that have state that depends on the clients state. Other mixins may want to be able to save and restore state with views. For instance, allowing different modewindows - or not having one - for different views might be useful. Rather than having the user chose a layout type, provide hooks so that other mixins can pass data to be saved with a view, and get that data back when the view is restored. For instance, have TYPE_viewsave and TYPE_viewrestore, with a listing of TYPE's in views.py. Making a mixin "play well" with views would then require adding TYPE to the central list, writing TYPE_viewsave that returns a list of integers, and TYPE_viewrestore that accepts a list of integers and puts things back appropriately. python-plwm-2.6a+20080530/ideas/README0000644000175000017500000000056407407120515015416 0ustar stewstew* PLWM Ideas Here various ideas about bugs, new features and redesigns can be discussed. It is intented as a scratch pad for the developers to store ideas that isn't yet mature enough to be turned into code. Use whatever format that is the most convenient one for expressing the idea, but try to limit yourself to text files. Related ideas should share a single file. python-plwm-2.6a+20080530/ideas/misc.txt0000644000175000017500000000234610763647415016246 0ustar stewstewFrom mail from Peter Liljenberg when the topic of a backwards incompatible rewrite came up. He also included the idea found in events.txt. * Get rid of the entire mixin mechanism except when they actually add functionality to e.g. a Client object. Instead, let each little class that handles some function listen to AddClient events, and when that happens register event handlers for that new client object (using the mechanism above). Some base class can be written to do the AddClient/RemoveClient event listening. * Add some kind of proxy support, or some other abstraction to allow decorations or other manipulations of how a client window is presented (is also easier once the * Some kind of rudimentary widget kit would be helpful for decorations/menues etc. I've seen attempts at supporting python-xlib with generic widget kits, maybe they can be used? If so, one might have to use the kits event model too. The original discussion was prompted by: David Bronke wanting to do window decorations, which Peter Liljenberg did with a proxy object for the Window, and Mike Meyer wanting to make the window manager xinerama aware, which has similar problems: a screen can be an X screen, or a Xinerama screen (a viewport into an X screen). python-plwm-2.6a+20080530/ideas/internalwins.txt0000644000175000017500000000137707436474570020036 0ustar stewstew* Internal windows ** Problem PLWM is increasingly creating windows: modewindow, menu, and the upcoming frames support will also have windows of a sort. There should be a basic class to use for this, which provides event handling, and if needed, masquerading the internal window as a client window so that the ordinary functions for handling things like focus, borders, and resizing work transparently. ** Solution Define a basic class, or maybe two: one for simple internal windows and one for clientish windows. ** Followup Most of this has been done, although internal windows still can't behave as clients all the time. Some mixins, e.g. the BorderClient could be abstracted to allow internal windows to have the same kind of borders as client windows. python-plwm-2.6a+20080530/ideas/events.txt0000644000175000017500000000435007407120515016600 0ustar stewstew* Event handling ** Problem The current event handling model is beginning to feel awkward. The EventDispatcher scheme was created to unify event handler registration with setting the appropriate event masks. The event mask handling works well, but it turns out that only a rather small part of all events in the window manager actually is X events requiring setting event masks. The big problem is the coarseness of the mathing of events to handlers. The idea with generic and specialized dispatchers (with the sequence wm.dispatch -> screen.dispatch -> client.dispatch) is not wholly appropriate. For illustration, consider an idealized focus handler tracking EnterNotify events on the screen root and on client windows: screen.dipatch.add_handler(EventNotify, self.handle_screen_enter) client.dipatch.add_handler(EventNotify, self.handle_client_enter) Here's the catch: when an EventNotify is generated for a client window, _both_ handlers will be called: obviously not what was expected. The sequential event dispatching was created to allow grabbing events from other parts of the window manager, and for allowing to install a single handler on e.g. a screen to catch all client events on that screen. When we start adding client frames (splitting the simple client.dispatch into a client.dispatch -> (client.frame_dispatch or client.window_dispath) and internal windows, these scheme breaks. ** Solution Keep the event mask handling of EventDispatches, but drop their event handler calling support. Instead have a single wm.dispatch in which you register event handlers together with an _event_filter_ that specifies when that handler should be called. Event filters build on the successful client filters idea, of constructing an arbitrarly complex boolean expression. Calling event handlers could then be simplified to for efilter, handler in event_handlers: if efilter(event): handler(event) One problem left to work out is event handler ordering. We should at least support grab handlers, and possibly also system handlers (which are oblivious to grabbing). This could simply be handled by having three lists of event handlers. I doubt that more advanced event ordering is actually needed, currently (v2.4) no part of PLWM uses it. python-plwm-2.6a+20080530/MANIFEST0000644000175000017500000000153610035012624014572 0ustar stewstew.cvsignore Manifest COPYING ChangeLog INSTALL README NEWS ONEWS setup.py doc/.cvsignore doc/Makefile doc/plwm.texi doc/plwm.info doc/plwm.ps doc/plwm.pdf examples/.cvsignore examples/README.examplewm examples/README.fr.examplewm examples/examplewm.py examples/petliwm.py examples/plpwm.py examples/hrwwm.py examples/senapwm.py examples/test.py utils/.cvsignore utils/ChangeLog utils/inspect_plwm.py utils/wmm.py plwm/__init__.py plwm/border.py plwm/cfilter.py plwm/color.py plwm/cycle.py plwm/deltamove.py plwm/event.py plwm/focus.py plwm/font.py plwm/input.py plwm/inspect.py plwm/keys.py plwm/menu.py plwm/message.py plwm/misc.py plwm/mixer.py plwm/modestatus.py plwm/modewindow.py plwm/moveresize.py plwm/mw_apm.py plwm/mw_biff.py plwm/mw_clock.py plwm/mw_load.py plwm/outline.py plwm/panes.py plwm/views.py plwm/wmanager.py plwm/wmevents.py plwm/xlibpath.py python-plwm-2.6a+20080530/utils/0000755000175000017500000000000011020017212014565 5ustar stewstewpython-plwm-2.6a+20080530/utils/ChangeLog0000644000175000017500000000263007230545431016361 0ustar stewstew2001-01-14 * inspect_plwm.py: PLWM inspect client program. Reads expressions like a Python intepreter, but sends them to a running PLWM for evaluation. 2001-01-11 * wmm.py (readrc): Update append calls to Python 2.0 semantics. Thu Feb 3 16:29:52 2000 Peter Liljenberg * xmodewin.py (ModeWindow.__init__): * wmm.py (Main.__init__): More stable font loading: if the desired font can't be loaded 'fixed' is loaded. Previously the programs crashed merrily instead. (readrc): Don't die when .wmmrc can't be read, provide some reasonable default buttons instead. 1999-10-04 * xmodewin.py (ModeWindow.handle_event): Use resources and options to set colors and fonts etc. * xmwbiff.py: Check for both old and new mail in inbox. Use resources to configure strings. 1999-09-27 * xmodewin.py: Rewritten: put much more intelligence in xmodewindow, and simplify for clients. * xmwclock.py: * xmwbiff.py: Rewritten to handle new xmodewin. Thu Sep 23 21:00:38 1999 Peter Liljenberg * xmwbiff.py: * xmwclock.py: * xmodewin.py: Properties ICCCM:ified. 1999-09-21 * xmwbiff.py: Small biff for the mode window. * xmwclock.py: Small clock in the mode window. 1999-09-20 * xmodewin.py: Emacs-styled mode-line window. Very versatile. python-plwm-2.6a+20080530/utils/inspect_plwm.py0000755000175000017500000000733610765600750017703 0ustar stewstew#!/usr/bin/env python # # inspect_plwm.py -- PLWM inspect client # # Copyright (C) 2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys import os from Xlib import display, rdb import readline import socket import struct import string class InspectError(Exception): pass class Inspect: def __init__(self, disp): # Get property containing inspect port and cookie self.PLWM_INSPECT_SERVER = disp.intern_atom('_PLWM_INSPECT_SERVER') p = disp.screen().root.get_property(self.PLWM_INSPECT_SERVER, self.PLWM_INSPECT_SERVER, 0, 2) if not p or p.format != 32 or len(p.value) != 2: raise InspectError('valid _PLWM_INSPECT_SERVER property not found') port = int(p.value[0]) cookie = int(p.value[1]) # Connect to the same host as the display host = string.split(disp.get_display_name(), ':')[0] if host == '': host = '127.0.0.1' self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((host, port)) self.recv_buf = '' # Send cookie, little-endian coded self.send_raw(struct.pack('>l', cookie)) # Recieve and print welcome message sys.stdout.write(self.recv()) def send_raw(self, data): while data: n = self.socket.send(data) data = data[n:] def send(self, data): self.send_raw(struct.pack('>l', len(data)) + data) def recv(self): length = None while length is None or len(self.recv_buf) < length: d = self.socket.recv(1000) if not d: raise InspectError('connection closed by server') self.recv_buf = self.recv_buf + d if length is None: if len(self.recv_buf) < 4: continue length = struct.unpack('>l', self.recv_buf[:4])[0] self.recv_buf = self.recv_buf[4:] d = self.recv_buf[:length] self.recv_buf = self.recv_buf[length:] return d def loop(self): try: while 1: expr = raw_input('>>> ') if expr: # If first character of expr is a space, # then this is a multiline statement. # Read more lines until we get an empty one if expr[0] == ' ': lines = [expr[1:]] while 1: d = raw_input('... ') lines.append(d[1:]) if not d: break expr = string.join(lines, '\n') self.send(expr) sys.stdout.write(self.recv()) except EOFError: self.socket.close() def main(): d, name, db, argv = rdb.get_display_opts(rdb.stdopts) Inspect(d).loop() if __name__ == '__main__': main() python-plwm-2.6a+20080530/utils/wmm.py0000755000175000017500000002027010765600750015767 0ustar stewstew#!/usr/bin/python # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """WMM, or Window Manager Manager, is a small X application which is meant start window managers. It could of course start most any application, but has a feature which makes it suitable to debugging window managers: when mapped (e.g. if iconified when the window manager crashes) it will raise itself to the top of all other windows. So even without a window manager, with focus maybe locked on some unsuitable window, you can start a fall-back window manager and clean up the situation. WMM is configured with ~/.wmmrc. For each non-blank line, not starting with a #, WMM will create a button. Each line should consist of two tab-separated fields, the first is the button label and the second is the command to run when the button is clicked. If the second field is missing, WMM will instead quit when the button is clicked. """ import sys import os from Xlib import display, rdb, X, Xutil import string class Button: def __init__(self, parent, x, y, width, height, name): self.name = name self.window = parent.window.create_window(x, y, width, height, 1, X.CopyFromParent, X.InputOutput, X.CopyFromParent, background_pixel = parent.background, border_pixel = parent.foreground, event_mask = (X.ExposureMask | X.ButtonReleaseMask)) self.gc = parent.gc self.width = width self.height = height # Center string ext = parent.font.query_text_extents(name) self.textwidth = ext.overall_width self.asc = ext.overall_ascent self.desc = ext.overall_descent def wantedwidth(self): return self.textwidth + 8 def redraw(self): sx = max((self.width - self.textwidth) / 2, 3) sy = self.height / 2 + (self.asc + self.desc) / 2 - self.desc self.window.image_text(self.gc, sx, sy, self.name) def resize(self, width, height): self.width = width self.height = height self.window.configure(width = width, height = height) self.window.clear_area(exposures = 1) def handle_event(self, event): if event.type == X.Expose: if event.count == 0: self.redraw() if event.type == X.ButtonRelease: self.button(event) def button(self, event): pass class QuitButton(Button): def button(self, event): raise 'quit', 'quitting' class CommandButton(Button): def __init__(self, parent, x, y, width, height, name, command): Button.__init__(self, parent, x, y, width, height, name) self.command = command def button(self, event): os.system(self.command) class Main: def __init__(self, buttons, displayname = None): self.display = display.Display(displayname) self.screen = self.display.screen() self.foreground = self.screen.black_pixel self.background = self.screen.white_pixel font = '-*-lucida-medium-r-*-sans-12-*' self.font = self.display.open_font(font) if self.font is None: sys.stderr.write('%s: Failed to load font: %s\n' % (sys.argv[0], font)) self.font = self.display.open_font('fixed') fontsize = self.font.query() self.buttonheight = fontsize.font_ascent + fontsize.font_descent + 6 height = len(buttons) * (self.buttonheight + 4) + 2 width = 50 root = self.screen.root self.window = root.create_window(100, 0, width, height, 1, X.CopyFromParent, X.InputOutput, X.CopyFromParent, background_pixel = self.background, border_pixel = self.foreground, event_mask = X.StructureNotifyMask) self.gc = self.window.create_gc(foreground = self.foreground, background = self.background, font = self.font) self.buttons = [] x = 2 y = 2 w = width - 2 * x - 2 h = self.buttonheight - 2 for name, command in buttons: if command: self.buttons.append(CommandButton(self, x, y, w, h, name, command)) else: self.buttons.append(QuitButton(self, x, y, w, h, name)) y = y + h + 6 self.buttonwidth = 0 for b in self.buttons: if b.wantedwidth() > self.buttonwidth: self.buttonwidth = b.wantedwidth() self.window.configure(width = self.buttonwidth + 4, height = height) self.window.set_wm_hints(flags = (Xutil.InputHint | Xutil.StateHint), input = 1, initial_state = Xutil.NormalState) self.window.set_wm_normal_hints(flags = (Xutil.PMinSize | Xutil.PMaxSize), min_width = self.buttonwidth + 4, min_height = height, max_width = self.buttonwidth + 4, max_height = height) self.window.set_wm_name('WMManager') self.window.set_wm_class('wmm', 'WMManager') for b in self.buttons: b.resize(self.buttonwidth - 2, self.buttonheight) b.window.map() self.window.map() def loop(self): try: while 1: event = self.display.next_event() try: win = event.window except AttributeError: pass else: if win == self.window: self.handle_event(event) else: for b in self.buttons: if win == b.window: b.handle_event(event) break except 'quit': self.window.destroy() sys.exit(0) def handle_event(self, event): if event.type == X.ConfigureNotify: self.buttonwidth = event.width - 4 for b in self.buttons: b.resize(self.buttonwidth - 2, self.buttonheight) elif event.type == X.MapNotify: self.window.configure(stack_mode = X.Above) elif event.type == X.DestroyNotify: sys.exit(0) def readrc(): fn = os.path.join(os.environ['HOME'], '.wmmrc') try: lines = open(fn, 'r').readlines() buttons = [] for line in lines: if line != '' and line[0] != '#': try: label, comm = string.split(line, '\t', 1) buttons.append((label, comm)) except ValueError: buttons.append((line, None)) return buttons except IOError, msg: sys.stderr.write('%s: failed to read %s: %s\n' % (sys.argv[0], fn, msg.strerror)) return [('PLWM', 'plwm &'), ('TWM', 'twm &'), ('Quit', None)] if __name__ == "__main__": m = Main(readrc()) m.loop() python-plwm-2.6a+20080530/setup.py0000755000175000017500000000257210761724253015175 0ustar stewstew#!/usr/bin/env python from distutils.core import setup import sys if sys.version < '2.3.3': from distutils.dist import DistributionMetadata DistributionMetadata.classifiers = None DistributionMetadata.download_url = None setup(name="PLWM", version="2.6a", packages=['plwm'], author="Peter Liljenberg", maintainer='Mike Meyer', maintainer_email='mwm@mired.org', description='Modularized X window manager for keyboard-loving programmers', download_url="http://sourceforge.net/project/showfiles.php?group_id=1664", url='http://plwm.sourceforge.net/', license="GPL", classifiers=["Development Status :: 5 - Production/Stable", "Environment :: X11 Applications", 'Intended Audience :: Developers', 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Desktop Environment :: Window Managers', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: User Interfaces']) python-plwm-2.6a+20080530/README0000644000175000017500000000265410034350264014327 0ustar stewstew The Pointless Window Manager or Python As A Configuration File Language or Unlightenment *** Copyright Copyright 1999-2004 Peter Liljenberg (unless indicated otherwisely). The GNU General Public Licence applies to all the code, see COPYING for the usual details. *** Installation See the file INSTALL. *** Using plwm without installing PLWM can be run from the distribution tree. Start it with python (if you dearchived the distribution in /home/foo): python /home/foo/plwm-2.6/examples/examplewm.py *** Documentation In doc, you'll want to build and read plwm.info. (After running make, you can read the built info-file in an emacs by pressing C-u C-h i and giving the path to plwm.info.) A terse description of the example window manager is in examples/README.examplewm. *** Using it. There are a number of example window managers in examples/. Running the examples will start that window manager. Examing the examples is the best way to get a feel for how to configure a plwm window manager. If you want to test window managers, or switch between them, then utils/wmm.py is a window manager manager. Read the source file for documentation. *** Contacts: Mike Meyer Peter Liljenberg The PLWM website is located at http://plwm.sourceforge.net/. PLWM is a SourceForge project, its project page can be found at http://sourceforge.net/project/plwm/. python-plwm-2.6a+20080530/plcm/0000755000175000017500000000000011020017212014360 5ustar stewstewpython-plwm-2.6a+20080530/plcm/main.c0000644000175000017500000001062511007513675015477 0ustar stewstew #include #include #include #include "plcm.h" const char *progname = "unknown"; /* Info about all the extensions, even though not many of them will be used. */ int fixes_event_base, fixes_error_base; int composite_event_base, composite_error_base; int damage_event_base, damage_error_base; Display *disp; Window ctrl_win; create_trans_func_t create_trans; int last_x_error = 0; Atom _PLCM_CONTROL_WINDOW; Atom _PLCM_ENABLE; Atom _PLCM_DISABLE; Atom _PLCM_BRIGHTNESS; static int x_error_handler(Display *d, XErrorEvent *error); int main(int argc, char **argv) { int has_render = 0; glx_version_t glx_version = NO_GLX; int major, minor; XSetWindowAttributes attrs; /* FIXME: process command line args */ progname = argv[0]; disp = XOpenDisplay(NULL); if (disp == NULL) die("could not open display"); /* The default error handler will terminate us, which isn't very nice. Use our own instead. */ XSetErrorHandler(x_error_handler); /* Check that all the necessary extensions are here. Maybe we should check that the required minimum versions are available, but that has to wait until we know that the minimal might be. */ if (!XFixesQueryExtension(disp, &fixes_event_base, &fixes_error_base)) die("could not find fixes extension"); if (!XFixesQueryVersion(disp, &major, &minor)) die("could not get fixes version"); info("using fixes extension %d.%d (lib %d)", major, minor, XFixesVersion()); if (!XCompositeQueryExtension(disp, &composite_event_base, &composite_error_base)) die("could not find composite extension"); if (!XCompositeQueryVersion(disp, &major, &minor)) die("could not get composite version"); info("using composite extension %d.%d (lib %d)", major, minor, XCompositeVersion()); if (!XDamageQueryExtension(disp, &damage_event_base, &damage_error_base)) die("could not find damage extension"); if (!XDamageQueryVersion(disp, &major, &minor)) die("could not get damage version"); info("using damage extension %d.%d", major, minor); has_render = check_render(); glx_version = check_glx(); /* Use best trans method available */ if (glx_version == GLX_13) { info("WARNING: using GLX 1.3, this code is completely untested"); create_trans = create_trans_glx13; } else if (has_render) { info("using RENDER extension"); create_trans = create_trans_render; } else if (glx_version == GLX_12) { info("using GLX 1.2 (which is _slow_)"); create_trans = create_trans_glx12; } else die("requires either render or glx extensions"); /* Intern all the atoms used in the communication with plwm */ _PLCM_CONTROL_WINDOW = XInternAtom(disp, "_PLCM_CONTROL_WINDOW", False); _PLCM_ENABLE = XInternAtom(disp, "_PLCM_ENABLE", False); _PLCM_DISABLE = XInternAtom(disp, "_PLCM_DISABLE", False); _PLCM_BRIGHTNESS = XInternAtom(disp, "_PLCM_BRIGHTNESS", False); /* Create a control window, which plwm sends messages to to enable composition. */ ctrl_win = XCreateWindow(disp, RootWindow(disp, 0), 0, 0, 10, 10, 0, 0, InputOnly, CopyFromParent, 0, &attrs); /* Register rendez-vous property */ XChangeProperty(disp, RootWindow(disp, 0), _PLCM_CONTROL_WINDOW, XA_WINDOW, 32, PropModeReplace, (unsigned char*) &ctrl_win, 1); /* Run until told to die */ event_loop(); /* Remove any redirections */ /* Remove rendez-vous property */ XDeleteProperty(disp, ctrl_win, _PLCM_CONTROL_WINDOW); XDestroyWindow(disp, ctrl_win); XCloseDisplay(disp); disp = NULL; return 0; } void info(const char *fmt, ...) { char buf[2000]; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); fprintf(stderr, "%s: %s\n", progname, buf); } void die(const char *fmt, ...) { char buf[2000]; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); fprintf(stderr, "%s: error: %s\n", progname, buf); exit(1); } static int x_error_handler(Display *d, XErrorEvent *error) { char name[50]; if (XGetErrorText(d, error->error_code, name, sizeof(name)) != Success) { sprintf(name, "X error %d", error->error_code); } info("%s for request %d minor %d resource 0x%08x", name, error->request_code, error->minor_code, (int) error->resourceid); last_x_error = error->error_code; return 0; } python-plwm-2.6a+20080530/plcm/trans_render.c0000644000175000017500000001422511007513675017241 0ustar stewstew #include #include #include #include "plcm.h" static int render_event_base, render_error_base; typedef struct trans_render_s { trans_base_t base; Picture source_picture; Picture target_picture; Pixmap mask_pixmap; Picture mask_picture; } trans_render_t; static void render_trans_destroy(trans_base_t *trans); static void render_trans_rectangle(trans_base_t *trans, XRectangle *rect); int check_render() { int major, minor; if (XRenderQueryExtension(disp, &render_event_base, &render_error_base)) { if (!XRenderQueryVersion(disp, &major, &minor)) { info("could not get render version"); return 0; } if (!XRenderQueryFormats(disp)) { info("could not query render formats"); return 0; } { XRenderPictFormat *format = XRenderFindStandardFormat(disp, PictStandardA8); info("format: %p", format); } info("found render extension %d.%d", major, minor); return 1; } else { info("no render extension"); return 0; } } static trans_ops_t render_ops = { render_trans_destroy, render_trans_rectangle, NULL, /* trans_target_resized */ }; trans_base_t* create_trans_render(projection_t *proj) { trans_render_t *trans; XRenderPictFormat *pict_format; XRenderPictureAttributes pict_attrs; Window dummy_window; int dummy_int; int depth; trans = malloc(sizeof(trans_render_t)); if (trans == NULL) { info("out of memory allocating trans_render_t"); return NULL; } memset(trans, 0, sizeof(trans_render_t)); trans->base.ops = &render_ops; trans->base.proj = proj; pict_format = XRenderFindVisualFormat(disp, proj->visual); if (pict_format == NULL) { info("could not find XRenderPictFormat for visual"); goto error; } /* Catch pixmap/picture creation errors from here onwards */ XSync(disp, False); last_x_error = 0; memset(&pict_attrs, 0, sizeof(XRenderPictureAttributes)); pict_attrs.subwindow_mode = IncludeInferiors; trans->source_picture = XRenderCreatePicture(disp, proj->source_window, pict_format, CPSubwindowMode, &pict_attrs); memset(&pict_attrs, 0, sizeof(XRenderPictureAttributes)); pict_attrs.subwindow_mode = IncludeInferiors; trans->target_picture = XRenderCreatePicture(disp, proj->target_window, pict_format, CPSubwindowMode, &pict_attrs); /* FIXME: XFree(pict_format) here? */ /* Use standard format of 8-bit RGB for mask, must exist */ pict_format = XRenderFindStandardFormat(disp, PictStandardRGB24); if (pict_format == NULL) { info("could not find XRenderPictFormat for standard format RGB24"); goto error; } /* Use depth of root window for pixmap */ if (!XGetGeometry(disp, proj->root, &dummy_window, &dummy_int, &dummy_int, &dummy_int, &dummy_int, &dummy_int, &depth)) { info("failed to get depth of root window"); goto error; } trans->mask_pixmap = XCreatePixmap(disp, proj->root, 1, 1, depth); /* The mask use separate alphas for the different colour components, to allow better brightness control. */ memset(&pict_attrs, 0, sizeof(XRenderPictureAttributes)); pict_attrs.repeat = True; pict_attrs.component_alpha = True; trans->mask_picture = XRenderCreatePicture(disp, trans->mask_pixmap, pict_format, CPRepeat | CPComponentAlpha, &pict_attrs); /* FIXME: XFree(pict_format) here? */ /* Check for creation errors */ XSync(disp, False); if (last_x_error) { info("error while setting up render projection"); goto error; } return (trans_base_t*) trans; error: if (trans->mask_picture) XRenderFreePicture(disp, trans->mask_picture); if (trans->mask_pixmap) XFreePixmap(disp, trans->mask_pixmap); if (trans->target_picture) XRenderFreePicture(disp, trans->target_picture); if (trans->source_picture) XRenderFreePicture(disp, trans->source_picture); free(trans); return NULL; } static void render_trans_destroy(trans_base_t *trans_base) { trans_render_t *trans = (trans_render_t*) trans_base; if (trans != NULL) { XRenderFreePicture(disp, trans->mask_picture); XFreePixmap(disp, trans->mask_pixmap); XRenderFreePicture(disp, trans->target_picture); XRenderFreePicture(disp, trans->source_picture); free(trans); } } static void render_trans_rectangle(trans_base_t *trans_base, XRectangle *rect) { trans_render_t *trans = (trans_render_t*) trans_base; projection_t *proj = trans->base.proj; if (proj->brightness < 0) { /* Dim the window by using the brightness as an alpha value when copying the source window into the target. FIXME: calculate alphas for the different colour components to take relative brightness into account. For now, use a single value for all. */ XRenderColor color; int alpha; /* Translate into 16-bit positive value */ alpha = 255 + proj->brightness; alpha = (alpha << 8) | alpha; color.red = color.green = color.blue = alpha; color.alpha = 0xffffU; XRenderFillRectangle(disp, PictOpSrc, trans->mask_picture, &color, 0, 0, 1, 1); XRenderComposite(disp, PictOpSrc, trans->source_picture, trans->mask_picture, trans->target_picture, rect->x, rect->y, 0, 0, rect->x, rect->y, rect->width, rect->height); } else { /* Make the window brighter by adding a value to the source colour components. FIXME: calculate increases for the different colour components to take relative brightness into account. For now, use a single value for all. */ XRenderColor color; int inc; /* Translate into 16-bit positive value */ inc = (proj->brightness << 8) | proj->brightness; color.red = color.green = color.blue = inc; color.alpha = 0xffffU; XRenderFillRectangle(disp, PictOpSrc, trans->target_picture, &color, rect->x, rect->y, rect->width, rect->height); XRenderComposite(disp, PictOpAdd, trans->source_picture, None, trans->target_picture, rect->x, rect->y, 0, 0, rect->x, rect->y, rect->width, rect->height); } } python-plwm-2.6a+20080530/plcm/plcm.h0000644000175000017500000001215311007513675015511 0ustar stewstew #ifndef __PLCM_H_INCLUDED__ #define __PLCM_H_INCLUDED__ /* We need a lot of X magic */ #define GLX_GLXEXT_PROTOTYPES #include #include #include #include #include /* "API" for the control interface presented by plcm */ /* Property put on root window of screen 0, informing plwm about the window it should send ClientMessages too to enable or disable composition of certain windows. */ extern Atom _PLCM_CONTROL_WINDOW; /* ClientMessage type for enabling rendering of a window. Takes two 32-bit arguments: source window target window The source window should already have been redirected in manual update mode by the caller. A single source window can (for now) only be mapped to a single target window. */ extern Atom _PLCM_ENABLE; /* ClientMessage type for disabling composition of a window. Takes two 32-bit arguments: source window target window */ extern Atom _PLCM_DISABLE; /* Property set on the target window to control the brightness. Type: INTEGER Format: 32 Value: brightness The range is -255 (black) to +255 (white). */ extern Atom _PLCM_BRIGHTNESS; typedef struct trans_base_s trans_base_t; /* Single-linked list of managed source windows projected onto a target window. Not the fastest way to keep track of them, but at a given time probably only a few windows will be handled, and of them the bulk of the processing time will be rendering and not traversing this list. */ typedef struct projection_s { struct projection_s *next; Window source_window; Window target_window; Window root; Screen *screen; Visual *visual; GC gc; int target_is_visible; int target_width; int target_height; int source_is_mapped; XRectangle source_geometry; /* Track when source redraws itself */ Damage damage; XserverRegion damage_region; /* Current projection settings */ int brightness; /* Pixel trans implementation object */ trans_base_t *trans; } projection_t; /* Pixels can be transferred and transformed from the source window to the target window using different methods, each with their advantages and disadvantages. The actual method is hidden by this C-style OO abstraction. */ /* Trans objects interface */ /* Destroy the trans object */ typedef void (*trans_destroy_func_t)(trans_base_t *trans); /* Transfer and transform source pixels in the rectangle to the target window. */ typedef void (*trans_rectangle_func_t)(trans_base_t *trans, XRectangle *rect); /* Optional: react to target window size changes. */ typedef void (*trans_target_resized_func_t)(trans_base_t *trans); typedef struct trans_ops_s { trans_destroy_func_t destroy; trans_rectangle_func_t trans_rectangle; trans_target_resized_func_t target_resized; /* Can be NULL */ } trans_ops_t; /* Base for all trans objects, they must define their own struct with this struct as the first member. (Typedefed above) */ struct trans_base_s { trans_ops_t *ops; projection_t *proj; }; /* Given a new projection, return a trans object. The function must set all members in the base object. */ typedef trans_base_t* (*create_trans_func_t)(projection_t *proj); typedef enum { NO_GLX, GLX_12, GLX_13 } glx_version_t; extern const char *progname; /* Info about all the extensions, even though not many of them will be used. */ extern int fixes_event_base, fixes_error_base; extern int composite_event_base, composite_error_base; extern int damage_event_base, damage_error_base; extern Display *disp; extern Window ctrl_win; extern create_trans_func_t create_trans; extern int last_x_error; void info(const char *fmt, ...) __attribute__((format(printf, 1, 2))); void die(const char *fmt, ...) __attribute__((format(printf, 1, 2), noreturn)); void event_loop(void); /* Trans implementations */ int check_render(); trans_base_t* create_trans_render(projection_t *proj); glx_version_t check_glx(); trans_base_t* create_trans_glx12(projection_t *proj); trans_base_t* create_trans_glx13(projection_t *proj); /* Add a projection for source -> target, setting up all necessary resources and requesting the composition redirection. */ projection_t* add_projection(Window source, Window target); /* Remove a projection, releasing all resources associated with it. */ void free_projection(projection_t *proj); /* Look up a projection based on a window id */ projection_t* find_source_window(Window window); projection_t* find_target_window(Window window); projection_t* find_any_window(Window window); /* Update the brightness setting, but don't actually redraw anything. Returns true if the brightness have changed. */ int update_brightness(projection_t *proj); /* Draw entire window */ void project_all(projection_t *proj); /* Draw the damage recorded in region */ void project_region(projection_t *proj); /* Draw a given source rectangle */ void project_rectangle(projection_t *proj, XRectangle *rect); /* Target window has changed size */ void project_target_resized(projection_t *proj, int width, int height); #endif /* __PLCM_H_INCLUDED__ */ python-plwm-2.6a+20080530/plcm/events.c0000644000175000017500000001264511007513675016063 0ustar stewstew #include "plcm.h" static void handle_message(const XClientMessageEvent *ev); static void handle_expose(const XExposeEvent *ev); static void handle_configure(const XConfigureEvent *ev); static void handle_destroy(const XDestroyWindowEvent *ev); static void handle_map(const XMapEvent *ev); static void handle_unmap(const XUnmapEvent *ev); static void handle_property(const XPropertyEvent *ev); static void handle_visibility(const XVisibilityEvent *ev); static void handle_damage(const XDamageNotifyEvent *ev); void event_loop(void) { while (1) { XEvent ev; XNextEvent(disp, &ev); switch (ev.type) { case ClientMessage: handle_message(&ev.xclient); break; case Expose: handle_expose(&ev.xexpose); break; case ConfigureNotify: handle_configure(&ev.xconfigure); break; case DestroyNotify: handle_destroy(&ev.xdestroywindow); break; case MapNotify: handle_map(&ev.xmap); break; case UnmapNotify: handle_unmap(&ev.xunmap); break; case PropertyNotify: handle_property(&ev.xproperty); break; case VisibilityNotify: handle_visibility(&ev.xvisibility); break; default: if (ev.type == damage_event_base + XDamageNotify) handle_damage((XDamageNotifyEvent*) &ev.xany); } } } static void handle_message(const XClientMessageEvent *ev) { if (ev->window != ctrl_win) { info("received message for unexpected window 0x%08lx", (unsigned long) ev->window); return; } if (ev->message_type == _PLCM_ENABLE && ev->format == 32) { Window source, target; projection_t* proj; source = ev->data.l[0]; target = ev->data.l[1]; info("recieved ENABLE message for 0x%08x -> 0x%08x", (int) source, (int) target); proj = add_projection(source, target); if (proj != NULL) { info("projection enabled"); } else { info("projection NOT enabled"); } } else if (ev->message_type == _PLCM_DISABLE && ev->format == 32) { Window source, target; projection_t* proj; source = ev->data.l[0]; target = ev->data.l[1]; info("recieved DISABLE message for 0x%08x -> 0x%08x", (int) source, (int) target); proj = find_source_window(source); if (proj == NULL || proj->target_window != target) { info("DISABLE for unknown projection, ignoring"); } else { free_projection(proj); } } else { info("unknown message: %d (format %d)", (int) ev->message_type, ev->format); return; } } static void handle_expose(const XExposeEvent *ev) { projection_t *proj = find_target_window(ev->window); if (proj) { XRectangle rect; /* Translate from target to source coordinated */ rect.x = ev->x + proj->source_geometry.x; rect.y = ev->y + proj->source_geometry.y; rect.width = ev->width; rect.height = ev->height; project_rectangle(proj, &rect); } } static void handle_configure(const XConfigureEvent *ev) { projection_t *proj = find_any_window(ev->window); if (proj) { /* Update the geometry info for the windows. But don't trigger a redraw, wait for Expose or Damage events for that. */ if (proj->target_window == ev->window) { project_target_resized(proj, ev->width, ev->height); } else { /* Ignore the border when it comes to projection */ proj->source_geometry.x = ev->x + ev->border_width; proj->source_geometry.y = ev->y + ev->border_width; proj->source_geometry.width = ev->width; proj->source_geometry.height = ev->height; } } } static void handle_destroy(const XDestroyWindowEvent *ev) { projection_t *proj = find_any_window(ev->window); if (proj) { info("0x%08x: destroyed, taking down projection", (int) ev->window); /* Reset either window parameter in proj to avoid getting XBadWindow errors during shutdown. */ if (proj->target_window == ev->window) proj->target_window = None; else proj->source_window = None; free_projection(proj); } } static void handle_map(const XMapEvent *ev) { projection_t *proj = find_any_window(ev->window); if (proj) { info("0x%08x: mapped", (int) ev->window); if (proj->target_window == ev->window) proj->target_is_visible = 1; else proj->source_is_mapped = 1; } } static void handle_unmap(const XUnmapEvent *ev) { projection_t *proj = find_any_window(ev->window); if (proj) { info("0x%08x: unmapped", (int) ev->window); if (proj->target_window == ev->window) proj->target_is_visible = 0; else proj->source_is_mapped = 0; } } static void handle_property(const XPropertyEvent *ev) { projection_t *proj = find_target_window(ev->window); if (!proj) return; if (ev->atom == _PLCM_BRIGHTNESS) { if (update_brightness(proj)) { /* Need redraw */ project_all(proj); } } } static void handle_visibility(const XVisibilityEvent *ev) { projection_t *proj = find_target_window(ev->window); if (proj) { proj->target_is_visible = (ev->state != VisibilityFullyObscured); info("0x%08x: is visible: %d", (int) proj->target_window, proj->target_is_visible); } } static void handle_damage(const XDamageNotifyEvent *ev) { projection_t *proj = find_source_window(ev->drawable); if (proj) { /* Get the accumulated damage */ XDamageSubtract(disp, proj->damage, None, proj->damage_region); project_region(proj); } } python-plwm-2.6a+20080530/plcm/Makefile0000644000175000017500000000077711007513675016056 0ustar stewstew# FIXME: this should be replaced by a configure:d makefile CC = gcc LD = gcc CPPFLAGS += -I/usr/X11R6/include CFLAGS += -Wall -g -O LDFLAGS += -g LIBS += -L/usr/X11R6/lib -lX11 -lXfixes -lXcomposite -lXdamage -lXrender -lGL TARGET = plcm OBJS = main.o events.o projection.o trans_glx.o trans_render.o HEADERS = plcm.h all: $(TARGET) $(TARGET): $(OBJS) $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) # manual header dependency is good enough for this little hack $(OBJS): $(HEADERS) clean: rm -f $(TARGET) *.o python-plwm-2.6a+20080530/plcm/projection.c0000644000175000017500000002075711007513675016736 0ustar stewstew #include #include #include #include #include "plcm.h" static int init_projection(projection_t *proj); static void destroy_projection(projection_t *proj); static projection_t* projections = NULL; projection_t* add_projection(Window source, Window target) { projection_t* proj; /* These two windows must not be involved in any existing projection */ for (proj = projections; proj != NULL; proj = proj->next) { if (proj->source_window == source || proj->source_window == target || proj->target_window == source || proj->target_window == target) { info("requested projection 0x%08x -> 0x%08x matches existing projection 0x%08x -> 0x%08x", (int) source, (int) target, (int) proj->source_window, (int) proj->target_window); return NULL; } } proj = malloc(sizeof(projection_t)); if (proj == NULL) { info("out of memory"); return NULL; } memset(proj, 0, sizeof(projection_t)); proj->source_window = source; proj->target_window = target; if (!init_projection(proj)) { info("failed to init projection"); free(proj); return NULL; } proj->next = projections; projections = proj; return proj; }; void free_projection(projection_t *proj) { projection_t **prev; for (prev = &projections; *prev != NULL; prev = &((*prev)->next)) { if (*prev == proj) { *prev = proj->next; destroy_projection(proj); free(proj); return; } } die("internal error: freeing projection not in list"); } projection_t* find_source_window(Window window) { projection_t* proj; for (proj = projections; proj != NULL; proj = proj->next) { if (proj->source_window == window) return proj; } return NULL; } projection_t* find_target_window(Window window) { projection_t* proj; for (proj = projections; proj != NULL; proj = proj->next) { if (proj->target_window == window) return proj; } return NULL; } projection_t* find_any_window(Window window) { projection_t* proj; for (proj = projections; proj != NULL; proj = proj->next) { if (proj->target_window == window || proj->source_window == window) return proj; } return NULL; } static int init_projection(projection_t *proj) { XGCValues gc_values; XWindowAttributes target_attr, source_attr; XSetWindowAttributes set_attr; /* Get some info about the windows and check that we can handle this projection */ if (!XGetWindowAttributes(disp, proj->target_window, &target_attr)) { info("failed to get attributes for target 0x%08x", (int) proj->target_window); return 0; } if (!XGetWindowAttributes(disp, proj->source_window, &source_attr)) { info("failed to get attributes for source 0x%08x", (int) proj->source_window); return 0; } /* Windows must belong to the same screen... */ if (target_attr.root != source_attr.root) { info("can't project from one screen to another"); return 0; } /* ...and have same depth... */ if (target_attr.depth != source_attr.depth) { info("can't project between different depths"); return 0; } /* ...and the same visual. */ if (XVisualIDFromVisual(target_attr.visual) != XVisualIDFromVisual(source_attr.visual)) { info("can't project between different visuals"); return 0; } /* Projection looks ok */ proj->screen = target_attr.screen; proj->root = target_attr.root; proj->visual = target_attr.visual; /* Translate map state to target visibility */ proj->target_is_visible = (target_attr.map_state == IsViewable); proj->target_width = target_attr.width; proj->target_height = target_attr.height; proj->source_is_mapped = (source_attr.map_state != IsUnmapped); /* Ignore the border when it comes to projection */ proj->source_geometry.x = source_attr.x + source_attr.border_width; proj->source_geometry.y = source_attr.y + source_attr.border_width; proj->source_geometry.width = source_attr.width; proj->source_geometry.height = source_attr.height; /* Set up required event masks */ /* For target window, we must know about its state (mapped, destroyed, size), exposure, visibility (to know if there's any point in rendering damage) and the rendering properties set by plwm. */ set_attr.event_mask = (StructureNotifyMask | ExposureMask | VisibilityChangeMask | PropertyChangeMask); XChangeWindowAttributes(disp, proj->target_window, CWEventMask, &set_attr); /* For source window, we only need to know about its state (mapped, destroyed, size). */ set_attr.event_mask = StructureNotifyMask; XChangeWindowAttributes(disp, proj->source_window, CWEventMask, &set_attr); /* Create a normal X context for copying pixels when no transformation is required. */ gc_values.subwindow_mode = IncludeInferiors; proj->gc = XCreateGC(disp, proj->root, GCSubwindowMode, &gc_values); /* Fetch any initial render settings */ update_brightness(proj); /* Ask for damage notification. We use the model where we're told whenever some damage occur, and then render all that damage in a batch. Might not be optimal, but easy(ish). */ proj->damage = XDamageCreate(disp, proj->source_window, XDamageReportNonEmpty); proj->damage_region = XFixesCreateRegion(disp, NULL, 0); /* Create trans object */ proj->trans = create_trans(proj); if (proj->trans == NULL) { info("failed to create trans object"); return 0; } /* And finally render the initial contents */ project_all(proj); return 1; } static void destroy_projection(projection_t *proj) { XSetWindowAttributes attr; if (proj->trans) { proj->trans->ops->destroy(proj->trans); proj->trans = NULL; } XFixesDestroyRegion(disp, proj->damage_region); XDamageDestroy(disp, proj->damage); /* We're no longer interested in any events on these windows */ attr.event_mask = NoEventMask; if (proj->target_window != None) XChangeWindowAttributes(disp, proj->target_window, CWEventMask, &attr); if (proj->source_window != None) XChangeWindowAttributes(disp, proj->source_window, CWEventMask, &attr); if (proj->gc != None) XFreeGC(disp, proj->gc); } int update_brightness(projection_t *proj) { Atom type; int format; unsigned long items; unsigned long more_bytes; unsigned char *mem; long *data; int old_brightness = proj->brightness; if (XGetWindowProperty(disp, proj->target_window, _PLCM_BRIGHTNESS, 0, 1, False, XA_INTEGER, &type, &format, &items, &more_bytes, &mem) == Success && type == XA_INTEGER && format == 32 && items == 1) { data = (long*) mem; proj->brightness = data[0]; XFree(mem); if (proj->brightness > 255) proj->brightness = 255; else if (proj->brightness < -255) proj->brightness = -255; info("0x%08x: brightness = %d", (int) proj->target_window, proj->brightness); } else { proj->brightness = 0; info("0x%08x: brightness = 0 (no _PLCM_BRIGHTNESS)", (int) proj->target_window); } return old_brightness != proj->brightness; } void project_region(projection_t *proj) { XRectangle *rects; int num_rects; int i; rects = XFixesFetchRegion(disp, proj->damage_region, &num_rects); for (i = 0; i < num_rects; ++i) { project_rectangle(proj, rects + i); } XFree(rects); } void project_all(projection_t *proj) { XRectangle rect; rect.x = 0; rect.y = 0; rect.width = proj->source_geometry.width; rect.height = proj->source_geometry.height; project_rectangle(proj, &rect); } void project_rectangle(projection_t *proj, XRectangle *rect) { /* info("0x%08x: drawing %d,%d %dx%d", (int) proj->source_window, rect->x, rect->y, rect->width, rect->height); */ if (proj->brightness == 0) { /* If no effects are active, copy pixels directly */ XCopyArea(disp, proj->source_window, proj->target_window, proj->gc, rect->x, rect->y, rect->width, rect->height, rect->x - proj->source_geometry.x, rect->y - proj->source_geometry.y); } else { /* Let the trans object handle the effects */ proj->trans->ops->trans_rectangle(proj->trans, rect); } } void project_target_resized(projection_t *proj, int width, int height) { proj->target_width = width; proj->target_height = height; if (proj->trans->ops->target_resized) proj->trans->ops->target_resized(proj->trans); } python-plwm-2.6a+20080530/plcm/trans_glx.c0000644000175000017500000002343611007513675016560 0ustar stewstew #include #include #include #include #include #include "plcm.h" typedef struct trans_glx_s { trans_base_t base; /* Common fields */ XVisualInfo visual_info; /* Fields created differently in 1.2 and 1.3 */ GLXContext context; /* Following fields are only used by GLX_13 */ GLXFBConfig fb_config; GLXWindow glx_source_window; GLXWindow glx_target_window; } trans_glx_t; static int glx_event_base, glx_error_base; static trans_glx_t* create_trans_glx_common(projection_t *proj, trans_ops_t *ops); static void glx_common_trans_destroy(trans_glx_t *trans); glx_version_t check_glx() { /* Sorry, I can't this to work and can't be bothered at the moment. Seems to be a problem with rendering over the reparented window into the proxy window. In an GC IncludeInferiors can be set to work around that, but I can't find any corresponding flag for GLX contexts. The render extension should be all anyone needs to set the brightness of windows, but zooming would rquire GLX. */ /* int major, minor; if (glXQueryExtension(disp, &glx_event_base, &glx_error_base)) { if (!glXQueryVersion(disp, &major, &minor)) die("could not get glx version"); info("found glx extension %d.%d", major, minor); if (major == 1 && minor == 2) return GLX_12; else if (major == 1 && minor == 3) return GLX_13; else info("unsupported glx version"); } else info("no glx extension"); */ return NO_GLX; } /* GLX 1.2 implementation */ static void glx12_trans_destroy(trans_base_t *trans); static void glx12_trans_rectangle(trans_base_t *trans, XRectangle *rect); static trans_ops_t glx12_ops = { glx12_trans_destroy, glx12_trans_rectangle, NULL, /* trans_target_resized */ }; trans_base_t* create_trans_glx12(projection_t *proj) { trans_glx_t *trans; trans = create_trans_glx_common(proj, &glx12_ops); if (trans == NULL) return NULL; trans->context = glXCreateContext(disp, &trans->visual_info, NULL, True); if (trans->context == NULL) { info("failed to create glx 1.2 context"); glx_common_trans_destroy(trans); return NULL; } return (trans_base_t*) trans; } static void glx12_trans_destroy(trans_base_t *trans_base) { glx_common_trans_destroy((trans_glx_t*) trans_base); } static void glx12_trans_rectangle(trans_base_t *trans_base, XRectangle *rect) { trans_glx_t *trans = (trans_glx_t*) trans_base; projection_t *proj = trans->base.proj; int x, y; int max_rows, remaining_rows; GLfloat scale; GLfloat bias; /* Translate from X coordinates (left,top) to glX coordinates (left, bottom) */ x = rect->x; y = proj->source_geometry.height - rect->y - rect->height; /* Render brightness. This doesn't take the relative intensities of R, G, and B into account, and thus will slightly change the perceived color when changing the brightness. */ if (proj->brightness < 0) { /* Transpose -255,0 to [0.0, 1.0] */ scale = (255 + proj->brightness) / 255.0; bias = 0; } else { /* Transpose 0,255 to [0.0, 1.0] */ bias = proj->brightness / 255.0; scale = 1.0; } /* With GLX 1.2 we must bounce the pixels in our memory. Lose lose. Don't transfer more than 512k at a time, though. */ max_rows = 0x80000 / (rect->width * 3 + 4); if (max_rows < 1) max_rows = 1; void *mem = malloc((rect->width * 3 + 4) * max_rows); if (mem == NULL) { info("out of memory"); return; } for (remaining_rows = rect->height; remaining_rows > 0; remaining_rows -= max_rows) { if (remaining_rows < max_rows) max_rows = remaining_rows; info("copying %d rows to (%d,%d)", max_rows, x, y); glXMakeCurrent(disp, proj->source_window, trans->context); glReadBuffer(GL_FRONT); glPixelTransferf(GL_RED_SCALE, 1.0); glPixelTransferf(GL_RED_BIAS, 0.0); glPixelTransferf(GL_GREEN_SCALE, 1.0); glPixelTransferf(GL_GREEN_BIAS, 0.0); glPixelTransferf(GL_BLUE_SCALE, 1.0); glPixelTransferf(GL_BLUE_BIAS, 0.0); glReadPixels(x, y, rect->width, max_rows, GL_RGB, GL_UNSIGNED_BYTE, mem); glXMakeCurrent(disp, proj->target_window, trans->context); glViewport(0, 0, proj->target_width, proj->target_height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, (GLfloat) proj->target_width, 0.0, (GLfloat) proj->target_height, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRasterPos2i(x, y); glDrawBuffer(GL_FRONT); glPixelTransferf(GL_RED_SCALE, scale); glPixelTransferf(GL_RED_BIAS, bias); glPixelTransferf(GL_GREEN_SCALE, scale); glPixelTransferf(GL_GREEN_BIAS, bias); glPixelTransferf(GL_BLUE_SCALE, scale); glPixelTransferf(GL_BLUE_BIAS, bias); glDrawPixels(rect->width, max_rows, GL_RGB, GL_UNSIGNED_BYTE, mem); y += max_rows; } glFlush(); glXMakeCurrent(disp, None, NULL); free(mem); glXWaitGL(); } /* GLX 1.3 implementation */ static void glx13_trans_destroy(trans_base_t *trans_base); static void glx13_trans_rectangle(trans_base_t *trans_base, XRectangle *rect); static trans_ops_t glx13_ops = { glx13_trans_destroy, glx13_trans_rectangle, NULL, /* trans_target_resized */ }; trans_base_t* create_trans_glx13(projection_t *proj) { trans_glx_t *trans; GLXFBConfig *fb_confs; int num_fb_confs; int fb_attrs[] = { GLX_X_VISUAL_TYPE, GLX_DONT_CARE, /* over-written below */ None }; trans = create_trans_glx_common(proj, &glx13_ops); if (trans == NULL) return NULL; switch (trans->visual_info.class) { case TrueColor: fb_attrs[1] = GLX_TRUE_COLOR; break; case DirectColor: fb_attrs[1] = GLX_DIRECT_COLOR; break; default: info("unsupported visual class"); goto error; } // FIXME: find screen number fb_confs = glXChooseFBConfig(disp, 0, fb_attrs, &num_fb_confs); if (fb_confs == NULL) { info("couldn't find any glXFBConfig"); goto error; } trans->fb_config = fb_confs[0]; XFree(fb_confs); trans->context = glXCreateNewContext(disp, trans->fb_config, GLX_RGBA_TYPE, NULL, False); if (trans->context == NULL) { info("failed to create glX 1.3 context"); goto error; } trans->glx_source_window = glXCreateWindow(disp, trans->fb_config, proj->source_window, NULL); trans->glx_target_window = glXCreateWindow(disp, trans->fb_config, proj->target_window, NULL); return (trans_base_t*) trans; error: glx13_trans_destroy((trans_base_t*) trans); return NULL; } static void glx13_trans_destroy(trans_base_t *trans_base) { trans_glx_t *trans = (trans_glx_t*) trans_base; if (trans != NULL) { if (trans->glx_source_window) glXDestroyWindow(disp, trans->glx_source_window); if (trans->glx_target_window) glXDestroyWindow(disp, trans->glx_target_window); glx_common_trans_destroy(trans); } } static void glx13_trans_rectangle(trans_base_t *trans_base, XRectangle *rect) { trans_glx_t *trans = (trans_glx_t*) trans_base; projection_t *proj = trans->base.proj; int x, y; GLfloat scale; GLfloat bias; /* Translate from X coordinates (left,top) to glX coordinates (left, bottom) */ x = rect->x; y = proj->source_geometry.height - rect->y - rect->height; /* Render brightness. This doesn't take the relative intensities of R, G, and B into account, and thus will slightly change the perceived color when changing the brightness. */ if (proj->brightness < 0) { /* Transpose -255,0 to [0.0, 1.0] */ scale = (255 + proj->brightness) / 255.0; bias = 0; } else { /* Transpose 0,255 to [0.0, 1.0] */ bias = proj->brightness / 255.0; scale = 1.0; } /* With glX 1.3 we can render directly from the source to the target window. */ if (!glXMakeContextCurrent(disp, trans->glx_target_window, trans->glx_source_window, trans->context)) { info("failed to set glX drawables"); return; } glPixelTransferf(GL_RED_SCALE, scale); glPixelTransferf(GL_RED_BIAS, bias); glPixelTransferf(GL_GREEN_SCALE, scale); glPixelTransferf(GL_GREEN_BIAS, bias); glPixelTransferf(GL_BLUE_SCALE, scale); glPixelTransferf(GL_BLUE_BIAS, bias); glPixelTransferf(GL_ALPHA_SCALE, 1.0); glPixelTransferf(GL_ALPHA_BIAS, 0.0); glRasterPos2i(x, y); glCopyPixels(x, y, rect->width, rect->height, GL_COLOR); glXMakeContextCurrent(disp, None, None, NULL); glFlush(); // glXWaitGL(); } /* Common code for both 1.2 and 1.3 */ static trans_glx_t* create_trans_glx_common(projection_t *proj, trans_ops_t *ops) { trans_glx_t *trans; XVisualInfo visual_template; XVisualInfo *visual_info = NULL; int num_visuals; trans = malloc(sizeof(trans_glx_t)); if (trans == NULL) { info("out of memory allocating trans_glx_t"); return NULL; } memset(trans, 0, sizeof(trans_glx_t)); trans->base.ops = ops; trans->base.proj = proj; /* Find out the full XVisualInfo for visual ID so we can create a glX context for it. All visual IDs are unique, so we should only get one. */ visual_template.visualid = XVisualIDFromVisual(proj->visual); visual_info = XGetVisualInfo(disp, VisualIDMask, &visual_template, &num_visuals); if (visual_info == NULL) { info("couldn't get visual info"); glx_common_trans_destroy(trans); return NULL; } trans->visual_info = visual_info[0]; XFree(visual_info); return trans; } static void glx_common_trans_destroy(trans_glx_t *trans) { if (trans != NULL) { if (trans->context != NULL) glXDestroyContext(disp, trans->context); free(trans); } } python-plwm-2.6a+20080530/NEWS0000644000175000017500000003037310410020352014133 0ustar stewstew-*-outline-*- * Version ?.? (? ??? ????) ** New synthetic events for iconification When a client window is iconified or deiconified, the synthetic events wmevents.ClientIconified and wmevents.ClientDeiconified is sent, respectively. ** border.BorderClient rewritten The ancient BorderClient mixin has been rewritten to allow more dynamic color choosing schemes. It should be backward-compatible, but for those willing to change their wm scripts it is now possible to choose different colors for different windows. This is handled by letting client filters map to a certain BorderColorManager subclass object. FixedBorderColor gives clients a static color, while TitleBorderColor chooses a color based on the window title. This is useful to distinguish different xterm windows, if e.g. the current directory is included in the window title. It should be easy to define your own color managers, to get even more dynamic colors. For details, run "pydoc plwm.border". * Version 2.6a (7 Apr 2004) This is mostly a bug fix release, to get the latest set of fixes out the door. ** Distutils is now used to build and install plwm. ** We now require Python 2.0 or later. Python 1.5 is no longer supported. ** Mike Meyer (mwm@mired.org) is now administering PLWM. ** New mixin: mixer.py. This is a new mixin for controlling the audio mixer. Currently, it only supports "rexima", but it should be easy to extend to "aumix" etc. ** Menu screen mixin is now configurable. The menu screen mixin has been extended to use menu_font, menu_foreground, menu_background, menu_bordercolor, and menu_borderwidth when it draws menus on the screen. The default is 9x15Bold, black, white, black and 3. menu_seconds is the number of seconds the menu will be onscreen before it vanishes of it's own accord. The default value is 0, which means to stay until a selection is made or the menu is cancelled. ** Message screen mixin is now configurable. The message screen mixin can now be configured with message_font, message_foreground, message_background, message_bordercolor and message_borderwidth. message_seconds controls how long the message is displayed for, with a default value of 5 seconds. ** Screen.system now allows i/o redirection. The Screen.system method now takes an extra argument, redirect. This argument should either be a single integer between 0 and 2 inclusive, or a tuple of such integers. These fd's are redirected to pipes, and the resulting pipes are returned as a three-tuple. Supplying redirect implies that the command will be run in the background. * Version 2.5 (6 Mar 2002) This is mostly a get-it-out-of-the-door release, and is identical with the preceeding 2.5 alpha. Some of the new features are not documented, so you may have to resort to reading code to use them. ** Abstraction for internal windows The core class Client now subclasses the new class Window. The new class InternalWindow is also a subclass of Window. The main advantage of this is that wm-internal windows (e.g. the ModeWindow) can be wrapped in an InternalWindow. The internal window can use the same event system as Client windows, and also use a number of methods previously only provided for Client windows. ** New extensions: message and input Message is an abstraction refactored out of menu for displaying windows containing text. The new input extension uses this to display a text input window. It can e.g. be used to let the user input shell commands or python code. ** New mode window mixin: mw_load mw_load displays the current load average in the mode window. It has support for Linux /proc and the more general Unix /usr/bin/uptime. ** Code reorganization: plwm.misc The module plwm.misc contains various useful hacks. Most of these were previously found in the example wm petliwm.py, but having them here makes them easier to use in other peoples wms. Take a look in the file to see what is available, it is self-documented. ** New feature: Screen border allocation The Screen class now provides the method alloc_border(), which can be used by mixins to reserve screen area at the edges. Client windows will not be allowed to cover these borders. Initially it is used by the mode window. * Version 2.4 (12 Dec 2001) ** New extensions: panes and menu Mike Meyer has written two nice extension modules. Panes provides a ratpoison-ish, or screen(1)-ish, or Emacs-ish way of handling windows on the screen. See the comment at the top of plwm.panes for more details, and try out the example window manager example/plpwm.py. Menu opens a keyboard-controlled menu in the middle of the screen, and can be used to start applications, selecting among windows and other niceties. ** Focus management rewritten There is now a WindowManager method set_current_client() which takes care of all the details in switching focus between different clients. The extension module plwm.focus now uses the new method, much reducing the former clutter in that module. The mixin FocusHandler has been renamed to PointToFocus, and the mixin FocusClient is no longer needed. If the old names are used, a warning is printed. The method move_focus() has been moved to the new mixin MoveFocus. ** Internal events collected in plwm.wmevents, and also documented The various internal events previously scattered over plwm.wmanager and plwm.focus has now been collected in the new module plwm.wmevents. They have also been documented in the manual chapter "Core Events". ** Dropped support for old xmodewin The old stand-alone xmodewin, the utilities xmwbiff and xmwclock, and the related modules modewinctl and modetitle have been removed. They were superceded by the plwm.modewindow and plwm.mw_* extension modules in 2.1. ** View remembers focus, and stores info at exit Views now also remembers the focused client, and is thus able to give it focus no matter where the pointer is located. Very nice when using SloppyFocus. The current view configuration is now stored when exiting the window manager, in addition to when switching views. This allows even more view configuration to be restored when PLWM is restarted. ** CycleActivate improved plwm.cycle.CycleActivate has been improved to work well with both iconified and deiconified clients. Inspiration came from Meik Hellmund. ** Fixed bug in Screen.system() If PLWM was killed by a signal, all programs started with Screen.system() also got that signal. This has been fixed by creating a new process group for the child process executing the program. ** WindowOutlineClient added An outline client mixin drawing the outline using a set of windows has been added. It is less effecient than the XorOutlineClient, but avoids some of its problems. See the section for the "outline" extension in the documentation for further details. ** NetBSD APM interface added to mw_apm The modewindow APM mixin can now fetch battery status both from Linux' /proc/apm and NetBSD's /dev/apm, thanks to Henrik Rindlöw. ** Finalization methods added Mixin classes can now clean up after themselves, by providing the appropriate method of __wm_del__, __screen_del__, or __client_del__. ** Bugfix in MapRequest handling An if-expression missed a "not", which resulted in that windows created after PLWM has started always was mapped, no matter the value of Client.start_iconified. Consequently existing windows' map requests always was ignored. The first effect is a bug, the second one turned out to be the correct behaviour. (This is because Netscape 6.1 abuses window mapping by attempting to deiconify whenever a web page has loaded. This is _not_ what you want if the Netscape windows is iconified because it is on another view than the current one...) ** Python 2.1 incompitability fixed It turned out that Python 2.1 is very picky about the __all__ list in __init__.py, so installation failed since a now removed module was listed in it. Fixed now. * Version 2.3 (29 Aug 2001) ** Documentation revised The oldest part of the documentation have been revised for grammar, correctness and prettyness. ** Key handling modified The key handling of keys.py has been modified so that keymaps can now be bound to specific screens or windows. For the first time in the history of PLWM, this is a modification that is backward-compatible. However, when the old interface is used, a warning is issued. ** Focus tracking conforms better to ICCCM focus.py has been somewhat revised to comply more fully with ICCCM. Now no-input clients will never get focus, which is required for applications like xvkbd. This introduces a new WindowManager attribute: current_client. This should replace most instances where focus_client previously was used. A new event is sent when focus changes: CurrentClientChange. The modestatus.py code that displays the currently active client has been rewritten as a result of this: the Screen mixin ModeFocusedTitle is now replaced by the Client mixin ModeFocusedTitleClient together with the new Screen mixin ModeFocusedTitleScreen. ** New modewindow module: mw_apm mw_apm displays the battery status of laptops, by reading from the special file /proc/apm provided by Linux systems. It should be easy to extend the module to support other APM systems. * Version 2.2 (6 Feb 2001) ** Complete documentation available The documentation has been extended with detailed descriptions for all relevant extension modules, plus a chapter on client filters. Now there are no excuses for not writing your own plwm.py! ** Changed default window manager The old default window manager, examples/petliwm.py, has been transformed into something not necessarily useful for other people than Petli himself, so now examples/examplewm.py is the default instead. It has the same key bindings as the old petliwm.py, though, so most default window manager users will not notice any difference. ** Event system improvements The core event system has been extended to provide support for file events. This allows events to be generated when a file is ready for reading or writing. Also, a bug causing the modewindow clock to freeze has been fixed. ** Functionality to inspect the internals of a running PLWM The module plwm.inspect together with the utility inspect_plwm can be used to inspect and modify the internals of a running PLWM. This can be thought of as telnetting, or logging in, to the window manager. You get a prompt, and any entered expression is evaluated inside the running window manager. See the documentation for details. ** Modewindow cleanups Modewindow is a little cleaner coded, and now avoids redrawing more of the modewindow than necessary. A single Message object can also be added to several modewindows. ** Key grabbing improved PLWM now grabs all keys that have a certain keysym, not just the first one. PLWM also knows about shifting, so a handler for C_Q is equivalent to C_S_q. ** move_focus bug fix move_focus failed to move focus when there was only one mapped window. * Version 2.1 (12 Jan 2001) ** Python 2.0 fixes. Some incompatibilities with Python 2.0 has been fixed. ** Modewindow is now a PLWM module. xmodewin has been replaced by the module plwm.modewindow. It contains a Screen mixin which opens a modewindow on all screens, removing the need for separate xmodewin instances. xmwbiff and xmwclock has also been replaced by the modules plwm.mw_biff and plwm.mw_clock, respectively. These contains WindowManager mixins that perform those functions, using plwm.modewindow. The reason for this change is the quite large footprint of Python Xlib, which causes even these very modest applications to be some 3Mb large. This makes a PLWM environment somewhat more lightweight. ** keys: support for handling KeyRelease events. The keys.KeyHandler has been extended to allow methods to be bound to key releases, and not just key presses. This is done by prefixing the KeyHandler method name with "R_", e.g. "R_C_Return". * Version 2.0 (8 Jan 2001) ** Testing completed. PLWM 2.0a1 has been run for a while, and has uncovered a number of bugs and performance problems with Python Xlib. These have been fixed in version 0.7 of Python Xlib. No bugs have been found in PLWM 2.0 (relatively easy, as there is no new functionality), so lets call this a real release. * Version 2.0a1 (21 Dec 2000) ** plxlib no more PLWM has been rewritten to use the Python X Library, instead of the disgusting pile of C code that was plxlib. Well, it seems to work... python-plwm-2.6a+20080530/ONEWS0000644000175000017500000001770007227617012014272 0ustar stewstew-*-outline-*- * Version 1.4 (15 Dec 2000) ** PLWM *** Abstraction: Client filters Client filters are a way to create arbitrarily complex conditional expressions to select clients. This can be used to e.g. cycle among all iconified windows with a name matching "xterm*". Client filters are located in plwm.cfilter, see the documentation in it for details. All code which previously used Client.client_in_list (e.g. the client attributes no_border_clients, start_iconified) and also some new pieces of code now use client filters. The query_clients methods in WindowManager and Screen also uses client filters now. *** Client cycle module completely rewritten Module plwm.cycle has been completely rewritten. It is now much more general and uses the new client filters to select which filters to use. A keyhandler template class, plwm.cycle.CycleKeys is also provided to make it easier to define cycling key bindings. *** New feature: mouse clicks can be simulated If the XTEST extension is available (see below), mouse clicks can be simulated by calling the function WindowManager.fake_button_click(). More complicated button press simulations could also be implemented by the adventurous. *** Better keymaps: `Any' modifier Key handlers can now be bound to a certain key, ignoring any modifiers. This is done by using the special prefix `Any_' for key handlers, e.g. `Any_F9'. *** Abstraction: outline drawing as client mixin classes Outline drawing has been generalised into client mixin classes. This means that any code which needs to draw an outline simply calls methods on the client. It is therefore now easy to write different outline mixin classes. Currently the classic xor outline is provided, but future outline methods could, e.g., use a shaped window or a number of small windows, one for each line. ** plxlib *** New Display methods: FakeKeyEvent and FakeButtonEvent If the X library support the XTEST extension, the Display methods FakeKeyEvent and FakeButtonEvent are available. These can be used to simulate user input. *** New Window method: Reparent The Window method Reparent has been implemented. This will make it possible to have real window frame support in PLWM, but for now it is only used to work around a bug in a stupid client application (Sun's Java AWT for Linux). * Version 1.3 (4 Jul 2000) ** PLWM *** Abstraction: template key binding class for moveresize plwm.moveresize.MoveResizeKeys is a template class providing functions for all move and resize functions. To use it, simple subclass it and assign the functions to key bindings. This is meant to make it easier to define your own plwm.py keybindings, and more template classes will probably follow. *** Bug fix: Resizing windows with size hints (WMNormalHints) In some quite obscure situations the size hints (WMNormalHints) on windows wasn't used correctly when moving or resizing. This meant that windows (typically Emacs windows created with C-x 5 2) became a little too large, quite ugly. A more careful study of the ICCCM pointed out the misusage. ** plxlib *** Ridiculously large memory leaks fixed A misunderstanding of the Python object C API meant that window objects never freed their memory when garbed. This is now fixed, and means that the large and annoying memory leaks which previously had been attributed to circular reference chains are finally fixed. Additionally, a few cases of incorrect refcounts (too high) has also been fixed, again mostly affecting window objects. *** New module: XK XK provided constants for keysyms. *** Small new function: Window objects can be hashed A hash function is now provided for window objects, which, together with the compare function, means that window objects can be used as dictionary keys. ** Miscellaneous *** New configure flag: --with-python The new configure script flag --with-python tells PLWM to use a specific Python binary. Can be used if Python isn't in your $PATH, or if you want to use a special Python, e.g. one with refcount debugging enabled. * Version 1.2 (4 Apr 2000) ** PLWM *** New feature: window manager status display. The window title display in an xmodewin has been incorporated in a new, more flexible system. PLWM can now display a status message about what it is doing, and temporarily switch this to some other message temporarily. Typically, the title of the currently focused window is displayed by default. But when moving/resizing a window it is replaced with the title and current geometry of the moved/resized window. There are currently no other modules using of this feature, but more will probably follow. An example: when deiconifying, display the title of the currently selected window. *** Yet more memory leaks fixed. Those annoying circular references continue to hog memory. This time it was the Client objects which never got garbed because of the circular reference Client -> WindowDispatcher -> Client method -> Client. Solved by having the Client object dropping the reference to the WindowDispatcher when its window is withdrawn. ** Utilities/plxlib *** Better handling when no X resources are set. xmodewin, xmwbiff and friends crashed at startup if no X resources had been set with xrdb. The function plxlib.GetStringDatabase is now more permissive and allows the input argument None, which solves this problem. ** Miscellaneous *** Fully controllable ./configure ./configure now has the option --with-python-path which controls where the plwm and plxlib modules is installed. This path does not have to be in sys.path by default, the various PLWM programs will insert it if necessary. *** Better installation The install target now depends on all. All modules will be compiled at installation. * Version 1.1 (24 Feb 2000) ** PLWM *** Better documentation. The documentation (doc/plwm.texi) has been revised and extended, most importantly with a section on how to actually use PLWM and the various utilities together. The Makefile now contains rules for building PostScript and HTML in addition to info files. *** Module import bug fixed. Embarrasingly, when changing to module layout to use packages I forgot to update an import statement in modetitle.py. I didn't find this myself because an old PLWM installation lurked in a dark corner of my $PYTHONPATH. *** Multihead has been tested, bugfixed and now actually works. Multihead required two improvements to the core classes. First, event handling was modified to make a global, screen-independent EventDispatcher possible. Second, instaed of using os.system to run program PLWM now has a system call for the screens. This system sets the $DISPLAY correctly, and also have some nice bells-and-whistles (unused right now, however). *** Memory leak fixed. There was a memory leak in the KeyHandler class, caused by a nasty circular reference preventing KeyHandler objects to be deleted. So each time one moved a window or deiconified a window, a few kilobytes was lost. *** Better font and color handling. The font and color allocation routines have been rewritten to better use a fallback font or color. This means that PLWM will not crash disgustingly just because a font cannot be found, instead using "fixed". Fonts and colors are now set with standard X resources instead of using methods in the plwm. This makes it easier to have a single PLWM executable for different environments. *** More view featuritis. Views can be tagged with arbitrary strings. You can then jump to a view based on these tags, similar to the way you could previously jump to a view based on the clients on it. ** Utilites *** xmodewin and wmm Easier to use in new environments: if the desired font doesn't exist they use "fixed" instead. wmm doesn't crash if its configuration file .wmmrc doesn't exist, and uses a default configuration instead. ** Miscellaneous *** Build procedure A common error is that the development files of Python isn't installed. The ./configure script now detects this and prints a helpful message. * Version 1.0 (8 Jan 2000) First public release.python-plwm-2.6a+20080530/plwm/0000755000175000017500000000000011020017212014404 5ustar stewstewpython-plwm-2.6a+20080530/plwm/mw_gmail.py0000644000175000017500000000721410765600750016602 0ustar stewstew# # mw_gmail.py -- display gmail info in the mode window # # Copyright (C) 2004 Mark Tigges mtigges@gmail.com # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os from plwm import modewindow, event, wmanager, keys from gmailconstants import * try: import libgmail GMAILTimerEvent = event.new_event_type() class ModeWindowGmail: # To use this mix-in, create a ~/.mw_gmailrc file. # The first line should be your account name, the second # your password ... and make sure the file is only user # readable, if you want to be secure. mw_gmail_position = 0.01 mw_gmail_justification = modewindow.LEFT def __wm_init__(self): self.wm_gmail_message = modewindow.Message(self.mw_gmail_position, self.mw_gmail_justification) try: conf = file('%s/.mw_gmailrc' % os.environ['HOME'],'r') lines = conf.readlines() conf.close() self.account = lines[0].strip() self.password = lines[1].strip() for s in self.screens: s.modewindow_add_message(self.wm_gmail_message) self.dispatch.add_handler(GMAILTimerEvent, self.mw_gmail_tick) self.mw_gmail_update() except: sys.stderr.write('mw_gmail: no %s/.mw_gmailrc file found\n' % os.environ['HOME']) import traceback traceback.print_exc() def getInboxMsgCount(self,ga): # For some silly reason, not all the queries are implemented # in member funcionts, so we have to add this function. items = ga._parseSearchResult(U_QUERY_SEARCH, q = "is:" + U_AS_SUBSET_INBOX) return items[D_THREADLIST_SUMMARY][TS_TOTAL_MSGS] def mw_gmail_update(self): try: ga = libgmail.GmailAccount(self.account,self.password) ga.login() #result = ga.getMessagesByFolder('inbox', True) unread = ga.getUnreadMsgCount() inbox = self.getInboxMsgCount(ga) # Format the message self.wm_gmail_message.set_text('%d/%d' % (unread,inbox)) except: import traceback traceback.print_exc() self.wm_gmail_message.set_text('N.A.') # Check again in 10 minutes. self.events.add_timer(event.TimerEvent(GMAILTimerEvent, after = 600)) def mw_gmail_tick(self,evt): self.mw_gmail_update() except: import sys sys.stderr.write('mw_gmail: could not load libgmail python module\n') sys.stderr.write(' see: http://libgmail.sourceforge.net/\n') class ModeWindowGmail: pass python-plwm-2.6a+20080530/plwm/inspect.py0000644000175000017500000002502410765600750016452 0ustar stewstew# # inspect.py -- Allow inspection of PLWM internals # # Copyright (C) 2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import wmanager, event, modewindow, wmevents import socket import sys import traceback import struct import random import cStringIO InspectFileEventType = event.new_event_type() # wm mixin class InspectServer: inspect_enabled_at_start = 0 def __wm_init__(self): self.inspect_socket = None self.inspect_cookie = None self.inspect_socket_event = None self.inspect_clients = None self.inspect_message = None self.PLWM_INSPECT_SERVER = self.display.intern_atom('_PLWM_INSPECT_SERVER') self.dispatch.add_handler(InspectFileEventType, self.inspect_handle_file_event) self.dispatch.add_handler(wmevents.QuitWindowManager, self.inspect_quitwm_handler) if self.inspect_enabled_at_start: self.inspect_enable() def inspect_quitwm_handler(self, evt): self.inspect_disable(force = 1) def inspect_enable(self): # Inspection already enabled if self.inspect_socket is not None: return wmanager.debug('inspect', 'enabling inspect server') # Listen on any port on the local interfaces self.inspect_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.inspect_socket.bind(('', 0)) self.inspect_socket.listen(5) self.inspect_socket_event = event.FileEvent(InspectFileEventType, self.inspect_socket, event.FileEvent.READ) self.events.add_file(self.inspect_socket_event) # Create a authentication cookie, and store it and the # portnumber in a property on screen 0 addr, port = self.inspect_socket.getsockname() self.inspect_cookie = random.randint(0, 0x7ffffffe) self.default_screen.root.change_property(self.PLWM_INSPECT_SERVER, self.PLWM_INSPECT_SERVER, 32, [port, self.inspect_cookie]) self.inspect_clients = {} self.inspect_message = modewindow.Message(.2, modewindow.LEFT, 0, '[Inspect]') for s in self.screens: try: s.modewindow_add_message(self.inspect_message) except AttributeError: pass def inspect_disable(self, force = 0): # Inspect already disabled if self.inspect_socket is None: return if self.inspect_clients: # Shut down all clients if force: for c in self.inspect_clients.values(): c.close() # Beep and abort else: self.display.bell(50) return wmanager.debug('inspect', 'disabling inspect server') for s in self.screens: try: s.modewindow_remove_message(self.inspect_message) except AttributeError: pass self.inspect_message = None self.inspect_clients = None self.default_screen.root.delete_property(self.PLWM_INSPECT_SERVER) self.inspect_cookie = None self.inspect_socket_event.cancel() self.inspect_socket_event = None self.inspect_socket.close() self.inspect_socket = None def inspect_toggle(self, force = 0): if self.inspect_socket is None: self.inspect_enable() else: self.inspect_disable(force) def inspect_handle_file_event(self, evt): if self.inspect_socket is None: return if evt is self.inspect_socket_event: self.inspect_create_new_client() else: try: c = self.inspect_clients[evt] except KeyError: pass else: c.handle_file_event(evt) def inspect_create_new_client(self): conn, addr = self.inspect_socket.accept() wmanager.debug('inspect', 'connection from %s', addr) client = InspectClient(self, conn, addr) self.inspect_clients[client.event] = client self.inspect_set_message() def inspect_set_message(self): if len(self.inspect_clients) == 0: self.inspect_message.set_text('[Inspect]') elif len(self.inspect_clients) == 1: self.inspect_message.set_text('[Inspect: 1 client]') else: self.inspect_message.set_text('[Inspect: %d clients]' % len(self.inspect_clients)) def inspect_client_closed(self, client): try: del self.inspect_clients[client.event] except KeyError: pass self.inspect_set_message() class InspectClient: def __init__(self, wm, sock, addr): self.wm = wm self.socket = sock self.addr = addr self.authed = 0 self.event = event.FileEvent(InspectFileEventType, self.socket, event.FileEvent.READ) self.wm.events.add_file(self.event) self.recv_len = 0 self.recv_buf = '' self.send_buf = '' self.globals = __builtins__.copy() self.globals['wm'] = self.wm def handle_file_event(self, evt): if evt.state & event.FileEvent.READ: try: d = self.socket.recv(500) except socket.error, err: wmanager.debug('inspect', 'client %s closed on failed recv: %s', self.addr, err) self.close() return if not d: wmanager.debug('inspect', 'client %s closed', self.addr) self.close() return self.recv_buf = self.recv_buf + d # First four bytes sent must the the authentication cookie if not self.authed: if len(self.recv_buf) >= 4: cookie = struct.unpack('>l', self.recv_buf[:4])[0] self.recv_buf = self.recv_buf[4:] if cookie == self.wm.inspect_cookie: self.authed = 1 self.output('Welcome to PLWM at %s\n' % self.wm.display.get_display_name()) else: wmanager.debug('inspect', 'client %s closed on wrong cookie: %d', self.addr, cookie) self.close() return while 1: # No length recieved yet, parse a little-endian fourbyte length if self.recv_len == 0: if len(self.recv_buf) >= 4: self.recv_len = struct.unpack('>l', self.recv_buf[:4])[0] # Do sanity check on length, abort connection if it is < 0 if self.recv_len < 0: wmanager.debug('inspect', 'client %s closed, sent negative length: %d', self.addr, self.recv_len) self.close() return self.recv_buf = self.recv_buf[4:] else: break # All data of expression read, execute it if self.recv_len <= len(self.recv_buf): data = self.recv_buf[:self.recv_len] self.recv_buf = self.recv_buf[self.recv_len:] self.recv_len = 0 self.exec_data(data) else: break if evt.state & event.FileEvent.WRITE: # Send any unsent data try: n = self.socket.send(self.send_buf) except socket.error, err: wmanager.debug('inspect', 'client %s closed on failed send: %s', self.addr, err) self.close() return self.send_buf = self.send_buf[n:] # If there are no data left to send, clear the # WRITE flag in the event to avoid a lot of # select follies. if len(self.send_buf) == 0: evt.set_mode(clear = event.FileEvent.WRITE) def exec_data(self, data): # We replace the standard files with temporary ones. stdin is # redirected from /dev/null, and stdout and stderr is sent to # a StringIO object. old_stdin = sys.stdin old_stdout = sys.stdout old_stderr = sys.stderr f = cStringIO.StringIO() try: sys.stdin = open('/dev/null', 'r') sys.stdout = sys.stderr = f # Compile and execute the expressions. print statements # and expression values will be sent to the StringIO # object, as will any exception traceback try: c = compile(data, '', 'single') exec c in self.globals except: traceback.print_exc(None, f) finally: # Restore the standard files sys.stdin = old_stdin sys.stdout = old_stdout sys.stderr = old_stderr self.output(f.getvalue()) def output(self, data): # Encode output for sending, and tell the event loop that # we are interested in WRITE readiness self.send_buf = self.send_buf + struct.pack('>l', len(data)) + data self.event.set_mode(set = event.FileEvent.WRITE) def close(self): self.socket.close() self.event.cancel() if self.wm: self.wm.inspect_client_closed(self) self.wm = None self.globals = None python-plwm-2.6a+20080530/plwm/mw_load.py0000644000175000017500000001001510765600750016421 0ustar stewstew# mw_load.py -- display load averages in a modewindow # # Copyright (C) 2001 Meik Hellmund # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # contributed by Meik Hellmund , heavily reusing # other plwm code # # Two methods are tried to get the load averages: # LinuxLoad - read from a file (/proc/loadavg) # UnixLoad - read output from a command (/usr/bin/uptime) # In both cases, the output is split on whitespaces and, depending on the # displaylist variable, some elements of this list are shown. # # # configuration variables: # mw_load_position, mw_load_justification: describe the position in # the modeline, as usual # mw_load.LinuxLoad.loadfile: name of file to read # mw_load.LinuxLoad.displaylist: list of entries to be displayed # 0,1,2 : 1,5,15-min load average # 4 : no. of running processes and total number of processes # mw_load.UnixLoad.loadcmd: command which prints load averages # mw_load.UnixLoad.displaylist: list of entries to be displayed # (for my uptime command, numbers 9,10 and 11 are the load averages) from plwm import modewindow, event, wmanager import os.path import string import sys import re import errno LoadTimerEvent = event.new_event_type() class LinuxLoad: loadfile = "/proc/loadsavg" displaylist = [0,1,2,3] def probe(self): return os.path.isfile(self.loadfile) def get(self, wm): f=open(self.loadfile,'r') l=string.split(f.readline()) f.close() str="" for x in self.displaylist: str = str + l[x] + " " return string.strip(str) class UnixLoad: loadcmd = "/usr/bin/uptime" load_re = re.compile(r'(\d+.\d\d)[, ]+(\d+.\d\d)[, ]+(\d+.\d\d)') def probe(self): return os.path.isfile(self.loadcmd) def get(self, wm): pipes = wm.system(self.loadcmd, redirect = 1) out = pipes[1] try: s = out.readline() except IOError, err: if err.errno == errno.EINTR: s = out.readline() out.close() wmanager.debug('mw_load', 'output: %s', s) m = self.load_re.search(s) if m: return string.join(m.groups(), ' ') else: return '' load_interfaces = [ LinuxLoad(), UnixLoad() ] class ModeWindowLoad: mw_load_position = 0.05 mw_load_justification = modewindow.LEFT def __wm_init__(self): for i in load_interfaces: if i.probe(): self.mw_load_interface = i break else: sys.stderr.write('%s: failed to find a load interface, disabling mw_load\n' % sys.argv[0]) return self.mw_load_message = modewindow.Message(self.mw_load_position, self.mw_load_justification) for s in self.screens: s.modewindow_add_message(self.mw_load_message) self.dispatch.add_handler(LoadTimerEvent, self.mw_load_tick) self.mw_load_update() def mw_load_update(self): msg = self.mw_load_interface.get(self) self.mw_load_message.set_text(msg) self.events.add_timer(event.TimerEvent(LoadTimerEvent, after = 60)) def mw_load_tick(self, evt): self.mw_load_update() python-plwm-2.6a+20080530/plwm/focus.py0000644000175000017500000001570010765600750016124 0ustar stewstew# # Track which client contains the pointer, and provide some functions for # moving focus # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys from Xlib import X, Xutil import wmanager import wmevents import cfilter MOVE_UP = 1 MOVE_DOWN = 2 MOVE_LEFT = 3 MOVE_RIGHT = 4 class JumpstartClient: """Windows get focus when opened. """ def __client_init__(self): if not self.start_iconified: self.activate() # WM mixin class PointToFocus: # This mixin tracks focus changes by following EnterNotify and # LeaveNotify events on client windows. WM global event handlers # are set up for these events, and whenever a new client is # created the corresponding masks are set on it. def __wm_init__(self): # Handler to set up required X masks on client windows self.dispatch.add_handler(wmevents.AddClient, self.ptfocus_handle_new_client) # And handlers for focus tracking self.dispatch.add_handler(X.EnterNotify, self.focus_enter) self.dispatch.add_handler(X.LeaveNotify, self.focus_leave) # Set up initial focus self.set_current_client(self.ptfocus_get_focused_client(), X.CurrentTime) def ptfocus_handle_new_client(self, evt): evt.client.dispatch.set_masks((X.EnterWindowMask, X.LeaveWindowMask)) # We handle the event by finding the window that contains the # pointer, since we get leave events when the pointer leaves the # border and enters the window... def ptfocus_get_focused_client(self): """Return the focused client, or None. Override to implement some other focus policy Default is that the pointer root is also the focus window. """ # Find the screen and window containing pointer for s in self.screens: r = s.root.query_pointer() if r.same_screen: if r.child != None: return s.get_client(r.child) else: return None return None def focus_enter(self, evt): wmanager.debug('focus', 'Pointer enter %s', evt.window) self.set_current_client(self.ptfocus_get_focused_client(), evt.time) def focus_leave(self, evt): wmanager.debug('focus', 'Pointer leave %s', evt.window) self.set_current_client(self.ptfocus_get_focused_client(), evt.time) class SloppyFocus(PointToFocus): # Variety of PointToFocus, which does not drop focus when the # pointer moves to the root window def ptfocus_get_focused_client(self): client = PointToFocus.ptfocus_get_focused_client(self) if client: return client elif self.focus_client and self.focus_client.is_mapped(): return self.focus_client else: return None # Very trivial focus moving code class MoveFocus: move_focus_ignore_clients = cfilter.false def get_client_pos(self, client, dir): """Return the position of CLIENT to be used when moving focus in direction DIR. """ if dir == MOVE_UP: return client.get_bottom_edge() elif dir == MOVE_DOWN: return client.get_top_edge() elif dir == MOVE_LEFT: return client.get_right_edge() else: return client.get_left_edge() def move_focus(self, dir): """Move focus to the next mapped client in direction DIR. DIR is either MOVE_UP, MOVE_DOWN, MOVE_LEFT or MOVE_RIGHT. """ # Get all the mapped clients, if any, sorted in stacking # order. Then inverse the list so the top-most client is # first in it. if self.current_screen is None: return clients = self.current_screen.query_clients(cfilter.mapped, stackorder = 1) clients.reverse() # No clients, meaningless to try to change focus if len(clients) == 0: return # A client is focused, so find the closest client if self.focus_client: pos = self.get_client_pos(self.focus_client, dir) best = None bestdiff = None for c in clients: if c is self.focus_client \ or self.move_focus_ignore_clients(c): continue p = self.get_client_pos(c, dir) if dir == MOVE_UP or dir == MOVE_LEFT: diff = pos - p else: diff = p - pos # Only use positive diffs, so the clients is on the right # side of the focused client # If no client has been found, or this diff is smaller # than a previous, use this diff as the best if diff > 0 and (bestdiff is None or diff < bestdiff): bestdiff = diff best = c # Okay, have we found some clients? Then just use the first found # and return. if bestdiff != None: best.activate() return # We get here if no client is focused, or if it is the outermost # client which is focused. Get the first client on the opposite # side best = None bestpos = None for c in clients: if self.move_focus_ignore_clients(c): continue pos = self.get_client_pos(c, dir) if bestpos is None: best = c bestpos = pos elif dir == MOVE_UP or dir == MOVE_LEFT: if pos > bestpos: best = c bestpos = pos else: if pos < bestpos: best = c bestpost = pos if best: best.activate() # For backward compitability class FocusHandler(PointToFocus): def __wm_init__(self): sys.stderr.write("%s: warning: using deprecated FocusHandler class (use PointToFocus instead)\n" % sys.argv[0]) PointToFocus.__wm_init__(self) class FocusClient: def __client_init__(self): sys.stderr.write("%s: warning: using deprecated FocusClient class (just remove it from mixin list)\n" % sys.argv[0]) python-plwm-2.6a+20080530/plwm/misc.py0000644000175000017500000001375210765600750015745 0ustar stewstew# # misc.py -- various small mixins and other functions # # Copyright (C) 2002 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from Xlib import X, XK, Xatom import wmanager import keys class InitialKeepOnScreenClient: """This Client mixin will make sure that new windows is entirely visible on the screen. If necessary, they will be moved and resized to fit on the screen. Put this mixin after any mixin that might change the window size or the border width. """ def __client_init__(self): x, y, w, h = self.keep_on_screen(self.x, self.y, self.width, self.height) w, h = self.follow_size_hints(w, h) self.configure(x = x, y = y, width = w, height = h) class MozillaPopupKeymap(keys.KeyHandler): """Keymap for the InhibitMozillaPopups mixin. Subclass it to define your own accept key, e.g.: class MyMozillaPopupKeymap(misc.MozillaPopupKeymap): M5_a = misc.MozillaPopupKeymap._accept _key_name = 'M5-a' """ timeout = 5 _key_name = 'special key' def __init__(self, client): keys.KeyHandler.__init__(self, client.wm) self.client = client self.status_msg = self.wm.current_screen.modestatus_new('Mozilla popup detected, press %s to accept' % self._key_name) def _accept(self, ev): wmanager.debug('mozilla', 'Accepting popup') self.status_msg.pop() # Deiconify-by-moving-back... # self.client.moveresize(0, 20, self.client.width, self.client.height, 1) self.client.deiconify() self._cleanup() def _timeout(self, ev): wmanager.debug('mozilla', 'Rejecting popup') self.status_msg.pop() # Delete the window, unless the user already has deiconified it if not self.client.is_mapped(): self.client.delete(1) self._cleanup() # client mixin class InhibitMozillaPopups: """This client mixin will detect popup-windows from Netscape 6.1, and possibly other Mozilla versions. They will not be allowed to be displayed unless the user press a certain key within five seconds. You must define the client attribute mozpopup_keymap to the subclass of InhibitMozillaKeymap to use as popup keymap. """ mozpopup_keymap = None def __client_init__(self): assert self.mozpopup_keymap is not None # Recognize Netscape 6.1 popups in this manner: # They have class Mozilla-bin. # They are not WM_TRANSIENT_FOR windows. # They have _MOTIF_WM_HINTS set, where MwmHints.flags have # MWM_HINTS_DECORATIONS set, # and MwmHints.decorations not in (MWM_DECOR_ALL, 0x7e) # (struct and flags defined in MwmUtil.h) # Only act when the window was found thanks to a maprequest. if not self.from_maprequest: return if self.res_class != 'Mozilla-bin': return MWM_HINTS = self.wm.display.intern_atom("_MOTIF_WM_HINTS") WM_TRANSIENT_FOR = self.wm.display.intern_atom("WM_TRANSIENT_FOR") r = self.window.get_property(WM_TRANSIENT_FOR, Xatom.WINDOW, 0, 1) if r is not None: return r = self.window.get_property(MWM_HINTS, MWM_HINTS, 0, 5) if r is None or r.format != 32 or len(r.value) != 5: return # Check flags and decoration if r.value[0] & 2 and r.value[2] not in (1, 0x7edd): wmanager.debug('mozilla', 'detected mozilla popup') # Don't map window immediately, and install # a temporary keymap to allow activating the window self.start_iconified = 1 self.mozpopup_keymap(self) self.wm.display.bell(0) # keymap class RunKeys(keys.KeyGrabKeyboard): """This keymap uses the modewindow to allow the user to enter a command to be run. Start it in a key handler method from your ordinary keymap, e.g.: def M5_e(self, evt): misc.RunKeys(self, evt) The prompt is indicated with an underscore, but editing is limited to deleting the last character. The command entered will be executed in the background when return is pressed. Escape aborts. """ propagate_keys = 0 timeout = 0 def __init__(self, keyhandler, evt): keys.KeyGrabKeyboard.__init__(self, keyhandler.wm, evt.time) self.run_cmd = '' self.status_msg = self.wm.current_screen.modestatus_new('Enter command: _') def _keyevent(self, event): if event.type != X.KeyPress: return sym = self.wm.display.keycode_to_keysym(event.detail, event.state & X.ShiftMask != 0) if not sym: return if sym == XK.XK_Return: if self.run_cmd: self.wm.system(self.run_cmd) self.status_msg.pop() self._cleanup() return if sym == XK.XK_Escape: self.status_msg.pop() self._cleanup() return if sym in (XK.XK_BackSpace, XK.XK_Delete): self.run_cmd = self.run_cmd[:-1] else: chr = self.wm.display.lookup_string(sym) if chr: self.run_cmd = self.run_cmd + chr self.status_msg.set('Enter command: %s_' % self.run_cmd) python-plwm-2.6a+20080530/plwm/color.py0000644000175000017500000000516310765600750016125 0ustar stewstew# # color.py -- Handle colors in a more symbolic way # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import types class ColorError(Exception): pass # Screen mixin class class Color: def __screen_client_init__(self): self.color_map = self.info.default_colormap self.color_alloced = {} def get_color(self, color, default = None): """Return the pixel value corresponding to COLOR. COLOR can be a string or tuple of (R, G, B). If the color can't be found and DEFAULT is provided, try to return that color instead. """ try: return self.color_alloced[color] except KeyError: pass if type(color) is types.StringType: col = self.color_map.alloc_named_color(color) elif type(color) is types.TupleType and len(color) == 3: col = self.color_map.alloc_color(color[0], color[1], color[2]) else: raise TypeError("string or 3-tuple expected") # If color allocation fails and there is a default color, simply # recurse to try to allocate it if col is None: if default: return self.get_color(default) else: raise ColorError(color) else: self.color_alloced[color] = col.pixel return self.color_alloced[color] def get_color_res(self, res_name, res_class, default = None): """Return the pixel value for the color defined in the X resource RES_NAME/RES_CLASS. If DEFAULT is provided, that name will be used if no matching X resource is found. If omitted, ColorError will be raised. """ col = self.wm.rdb_get(res_name, res_class, default) if col is None: raise ColorError('No color resource defined for %s/%s' % (res_name, res_class)) return self.get_color(col, default) python-plwm-2.6a+20080530/plwm/mw_biff.py0000644000175000017500000001262710765600750016423 0ustar stewstew# # mw_biff.py -- new mail notification in a ModeWindow # # Copyright (C) 2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from stat import * import os import sys from plwm import modewindow, event BiffEventType = event.new_event_type() # wm mixin class ModeWindowBiff: mw_biff_position = 0.0 mw_biff_justification = modewindow.LEFT mw_biff_mail_message = 'Mail' mw_biff_new_mail_message = 'New mail' def __wm_init__(self): try: self.mw_biff_mailpath = os.environ['MAIL'] except KeyError: sys.stderr.write('%s: $MAIL not set, mw_biff disabled\n' % sys.argv[0]) return self.mw_biff_mailp = 0 self.mw_biff_mailmsg = self.rdb_get('.modewindow.mail.text', '.ModeWindow.Mail.Text', self.mw_biff_mail_message) self.mw_biff_newmsg = self.rdb_get('.modewindow.newMail.text', '.ModeWindow.NewMail.Text', self.mw_biff_new_mail_message) self.mw_biff_message = modewindow.Message(self.mw_biff_position, self.mw_biff_justification) for s in self.screens: s.modewindow_add_message(self.mw_biff_message) self.dispatch.add_handler(BiffEventType, self.mw_biff_tick) self.mw_biff_update() def mw_biff_update(self): text, ding = self.mw_biff_check_mail() self.mw_biff_update_message(text, ding) # Trig a timer to recheck in 15 seconds self.events.add_timer(event.TimerEvent(BiffEventType, after = 15)) def mw_biff_update_message(self, text, ding): if text is not None: self.mw_biff_message.set_text(text) if ding: self.display.bell(50) def mw_biff_check_mail(self): try: s = os.stat(self.mw_biff_mailpath) if S_ISREG(s[ST_MODE]) and s[ST_SIZE] > 0: if s[ST_MTIME] >= s[ST_ATIME] or s[ST_CTIME] >= s[ST_ATIME]: mailp = 2 else: mailp = 1 else: mailp = 0 except os.error: mailp = 0 if mailp != self.mw_biff_mailp: self.mw_biff_mailp = mailp if self.mw_biff_mailp == 0: return '', 0 elif self.mw_biff_mailp == 1: return self.mw_biff_mailmsg, 0 else: return self.mw_biff_newmsg, 1 else: return None, 0 def mw_biff_tick(self, evt): self.mw_biff_update() class ThreadedModeWindowBiff(ModeWindowBiff): """This is a version of ModeWindowBiff that operates on the mailspool in a separate thread. The point of this is to make sure that the entire PLWM doesn't lock up when NFS does that. It is recommended to use this if you mailspool is NFS-mounted. """ def __wm_init__(self): # Attribute used to synchronise main PLWM thread # with mail check thread. We use the property of # Python threading that thread-switching is only done # between atomic Python instructions to avoid having # to deal with locks too. # And no, PLWM is absolutely not thread safe, thats why the # mail checking thread doesn't change the message by itself. self.mw_biff_set_message = None ModeWindowBiff.__wm_init__(self) def mw_biff_update(self): # A thread is working on checking that pesky mail # Sleep some more. if self.mw_biff_set_message == (): self.events.add_timer(event.TimerEvent(BiffEventType, after = 15)) # If a mail check thread has finished, display # the message, if any, and reschedule a tick elif self.mw_biff_set_message: text, ding = self.mw_biff_set_message self.mw_biff_update_message(text, ding) self.mw_biff_set_message = None self.events.add_timer(event.TimerEvent(BiffEventType, after = 12)) # Else its time to start a new thread elif self.mw_biff_set_message is None: import thread # Indicate that we're working on it self.mw_biff_set_message = () thread.start_new_thread(self.mw_biff_start_check, ()) # Give the other thread three seconds to check for new mail. # If it takes longer than that, we're probably having NFS problems. self.events.add_timer(event.TimerEvent(BiffEventType, after = 3)) def mw_biff_start_check(self): # Store real info when that has been recieved self.mw_biff_set_message = self.mw_biff_check_mail() python-plwm-2.6a+20080530/plwm/views.py0000644000175000017500000005125110765600750016143 0ustar stewstew# # views.py -- Handle views ("workspaces") # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from Xlib import X, Xatom import types import time import modewindow import string import cfilter import wmanager import wmevents class ViewedClient: def __client_init__(self): if not self.view_create_latent(self): self.start_iconified = 1 # screen mixin class ViewHandler: view_always_visible_clients = cfilter.false # If true, views will be reorded with the most-recently visited at # the front. This means that finding a view when several match # the last visited view will be found first, rather than the one # that happens to follow the currently visited view. view_reorder_views = 0 # How long a view must have been current to be deemed visited, in # seconds. Must be > 0, otherwise only the last visited view can # be reached. view_reorder_delay = 2.0 def __screen_client_init__(self): if self.view_reorder_views and self.view_reorder_delay <= 0: raise ValueError('view_reorder_delay must be > 0: %f' % self.view_reorder_delay) self.PLWM_VIEW_LIST = self.wm.display.intern_atom('_PLWM_VIEW_LIST') self.PLWM_VIEW_WINCONF = self.wm.display.intern_atom('_PLWM_VIEW_WINCONF') self.view_winconf = None self.view_latent_clients = {} self.view_visible_clients = [] self.view_clear_all() def __screen_init__(self): self.view_fetch() self.wm.dispatch.add_handler(wmevents.QuitWindowManager, self.view_handle_quit) def view_handle_quit(self, evt): # Store configuration when plwm exits if self.view_current: self.view_leave() self.view_store() def view_clear_all(self): self.view_next_id = 0 v = View(self, self.view_get_next_id()) self.view_list = [v] self.view_current = v self.view_index = 0 self.view_enter_time = time.time() self.view_enter_method = None def view_get_next_id(self): """Return the next unique view identifier. """ self.view_next_id = self.view_next_id + 1 return self.view_next_id def view_store(self): """Store PLWM_VIEW_LIST property on root window. This does not store properties for each view, that is done by View.leave. The data is an 32-bit array of integers. data[0] is the index of the current view in the list of views. data[1:] is the view ids. """ va = [self.view_index] for v in self.view_list: va.append(v.id) self.root.change_property(self.PLWM_VIEW_LIST, self.PLWM_VIEW_LIST, 32, va) def view_fetch(self): """Read the PLWM_VIEW* properties to replicate that view set. Returns true if the views were successfully read, false otherwise. """ # First fetch the current view index f = self.root.get_full_property(self.PLWM_VIEW_LIST, self.PLWM_VIEW_LIST) if not f or f.format != 32 or not f.value: return 0 index = int(f.value[0]) # Build the view list self.view_list = [] for d in map(int, f.value[1:]): if d > self.view_next_id: self.view_next_id = d v = View(self, d) v.fetch_winconf() v.fetch_tags() self.view_list.append(v) self.view_index = index self.view_current = self.view_list[self.view_index] self.view_enter() return 1 def view_current_index(self): return self.view_index def view_create(self, clientdefs, latent = 0): """Create a new veiw, but doesn't go to it. CLIENTDEFS is a list of clients to view initially. Each element should be either a client name or a tuple of one or more fields: (name, geometry, grab) If LATENT if false, the view will not be created unless at least one specified client exists. If LATENT is true, the view will be created when a matching client is created. Returns the new view number, or None if a latent view isn't created immediately. """ clients = [] defs = {} anymapped = 0 for cdef in clientdefs: name = cdef geometry = None grab = None try: name = cdef[0] geometry = cdef[1] grab = cdef[2] except IndexError: pass defs[name] = (geometry, grab) for c in self.clients.values(): if name == c.res_name or name == c.res_class: clients.append((c, geometry, grab)) if c.is_mapped(): anymapped = 1 # Named clients exists, so create a view now if anymapped: v = View(self, self.get_next_view_id()) self.view_list.append(v) for c, geometry, grab in clients: v.donate_client(c, geometry) if grab: c.iconify() return len(self.view_list) - 1 elif latent: self.view_latent_clients.update(defs) return None else: return None def view_create_latent(self, client): """Create a latent view if it waits for CLIENT. Return true if this client should be mapped now, false otherwise. """ found = 0 if self.view_latent_clients.has_key(client.res_class): geometry, grab = self.view_latent_clients[client.res_class] del self.view_latent_clients[client.res_class] found = 1 if self.view_latent_clients.has_key(client.res_name): geometry, grab = self.view_latent_clients[client.res_name] del self.view_latent_clients[client.res_name] found = 1 name = client.fetch_name() if self.view_latent_clients.has_key(name): geometry, grab = self.view_latent_clients[name] del self.view_latent_clients[name] found = 1 if not found: return 1 v = View(self, self.get_next_view_id()) self.view_list.append(v) v.donate_client(client, geometry) return not grab def view_goto(self, index, noexc = 0): """Display the view index by INDEX. If NOEXC is not passed or is false an IndexError exception will be raised if the view doesn't exist. If NOEXC is true nothing will happen if the view doesn't exist. """ if index != self.view_index: if noexc and len(self.view_list) <= index: return v = self.view_list[index] self.view_index = index self.view_leave() self.view_enter() def view_find_with_client(self, clients): """Display the next view with a client matching the filter CLIENTS. """ def view_has_client(view, clients): for winconf in view.winconf: if winconf.mapped and clients(winconf.client): return 1 return 0 self.view_reorder_before_move('find %s' % str(clients)) for i in range(self.view_index + 1, len(self.view_list)): if view_has_client(self.view_list[i], clients): self.view_goto(i) return for i in range(0, self.view_index): if view_has_client(self.view_list[i], clients): self.view_goto(i) return if not view_has_client(self.view_current, clients): self.wm.display.bell(0) def view_next(self): """Display the next view. """ if len(self.view_list) > 1: self.view_reorder_before_move('prevnext') self.view_index = (self.view_index + 1) % len(self.view_list) self.view_leave() self.view_enter() def view_prev(self): """Display the previous view. """ if len(self.view_list) > 1: self.view_reorder_before_move('prevnext') self.view_index = (self.view_index - 1) % len(self.view_list) self.view_leave() self.view_enter() def view_new(self, copyconf = 0): """Create a new view after the current. If COPYCONF is not passed or is false, the new View will be empty, otherwise it will be a copy of the current view. """ self.view_reorder_before_move('new') self.view_index = self.view_index + 1 self.view_list.insert(self.view_index, View(self, self.view_get_next_id())) self.view_leave() if copyconf: self.view_current.winconf = self.view_winconf self.view_enter() def view_tag(self, tag): """Set or remove TAG from the current view. """ self.view_current.tag(tag) def view_find_tag(self, tag): """Goto the next view tagged with TAG. """ self.view_reorder_before_move('tag %s' % tag) for i in range(self.view_index + 1, len(self.view_list)): if self.view_list[i].has_tag(tag): self.view_goto(i) return for i in range(0, self.view_index): if self.view_list[i].has_tag(tag): self.view_goto(i) return if not self.view_current.has_tag(tag): self.wm.display.bell(0) def view_leave(self): """Internal function for ViewHandler objects. Call after setting self.view_index to the new view, but with self.current still pointing to the old view. self.current will point to the new view when this functions is finished. """ winconf, empty = self.view_current.leave() if empty: old_index = self.view_list.index(self.view_current) # Delete this view as it has become empty del self.view_list[old_index] # And adjust the destination index, if it was after the now deleted view if self.view_index > old_index: self.view_index -= 1 try: self.view_current = self.view_list[self.view_index] except IndexError: self.view_current = self.view_list[0] self.view_index = 0 self.view_winconf = winconf # Store views as a property self.view_store() def view_enter(self): """Internal function for ViewHandler objects. Call after view_leave() to reconfigure the windows. """ if self.view_winconf: self.view_current.enter(self.view_winconf) self.view_enter_time = time.time() def view_reorder_before_move(self, method): """Internal function. Moves this view to the front of the list, if reorder is enabled and the view has been visible for long enough. Also move if the enter method differs from the previous move type. Call this in any view changing methods that steps or searches among views, to get the go-to-most-recently-visited functionality. """ if not self.view_reorder_views: return wmanager.debug('view', 'reordering, method %s', method) old_method = self.view_enter_method self.view_enter_method = method if self.view_index == 0: return now = time.time() if (now - self.view_enter_time >= self.view_reorder_delay or old_method != method): wmanager.debug('view', 'reordering, moving view %d from index %d to front', self.view_current.id, self.view_list.index(self.view_current)) self.view_list.remove(self.view_current) self.view_list.insert(0, self.view_current) self.view_index = 0 class XMW_ViewHandler(ViewHandler): def __screen_client_init__(self): ViewHandler.__screen_client_init__(self) self.view_xmw_count_message = modewindow.Message(.9, modewindow.LEFT) self.modewindow_add_message(self.view_xmw_count_message) def view_enter(self): ViewHandler.view_enter(self) self.view_xmw_update() def view_tag(self, tag): ViewHandler.view_tag(self, tag) self.view_xmw_update() def view_xmw_update(self): text = '%d/%d' % (self.view_index + 1, len(self.view_list)) tags = self.view_current.get_tags() if tags: text = text + ' (' + string.join(tags, ', ') + ')' self.view_xmw_count_message.set_text(text) WINCONF_CLIENT = 0 WINCONF_PTRPOS = 1 WINCONF_FOCUS_CLIENT = 2 class WinConf: def __init__(self, client, x, y, width, height, mapped): self.client = client self.x = max(x, 0) # change_property breaks on negative... self.y = max(y, 0) self.width = width self.height = height self.mapped = mapped def get_tuple(self): """Return a tuple encoding this winconf. """ return (self.client.window.__window__(), self.x, self.y, self.width, self.height, self.mapped) class View: def __init__(self, screen, viewid): self.screen = screen self.wm = screen.wm self.id = viewid self.tags = [] self.WINCONF = self.wm.display.intern_atom('_PLWM_VIEW_WINCONF_U%d' % self.id) self.TAGS = self.wm.display.intern_atom('_PLWM_VIEW_TAGS_U%d' % self.id) # A winconf is a list of WinConfs # in stacking order, bottommost first. self.winconf = [] # Keep track of where the pointer is when we leave this view self.ptrx = None self.ptry = None # Keep track of the focused client self.focus_client = None def store_winconf(self): """Store the winconf for this view. The format is a 32 bit list where each winconf is sequentially stored. """ wca = () # Encode winconfs for w in self.winconf: t = w.get_tuple() wca = wca + (WINCONF_CLIENT, len(t)) + t # Encode pointer position if self.ptrx is not None and self.ptry is not None: wca = wca + (WINCONF_PTRPOS, 2, self.ptrx, self.ptry) # Encode focused client if self.focus_client: wca = wca + (WINCONF_FOCUS_CLIENT, 1, self.focus_client.window.id) # Store the view configuration self.screen.root.change_property(self.WINCONF, self.screen.PLWM_VIEW_WINCONF, 32, wca) def fetch_winconf(self): """Fetch the winconf property for this view. """ # Fetch the first bytes f = self.screen.root.get_full_property(self.WINCONF, self.screen.PLWM_VIEW_WINCONF, 40) if not f or f.format != 32 or not f.value: return data = map(int, f.value) while len(data) >= 2: # Fetch the type of configuration and length wt = data[0] wl = data[1] # Abort if there isn't enough data left if wl > len(data) - 2: break # Extract config data and remove the used part wd = data[2 : wl + 2] del data[0 : wl + 2] if wt == WINCONF_PTRPOS: if wl >= 2: self.ptrx, self.ptry = wd[:2] elif wt == WINCONF_CLIENT: # Read the length of winconf item, minimum 6 # and we ignore everything above 6 if wl >= 6: win = self.wm.display.create_resource_object('window', wd[0]) c = self.screen.get_client(win) if c: self.winconf.append(apply(WinConf, (c,) + tuple(wd[1:6]))) elif wt == WINCONF_FOCUS_CLIENT: if wl >= 1: window = self.wm.display.create_resource_object('window', wd[0]) self.focus_client = self.screen.get_client(window) def store_tags(self): """Store the tags property for this view. """ if self.tags: self.screen.root.change_property(self.TAGS, Xatom.STRING, 8, string.join(self.tags, '\0')) else: self.screen.root.delete_property(self.TAGS) def fetch_tags(self): """Fetch the tags property for this view. """ # Fetch the first bytes f = self.screen.root.get_full_property(self.TAGS, Xatom.STRING) if not f or f.format != 8: return self.tags = string.split(f.value, '\0') def leave(self): """Notify the View that it is being unviewed. Return a tuple: (winconf, empty). WINCONF is the View's winconf. EMPTY is true if there are no visible windows left. """ empty = 1 # Find all the current windows clients = self.screen.query_clients(stackorder = 1) self.winconf = [] for c in clients: x, y, w, h = c.geometry()[0:4] self.winconf.append(WinConf(c, x, y, w, h, c.is_mapped())) if c.is_mapped() \ and not self.screen.view_always_visible_clients(c): empty = 0 # Find the pointer positon r = self.screen.root.query_pointer() if r.same_screen: self.ptrx = r.win_x self.ptry = r.win_y else: self.ptrx = self.ptry = None # Find the focused client if self.wm.focus_client and self.wm.focus_client.screen == self.screen: self.focus_client = self.wm.focus_client else: self.focus_client = None # Store winconf as property if not empty: self.store_winconf() # Or delete it if we're empty else: self.screen.root.delete_property(self.WINCONF) self.screen.root.delete_property(self.TAGS) return self.winconf, empty def enter(self, winconf): """Enter a View. The View gets the last View's WINCONF and shall install its own. """ # Unmap unwanted but mapped clients mapc = {} for w in self.winconf: mapc[w.client] = w.mapped for w in winconf: if not mapc.get(w.client, 0) and not self.screen.view_always_visible_clients(w.client): w.client.iconify() # Move the pointer, if that has been stored previously if self.ptrx is not None and self.ptry is not None: self.screen.root.warp_pointer(self.ptrx, self.ptry) # Reconfigure all clients, and map the visible ones deicon = [] for w in self.winconf: # Configure window, delayed if iconified w.client.moveresize(w.x, w.y, w.width, w.height, 1) w.client.raisewindow() if w.mapped: deicon.append(w.client) deicon.reverse() for c in deicon: c.deiconify() # Refocus if applicable if self.focus_client: self.wm.set_current_client(self.focus_client, X.CurrentTime) def donate_client(self, client, geometry): """Donate a client to this view. """ ### FIXME: actually use geometry argument x, y, w, h = client.geometry()[0:4] self.winconf.append(WinConf(client, x, y, w, h, 1)) def tag(self, tag): """Set or remove TAG from this view. """ if tag in self.tags: self.tags.remove(tag) else: self.tags.append(tag) self.store_tags() def has_tag(self, tag): """Return true if this view is tagged with TAG. """ return tag in self.tags def get_tags(self): """Return all tags on this view. """ return self.tags python-plwm-2.6a+20080530/plwm/frame.py0000644000175000017500000001666411010153410016065 0ustar stewstew# frame.py - reparent client windows into a decorated frame # # Copyright (C) 2008 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from Xlib import X, Xutil, Xatom import wmanager import wmevents class FrameProxy(wmanager.WindowProxyBase): # simple demo right now, but still a little configurable _frame_width = 2 _title_font = 'fixed' _title_color = '#ffffff' _unfocused_color = '#444444' _focused_color = '#2244aa' def __init__(self, screen, window, *args, **keys): wmanager.WindowProxyBase.__init__(self, screen, window, *args, **keys) self._font = self._wm.get_font(self._title_font) self._title_pixel = self._screen.get_color(self._title_color) self._unfocused_pixel = self._screen.get_color(self._unfocused_color) self._focused_pixel = self._screen.get_color(self._focused_color) fq = self._font.query() self._extra_height = fq.font_ascent + fq.font_descent + 3 * self._frame_width self._extra_width = 2 * self._frame_width self._title_x = self._frame_width self._title_y = self._frame_width self._title_base = self._frame_width + fq.font_ascent self._client_x = self._frame_width self._client_y = fq.font_ascent + fq.font_descent + 2 * self._frame_width # Create a proxy window for the frame that will contain the # real window g = window.get_geometry() self._frame = self._screen.root.create_window( g.x - self._client_x - g.border_width, g.y - self._client_y - g.border_width, g.width + self._extra_width, g.height + self._extra_height, 0, X.CopyFromParent, X.InputOutput, X.CopyFromParent, background_pixel = self._unfocused_pixel, event_mask = X.ExposureMask | X.SubstructureRedirectMask ) wmanager.debug('frame', 'created frame %s for client %s', self._frame, self._window) self._gc = self._frame.create_gc( foreground = self._title_pixel, font = self._font) # Reparent the real window into the frame window, blocking any # UnmapNotify that might generate screen.dispatch.block_masks(X.SubstructureNotifyMask) window.configure(border_width = 0) window.reparent(self._frame, self._client_x, self._client_y) screen.dispatch.unblock_masks(X.SubstructureNotifyMask) # Some methods can be overridden simply by only working on the proxy window self.get_geometry = self._frame.get_geometry self.circulate = self._frame.circulate self.reparent = self._frame.reparent self._screen.add_proxy_window(self._frame, window) def __proxy_event_init__(self, client, *args, **keys): wmanager.WindowProxyBase.__proxy_event_init__(self, client, *args, **keys) # Register event handlers now that the client is set up. # Don't set any event masks, we did that already when creating # the frame window client.dispatch.add_handler(X.Expose, self._expose, masks = ()) client.dispatch.add_handler(wmevents.ClientFocusIn, self._focus_in) client.dispatch.add_handler(wmevents.ClientFocusOut, self._focus_out) client.dispatch.add_handler(X.PropertyNotify, self._property_notify) def _proxy_withdraw(self): # Move window back to be an immediate child of root g = self._frame.get_geometry() self._frame.change_attributes(event_mask = 0) self._window.reparent(self._screen.root, g.x + self._client_x, g.y + self._client_y) self._screen.remove_proxy_window(self._frame) self._frame.destroy() def _expose(self, e): # Trivial exposure handling: redraw everything on final expose event if e.count == 0: self._redraw() def _focus_in(self, e): self._frame.change_attributes(background_pixel = self._focused_pixel) self._redraw() def _focus_out(self, e): self._frame.change_attributes(background_pixel = self._unfocused_pixel) self._redraw() def _property_notify(self, evt): if evt.atom == Xatom.WM_NAME: self._redraw() def _redraw(self): wmanager.debug('frame', 'redrawing') self._frame.clear_area() # Don't draw text in frame border g = self._frame.get_geometry() self._gc.set_clip_rectangles( 0, 0, [(self._frame_width, self._frame_width, g.width - self._extra_width, g.height - self._extra_height)], X.YXBanded) self._frame.draw_text(self._gc, self._title_x, self._title_base, self._client.get_title()) self._gc.change(clip_mask = X.NONE) # Override the necessary window methods def destroy(self, onerror = None): self._window.destroy() self._frame.destroy() def map(self, onerror = None): # Map both windows, so that the real window gets a MapNotify self._window.map() self._frame.map() def unmap(self, onerror = None): # Ditto but reverse self._frame.unmap() self._window.unmap() def configure(self, onerror = None, **keys): # Resize in lockstep, but otherwise all configuration is made # on the proxy real_keys = {} if 'width' in keys: real_keys['width'] = keys['width'] - self._extra_width if 'height' in keys: real_keys['height'] = keys['height'] - self._extra_height self._frame.configure(onerror = onerror, **keys) if real_keys: self._window.configure(onerror = onerror, **real_keys) def get_wm_normal_hints(self): # Must add in the frame to the size hints hints = self._window.get_wm_normal_hints() if hints: hints.min_width = hints.min_width + self._extra_width hints.min_height = hints.min_height + self._extra_height if hints.max_width: hints.max_width = hints.max_width + self._extra_width if hints.max_height: hints.max_height = hints.max_height + self._extra_height if hints.flags & Xutil.PBaseSize: hints.base_width = hints.base_width + self._extra_width hints.base_height = hints.base_height + self._extra_height return hints def __str__(self): return '<%s 0x%08x for %s 0x%08x>' % ( self.__class__, self._frame.id, self._window.__class__, self._window.id) def __repr__(self): return self.__str__() python-plwm-2.6a+20080530/plwm/modewindow.py0000644000175000017500000001173610765600750017166 0ustar stewstew# # modewindow.py -- manage a mode window within PLWM # # Copyright (C) 2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from Xlib import X import wmanager TOP = 0 BOTTOM = 1 LEFT = 0 CENTER = 1 RIGHT = 2 # Screen mixin class ModeWindowScreen: modewindow_pos = TOP def __screen_client_init__(self): fg = self.get_color_res('.modewindow.foreground', '.ModeWindow.Foreground', '#ffffff') bg = self.get_color_res('.modewindow.background', '.ModeWindow.Background', '#000000') self.modewindow_mw = ModeWindow(self, fg, bg, self.wm.get_font_res('.modewindow.font', '.ModeWindow.Font', 'fixed'), self.modewindow_pos) def modewindow_add_message(self, message): self.modewindow_mw.add_message(message) def modewindow_remove_message(self, message): self.modewindow_mw.remove_message(message) class ModeWindow: def __init__(self, screen, fg, bg, font, pos): self.messages = [] fq = font.query() font_center = (fq.font_ascent + fq.font_descent) / 2 - fq.font_descent height = fq.font_ascent + fq.font_descent + 6 if pos == TOP: c = screen.alloc_border('top', height) else: c = screen.alloc_border('bottom', height) self.x, self.y, self.width, self.height = c self.base = self.height / 2 + font_center window = screen.root.create_window( self.x, self.y, self.width, self.height, 0, X.CopyFromParent, X.InputOutput, X.CopyFromParent, background_pixel = bg, event_mask = X.ExposureMask ) self.gc = window.create_gc(foreground = fg, font = font) self.window = screen.add_internal_window(window) self.window.dispatch.add_handler(X.Expose, self.redraw) window.map() def add_message(self, message): try: self.messages.index(message) except ValueError: self.messages.append(message) message.add_to_mw(self) def remove_message(self, message): try: message.remove_from_mw(self) self.messages.remove(message) except ValueError: pass def redraw(self, event): for m in self.messages: m.draw(self) class Message: def __init__(self, position, justification = CENTER, nice = 0, text = None): if position < 0 or position > 1: raise ValueError('position should be in the interval [0.0, 1.0], was %s' % position) self.position = position self.justification = justification self.nice = nice self.text = text # Mapping of modewins to text widths self.modewins = {} def add_to_mw(self, mw): self.modewins[mw] = None self.draw(mw) def remove_from_mw(self, mw): self.undraw(mw) del self.modewins[mw] def set_text(self, text): if text == self.text: return self.text = text for mw in self.modewins.keys(): self.undraw(mw) self.modewins[mw] = None self.draw(mw) def draw(self, mw): if not self.text: return pos = self.modewins[mw] if pos is None: # Get width if self.text: f = mw.gc.query_text_extents(self.text) width = f.overall_width + 4 else: width = 0 # Get x pos x = (mw.width * self.position) if self.justification == CENTER: x = x - width / 2 elif self.justification == RIGHT: x = x - width self.modewins[mw] = (width, x) else: width, x = pos mw.window.draw_text(mw.gc, x + 2, mw.base, self.text) def undraw(self, mw): pos = self.modewins[mw] if pos is not None: width, x = pos mw.window.clear_area(x = x, width = width) python-plwm-2.6a+20080530/plwm/font.py0000644000175000017500000000423610765600750015755 0ustar stewstew# # font.py -- handle fonts in a more symbolic way # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA class FontError(Exception): pass # WindowManager mixin class class Font: def get_font(self, fontname, default = None): """Return the font object corresponding to FONTNAME. If FONTNAME doesn't match any font, attemt to return the font named DEFAULT instead, if DEFAULT is provided. If no font can be found, FontError is raised """ font = self.display.open_font(fontname) if font is None: if default is None: raise FontError("can't open font %s" % fontname) font = self.display.open_font(default) if font is None: raise FontError("can't open font %s" % fontname) return font def get_font_res(self, res_name, res_class, default = None): """Return the font object corresponding to the X resource RES_NAME, RES_CLASS. If this resource isn't found or doesn't match any font, attemt to return the font named DEFAULT instead, if DEFAULT is provided. If no font can be found, FontError is raised """ fontname = self.rdb_get(res_name, res_class, default) if fontname is None: raise FontError('No font resource defined for %s/%s' % (res_name, res_class)) return self.get_font(fontname) python-plwm-2.6a+20080530/plwm/border.py0000644000175000017500000002165310777306066016274 0ustar stewstew# # border.py -- change border color on focused client # # Copyright (C) 1999-2001,2006 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from Xlib import X, Xatom import wmanager, wmevents, cfilter class BorderClient: """Client mixin class managing a simple window border. The border has a width defined by the attribute border_default_width. The attribute no_border_clients is a client filter that causes matching clients to have no border at all. The color of the border is managed by objects of some BorderColorManager subclass. border_colors is a list of tuples (filter, manager). When a new client is created the list is processed from the beginning and clients matching filter will have their border colors managed by the manager object. If no filter matches the manager border_default_color is used instead. """ no_border_clients = cfilter.false border_default_width = 3 border_colors = () border_default_color = None # These two attributes are for backward compatability, and are # equivalent to this: # border_default_color = FixedBorderColor(border_color_name, # border_focuscolor_name) border_color_name = None border_focuscolor_name = None def __client_init__(self): self.border_color = None self.border_focuscolor = None if self.no_border_clients(self): self.setborderwidth(0) else: self.setborderwidth(self.border_default_width) manager = None for f, m in self.border_colors: if f(self): manager = m break else: manager = self.border_default_color if manager is None: # Use old color attributes manager = FixedBorderColor(self.border_color_name, self.border_focuscolor_name) manager.set_client_border_colors(self) # Set up dispatchers for the colors self.dispatch.add_handler(wmevents.ClientFocusIn, self.border_get_focus) self.dispatch.add_handler(wmevents.ClientFocusOut, self.border_lose_focus) def border_set_colors(self, blurred, focused): """This method should be called by the BorderColor object to set the border colors of the client, but might be called by other mixins too blurred and focused are the pixel values to be used for non-focused and focused clients, respectively. """ self.border_color = blurred self.border_focuscolor = focused if self.focused: if self.border_focuscolor is not None: self.window.change_attributes(border_pixel = self.border_focuscolor) else: if self.border_color is not None: self.window.change_attributes(border_pixel = self.border_color) # # Event handlers # def border_get_focus(self, event): if self.border_focuscolor is not None: self.window.change_attributes(border_pixel = self.border_focuscolor) def border_lose_focus(self, event): if self.border_color is not None: self.window.change_attributes(border_pixel = self.border_color) class BorderColorManager: """Base class for border color manager objects. It should be subclassed by actual managers, which must implement the set_client_border_colors() method. """ def set_client_border_colors(self, client): """Override this to implement the border color selection for client. """ raise NotImplementedError('%s.set_client_border_colors()' % self.__class__) class FixedBorderColor(BorderColorManager): """Use fixed border colors, one for non-focused and one for focused clients. """ def __init__(self, blurred, focused): """Set the color of all clients to blurred and focused, resp. They can either be strings naming a color, or three-tuples specifying a color as an (r, g, b) value in the range [0, 65535]. """ self.blurred_color = blurred self.focused_color = focused def set_client_border_colors(self, client): # The rdb stuff should be removed, but maybe someone is still using it... resname = client.wm.rdb_get('.border.color', '.Border.Color', '#000000') if self.blurred_color is not None: blurred = client.screen.get_color(self.blurred_color, default = resname) else: blurred = client.screen.get_color(resname) resname = client.wm.rdb_get('.border.focus.color', '.Border.Focus.Color', '#000000') if self.focused_color is not None: focused = client.screen.get_color(self.focused_color, default = resname) else: focused = client.screen.get_color(resname) client.border_set_colors(blurred, focused) class TitleBorderColor(BorderColorManager): """Change the color of the border based on the client title. The hue will be the same for a client (as long as the title doesn't change) but the saturation and brightness is changed depending on the client is focused or not, using an HSV-to-RGB translation. However, experimentation shows that HSV isn't very good, as confirmed by http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC36 Varying the brightness changes the perceived color, only just changing the saturation works reasonably well. This might be a problem with the colorsys module, too. If anyone manages to understand it, maybe the CIE XYZ model should be tried instead. """ class TitleUpdater: def __init__(self, manager, client): self.manager = manager self.client = client def __call__(self, event): if event.atom == Xatom.WM_NAME: self.manager.choose_color(self.client) def __init__(self, blurred_saturation = 0.3, blurred_brightness = 0.7, focused_saturation = 1.0, focused_brightness = 0.7): """The saturation and brightness values should all be in the range [0.0, 1.0]. As noted above, the two brightness values should typically be the same, otherwise the color will be perceived to change in hue when the window is focused. """ self.blurred_saturation = blurred_saturation self.blurred_brightness = blurred_brightness self.focused_saturation = focused_saturation self.focused_brightness = focused_brightness def set_client_border_colors(self, client): # Add a dispatcher that updates the colors when the title changes client.dispatch.add_handler(X.PropertyNotify, self.TitleUpdater(self, client)) # And set the initial colors self.choose_color(client) def choose_color(self, client): # FIXME: release old colors first to work well in PseudoColor # visuals. Would require extensions to the color module, # though, reference counting and so on, and also an event # handler to free colors when the client is closed. hue = self.get_hue(client) blurred = self.get_color_hsv(client, hue, self.blurred_saturation, self.blurred_brightness) focused = self.get_color_hsv(client, hue, self.focused_saturation, self.focused_brightness) client.border_set_colors(blurred, focused) def get_hue(self, client): """Return the hue for this client, in the range [0.0, 1.0]""" # We can't really know the range of hash, but there's probably # at least 24 bits. Xor them into eight bits and use that for hue th = hash(client.get_title()) h = th & 0xff h ^= (th >> 8) & 0xff h ^= (th >> 16) & 0xff return float(h) / 0xff def get_color_hsv(self, client, hue, saturation, brightness): import colorsys r, g, b = colorsys.hsv_to_rgb(hue, saturation, brightness) return client.screen.get_color((r * 65535, g * 65535, b * 65535)) python-plwm-2.6a+20080530/plwm/composite.py0000644000175000017500000003417311017350020016773 0ustar stewstew# composite.py - interface plcm to produce visual effects # # Copyright (C) 2007 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ plwm.composite supports plcm (the pointless composition manager) in manipulating the display of client window contents. This is done by reparenting each client window into a proxy window. plcm can then be told to render the client window into this proxy window allowing for effects as dimming the brightness, reversing brightness, or zooming. The WindowManager mixin CompositeManager handles some of the communication with plcm. """ import struct from Xlib import X, Xatom, error import Xlib.protocol.event from Xlib.ext.composite import RedirectManual import wmanager class ClientSettings: def __init__(self): # -255 to +255 self.brightness = 0 # Ratio, None == 1.0 self.zoom = None def no_effects(self): """Return true if these settings means that no effects will be performed by plcm. """ return self.brightness == 0 and self.zoom is None class CompositionManager: """WindowManager mixin, providing an interface to plcm. """ def __wm_screen_init__(self): self.comp_control_window = None # Map of clients managed by plcm to the current settings self.comp_clients = {} # The plcm interface atoms self._PLCM_CONTROL_WINDOW = self.display.intern_atom('_PLCM_CONTROL_WINDOW') self._PLCM_ENABLE = self.display.intern_atom('_PLCM_ENABLE') self._PLCM_DISABLE = self.display.intern_atom('_PLCM_DISABLE') self._PLCM_BRIGHTNESS = self.display.intern_atom('_PLCM_BRIGHTNESS') def comp_set_brightness(self, client, value): """Set the brightness of CLIENT to VALUE. Range of VALUE is -255 (black) to +255 (white). Set to 0 to disable any brightness adjustment. """ value = int(value) if value < -255 or value > 255: raise ValueError('brightness %s out of range' % value) try: settings = self.comp_clients[client] # Noop, no point in doing anything if settings.brightness == value: return new_client = 0 except KeyError: # No adjustment for new client? No need to do anything, then if value == 0: return settings = ClientSettings() new_client = 1 self.comp_do_set_brightness(client, settings, value) if new_client: self.comp_enable_client(client, settings) def comp_change_brightness(self, client, change): """Change the brightness of CLIENT by CHANGE (negative means darker, positive means brighter. """ change = int(change) if change == 0: return try: settings = self.comp_clients[client] new_client = 0 except KeyError: settings = ClientSettings() new_client = 1 new_value = max(-255, min(settings.brightness + change, 255)) # Already maxed out? if new_value == settings.brightness: return self.comp_do_set_brightness(client, settings, new_value) if new_client: self.comp_enable_client(client, settings) def comp_enable_client(self, client, settings = None): """Explicitly enable composition effects for CLIENT. Normally, this is done by calling e.g. comp_change_brightness(). """ # Is client already composition enabled? if client in self.comp_clients: return # Is composition disabled due to strange visuals? if client.window._composition_disabled: wmanager.debug('composite', 'composition disabled for client %s', client) return # When render redirection is enabled for the client, it will # be unmapped and then remapped. Block those events so they # aren't misinterpreted as a withdrawal. # This is an disadvantage of splitting the main work of # composition into plcm: it would be much more natural for # plcm to handle everything there is about composition, but it # is nigh on impossible to reliably block plwm from receiving # these events then. client.window._proxy.change_attributes(event_mask = X.NoEventMask) client.dispatch.block_masks(X.StructureNotifyMask) client.window._window.composite_redirect_window(RedirectManual) client.dispatch.unblock_masks(X.StructureNotifyMask) client.window._proxy.change_attributes(event_mask = X.SubstructureRedirectMask) if self.comp_send_plcm_message( self._PLCM_ENABLE, client.window._window, client.window._proxy): wmanager.debug('composite', 'enabled composition for client %s', client) if settings is None: settings = ClientSettings() self.comp_clients[client] = settings else: self.comp_remove_redirection(client) def comp_disable_client(self, client): """Explicitly disable composition effects for CLIENT. Normally, this is done by calling e.g. comp_set_brightness() with value 0. """ # Is client composition enabled? if client not in self.comp_clients: return self.comp_send_plcm_message( self._PLCM_DISABLE, client.window._window, client.window._proxy) wmanager.debug('composite', 'disabled composition for client %s', client) del self.comp_clients[client] self.comp_remove_redirection(client) # # Internal methods # def comp_do_set_brightness(self, client, settings, value): wmanager.debug('composite', 'changing brightness from %d to %d for %s', settings.brightness, value, client) settings.brightness = value if value != 0: # value must be unsigned if value < 0: value = struct.unpack('=L', struct.pack('=l', value))[0] client.window._proxy.change_property( self._PLCM_BRIGHTNESS, Xatom.INTEGER, 32, [value]) else: client.window._proxy.delete_property(self._PLCM_BRIGHTNESS) # No longer any settings in effect? if settings.no_effects(): self.comp_disable_client(client) def comp_remove_redirection(self, client): # See comment in comp_enable_client client.window._proxy.change_attributes(event_mask = X.NoEventMask) client.dispatch.block_masks(X.StructureNotifyMask) client.window._window.composite_unredirect_window(RedirectManual) client.dispatch.unblock_masks(X.StructureNotifyMask) client.window._proxy.change_attributes(event_mask = X.SubstructureRedirectMask) client.window._proxy.delete_property(self._PLCM_BRIGHTNESS) def comp_send_plcm_message(self, message, source, target): if self.comp_control_window is None: # Try to find the control window on demand, instead of # being fancy and waiting for events about it root = self.display.screen(0).root r = root.get_full_property(self._PLCM_CONTROL_WINDOW, Xatom.WINDOW) if r is None or r.format != 32 or len(r.value) != 1: wmanager.debug('composite', 'no plcm control window, not doing anything') return 0 self.comp_control_window = self.display.create_resource_object( 'window', r.value[0]) wmanager.debug('composite', 'plcm control window: %s', self.comp_control_window) # We knows the ID of the control window now ev = Xlib.protocol.event.ClientMessage( window = self.comp_control_window, client_type = message, data = (32, [source.id, target.id, 0, 0, 0])) self.comp_control_window.send_event(ev, onerror = self.comp_send_message_error) return 1 def comp_send_message_error(self, error, request): wmanager.debug('composite', 'error when sending message to plcm: %s', error) wmanager.debug('composite', 'disabling all enabled clients') for client in self.comp_clients.iterkeys(): self.comp_remove_redirection(client) self.comp_clients = {} self.comp_control_window = None class CompositeProxy(wmanager.WindowProxyBase): def __init__(self, screen, window, *args, **keys): wmanager.WindowProxyBase.__init__(self, screen, window, *args, **keys) # Create a proxy window that will contain the real window as a # clone of the source window. g = window.get_geometry() a = window.get_attributes() # Never trust an X server. The visual and depth as returned # by the methods just called can result in a BadMatch, despite # the reasonable assumption that if the client could create a # window with those specs, then we can also do it. # The reason for this sad state of affairs is OpenGL: a GLX # visual that have 24 bits of colour info _and_ 8 bits of # alpha info appears to a non-GL application (e.g. plwm) to # have depth 24, but you can't create windows of that depth on # it. An example of an application that does this is # OpenOffice 2.0, which is how I found it. # In this case, just create a window on the default visual for # the proxy and disable composition for this client (as plcm # require that the proxy and the client windows use the same # visual). self._composition_disabled = 0 ec = error.CatchError(error.BadMatch) self._proxy = screen.root.create_window( g.x, g.y, g.width, g.height, g.border_width, g.depth, a.win_class, a.visual, onerror = ec) # Check if the mismatch was triggered self._wm.display.sync() if ec.get_error(): wmanager.debug('composite', 'strange visual, disabling composition for %s', window) self._composition_disabled = 1 self._proxy = screen.root.create_window( g.x, g.y, g.width, g.height, g.border_width, X.CopyFromParent) # The proxy window must have the SubstructureRedirectMask set, so # we still get MapRequest, ConfigureRequest etc for the client window. self._proxy.change_attributes(event_mask = X.SubstructureRedirectMask) # Reparent the real window into the proxy window, blocking any # UnmapNotify that might generate screen.dispatch.block_masks(X.SubstructureNotifyMask) window.configure(border_width = 0) window.reparent(self._proxy, 0, 0) screen.dispatch.unblock_masks(X.SubstructureNotifyMask) # Some methods can be overridden simply by only working on the proxy window self.get_geometry = self._proxy.get_geometry self.circulate = self._proxy.circulate self.reparent = self._proxy.reparent self._screen.add_proxy_window(self._proxy, window) def _proxy_withdraw(self): # Move window back to be an immediate child of root g = self._proxy.get_geometry() self._proxy.change_attributes(event_mask = 0) self._window.configure(border_width = g.border_width) self._window.reparent(self._screen.root, g.x, g.y) self._screen.remove_proxy_window(self._proxy) self._proxy.destroy() # Override those methods that must do more to get control over the # source window def change_attributes(self, onerror = None, **keys): # These two attrs should be set on the proxy, the # others on the real window proxy_keys = {} if 'border_pixmap' in keys: proxy_keys['border_pixmap'] = keys['border_pixmap'] del keys['border_pixmap'] if 'border_pixel' in keys: proxy_keys['border_pixel'] = keys['border_pixel'] del keys['border_pixel'] if proxy_keys: self._proxy.change_attributes(onerror = onerror, **proxy_keys) self._window.change_attributes(onerror = onerror, **keys) def destroy(self, onerror = None): self._window.destroy() self._proxy.destroy() def map(self, onerror = None): # Map both windows, so that the real window gets a MapNotify self._window.map() self._proxy.map() def unmap(self, onerror = None): # Ditto but reverse self._proxy.unmap() self._window.unmap() def configure(self, onerror = None, **keys): # Resize in lockstep, but otherwise all configuration is made # on the proxy real_keys = {} if 'width' in keys: real_keys['width'] = keys['width'] if 'height' in keys: real_keys['height'] = keys['height'] self._proxy.configure(onerror = onerror, **keys) if real_keys: self._window.configure(onerror = onerror, **real_keys) # For zoom: # def get_wm_normal_hints(self): def __str__(self): return '<%s 0x%08x for %s 0x%08x>' % ( self.__class__, self._proxy.id, self._window.__class__, self._window.id) def __repr__(self): return self.__str__() python-plwm-2.6a+20080530/plwm/menu.py0000644000175000017500000001320110777306076015752 0ustar stewstew# # menu.py -- Screen mixin to provide menus. # # Copyright (C) 2001 Mike Meyer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA "menu - a mixin to provide menus for plwm screens." from Xlib import X from keys import KeyGrabKeyboard, KeyHandler, allmap from message import Message class MenuKeyHandler(KeyGrabKeyboard): """Template for handling a menu. MenuKeyHandler defines the following event handler methods: _up, _down - move the cursor up or down the menu. _do - perform the currently selected menu action. _abort - exit the menu with no actions.""" timeout = None def __init__(self, menu): KeyGrabKeyboard.__init__(self, menu.window, X.CurrentTime) self.menu = menu def _up(self, event): self.menu.up() def _down(self, event): self.menu.down() def _do(self, event): self._cleanup() self.menu.do() def _abort(self, event): self._cleanup() self.menu.close() class MenuCharHandler(MenuKeyHandler): """MenuCharHandler allows "one-key access" to menu entries. This adds the _goto method, which goes to the first menu label that starts with a character >= the event keycode, then the latin1 symbols to that method.""" def _goto(self, event): char = chr(self.wm.display.keycode_to_keysym(event.detail, 0)) if event.state: char = char.upper() return self.menu.goto(char) allmap(MenuCharHandler, MenuCharHandler._goto) class MenuCharSelecter(MenuCharHandler): """A MenuCharHandler with intanst selection. Just like MenuCharHandler, except when you hit the character, it not only takes you there, it issues the command if the character is in the menu. Otherwise, it acts like MenuCharHandler.""" def _goto(self, event): if MenuCharHandler._goto(self, event): self._do(event) allmap(MenuCharSelecter, MenuCharSelecter._goto) class Menu(Message): "Holds and manipulates the menu." def setup(self, labels, align = 'center'): "Initialize the menu window, gc and font" width, height = Message.setup(self, labels, align) self.high = height / len(self.lines) self.current = 0 return width, height def start(self, x, y, action, handler, timeout = 0): """Start it up... Passing x,y = -1,-1 will cause the menu to be centred on the screen. """ if x==-1 and y==-1: x = self.wm.current_screen.root_x + \ self.wm.current_screen.root_width/2-self.width/2 y = self.wm.current_screen.root_y + \ self.wm.current_screen.root_height/2-self.height/2 Message.display(self, x, y, timeout) self.window.get_focus(X.CurrentTime) self.action = action handler(self) def up(self): "Move the menu selection up." self.current = self.current - 1 if self.current < 0: self.current = len(self.lines) - 1 self.redraw() def down(self): "Move the menu selection down" self.current = self.current + 1 if self.current >= len(self.lines): self.current = 0 self.redraw() def goto(self, char): """Goto the first entry with a label that starts >= char returns true if entry actually starts with char.""" length = len(self.lines) lc = char.lower() for i in range(length): if (lc, char) <= (self.lines[i].name[0].lower(), self.lines[i].name[0]): break if i < length: self.current = i else: self.current = length - 1 self.redraw() return char == self.lines[i].name[0] def do(self): "Run it!" self.close() self.wm.display.sync() self.action(self.lines[self.current].name) def redraw(self, event = None): "Redraw the window, with highlights" Message.redraw(self) self.window.fill_rectangle(self.gc, 0, self.current * self.high, self.width, self.high) class screenMenu: """PLWM Screen mixin to provide a per-screen menu. This mixin requires the color and font mixins be in the screen class.""" menu_font = "9x15Bold" menu_foreground = "black" menu_background = "white" menu_bordercolor = "black" menu_borderwidth = 3 menu_seconds = 0 menu_draw = X.GXinvert def menu_make(self, labels, align = 'center'): """Create a menu of labels. Returns the width and height of the resulting menu.""" self.menu = Menu(self, self.menu_font, self.menu_draw, self.menu_foreground, self.menu_background, self.menu_bordercolor, self.menu_borderwidth, self.menu_seconds) return self.menu.setup(labels, align) def menu_run(self, x, y, action): "Instantiate the menu, and return the label or None." self.menu.start(x, y, action, self.menu_handler) python-plwm-2.6a+20080530/plwm/mouse.py0000644000175000017500000004640610765600750016144 0ustar stewstew# # mouse.py -- Basic mouse event handling # Copyright (C) 2004 Mark Tigges mtigges@gmail.com # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # WARNING: # # This module has been cobbled together using the keys module and an almost # complete lack of understanding of X internals. Some of the comments may # sound irreverent, or you may feel disgust that this looks like such a hack. # Sorry, please let me know of any problems you find. I'd love to make it # robust ... it works for me, hopefully it will work for you. If it doesn't # hopefully we can fix it! # # mtigges@gmail.com # # TODO: # # 1. Provide support for multiple mouse buttons down at once, the # buttonsym thing might have to be completely reworked. # # 2. Figure out why there is always a pause before the event reaches # _mouseevent. This doesn't happen in keys, so I'm very curious # what's causing it. There seems to be a second long timer that is # polling events in a loop in the dispatcher ... but it's the same # beast that the keys system uses, so I'm confused about this. from Xlib import X, XK import string import wmanager import event import time from keys import modifiers, ReleaseModifier # These are organized like this so that idx%8 gives the same value # as the X.Button? codes # The L,R,M are just synonyms for B1,B2,B3 and are transformed to # those numbers in the string_to_buttonsym function buttonsyms = ['B1Down', 'B2Down', 'B3Down', 'B4Down', 'B5Down', 'LDown', 'MDown', 'RDown', 'B1Up', 'B2Up', 'B3Up', 'B4Up', 'B5Up', 'LUp', 'MUp', 'RUp', 'B1Move', 'B2Move', 'B3Move', 'B4Move', 'B5Move', 'LMove', 'MMove', 'RMove'] def string_to_buttonsym(str): try: idx = buttonsyms.index(str) # this silly looking arithmetic switches any L* to B1* and # similary for the R* and M* symbols. return 8*(idx/8) + (idx%8)%5 + 1 except: return None def hash_mousecode(sym,mods): return mods<<8 | sym # This is now the real grabbing class class MousegrabManager: # The MouseHandler class instantiates this class as many times as it # needs to ... depending on how you initialize it. You shouldn't # need to instantiate this guy. def __init__(self, wm, window): self.wm = wm self.window = window self.grabs = {} def ungrab_buttons(self, mouselist): """Ungrab some mouse buttons MOUSELIST is a list of (mousecode, modifier) tuples. """ for buttonsym, modifiers in mouselist: h = hash_mousecode(buttonsym, modifiers) c = self.grabs.get(h, 0) if c > 0: if c == 1: del self.grabs[h] self.window.ungrab_mouse(buttonsym, modifiers & ~ReleaseModifier) else: self.grabs[h] = self.grabs[h] - 1 def grab_buttons(self, mouselist): """Grab some mouse buttons MOUSELIST is a list of (mousecode, modifier) tuples. """ grabs = {} for buttonsym, modifiers in mouselist: h = hash_mousecode(buttonsym, modifiers) c = self.grabs.get(h, 0) if c == 0: button = buttonsym%8 mask = buttonsym/8 if mask==0: mask = X.ButtonPressMask elif mask==1: mask = X.ButtonReleaseMask else: mask = X.ButtonMotionMask try: mask |= grabs[hash_mousecode(button,modifiers)][2] except: pass grabs[hash_mousecode(button,modifiers)] = (button, modifiers, mask) self.grabs[h] = 1 else: self.grabs[h] = self.grabs[h] + 1 for k in grabs.keys(): button,modifiers,mask = grabs[k] self.window.grab_button(button, modifiers, True, mask, X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE, None) def grab_pointer(self, time): s = self.window.grab_pointer(0, X.GrabModeAsync, X.GrabModeAsync, time) if s != X.GrabSuccess: raise error, s def ungrab_pointer(self): self.wm.display.ungrab_pointer(X.CurrentTime) class MouseHandler: """This class allows you to get mouse events in the same way that the keys module allows you to get key presses. The modifiers on the methods are identical. The different buttons are: LDown LMove LUp left button MDown MMove MUp middle button RDown RMove RUp right button You can alternatively use B1 ... B5 for the different buttons. B1,B2,B3 correspond to left,middle,right. So, as an example the member method named: C_M_LDown will get left button mouse down events when control and alt are pressed. I haven't tested with all combinations, but hopefully it works. mtigges@gmail.com Unfortunately, at this time there is NO SUPPORT for events with multiple mouse buttons down, don't even try it, it won't work. Sorry. Fix it if you like. """ propagate_mouse = 1 timeout = None def __init__(self, obj): # The object can be a window manager, a screen or a window ... just # like keys. What happens depends on that. # The vast majority of my testing has been on passing a WindowManager # so ... your mileage may vary on the others. Please, go ahead # and try it and let me know if it needs work. if isinstance(obj, wmanager.WindowManager): wm = obj grabmgrs = [] for s in obj.screens: if not hasattr(s, 'mousegrab_mgr'): s.mousegrab_mgr = MousegrabManager(obj, s.root) grabmgrs.append(s.mousegrab_mgr) elif isinstance(obj, wmanager.Screen): wm = obj.wm if not hasattr(obj, 'mousegrab_mgr'): obj.mousegrab_mgr = MousegrabManager(obj.wm, obj.root) grabmgrs = [obj.mousegrab_mgr] elif isinstance(obj, wmanager.Window): wm = obj.wm if not hasattr(obj, 'keygrab_mgr'): obj.mousegrab_mgr = MousegrabManager(obj.wm, obj.window) grabmgrs = [obj.mousegrab_mgr] else: raise TypeError('expected WindowManager, Screen or Client object') # Dig through all names in this object, ignoring those beginning with # an underscore. # First collect all method names in this and it's base classes names = {} c = [self.__class__] while len(c): names.update(c[0].__dict__) c = c + list(c[0].__bases__) del c[0] # And now parse the names rawbinds = [] for name in names.keys(): if name[0] != '_': # Find modifiers in name mask = 0 parts = string.split(name, '_') while len(parts) >= 2 and modifiers.has_key(parts[0]): mask = mask | modifiers[parts[0]] del parts[0] if len(parts)==1: buttonsym = string_to_buttonsym(parts[0]) if buttonsym != None: rawbinds.append((buttonsym,mask, getattr(self,name))) self.wm = wm self.dispatch = obj.dispatch self.grabmgrs = grabmgrs self.rawbindings = rawbinds self.grabs = [] # Add handlers if self.propagate_mouse: # This is where you want to be ... it will result in only # those mouse events you care about triggering the _mouseevent # method, everything else will fall through to the apps. self.dispatch.add_handler(X.ButtonPress, self._mouseevent, handlerid = self) self.dispatch.add_handler(X.ButtonRelease, self._mouseevent, handlerid = self) self.dispatch.add_handler(X.MotionNotify, self._mouseevent, handlerid = self) else: # I left this part in from the keys module, but I think it's # a really bad idea. I wouldn't use this at all. Bout the # only time I could think of using it is if you're writing a # system for just one program ... maybe dedicating a laptop # to a single purpose ... like mp3 player or something. self.dispatch.add_grab_handler(X.ButtonPress, self._mouseevent, handlerid = self) self.dispatch.add_grab_handler(X.ButtonRelease, self._mouseevent, handlerid = self) self.dispatch.add_grab_handler(X.MotionNotify, self._mouseevent, handlerid = self) if self.timeout: self.last_mouse_time = None self.timer_id = event.new_event_type() self.timer = event.TimerEvent(self.timer_id, after = self.timeout) self.wm.events.add_timer(self.timer) self.dispatch.add_handler(self.timer_id, self._internal_timeout, handlerid = self) self._buildmap() def __del__(self): wmanager.debug('mem', 'Freeing keyhandler %s', self) self._cleanup() def _cleanup(self): # Remove all our event handlers self.dispatch.remove_handler(self) # Ungrab buttons self._ungrab() # Clear the bindings: essential as elements of this list refers # to bound methods of this object, i.e. circular references. self.rawbindings = None self.bindings = None # Unscedule any pending timeout if self.timeout: self.timer.cancel() def _grab(self): for g in self.grabmgrs: g.grab_buttons(self.grabs) def _ungrab(self): for g in self.grabmgrs: g.ungrab_buttons(self.grabs) self.grabs = [] def _buildmap(self): """Build mouse bindings mapping. Also sets passive grabs for the mouse bindings. """ self.bindings = {} # First ungrab the grabs we already have self._ungrab() # Build up new list of bindings for buttonsym, modifiers, func in self.rawbindings: self.bindings[hash_mousecode(buttonsym, modifiers)] = func self.grabs.append((buttonsym, modifiers)) # Install the new grabs self._grab() def _mappingnotify(self, event): """Pass as handler for MappingNotify events to rebuild the key bindings. """ self._buildmap() def _internal_timeout(self, ev): """Called when the timer event times out. Don't override this, override _timeout instead. """ # Call _timeout if it has been at least self.timeout # seconds since the last keypress if self.last_mouse_time is None \ or ev.time - self.last_mouse_time >= self.timeout: wmanager.debug('keys', 'timeout, last_mouse = %s, now = %s', self.last_mouse_time, ev.time) self._timeout(ev) # If not: reschedule a timeout else: wmanager.debug('mouse', 'rescheduling timeout at %s', self.last_mouse_time + self.timeout) self.timer = event.TimerEvent(self.timer_id, at = self.last_mouse_time + self.timeout) self.wm.events.add_timer(self.timer) def _timeout(self, event): """Called when we really timeout. """ pass def _mouseevent(self, event): # Store mouse press time (approximate to the current time # as the X event.time isn't synced with that) self.last_mouse_time = time.time() button = event.detail state = event.state # This function is not nearly as clean as the keys._keyevent, for # a couple of reasons ... 1. Mouse events are a little bit different # 2. I don't understand all of this well enough to make it really # clean, I hacked this all together. if button: # It seems that if the event.detail field is non-zero it's # one of our passive grabs for a press or a release. The # state holds the modifiers+which buttons are down BEFORE # the event occured, so if it's a release, we need to get # the button mask out of the state before computing the hash. state = state & ~(getattr(X,'Button%dMask' % button)) # Now we have to get the button symbol (for our list above) # from the button and the event type # We're presuming only one down at a time. button = button + (event._code-X.ButtonPress)*8 else: # So, if we're here, it means that the mouse moved. if event.state: # if it was a non-trivial move (there were modifiers and or # mouse buttons down, we come in here. for b in [1,2,3,4,5]: # This loop figures out which button is down. # Again, only one at once if event.state&getattr(X,'Button%dMotionMask'%b): button = b break #sys.stderr.write('\n') #sys.stderr.write('button: %d\n' % button) #sys.stderr.write('state : %d\n' % state) try: # Again we strip the button mask out to get only the # modifiers. state = state & ~(getattr(X,'Button%dMotionMask' % button)) except: pass #sys.stderr.write('state : %d\n' % state) # And convert to an index in our symbols. button = button+16 # Get the hash code for this state match = hash_mousecode(button,state) # If we have a function for that hash, call it. if self.bindings.has_key(match): self.bindings[match](event) class MoveResize(MouseHandler): """Simple mouse movement and resize class. Simply instantiate one of these in your windowmanager constructor and voila, you can move and resize your windows via your mouse Alt+left button will move your window around Ctrl+Alt+right button will resize your window (in a preferential dir) Alt+middle button will resize your window either horizontally or vertically. """ def __init__(self,wm): MouseHandler.__init__(self,wm) def C_M_LDown(self,evt): self.client = self.wm.current_client self.dir = None if self.client: self.mouse_x, self.mouse_y = evt.root_x, evt.root_y def C_M_LUp(self,evt): self.C_M_LMove(evt) self.client = None def C_M_LMove(self,evt): if self.client: c = self.client dx,dy = evt.root_x-self.mouse_x, evt.root_y-self.mouse_y if self.dir==None: if dx<0: if dy<0: self.dir = 0 # left and up if dy>0: self.dir = 3 # left and down else: if dy<0: self.dir = 1 # right and up if dy>0: self.dir = 2 # right and down if self.dir==0: c.configure(x=c.x+dx,y=c.y+dy, width=c.width-dx,height=c.height-dy) elif self.dir==1: c.configure(x=c.x,y=c.y+dy, width=c.width+dx,height=c.height-dy) elif self.dir==2: c.configure(x=c.x,y=c.y, width=c.width+dx,height=c.height+dy) elif self.dir==3: c.configure(x=c.x+dx,y=c.y, width=c.width-dx,height=c.height+dy) self.mouse_x = evt.root_x self.mouse_y = evt.root_y def M_LDown(self,evt): self.client = self.wm.current_client if self.client: self.mouse_x, self.mouse_y = evt.root_x, evt.root_y def M_LUp(self,evt): self.M_LMove(evt) self.client = None def M_LMove(self,evt): if self.client: c = self.client dx,dy = evt.root_x-self.mouse_x, evt.root_y-self.mouse_y c.configure(x=c.x+dx,y=c.y+dy,width=c.width,height=c.height) self.mouse_x = evt.root_x self.mouse_y = evt.root_y def M_MDown(self,evt): self.client = self.wm.current_client self.dir = None if self.client: self.mouse_x, self.mouse_y = evt.root_x, evt.root_y def M_MUp(self,evt): self.M_MMove(evt) self.client = None def M_MMove(self,evt): if self.client: c = self.client dx,dy = evt.root_x-self.mouse_x, evt.root_y-self.mouse_y if self.dir==None: if abs(dx)>abs(dy): if dx<0: self.dir = 3 else: self.dir = 1 elif abs(dy)>abs(dx): if dy<0: self.dir = 0 else: self.dir = 2 if self.dir!=None: if self.dir==0: c.configure(x=c.x,y=c.y+dy, width=c.width,height=c.height-dy) elif self.dir==1: c.configure(x=c.x,y=c.y, width=c.width+dx,height=c.height) elif self.dir==2: c.configure(x=c.x,y=c.y, width=c.width,height=c.height+dy) elif self.dir==3: c.configure(x=c.x+dx,y=c.y, width=c.width-dx,height=c.height) self.mouse_x = evt.root_x self.mouse_y = evt.root_y python-plwm-2.6a+20080530/plwm/ChangeLog0000644000175000017500000006161307513652766016224 0ustar stewstew2002-07-13 Peter Liljenberg * mixer.py: New mixin for controlling the audio mixer. Currently it only supports "rexima", but it should be easy to extend to "aumix" etc. 2002-07-12 Peter Liljenberg * wmanager.py (Screen.system): (WindowManager.sigchld_handler): This was messy: since we don't have reliable signal blocking, we have to do some quite complicated book-keeping to ensure that we neither lose foreground processes exit status, nor hang forever. * mw_load.py (UnixLoad.get): Use wm.system redirection to avoid using intermediate file. * wmanager.py (Client.follow_size_hints): (Window.keep_on_screen): If client filter Window.full_screen_windows is true, the window is allowed to occupy the entire screen. Otherwise it will be cropped by allocated edge area. (Screen.system): Support redirecting stdio, to allow interaction between PLWM and invoked command. 2002-07-07 Peter Liljenberg * wmanager.py (Screen.handle_map_request): Only map managed windows on MapRequest if the user allows it. 2002-03-04 Peter Liljenberg * border.py (BorderClient.__client_init__): Set border_[focus]color_name to None by default, so check against that. Also remember to set border_focuscolor event if border_focuscolor_name is not set. * wmanager.py (Screen.handle_client_message): Fetch a client instead of a window, and make sure that it exists. (Screen.remove_window): Check for null windows. Also pass the RemoveClient event manually to the deleted client. 2002-02-22 Peter Liljenberg * input.py (modeInput.__init__): Rename "ignored" argument to "length" as in inputWindow, to allow keyword argument comptibability. 2002-01-19 Peter Liljenberg * wmanager.py (Window.raiselower): New method to raise the window if it is below any other, or lower it if it is on top. * misc.py: New file, containing various useful mixins and other functions. Most of them comes from petliwm.py. * wmanager.py (Screen.alloc_border): New method that allows mixins to reserve a part of the root window located at one of the edges. (Window.keep_on_screen): Use the new Screen attributes root_x and root_y for positioning. (Client.__init__): Kludge to always reparent client windows, to fix broken widget sets. 2002-01-14 Peter Liljenberg * cfilter.py (StaticClientFilter): Let all static filters inherit this base class, improving performance a bit by binding the __call__ method at initialization. (is_client): New filter that is true for all client objects. * wmanager.py (Client.configure): (Window.configure): Update configure to the internal windows. 2002-01-10 Peter Liljenberg * wmanager.py (Client.__del__): Should not be a Window method. (Screen.__init__): (Screen.add_client): (Screen.remove_window): (Screen.add_internal_window): (Screen.get_window): (Screen.is_client): (Screen.is_internal_window): (Screen.get_client): Unified handling of external and internal windows. Makes event handling a bit less convoluted. (Client.withdraw): (Window.withdraw): Split into window-general and client-specific methods. (Window.raisewindow): (Window.lowerwindow): These are nice to have both on Window and Client. (InternalWindow): Subclass to Window, so that we can identify internal windows. 2001-12-12 Peter Liljenberg * __init__.py (__all__): Added mw_load module, contributed by Meik Hellmund. 2001-12-11 Peter Liljenberg * wmanager.py (Client): Removed some dead event handlers. (Screen.handle_circulate_request): (Screen.handle_configure_request): (Screen.event_to_change): Changed event_to_change to a Screen method, and added the flag "allow_raise_requests" to be able to ignore raise requests. 2001-12-09 Peter Liljenberg * event.py (SlaveDispatcher.__init__): Make sure we copy slave list, since we may modify it later. 2001-12-05 Peter Liljenberg * panes.py (panesManager.panes_next): (panesManager.panes_prev): * menu.py (MenuKeyHandler._down): (MenuKeyHandler._up): Skip -= and +=, to allow Python 1.5.2 compitability. * panes.py (Pane.deactivate): (Pane.do_activate): Use the new focus code in wmanager. * views.py (View): Keep track of the focused client on the view. (ViewHandler.view_handle_quit): Store configuration when window manager exits. * event.py (SlaveDispatcher.add_master): New method, allowing wm.dispatch to exist when Screens are created. * focus.py: Rewritten to use the new focus code in wmanager. FocusHandler has been renamed to PointToFocus, and FocusClient is no longer used. The old name will remain for some time for backward compitability, but will emit a warning when used. move_focus() is now in a separate mixin, MoveFocus. * wmanager.py (WindowManager.set_current_client): New method that handles switching between clients. Use this to give focus to a certain client. (Client.lose_focus): (Client.get_focus): Helper functions for client switching. * wmevents.py: Various internal events generated by the window manager core is collected in this new file. They have been moved from wmanager.py and focus.py. 2001-11-30 Peter Liljenberg * cycle.py (CycleActivate): Improved to work as expected both for mapped and iconified clients. Inspiration came from Meik Hellmund. 2001-11-28 Peter Liljenberg * wmanager.py (Screen.system): Create a new process group for the child process. This ensures that any signals sent to the parent from the terminal (e.g. SIGINT) is not also distributed to the processes started from the window manager. 2001-11-24 Peter Liljenberg * wmanager.py (Client.__del__): (Screen.__del__): (WindowManager.__del__): Provide mixin classes with finalization methods. * outline.py (WindowOutlineClient): Wrote a window-based outline mixin. 2001-11-21 Peter Liljenberg * wmanager.py (Screen.handle_map_request): Further hack: at least netscape abuses maprequests, so test disallowing established windows to map themselves again. 2001-11-13 Peter Liljenberg * wmanager.py (Screen.handle_map_request): The if-expression missed a "not", so we always called deiconify on new clients, when we instead should call it for existing clients. Explains why the Client attribute start_iconified only worked at PLWM startup, not for windows created when PLWM is running. 2001-10-16 Peter Liljenberg * mw_apm.py (NetBSDIoctlAPM.do_ioctl): Moved open() into try block, so that interface detection properly fall through when the device file is not present. 2001-10-15 Peter Liljenberg * mw_apm.py (NetBSDIoctlAPM): APM interface for NetBSD, contributed by Henrik Rindlöw. 2001-08-24 Peter Liljenberg * keys.py: Subtly rewritten interface now allows KeyHandler maps to be installed not only windowmanager-wide, but also on specific screens or clients. This is backward-compatible with the old keys.py interface, but a warning is issued whenever that is used. Tue Feb 20 14:59:28 2001 Peter Liljenberg * wmanager.py (WindowManager.handle_event): Added support for screen attribute of events, so events can be sent to a specific screen. * focus.py (FocusClient.__client_init__): (CurrentClientChange): (FocusHandler.focus_to_ptr): Split client focus model from having a single client which both have input focus and is the current client, into tracking which client which have input focus, and which client actually contains the pointer. This is needed for clients with the "no input" focus model to work. Most instances of wm.focus_client should be replaced with wm.current_client. The ClientFocusIn and ClientFocusOut events are probably not that useful anymore, instead CurrentClientChange might be more interesting. * modestatus.py (ModeFocusedTitleClient): (ModeFocusedTitleScreen): * moveresize.py (MoveResizeKeys.__init__): Updated to reflect the focus model changes. 2001-02-02 * modewindow.py: Some rewrites, to allow a single message to be added to several modewindows. Also the framework for using the nice value has been removed, until something actually is implemented. Redrawing made more effective, as only the relevant part of the modewindow is cleared, and not the entire one. 2001-01-23 * event.py (EventFetcher.next_event): Fix for a bug introduced by the addition of FileEvents; where PLWM would be one X event behind most of the time, due to a "i > 1" where it should be "i > 0". Sigh. Some minor cleanups too, to make the loop a little more effective. Tue Jan 23 16:31:37 2001 Peter Liljenberg * keys.py (KeyHandler._buildmap): Better keycode handling, now it grabs all keycodes which have bound the keysym, not just the first. If the keysym is bound in the shift position, add ShiftMask too. 2001-01-22 * cfilter.py (all, none): Synonyms for true and false, respectively. 2001-01-21 * focus.py (FocusHandler.move_focus_ignore_clients): Attribute moved to the FocusHandler, instead of being a FocusClient attribute. Wed Jan 17 16:23:48 2001 Peter Liljenberg * event.py (new_event_type): New function to return unique event types. * inspect.py (InspectServer.inspect_enable): (InspectServer.inspect_disable): (InspectServer.inspect_toggle): Allow on-the-fly enabling or disabling of the Inspect function, for added security. Mon Jan 15 12:06:10 2001 Peter Liljenberg * event.py (EventFetcher.next_event): Fixed a case when next_event erroneously return None when a real event was expected, introduced by the FileEvent support. (EventFetcher.add_timer): Remove all cancelled timers when a new timer is added. Not doing this resulted in the possibility that a cancelled timer blocked an uncancelled timer, with the symptom that mw_clock stopped. 2001-01-14 * event.py (EventFetcher.next_event): (EventFetcher.add_file): (FileEvent): Added support for file events to the EventFetcher. File events are generated when files are ready to be read or written. * inspect.py: New WindowManager mixin, which allows a special client program to evaluate Python expressions within PLWM. This can be used to inspect PLWMs data structures (hence its name) and also to change things in the PLWM. 2001-01-13 * focus.py (FocusHandler.move_focus): Allow focus to change even when there is only one client. Up until now focus couldn't change if there was only one window, resulting in it not being possible to activate it or normalize pointer position. This bug wasn't found previously, since there always was the xmodewin client on all screens... * mw_biff.py (ModeWindowBiff.__wm_init__): Added check for unset $MAIL. Print an error and disable mw_biff if no mailspool can be found. Fri Jan 12 13:20:13 2001 Peter Liljenberg * keys.py (KeyHandler._keyevent): Replaces _keypress, as we now listen for both KeyPress and KeyRelease events. The special modifier "R_" is used to add methods for keyrelease events. 2001-01-12 * modestatus.py: * modewindow.py: * mw_clock.py: * mw_biff.py: Move the separate utilities into PLWM proper, to avoid having four processes a 3Mb hanging around for these trivial services. Support for old xmodewin is now broken, as modestatus has been changed to use modewindow instead of modewinctl, and their interfaces are not similar. Also, the new modewindow doesn't listen for properties, so its fully PLWM internal. 2001-01-11 * wmanager.py (Screen.add_internal_window): (Screen.remove_internal_window): (Screen.get_internal_window): (WindowManager.handle_event): (Screen.handle_event): (Client.handle_event): Added support for low-level internal windows, which simply are assumed to have an handle_event method, like clients. * wmanager.py (WindowManager.brave_loop): (WindowManager.loop): (WindowManager.quit): Replaced ad-hoc method of quitting PLWM with the more elegant quit() method. * outline.py (calculate_parts): * views.py (ViewHandler.view_create): Update append calls to Python 2.0 semantics. 2001-01-10 * wmanager.py (Client.handle_property_notify): Only listen for changes to WM_NORMAL_HINTS, and ignore all other ICCCM properties. 2000-12-14 * wmanager.py (Client.client_in_list): Obsoleted by client filters, and hence removed. (WindowManager.brave_loop): Catch KeyboardInterrupt, to exit quietly and nicely on Ctrl-C. * cycle.py: Completely rewritten. There is now a general Cycle class, using client filters to get the set of clients to cycle among. A CycleKeys keyhandler template is also provided. * moveresize.py (MoveResizeKeys.__init__): Fetch keyboard grabbing errors. * wmanager.py (Screen.query_clients): (WindowManager.query_clients): Use filters instead of the mapped/unmapped arguments. (Client.activate): Deiconify if necessary. Fri Dec 1 13:55:54 2000 Peter Liljenberg * wmanager.py (Client.configure): Uses client_maxsize to limit how large a window is allowed to be when it resizes itself. 2000-11-12 * cycle.py (CycleUnmapped): * moveresize.py (MoveResizeOutline): Use the new outline functionality. * outline.py: Rewrote Outline class to be a Client mixin class. This makes it easier to replace the outline drawing method, e.g. from xor to shaped windows. * wmanager.py (Screen.__init__): Ugly kludge to make sure that wm.current_screen is set when initialising clients. 2000-10-31 * wmanager.py (Client.start_iconified_clients): * views.py (ViewHandler.view_find_with_client): (ViewHandler.view_always_visible_clients): * border.py: (BorderClient.no_border_clients): * focus.py (FocusClient.move_focus_ignore_clients): Converted from old client_in_list() to new filter functions. * cfilter.py: Added module. * keys.py (modifiers): (KeyHandler._keypress): Added support for the modifier "Any", allowing a key to be bound no matter the modifier state. Mon Jul 17 17:07:55 2000 Peter Liljenberg * wmanager.py (Client.follow_size_hints): Still more silly size hints bugs. This time, a window would snap between minsize and 2*minsize, and then allow all sizes above that if a minsize is set but no resize increment. Fixed by shuffling some code blocks around. Tue Jul 4 19:36:15 2000 Peter Liljenberg * focus.py (JumpstartClient.__client_init__): Check that the window will not be iconified on startup before activating it. Tue Jul 4 19:32:32 2000 Morgan Eklöf * focus.py(JumpstartClient): New. Windows get focus when opened. Tue Jun 27 17:48:09 2000 Peter Liljenberg * wmanager.py (Client.follow_size_hints): Fixed more errors with base sizes. If no base size is provided, the minimum size should be used instead, and now PLWM does that. (Screen._register_cycle_roots): (Screen._cleanup_cycle_roots): (WindowManager._cleanup_cycle_roots): (WindowManager._register_cycle_roots): Added support for circular reference detection with Cyclops. Mon May 29 14:54:18 2000 Peter Liljenberg * wmanager.py (Client.follow_size_hints): Base width and height was erroneously added to the w/h if size was adjusted to min or max bounds. Fri May 12 18:07:32 2000 Peter Liljenberg * moveresize.py (MoveResizeKeys): A template class providing most of the code needed in a keyhandler for moving and resizing windows. The user merely has to subclass it and define key bindings. Thu Mar 23 11:59:08 2000 Peter Liljenberg * wmanager.py (Client.withdraw): Break circular references in self.dispatch when the client is withdrawn to avoid memory leaks. 2000-03-08 * modestatus.py: Created. Improve display of window manager operation, where a default message can be tempararily replaced with other information. First implementations of this is a new window title display and geometry information when resizing. * wmanager.py (Client.get_title): (Client.base_size): Added. 2000-02-10 * border.py (BorderClient.__client_init__): * cycle.py (CycleUnmapped.__init__): Use the rewritten font and color handling. * color.py: Rewritten as font.py. * font.py: Fully rewritten. Out goes the set_font for defining symbolic font names, in goes get_font_res to fetch fonts based on X resources. Also more stable, by being able to provide default fonts such as "fixed". * wmanager.py (WindowManager.add_command_event): (WindowManager.sigchld_handler): (Screen.system): (WindowManager.system): Replace the rather coarse os.system with our own. Features: DISPLAY can be set to get X clients to appear on the current screen in multihead systems. When a child process exits an event can be generated to notify modules, e.g. to be able to restart it. (Screen.__init__): Start to use resources, still in a quite subdued way. 2000-02-08 * keys.py (KeyHandler._cleanup): Break circular reference to avoid memory leaks: self.rawbindings and self.bindings contain methods instantiated on self, so clear them. Eeew, that bug was hard to track down... Thu Feb 3 14:41:52 2000 Peter Liljenberg * modewinctl.py (ModeWinControl.set_strings): Updated to the new class errors. Fri Jan 28 16:53:25 2000 Peter Liljenberg * views.py (View.store_tags): (View.fetch_tags): (ViewHandler.view_tag): (ViewHandler.view_find_tag): (XMW_ViewHandler.view_xmw_update): (View.tag): (View.has_tag): (View.get_tags): Views can be tagged with an arbitrary string, which can then be used as a search criteria for switching views. Mon Jan 10 10:41:42 2000 Peter Liljenberg * keys.py (KeyHandler.__init__): (KeyGrabKeyboard.__init__): Now takes a WindowManager and an EventDispatcher as arguments, instead of a Screen, to use the better event handling. * wmanager.py (Screen.handle_event): (WindowManager.handle_event): Use the improved event handling by letting the WindowManager keep a dispatcher which sets event masks on all the screens. * event.py (EventDispatcher, WindowDispatcher, SlaveDispatcher): Better event handling by abstracting it a bit. * modetitle.py: Forgot to adapt to Python packages. Tue Jan 4 10:39:15 2000 Peter Liljenberg * border.py (BorderClient.__client_init__): * focus.py (FocusClient.__client_init__): * modetitle.py (ModeTitleClient.__client_init__): Don't mess with calling the __init__ of all client mixin classes, by letting Client.__init__ call all of the special initalisers __client_init__ instead. Mon Jan 3 18:58:38 2000 Peter Liljenberg * views.py (View.enter): (View.leave): (View.fetch_winconf): (View.store_winconf): (WinConf.get_tuple): Also remember where the pointer is on the screen. This also required that the view winconf property was redesigned to be able to store more data than just winconfs. * wmanager.py (Client.warppointer): Default pointer coordinates can now be specified on a per-client basis. * focus.py (FocusHandler.move_focus): (FocusHandler.get_client_pos): Move focus between windows, at least vagely based on their screen coordinates. * wmanager.py (Client.get_top_edge): (Client.get_top_edge): (Client.get_bottom_edge): (Client.get_left_edge): (WindowManager.query_clients): Added to facilitate moving clients, changing focus etc. Mon Dec 20 17:56:44 1999 Peter Liljenberg * wmanager.py (WindowManager.__init__): When starting, do not add any ole window as a client, just those that seems to have been managed already. Now Netscape won't flunk out when PLWM is restarted... Tue Nov 23 18:00:41 1999 Peter Liljenberg * keys.py (KeyHandler._internal_timeout): (KeyHandler.__init__): Fixed memory leak: self.timer.type was self, a circular reference which wasn't broken. Now self.timer.type is str(self) instead, no reference circles. Wed Nov 17 11:26:10 1999 Peter Liljenberg * keys.py (KeyHandler._cleanup): (KeyHandler._internal_timeout): New functions to handle timeouts more gracefully. * wmanager.py (EventFetcher.next_event): Make it possible to cancel timer events. Debug timer events. (do_debug): New debugging code. Fri Nov 12 19:09:45 1999 Peter Liljenberg * wmanager.py (EventFetcher.next_event): Fix typo to get timer events working. Mon Nov 8 10:26:49 1999 Peter Liljenberg * deltamove.py (DeltaMove.get): Catch overflow error. 1999-10-15 * keys.py (KeyGrabber.__init__): Added wm parameter. (KeyHandler.__init__): Added timeout option. * wmanager.py (TimerEvent, EventFetcher): Wrap X event handling in a select loop, thus enabling timer events (and later file events too). 1999-10-14 * wmanager.py (WindowManager.handle_event): (WindowManager.bad_window_error_handler): (WindowManager.remove_client): (Client.withdraw): Don't try to manipulate a withdrawn window if it is destroyed already. (Client.follow_size_hints): Didn't work when WMSizeHint wasn't set. 1999-10-13 * views.py (View.enter): Skip the old error handling now. Yay! * wmanager.py (Client.withdraw): Do housekeeping in withdrawing clients. * wmanager.py (WindowManager.bad_window_error_handler): (WindowManager.__init__): Handle BadWindow and BadDrawable errors. 1999-10-07 * wmanager.py (Client.property_notify_event): Ignore BadWindow errors. 1999-10-04 * getxopt.py (stdopts): Provide a dictionary of standard options. * wmanager.py (Client.deiconify): (Client.moveresize): * views.py (View.enter): Moveresizing can be delayed if the client is iconified. This is to make it less expensive to change a view. * modetitle.py (ModeTitleClient._mtc_property_notify_event): Only update title in mode window if it changed for the focused window. 1999-10-01 * getxopt.py: Written. 1999-09-30 * views.py (View.enter): Ignore BadWindow erros to speed up view change. (View.leave): Remove WINCONF property so we don't spam the root window with properties. (XMW_ViewHandler): New class. * wmanager.py (Client): Check if client is withdrawn before doing anything. Moved map() and unmap() into deiconify() and iconify(). 1999-09-28 * wmanager.py (WindowManager.remove_client): cut'n'paste mistake, used event.window.id instead of c.window.id. * deltamove.py: Got zero division error when X time became higher than 0x7ffffff. 1999-09-27 * wmanager.py (Client.unmap): (WindowManager.handle_event): Correctly handle withdrawn clients. * wmanager.py (Client.follow_size_hints): * moveresize.py (MoveResize.__init__): Don't calculate diff_widht/diff_height, use WMNormalHints basesize instead. * modewinctl.py (ModeWinControl): Rewritten to use the new, smarter, xmodewindow. * modetitle.py: Formerly known as the last 1/3 of modewinctl.py Thu Sep 23 20:42:05 1999 Peter Liljenberg * modewinctl.py: * views.py: Properties ICCCMified. * focus.py (FocusHandler.focus_to_ptr): Set focus ICCCM style. * wmanager.py (Client.__init__): (Client.property_notify_event): Fetch WMHints. 1999-09-21 * modewinctl.py: Removed dependencies on WindowManager class from ModeWinControl and put them in the child class ModeClientControl instead. ModeTitleClient now tracks window title changes. 1999-09-20 * modewinctl.py: New file, furry features. 1999-09-17 * views.py (ViewHandler.view_has_client): Search for more than one name, and beep if not found. (ViewHandler.get_next_view_id, ViewHandler.store_views, ViewHandler.fetch_views, ViewHandler.leave_view, WinConf.get_tuple, View.__init__, View.store_winconf, View.fetch_winconf, View.leave, ViewHandler.__init__, ViewHandler.clear_views): Store view configuration in properties to make it possible to recreate the views when plwm is restarted. * cycle.py (CycleUnmapped.end): (CycleMapped.mark): Use Client.warppointer. * wmanager.py (Client.warppointer): Written. * moveresize.py (MoveResize.end): Pointer didn't move with the window if its coordinates was (0, 0). python-plwm-2.6a+20080530/plwm/wmevents.py0000644000175000017500000000427210765600750016657 0ustar stewstew# # Internal events generated by the window manager core. # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os class AddClient: def __init__(self, client): self.type = AddClient self.client = client class RemoveClient: def __init__(self, client): self.type = RemoveClient self.client = client class QuitWindowManager: def __init__(self): self.type = QuitWindowManager class CurrentClientChange: def __init__(self, screen, client): self.type = CurrentClientChange self.screen = screen self.client = client class ClientFocusOut: def __init__(self, client): self.type = ClientFocusOut self.client = client class ClientFocusIn: def __init__(self, client): self.type = ClientFocusIn self.client = client class ClientIconified: def __init__(self, client): self.type = ClientIconified self.client = client class ClientDeiconified: def __init__(self, client): self.type = ClientDeiconified self.client = client class CommandEvent: def __init__(self, type): self.type = type self.status = None def termsig(self): if os.WIFSIGNALED(self.status): return os.WTERMSIG(self.status) else: return None def exitstatus(self): if os.WIFEXITED(self.status): return os.WEXITSTATUS(self.status) else: return None python-plwm-2.6a+20080530/plwm/opacity.py0000644000175000017500000000606611007513703016451 0ustar stewstew# # opacity.py -- set window opacity based on focus # # Copyright (C) 2008 David H. Bronke # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import wmanager, wmevents, cfilter from Xlib import X, Xatom # Client mixin class TransparentClient: """Client mixin. Sets opacity according to focus and client filters. Class members: opacity_(un)focused_default - sets the default opacity for (un)focused windows. opacity_(un)focused_clients - sets the opacity for specific windows, specified by client filters, depending on focus. opacity_ignore_clients - client filter which specifies clients to ignore. These clients will not have the _NET_WM_WINDOW_OPACITY property set. You can also set a client's opacity directly, using the opacity_set() method. """ opacity_focused_default = 1.0 opacity_unfocused_default = 0.7 opacity_focused_clients = () opacity_unfocused_clients = () opacity_ignore_clients = cfilter.false def __client_init__(self): self.opacity_focused = None self.opacity_unfocused = None self._NET_WM_WINDOW_OPACITY = self.wm.display.intern_atom( '_NET_WM_WINDOW_OPACITY' ) if not self.opacity_ignore_clients(self): for filter, opacity in self.opacity_focused_clients: if filter(self): self.opacity_focused = opacity break else: self.opacity_focused = self.opacity_focused_default for filter, opacity in self.opacity_unfocused_clients: if filter(self): self.opacity_unfocused = opacity break else: self.opacity_unfocused = self.opacity_unfocused_default if self.focused: self.opacity_get_focus(None) else: self.opacity_lose_focus(None) # Set up dispatchers for the opacity changes self.dispatch.add_handler( wmevents.ClientFocusIn, self.opacity_get_focus ) self.dispatch.add_handler( wmevents.ClientFocusOut, self.opacity_lose_focus ) def opacity_get_focus(self, event): if self.opacity_focused is not None: self.opacity_set(self.opacity_focused) def opacity_lose_focus(self, event): if self.opacity_unfocused is not None: self.opacity_set(self.opacity_unfocused) def opacity_set(self, percent): opacity = int(percent * 0xFFFFFFFF) w = self.window while 1: r = w.query_tree() if r.parent == r.root: break w = r.parent w.change_property( self._NET_WM_WINDOW_OPACITY, Xatom.CARDINAL, 32, [opacity], X.PropModeReplace ) self.wm.display.sync() python-plwm-2.6a+20080530/plwm/mw_watchfiles.py0000644000175000017500000000751410765600750017645 0ustar stewstew# -*- coding: iso-8859-1 -*- # # mw_acpi.py -- display ACPI AC/battery status in a modewindow # # Copyright (C) 2004 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # from plwm import modewindow, event, wmanager import sys import os import time import errno TimerEventType = event.new_event_type() class WatchedFile: """Watch FILE. When it is present, display PRESENT_MSG in the mode window. If FORMAT_CONTENT is true (default is false) PRESENT_MSG should include a %s format code which will be replaced with the stripped contents of the file. When file does not exist, MISSING_MSG is displayed. It defaults to an empty string. """ def __init__(self, file, present_msg, format_content = 0, missing_msg = ''): self.file = file self.present_msg = present_msg self.format_content = format_content self.missing_msg = missing_msg def update(self): """Called by ModeWindowWatchFiles to check for changes to files. Should return the current message. """ if self.format_content: try: content = open(self.file).read(256) # be sensible... msg = self.present_msg % content.strip() except IOError, e: if e.errno == errno.ENOENT: msg = self.missing_msg else: msg = self.present_msg % ("can't read: %s" % errno.errorcode.get(e.errno, 'unknown error')) else: if os.path.exists(self.file): msg = self.present_msg else: msg = self.missing_msg return msg class ModeWindowWatchFiles: """WindowManager mixin: Watch a number of files and display a mode window message depending on their existance and contents. mw_watchfiles is a list of WatchedFile objects. mw_watchfiles_interval is the recheck interval in seconds. """ mw_watchfiles_position = 0.7 mw_watchfiles_justification = modewindow.RIGHT mw_watchfiles = None mw_watchfiles_interval = 5 def __wm_init__(self): if not self.mw_watchfiles: sys.stderr.write('mw_watchfiles: no files to watch, disabling myself') return self.mw_watchfiles_last_msg = None self.mw_watchfiles_message = modewindow.Message(self.mw_watchfiles_position, self.mw_watchfiles_justification) for s in self.screens: s.modewindow_add_message(self.mw_watchfiles_message) self.dispatch.add_handler(TimerEventType, self.mw_watchfiles_tick) self.mw_watchfiles_tick(None) def mw_watchfiles_tick(self, evt): msgs = [] for file in self.mw_watchfiles: msg = file.update() if msg: msgs.append(msg) msg = ' '.join(msgs) if self.mw_watchfiles_last_msg != msg: self.mw_watchfiles_last_msg = msg self.mw_watchfiles_message.set_text(msg) self.events.add_timer(event.TimerEvent(TimerEventType, after = self.mw_watchfiles_interval)) python-plwm-2.6a+20080530/plwm/event.py0000644000175000017500000005374610765600750016142 0ustar stewstew# # event.py -- X event handling framework # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import types import time import select import errno from Xlib import X __next_event_type = 256 def new_event_type(): """Returns a new event type code. The code is an integer, unique with respect to X event types and other event types allocated with new_event_type(). """ global __next_event_type t = __next_event_type __next_event_type = __next_event_type + 1 return t class TimerEvent: def __init__(self, event_type, after = 0, at = 0): self.type = event_type if at > 0: self.time = at else: self.time = time.time() + after def cancel(self): self.time = None def str(self): if self.time is None: return '<%s cancelled>' % self.__class__.__name__ else: return '<%s at %d in %d seconds>' % (self.__class__.__name__, self.time, time.time() - self.time) class FileEvent: READ = 1 WRITE = 2 EXCEPTION = 4 def __init__(self, event_type, file, mode = None): self.type = event_type self.file = file if mode is None: self.mode = 0 if 'r' in self.file.mode: self.mode = self.mode | FileEvent.READ if 'w' in self.file.mode: self.mode = self.mode | FileEvent.WRITE else: self.mode = mode self.state = 0 self.unhandled = 0 def fileno(self): return self.file.fileno() def cancel(self): self.file = None def set_mode(self, newmode = None, set = 0, clear = 0): if newmode is not None: self.mode = newmode self.mode = (self.mode | set) & ~clear def str(self): if self.file is None: return '<%s cancelled>' % self.__class__.__name__ else: mode = '' if self.mode & FileEvent.READ: mode = mode + 'R' if self.mode & FileEvent.WRITE: mode = mode + 'W' if self.mode & FileEvent.EXCEPTION: mode = mode + 'E' return '<%s for %s mode %s>' % (self.__class__.__name__, self.file, mode) class EventFetcher: def __init__(self, display): self.display = display self.timers = [] self.events = [] self.x_events = [] self.files = [] def next_event(self, timeout = None): # If select is interrupted by a signal, we just want to # make everything from scratch while 1: # First return synthetic events if self.events: e = self.events[0] del self.events[0] # If e is an FileEvent, move unhandled to event state if isinstance(e, FileEvent): e.state = e.unhandled e.unhandled = 0 return e # Return any read but unprocessed X events if self.x_events: xe = self.x_events[0] del self.x_events[0] return xe # Attempt to read any events, and if there are any return the first xe = self._read_x_events() if xe: return xe now = time.time() # Normalize negative timeout values if timeout is not None and timeout < 0: timeout = 0 to = None # See if we have to wait for a timer while self.timers: te = self.timers[0] # Is this timer canceled? if te.time is None: del self.timers[0] continue to = te.time # This timer has already timed out, so return it if to <= now: del self.timers[0] return te # Is the general event timeout earlier than this timer? if timeout is not None and to > (timeout + now): to = timeout + now te = None # Break the loop, as we have found a valid timer break # Do we have a general event timeout? if to is None and timeout is not None: to = timeout + now te = None # Loop until we return an event while 1: # Wait for X data or a timeout read = [self.display] write = [] exc = [] # Iterate over all files, removing closed and cancelled. # The other are added to the corresponding lists i = 0 while i < len(self.files): f = self.files[i] # FileEvent has been cancelled if f.file is None: del self.files[i] # Uncancelled file event else: # Try to get the fileno, in an attempt to # find closed but uncancelled files, so we # can remove them. try: f.fileno() except ValueError: del self.files[i] # Seems to be an open file, add it else: # Get the interested, as yet not recieved, modes m = f.mode & ~f.unhandled if m & FileEvent.READ: read.append(f) if m & FileEvent.WRITE: write.append(f) if m & FileEvent.EXCEPTION: exc.append(f) i = i + 1 # Wrap select() in a loop, so that EINTRS are ignored # correctly while 1: try: if to is None: readable, writable, excable = select.select(read, write, exc) else: wait = max(to - time.time(), 0) readable, writable, excable = select.select(read, write, exc, wait) except select.error, val: if val[0] != errno.EINTR: raise val else: break # We have timed out, return the timer event or None if not readable and not writable and not excable: # Delete the timed out timer if te is not None: del self.timers[0] # Don't return canceled timers if te.time is not None: return te # break the inner while loop to find another timer else: break else: return None # Iterate over all ready files. Add all ready # FileEvents to the synthetic event list, unless # they already are there, and 'or' in the mode xe = None for f in readable: # By treating the display first, we ensure # that X events are prioritized over file events if f is self.display: xe = self._read_x_events() else: if f.unhandled == 0: self.events.append(f) f.unhandled = f.unhandled | FileEvent.READ for f in writable: if f.unhandled == 0: self.events.append(f) f.unhandled = f.unhandled | FileEvent.WRITE for f in excable: if f.unhandled == 0: self.events.append(f) f.unhandled = f.unhandled | FileEvent.EXCEPTION # If there was an X event, return it immedieately if xe is not None: return xe # If there was some file event, return it by breaking # out of the inner while-loop, so we get back to # the top of the function if self.events: break # Something was recieved, but not an event. Loop around # to select one more time def _read_x_events(self): # Read as many x events as possible, and return the first one. # Store the rest in x_events i = self.display.pending_events() if i > 0: # Store first event to be returned immediately, # and put remaining events on the events queue. xe = self.display.next_event() while i > 1: self.x_events.append(self.display.next_event()) i = i - 1 return xe else: return None def add_timer(self, timer): """Add a TimerEvent TIMER to the event list. """ # We iterate over the entire timer list, to remove cancelled timers i = 0 while i < len(self.timers): t = self.timers[i] # Remove cancelled timers if t.time is None: del self.timers[i] else: # If we haven't already inserted timer, perform checks if timer and timer.time < t.time: self.timers.insert(i, timer) i = i + 1 timer = None i = i + 1 if timer: self.timers.append(timer) def add_file(self, file): """Add the FileEvent FILE to the list of files to watch. FILE will be sent when it is ready for reading or writing, as specified by its mode. Remove FILE from list of interesting events by calling FILE.cancel(). """ self.files.append(file) def put_event(self, event): """Add a synthesized EVENT. """ self.events.append(event) # A list of X events and their default event masks. default_event_masks = { X.KeyPress: X.KeyPressMask, X.KeyRelease: X.KeyReleaseMask, X.ButtonPress: X.ButtonPressMask, X.ButtonRelease: X.ButtonReleaseMask, X.MotionNotify: [X.PointerMotionMask, X.ButtonMotionMask], X.EnterNotify: X.EnterWindowMask, X.LeaveNotify: X.LeaveWindowMask, X.FocusIn: X.FocusChangeMask, X.FocusOut: X.FocusChangeMask, X.KeymapNotify: X.KeymapStateMask, X.Expose: X.ExposureMask, X.GraphicsExpose: X.ExposureMask, X.NoExpose: X.ExposureMask, X.VisibilityNotify: X.VisibilityChangeMask, X.CreateNotify: X.SubstructureNotifyMask, # The following seven events can also be sent when # X.SubstructureNotifyMask is set, but the default # has to be considered to the the ordinary # X.StructureNotifyMask X.DestroyNotify: X.StructureNotifyMask, X.UnmapNotify: X.StructureNotifyMask, X.MapNotify: X.StructureNotifyMask, X.ReparentNotify: X.StructureNotifyMask, X.ConfigureNotify: X.StructureNotifyMask, X.GravityNotify: X.StructureNotifyMask, X.CirculateNotify: X.StructureNotifyMask, X.MapRequest: X.SubstructureRedirectMask, X.ConfigureRequest: X.SubstructureRedirectMask, X.CirculateRequest: X.SubstructureRedirectMask, X.ResizeRequest: X.ResizeRedirectMask, X.PropertyNotify: X.PropertyChangeMask, X.ColormapNotify: X.ColormapChangeMask, # The following events have no event mask: # X.SelectionClear # X.SelectionRequest # X.SelectionNotify # X.ClientMessage # X.MappingNotify } class EventHandler: def __init__(self, handler, masks, handlerid): self.handler = handler self.id = handlerid self.masks = masks def __cmp__(self, obj): return cmp(self.id, obj) def call(self, eventobj): self.handler(eventobj) def clear_masks(self, dispatcher): if self.masks is not None: dispatcher.unset_masks(self.masks) self.masks = None class EventDispatcher: def __init__(self): self.system_events = {} self.grab_events = {} self.normal_events = {} def handle_event(self, eventobj, systemonly = 0): """Dispatch the event EVENTOBJ to all matching handlers. If SYSTEMONLY is true, skip calling grab and normal event handlers. Returns 1 if a grab event handler was called, 0 otherwise. """ # Call all the system event handlers, and then # quit if systemonly is true. for eh in self.system_events.get(eventobj.type, []): eh.call(eventobj) if systemonly: return 1 # If there are any grab events for this type, # call the last one and then quit. Otherwise # call all the normal event handlers. try: eh = self.grab_events[eventobj.type][-1] except (KeyError, IndexError): eh = None if eh is not None: eh.call(eventobj) return 1 else: for eh in self.normal_events.get(eventobj.type, []): eh.call(eventobj) return 0 def add_handler(self, event, handler, masks = None, handlerid = None): """Add an event handler for this window. EVENT is an event type identifier. This can be one of the numeric constants defined in X.py, a string, or any other object which can be used as a key in a mapping. HANDLER is a function which gets called with a single argument: the event object. All event object has a `type' attribute which will have the value of EVENT. MASKS can be a tuple or list of X event masks to set on this window. If omitted or None, default masks for this event will be set, if any. If HANDLERID is provided, it will be an object used to identify this handler when calling EventDispatcher.remove_handler(). If omitted, HANDLER itself will be used. """ self.add_handler_type(self.normal_events, event, handler, masks, handlerid) def add_system_handler(self, event, handler, masks = None, handlerid = None): """Add a system handler for this window. The arguments are the same as for EventDispatcher.add_handler(). A system handler will be called before any grab or normal handlers. """ self.add_handler_type(self.system_events, event, handler, masks, handlerid) def add_grab_handler(self, event, handler, masks = None, handlerid = None): """Add a grab handler for this window. The arguments are the same as for EventDispatcher.add_handler(). A grab handler will prevent previously added grab handlers and any normal handlers from being called. System handlers will be called as normal, however. """ self.add_handler_type(self.grab_events, event, handler, masks, handlerid) def add_handler_type(self, dict, event, handler, masks, handlerid): """Internal function used to add handlers.""" if masks is None: masks = default_event_masks.get(event, None) if handlerid is None: handlerid = handler eh = EventHandler(handler, masks, handlerid) if dict.has_key(event): dict[event].append(eh) else: dict[event] = [eh] if masks is not None: self.set_masks(masks) def remove_handler(self, handlerid): """Remove the handler identified by HANDLERID. This will also clear any masks this handler has set. """ for dict in self.system_events, self.grab_events, self.normal_events: for hs in dict.values(): ok = 1 while ok: try: i = hs.index(handlerid) except ValueError: ok = 0 else: hs[i].clear_masks(self) del hs[i] def set_masks(self, masks, onerror = None): """Set MASKS on the window. MASKS can either be an X.*Mask constant or a list of such constants. """ pass def unset_masks(self, masks, onerror = None): """Unset MASKS on the window. The inverse of EventDispatcher.set_masks(). """ pass def block_masks(self, masks, onerror = None): """Temporarily block MASKS on the window. MASKS can either be an X.*Mask constant or a list of such constants. """ pass def unblock_masks(self, masks, onerror = None): """Remove block on MASKS on the window. The inverse of EventDispatcher.block_masks(). """ pass class WindowDispatcher(EventDispatcher): def __init__(self, window): """Create an event dispatcher for WINDOW. WINDOW is the window object we should set masks on. If omitted or None, no masks will be set but the event dispatcher can still be used for synthetic events. """ EventDispatcher.__init__(self) self.window = window self.masks = {} self.blocked_masks = {} def set_masks(self, masks, onerror = None): """Set MASKS on the window. MASKS can either be an X.*Mask constant or a list of such constants. """ if type(masks) is types.IntType: self.masks[masks] = self.masks.get(masks, 0) + 1 else: for m in masks: self.masks[m] = self.masks.get(m, 0) + 1 self.update_window_mask(onerror) def unset_masks(self, masks, onerror = None): """Unset MASKS on the window. The inverse of EventDispatcher.set_masks(). """ if type(masks) is types.IntType: masks = [masks] for m in masks: rc = self.masks.get(m, None) if rc is None: pass elif rc == 1: del self.masks[m] else: self.masks[m] = rc - 1 self.update_window_mask(onerror) def block_masks(self, masks, onerror = None): """Temporarily block MASKS on the window. MASKS can either be an X.*Mask constant or a list of such constants. """ if type(masks) is types.IntType: self.blocked_masks[masks] = self.blocked_masks.get(masks, 0) + 1 else: for m in masks: self.blocked_masks[m] = self.blocked_masks.get(m, 0) + 1 self.update_window_mask(onerror) def unblock_masks(self, masks, onerror = None): """Remove block on MASKS on the window. The inverse of EventDispatcher.block_masks(). """ if type(masks) is types.IntType: masks = [masks] for m in masks: rc = self.blocked_masks.get(m, None) if rc is None: pass elif rc == 1: del self.blocked_masks[m] else: self.blocked_masks[m] = rc - 1 self.update_window_mask(onerror) def update_window_mask(self, onerror = None): """Configure the window mask to reflect any changes. """ mask = 0 for m in self.masks.keys(): if not self.blocked_masks.has_key(m): mask = mask | m self.window.change_attributes(event_mask = mask, onerror = onerror) class SlaveDispatcher(EventDispatcher): def __init__(self, masters): """Create a SlaveDispatcher for MASTERS. MASTERS is a list of other EventDispatchers which this class will call when setting, clearing, blocking and unblocking masks. """ EventDispatcher.__init__(self) # Copy list, since we can modify list later self.masters = list(masters) def add_master(self, master): """Add a master dispatcher to this slave dispatcher. """ self.masters.append(master) def set_masks(self, masks, onerror = None): """Set MASKS on the window. MASKS can either be an X.*Mask constant or a list of such constants. """ for m in self.masters: m.set_masks(masks, onerror) def unset_masks(self, masks, onerror = None): """Unset MASKS on the window. The inverse of EventDispatcher.set_masks(). """ for m in self.masters: m.unset_masks(masks, onerror) def block_masks(self, masks, onerror = None): """Temporarily block MASKS on the window. MASKS can either be an X.*Mask constant or a list of such constants. """ for m in self.masters: m.block_masks(masks, onerror) def unblock_masks(self, masks, onerror = None): """Remove block on MASKS on the window. The inverse of EventDispatcher.block_masks(). """ for m in self.masters: m.unblock_masks(masks, onerror) python-plwm-2.6a+20080530/plwm/message.py0000644000175000017500000001330410767527561016441 0ustar stewstew# # message.py -- Screen mixin to messages. # # Copyright (C) 2001 Mike Meyer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA "Message - a mixin to provide message on plwm screens." import string from types import StringType from Xlib import X, Xutil import event class Message: "Holds and displays the message." def __init__(self, screen, fontname, draw_function, foreground, background, bordercolor, borderwidth, seconds): "Initialize the message window, gc and font." self.wm = screen.wm self.seconds = seconds fg = screen.get_color(foreground) bg = screen.get_color(background) bc = screen.get_color(bordercolor) window = screen.root.create_window(0, 0, 1, 1, borderwidth, X.CopyFromParent, X.InputOutput, X.CopyFromParent, background_pixel = bg, border_pixel = bc, event_mask = (X.ExposureMask | X.VisibilityChangeMask)) self.font = self.wm.get_font(fontname, 'fixed') self.gc = window.create_gc(font = self.font, function = draw_function, foreground = fg, background = bg) self.window = screen.add_internal_window(window) self.window.dispatch.add_handler(X.VisibilityNotify, self.raisewindow) self.window.dispatch.add_handler(X.Expose, self.redraw) def setup(self, labels, align = 'left'): "Create the window." if type(labels) == StringType: labels = [labels] fontsize = self.font.query() high = (fontsize.font_ascent + fontsize.font_descent + 1) self.height = len(labels) * high width = 0 lines = [] for l in range(len(labels)): line = Line(labels[l], self.font, l * high + fontsize.font_ascent + 1) w = line.width if w > width: width = w lines.append(line) self.width = width + 4 for i in range(len(lines)): lines[i].setup(self.window.window, self.gc, self.width, align, i) self.lines = lines return self.width, self.height def display(self, x, y, timeout = None): "Start it up" x, y, width, height = self.window.keep_on_screen(x, y, self.width, self.height) self.window.configure(x = x, y = y, width = width, height = height) self.window.map() self.timeout = timeout or self.seconds * len(self.lines) if self.timeout: timer_id = event.new_event_type() self.timer = event.TimerEvent(timer_id, after = self.timeout) self.wm.events.add_timer(self.timer) self.wm.dispatch.add_handler(timer_id, self.close, handlerid = self) def close(self, event = None): self.window.destroy() if self.timeout: self.timer.cancel() def raisewindow(self, event = None): self.window.raisewindow() def redraw(self, event = None): "Redraw the window, with highlights" self.window.clear_area(width = self.width, height = self.height) for i in range(len(self.lines)): self.lines[i].redraw() class Line: "A class for lines in a message." def __init__(self, label, font, where): "Figure out where to draw this string." self.name = label ext = font.query_text_extents(label) self.width = ext.overall_width self.y = where def setup(self, window, gc, width, align, count): "Save the drawing position." self.window, self.gc = window, gc if align == 'left': self.x = 0 elif align == 'center': self.x = (width - self.width) / 2 else: # right self.x = width - self.width def redraw(self): "Draw myself." self.window.image_text(self.gc, self.x, self.y, self.name) class screenMessage: """PLWM Screen mixin to provide messages.. This mixin requires the color and font mixins be in the screen class.""" message_font = "9x15Bold" message_foreground = "black" message_background = "white" message_bordercolor = "black" message_borderwidth = 3 message_seconds = 5 message_draw = X.GXset def message_make(self, labels, align = 'center'): """Create a message from the labels. Returns the width and height of the resulting menu.""" self.message = Message(self, self.message_font, self.message_draw, self.message_foreground, self.message_background, self.message_bordercolor, self.message_borderwidth, self.message_seconds) return self.message.setup(labels, align) def message_display(self, x, y): "Instantiate the menu, and return the label or None." self.message.display(x, y) python-plwm-2.6a+20080530/plwm/pane_utilities.py0000644000175000017500000002117211003156241020006 0ustar stewstew#!/usr/bin/env python # # pane_utilities.py -- Utility clases and functions for use with panes # # Copyright (C) 2005 Mike Meyer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from os import environ import cfilter, string from webbrowser import open_new from urllib import quote class appmenu: "Creates a menu of applications to run in a pane." def __init__(self, pane, apps): "Create and run the applications menu from the keys." labels = apps.keys() labels.sort(key=lambda x: (x[0].lower(), x)) width, height = pane.screen.menu_make(labels) self.system = pane.screen.system self.apps = apps pane.screen.menu_run((pane.width - width) / 2 + pane.x, (pane.height - height) / 2 + pane.y, self) def __call__(self, choice): "Call the system function on the value of the given choice." self.system(self.apps[choice] + " &") class codemenu: "Create a menu of Python actions to run." def __init__(self, pane, actions=None): "Create the menu and run it." if actions: self.actions = actions else: self._make(pane) labels = self.actions.keys() labels.sort(key=lambda c: (c[0].lower(), c)) width, height = pane.screen.menu_make(labels, align="left") pane.screen.menu_run((pane.width - width) / 2 + pane.x, (pane.height - height) / 2 + pane.y, self) def __call__(self, choice): "Run the selection for the user." apply(apply, self.actions[choice]) def _make(self, pane): """Make a dictionary from my methods and docstrings: For all method names that are one character long, the key will be the method name, a ': ', then the first word of the docstring. The value will be a the method.""" self.actions = dict() for name in dir(self): if len(name) == 1: meth = getattr(self, name) name = '%s: %s' % (name, meth.__doc__) self.actions[name] = meth, (pane,) class view_menu(codemenu): "The view for use when actions is an MVC controller." def __call__(self, choice): self.actions.choose(choice) class windowmenu: "Create a menu of windows to add to a pane." def __init__(self, pane, filter = cfilter.true, startlist = None): labels = [] clients = {} clientlist = startlist or pane.screen.query_clients(filter, 1) clientlist.sort(key=lambda c: (c.get_title()[0].lower(), c.get_title())) i = 'a' # We really need to deal with window lists longer than 26. for c in clientlist: l = "%c: %s" % (i, c.get_title()) labels.append(l) clients[l] = c i = chr(ord(i) + 1) if labels: width, height = pane.screen.menu_make(labels, align = 'left') self.add = pane.add_window self.clients = clients pane.screen.menu_run((pane.width - width) / 2 + pane.x, (pane.height - height) / 2 + pane.y, self) else: width, height = pane.screen.message_make("No windows") pane.screen.message_display((pane.width - width) / 2 + pane.x, (pane.height - height) / 2 + pane.y) def __call__(self, choice): "Add the selected window to the pane." self.add(self.clients[choice]) class panesmenu: "Create a menu of all the panes." def __init__(self, screen): wm = screen.wm labels = [] panes = {} for i in range(len(wm.panes_list)): w = wm.panes_list[i].window if w: l = "%d: %s" % (i, w.get_title()) else: l = "%d: " % i labels.append(l) panes[l] = i width, height = screen.menu_make(labels, align = 'left') self.goto = wm.panes_goto self.panes = panes screen.menu_run(screen.root_x + (screen.root_width - width) / 2, screen.root_y + (screen.root_height - height) / 2, self) def __call__(self, choice): "Activate the selected pane." self.goto(self.panes[choice]) class websearch: "Launch a browser with a web search from the user." def __init__(self, pane, name, winclass, format, browser = None): self.format = format self.browser = browser self.pane = pane window = winclass("Search %s: " % name, pane.screen, length=50) window.read(self, window.editHandler, pane.x, pane.y) def __call__(self, string): query = self.format % quote(string) if self.browser: self.browser(query) else: environ['DISPLAY'] = self.pane.screen.displaystring open_new(query) class runcommand: "Read a string from the user, and run it." def __init__(self, pane, winclass, prompt = "Command: "): self.system = pane.screen.system window = winclass(prompt, pane.screen, length = 50) window.read(self, window.editHandler, pane.x, pane.y) def __call__(self, string): self.system(string + " &") class splitpane: "Read in a fraction, and split that much off the current pane." def __init__(self, pane, splitter, winclass, prompt = "Fraction: "): self.pane, self.splitter = pane, splitter window = winclass(prompt, pane.screen, length = 50) window.read(self, window.editHandler, pane.x, pane.y) def __call__(self, fraction): try: f = string.atof(fraction) self.splitter(f) except ValueError: pass class numberpane: "Read a new number for the current pane." def __init__(self, pane, winclass, prompt = "New number: "): self.wm = pane.wm window = winclass(prompt, pane.screen) window.read(self, window.editHandler, pane.x, pane.y) def __call__(self, name): self.wm.panes_number(int(name)) class pullwindow: "Read a window's name, and pull it to the current pane." def __init__(self, pane, winclass, prompt = "Pull: "): self.pane = pane window = winclass(prompt, pane.screen) window.read(self, window.editHandler, pane.x, pane.y) def __call__(self, name): clients = self.pane.screen.query_clients(cfilter.re_title(name + ".*"), 1) if len(clients) == 1: self.pane.add_window(clients[0]) elif clients: windowmenu(self.pane, startlist = clients) else: width, height = self.pane.screen.message_make("No windows") self.pane.screen.message_display(self.pane.x, self.pane.y) class gotowindow: "Emulate the rp 'goto' command." def __init__(self, pane, winclass, prompt = "Goto: "): self.pane = pane window = winclass(prompt, pane.screen) window.read(self, window.editHandler) def __call__(self, name): clients = self.pane.screen.query_clients(cfilter.re_title(name + ".*"), 1) if clients: window = clients[0] if window.panes_pane.window and window.panes_pane.window == window: self.pane.wm.panes_activate(window.panes_pane) else: self.pane.add_window(window) else: width, height = self.pane.screen.message_make("No windows") self.pane.screen.message_display(0, 0) def split_pane(count, splitter): """Invoke a pane splitter to divide a pane up into count pieces.""" while count > 1: splitter(1. / count) count -= 1 def getapp(pane, name, command = None): "Find a window starting with name, and run command if it doesn't exist." clients = pane.screen.query_clients(cfilter.re_title(".*%s.*" % name), 1) if len(clients) > 1: windowmenu(pane, startlist = clients) elif clients: pane.add_window(clients[0]) else: pane.screen.system((command or name) + " &") python-plwm-2.6a+20080530/plwm/outline.py0000644000175000017500000002125510765600750016466 0ustar stewstew# # outline.py -- various client outline drawing functions # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from Xlib import X import wmanager # Client mixin classes class XorOutlineClient: """Create an outline by xoring the pixels on the screen. This is the fastest method to create an outline, but if window content changes under the outline the outline cannot be hidden properly. Also, some combinations of depth, visuals and colormaps will not produce a visible outline for e.g. a black background. Use the more expensive WindowOutlineClient in that case. """ def __client_init__(self): self.outline_font = self.wm.get_font_res('.outline.font', '.Outline.Font', 'fixed') self.outline_gc = self.screen.root.create_gc( function = X.GXxor, line_width = 0, foreground = (1 << self.screen.info.root_depth) - 1, subwindow_mode = X.IncludeInferiors, font = self.outline_font ) self.outline_segments = None self.outline_name = None def outline_show(self, x = None, y = None, w = None, h = None, name = None): # Undraw a possible existing outline self.outline_hide() coords, self.outline_segments, namepos = \ calculate_parts(self, x, y, w, h, name) self.screen.root.poly_segment(self.outline_gc, self.outline_segments) if name: self.outline_gc.set_clip_rectangles(0, 0, [coords], X.YXSorted) sx, sy, sw, sh, as = namepos self.screen.root.draw_text(self.outline_gc, sx, sy, name) self.outline_name = (sx, sy, name) else: self.outline_name = None def outline_hide(self): if self.outline_segments: self.screen.root.poly_segment(self.outline_gc, self.outline_segments) self.outline_segments = None if self.outline_name: sx, sy, name = self.outline_name self.screen.root.draw_text(self.outline_gc, sx, sy, name) self.outline_gc.change(clip_mask = X.NONE) self.outline_name = None class WindowOutlineClient: """Draw an outline by creating a grid of windows. This avoids the messiness of windows changing their contents under an XorOutline, and it also works on all varieties of screen depth and color maps. The windows in the grid will simulate lines, by being one pixel wide and having a border width of one pixel. The border is set to black and the window background is set to white, thus relying on the X server to clear the window area to create the line impression. The text, if any, is displayed in a separate window with black background and white foreground. No tracking of Exposure is done, since the outline is always on top, at least when it is first shown. TODO: add X resources for the colours used by the outline. """ def __client_init__(self): self.outline_font = self.wm.get_font_res('.outline.font', '.Outline.Font', 'fixed') # Postpone allocattion of outline windows until # they are needed self.outline_windows = None self.outline_name_window = None self.outline_name_gc = None self.outline_mapped = 0 def __client_del__(self): if self.outline_windows is None: return # Drop the resources held by the outline windows wmanager.debug('outline', 'freeing windows') for w in self.outline_windows: w.destroy() self.outline_name_gc.free() self.outline_name_window.destroy() def outline_show(self, x = None, y = None, w = None, h = None, name = None): coords, segments, namepos = \ calculate_parts(self, x, y, w, h, name) if self.outline_windows is None: self.allocate_windows() for i in range(0, 8): w = self.outline_windows[i] s = segments[i] w.configure(x = s[0] - 1, y = s[1] - 1, width = s[2] - s[0] + 1, height = s[3] - s[1] + 1) if name: sx, sy, sw, sh, as = namepos self.outline_name_window.configure(x = sx, y = sy - as, width = sw, height = sh) if not self.outline_mapped: for w in self.outline_windows: w.configure(stack_mode = X.Above) w.map() if name: self.outline_name_window.configure(stack_mode = X.Above) self.outline_name_window.map() self.outline_mapped = 1 # draw text first after having mapped the window, as it will # disappear otherwise... if name: self.outline_name_window.image_text(self.outline_name_gc, 0, as, name) def outline_hide(self): if self.outline_mapped: for w in self.outline_windows: w.unmap() self.outline_name_window.unmap() self.outline_mapped = 0 def allocate_windows(self): self.outline_windows = [] # Allocate horizontal and vertical bars for i in range(0, 8): w = self.screen.root.create_window( 0, 0, 1, 1, 1, X.CopyFromParent, border_pixel = self.screen.info.black_pixel, background_pixel = self.screen.info.white_pixel, save_under = 1 ) self.outline_windows.append(w) # Allocate text window w = self.screen.root.create_window( 0, 0, 1, 1, 1, X.CopyFromParent, border_pixel = self.screen.info.black_pixel, background_pixel = self.screen.info.black_pixel, save_under = 1 ) self.outline_name_window = w self.outline_name_gc = w.create_gc( foreground = self.screen.info.white_pixel, background = self.screen.info.black_pixel, font = self.outline_font ) def calculate_parts(client, x, y, w, h, name): """Calculates coordinates for drawing an outline. Returns three values: coords, segments, namepos coords is a four-tuple: (x, y, w, h), either the values supplied or taken from client if any values are None. segments is a list of four-tuples: (x1, y1, x2, y2), the coordinates to draw lines between. Eight tuples are returned: first the four horizontal lines in order from top to bottom, then the four vertical lines, from left to right. namepos is a four-tuple: (x, y, w, h), the coordinates to draw the name at (if supplied), and its width and height. """ if x is None: x = client.x if y is None: y = client.y if w is None: w = client.width + 2 * client.border_width if h is None: h = client.height + 2 * client.border_width s = [] w3 = w / 3 h3 = h / 3 xr = x + w - 1 yt = y + 1 yb = y + h - 2 # Horizontal lines s.append((x, y, xr, y)) s.append((x + 1, y + h3, xr - 1, y + h3)) s.append((x + 1, y + h3 + h3, xr - 1, y + h3 + h3)) s.append((x, y + h - 1, xr, y + h - 1)) # Vertical lines s.append((x, yt, x, yb)) s.append((x + w3, yt, x + w3, yb)) s.append((x + w3 + w3, yt, x + w3 + w3, yb)) s.append((xr, yt, xr, yb)) if name: # Center string r = client.outline_font.query_text_extents(name) sx = max(x + (w - r.overall_width) / 2, x) sy = y + h / 2 + (r.overall_ascent + r.overall_descent) / 2 - r.overall_descent sw = min(r.overall_width, w) sh = min(r.overall_ascent + r.overall_descent, h) as = r.overall_ascent else: sx = sy = sw = sh = as = 0 return (x, y, w, h), s, (sx, sy, sw, sh, as) python-plwm-2.6a+20080530/plwm/deltamove.py0000644000175000017500000000536410765600750016772 0ustar stewstew# # deltamove.py -- Calculate movement acceleration # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys from Xlib import X class DeltaMove: """Class to handle increasing (or decreasing) movements for e.g. mouse warping. Call the method get with a timestamp, and if the time is within a timeout (in milliseconds) from the last timestamp passed to get a movement length is calculated from the last. If the timeout has expired the movement length is decreased with factor the number of times the timeout was missed. The movement must be within a minimum and a maximum bound. The new movement length is calculated by multiplying the last movement with a factor (which can be less than one for decreasing lengths). The timestamp will wrap around once each 49.7 day, approximately. I'm not worried. """ def __init__(self, initstep = 1, minstep = 1, maxstep = 64, factor = 2, timeout = 200): self.time = -sys.maxint self.delta = initstep self.init = initstep self.min = minstep self.max = maxstep self.factor = factor self.timeout = timeout def get(self, time = X.CurrentTime): if time == X.CurrentTime: self.delta = self.init self.time = -sys.maxint return self.delta # At wraparound, and possibly at startup, we can get a overflow # error. I can't be bothered to figure out exactly how to avoid # this, so just ignore it and reset the delta. try: if time >= self.time and time - self.time < self.timeout: self.delta = self.delta * self.factor; else: self.delta = self.delta / pow(float(self.factor), (time - self.time) / self.timeout); except OverflowError: self.delta = self.init self.delta = int(max(self.min, min(self.delta, self.max))) self.time = time return self.delta python-plwm-2.6a+20080530/plwm/cycle.py0000644000175000017500000001226010765600750016102 0ustar stewstew# # cycle.py -- Cycle among clients to select one to activate # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from Xlib import X import keys import cfilter import wmanager class NoClientsError(Exception): pass class Cycle: def __init__(self, screen, client_filter): self.clients = screen.query_clients(client_filter, stackorder = 1) if not self.clients: raise NoClientsError('No clients matching filter') self.clients.reverse() self.ix = 0 self.mark() def next(self): self.unmark() self.ix = (self.ix + 1) % len(self.clients) self.mark() def previous(self): self.unmark() self.ix = (self.ix - 1) % len(self.clients) self.mark() def end(self): self.unmark() def abort(self): self.unmark() def mark(self): pass def unmark(self): pass class CycleActivate(Cycle): """Cycle among clients by activating each window in turn. Iconified windows will go back to being iconified when we step to the next window. """ def mark(self): self.last_mapped = self.clients[self.ix].is_mapped() self.clients[self.ix].activate() def unmark(self): if not self.last_mapped: self.clients[self.ix].iconify() def end(self): pass class CycleOutline(Cycle): """Cycle among clients by drawing an outline for each one. """ def mark(self): self.clients[self.ix].outline_show(name = self.clients[self.ix].get_title()) def unmark(self): self.clients[self.ix].outline_hide() def end(self): Cycle.end(self) self.clients[self.ix].activate() # # KeyHandler template for cycling # class CycleKeys(keys.KeyGrabKeyboard): """CycleKeys is a template keyhandler for cycling clients. You should subclass it to define your own keybindings and setting the approriate client filter. The filter for the cycle is specified by the attribute _cycle_filter. This can be set to any client filter, by default `true' is used. CycleKeys defines a number of event handler methods: _cycle_next Cycle to the next client _cycle_previous Cycle to the previous client _cycle_end Finish, selecting the current client _cycle_abort Abort, reverting to the previous state (if possible) By default outline cycling is used with the CycleOutline class. This can be changed by redefining the attribute _cycle_class to any subclass of Cycle. A small CycleKeys subclass example: class MyCycleKeys(CycleKeys): _cycle_class = CycleActivate _cycle_filter = cfilter.Not(cfilter.iconified) Tab = CycleKeys._cycle_next C_Tab = CycleKeys._cycle_next S_Tab = CycleKeys._cycle_previous S_C_Tab = CycleKeys._cycle_previous Return = CycleKeys._cycle_end Escape = CycleKeys._cycle_abort To activate your cycle keys, write a keyhandler event method like this in your basic keyhandler: def C_Tab(self, evt): MyCycleKeys(self, evt) """ propagate_keys = 0 timeout = 10 _cycle_class = CycleOutline _cycle_filter = cfilter.true def __init__(self, keyhandler, event): # Always initialize the keyhandler, otherwise # we'll get problems in the __del__ method. try: keys.KeyGrabKeyboard.__init__(self, keyhandler.wm, event.time) except keys.error, status: wmanager.debug('keys', 'Grabbing keyboard failed: %d', status) # Create the cycle object, but clean up and return immediately # if no clients match the filter try: wmanager.debug('keys', 'Entering cycle mode') self.cycle = self._cycle_class(keyhandler.wm.current_screen, self._cycle_filter) except NoClientsError: wmanager.debug('keys', 'No clients matching cycle filter, leaving cycle mode') self._cleanup() def _cycle_next(self, evt): self.cycle.next() def _cycle_previous(self, evt): self.cycle.previous() def _cycle_end(self, evt): wmanager.debug('keys', 'Leaving cycle mode') self.cycle.end() self._cleanup() def _cycle_abort(self, evt): wmanager.debug('keys', 'Aborting cycle mode') self.cycle.abort() self._cleanup() _timeout = _cycle_abort python-plwm-2.6a+20080530/plwm/mw_apm.py0000644000175000017500000001343410765600750016267 0ustar stewstew# # mw_apm.py -- display APM status in a modewindow # # Copyright (C) 2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # NetBSDIoctlAPM interface contributed by Henrik Rindlöw. from plwm import modewindow, event, wmanager import sys import time import re import fcntl import struct TimerEventType = event.new_event_type() class LinuxProcAPM: apm_file = '/proc/apm' apm_re = re.compile(r'^1\.\d+ (?P\d+\.\d+) ' r'0x(?P[0-9a-f]+) 0x(?P[0-9a-f]+) ' r'0x(?P[0-9a-f]+) ' r'0x(?P[0-9a-f]+) ' r'(?P-?\d+)% ' r'(?P-?\d+) (?P\S*)') battery_status_codes = { '00': ('High', 0), '01': ('Low', 1), '02': ('Critical', 2), '03': ('Charging', 0), '04': ('Missing', 0), } def __init__(self): self.old_status = None def get_match(self): try: f = open(self.apm_file, 'r') except IOError: return None line = f.read(100) return self.apm_re.match(line) # Interface functions below def probe(self): return (self.get_match() is not None) def get(self): m = self.get_match() if m is None: return None, None msg = '' if m.group('ac_line') == '01': msg = msg + 'AC ' raw_status = m.group('battery_status') status, beeps = self.battery_status_codes.get(raw_status, ('Unknown', 0)) msg = msg + status perc = int(m.group('charge_percentage')) if perc >= 0 and perc <= 100: msg = msg + ': %d%%' % perc ctime = int(m.group('charge_time')) if ctime >= 0: msg = msg + ' %d %s' % (ctime, m.group('time_units')) # Cancel out beeps if status hasn't changed if self.old_status == raw_status or self.old_status is None: beeps = 0 self.old_status = raw_status return msg, beeps class NetBSDIoctlAPM: apm_dev = '/dev/apm' api = struct.pack('4B7I', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) APM_IOC_GETPOWER = 0x40004103 | (len(api) << 16) battery_status_codes = { 0: ('High', 0), 1: ('Low', 1), 2: ('Critical', 2), 3: ('Charging', 0), 4: ('Missing', 0), } def __init__(self): self.old_status = None def do_ioctl(self): try: apm = open(self.apm_dev) return fcntl.ioctl(apm.fileno(), self.APM_IOC_GETPOWER, self.api) except IOError: return None # Interface functions below def probe(self): return (self.do_ioctl() is not None) def get(self): api = self.do_ioctl() if api is None: return None, None (battery_state, ac_state, battery_life, spare1, minutes_left, spare2, spare3, spare4, spare5, spare6, spare7) = struct.unpack('4B7I', api) msg = '' if ac_state == 1: msg = msg + 'AC ' status, beeps = self.battery_status_codes.get(battery_state, ('Unknown', 0)) msg = msg + status if battery_life >= 0 and battery_life <= 100: msg = msg + ': %d%%' % battery_life if minutes_left > 0: msg = msg + ' %d minutes' % minutes_left # Cancel out beeps if status hasn't changed if self.old_status == battery_state or self.old_status is None: beeps = 0 self.old_status = battery_state return msg, beeps apm_interfaces = [ LinuxProcAPM(), NetBSDIoctlAPM() ] # wm mixin class ModeWindowAPM: mw_apm_position = 0.2 mw_apm_justification = modewindow.RIGHT def __wm_init__(self): # Figure out which APM interface to use for ai in apm_interfaces: if ai.probe(): self.mw_apm_interface = ai break else: # No matching APM interface, skip rest of our installation sys.stderr.write('%s: failed to find a parsable APM interface, disabling mw_apm' % sys.argv[0]) return self.mw_apm_message = modewindow.Message(self.mw_apm_position, self.mw_apm_justification) for s in self.screens: s.modewindow_add_message(self.mw_apm_message) self.dispatch.add_handler(TimerEventType, self.mw_apm_tick) self.mw_apm_update() def mw_apm_update(self): msg, beeps = self.mw_apm_interface.get() self.mw_apm_message.set_text(msg) # Beep if status have changed if beeps: for i in range(beeps): self.display.bell(100) # Recheck in 30 seconds self.events.add_timer(event.TimerEvent(TimerEventType, after = 30)) def mw_apm_tick(self, evt): self.mw_apm_update() python-plwm-2.6a+20080530/plwm/cfilter.py0000644000175000017500000001512310765600750016434 0ustar stewstew# # cfilter.py -- Client filter functions # # Copyright (C) 1999-2002 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """plwm.filter contains various classes and functions which can be used to create client filters. These client filters can be used in various places, e.g. for selecting whether a client window should have a frame, which clients to cycle among for selection, etc. A filter is called with one argument, a client object. The return value is true or false, depending on how the filter evaluated for the client. Simple filter functions: true always true false always false is_client true if the object is a wmanager.Client instance iconified true if the client is iconified mapped true if the client is mapped Functions matching the client resource name (i.e. res_name or res_class): name(string) true if the name exactly matches string re_name(regexp) true if the name matches the regexp string glob_name(glob) true if the name matches the glob string Functions matching the client title: title(string) true if the title exactly matches string re_title(regexp) true if the title matches the regexp string glob_title(glob) true if the title matches the glob string Compound filters: And(filter, filter...) true if all the filters are true Or(filter, filter...) true if any of the filters are true Not(filter) true if the filter is false """ import re import fnmatch # We must use class objects everywhere. This is caused by the method # semantics used in Python: anytime we get the value of an attribute # in a class, and that value is a function, it is converted into a # method object. Since it is likely that filters will be assigned to # class attributes, e.g. Client.start_iconified, we can't allow any # filter to be ordinary functions. So instead we use Python objects # with a __call__ method to circumvent this problem. # To limit the performance penalty we sets self.__call__ to # self.__call__. This subtle little assignment will fetch the unbound # __call__ method from the class dict, bind it to the instance and # assign it to the instance dict. This reduces each filter invokation # to a normal function call. Additionally, this avoids having to # traverse the class tree to find the method. # The drawback is that we create a circular reference, which # introduces a memory leak for Python 1.5.2 users. Therefore, we only # use this technique for the static filters which shouldn't get # garbage collected anyway. When 1.5.2 support is dropped, we can let # the dynamic objects do this too. class StaticClientFilter: NAME = None def __init__(self): self.__call__ = self.__call__ def __str__(self): return Name class _True(StaticClientFilter): NAME = 'true' def __call__(self, c): return 1 true = _True() all = true class _False(StaticClientFilter): NAME = 'false' def __call__(self, c): return 0 false = _False() none = false class And: def __init__(self, *args): self.filters = args def __call__(self, c): for f in self.filters: if not f(c): return 0 return 1 def __str__(self): return 'And(%s)' % ', '.join([str(f) for f in self.filters]) class Or: def __init__(self, *args): self.filters = args def __call__(self, c): for f in self.filters: if f(c): return 1 return 0 def __str__(self): return 'Or(%s)' % ', '.join([str(f) for f in self.filters]) class Not: def __init__(self, filter): self.filter = filter def __call__(self, c): return not self.filter(c) def __str__(self): return 'Not(%s)' % str(self.filter) class _IsClient(StaticClientFilter): NAME = 'is_client' # We can't import wmanager when cfilter is loaded, # since wmanager imports cfilter. If we try, # nothing will have been evaluated so there is no # attributes here. So to avoid this circular dependency, # import wmanager when we first call this filter and fetch # the client class. Then reset the __call__ method to the real # __call__ method. def __call__(self, c): import wmanager self.client_class = wmanager.Client self.__call__ = self.real___call__ return self.real___call__(c) def real___call__(self, c): return isinstance(c, self.client_class) is_client = _IsClient() class _Iconified(StaticClientFilter): NAME = 'iconified' def __call__(self, c): return not c.is_mapped() iconified = _Iconified() class _Mapped(StaticClientFilter): NAME = 'mapped' def __call__(self, c): return c.is_mapped() mapped = _Mapped() class _NameBase: def __init__(self, pattern): self.pattern = pattern def check(self, str): if str is None: return self.pattern is None else: return self.check_pattern(str) def __str__(self): return '%s(%s)' % (self.__class__.__name__, repr(self.pattern)) class _StringName(_NameBase): def check_pattern(self, str): return self.pattern == str class _ReName(_NameBase): def __init__(self, pattern): _NameBase.__init__(self, re.compile(pattern)) def check_pattern(self, str): return self.pattern.search(str) is not None class _GlobName(_NameBase): def check_pattern(self, str): return fnmatch.fnmatchcase(str, self.pattern) class _Title: def __call__(self, c): return self.check(c.get_title()) class _Resource: def __call__(self, c): return self.check(c.res_name) or self.check(c.res_class) class name(_StringName, _Resource): pass class re_name(_ReName, _Resource): pass class glob_name(_GlobName, _Resource): pass class title(_StringName, _Title): pass class re_title(_ReName, _Title): pass class glob_title(_GlobName, _Title): pass python-plwm-2.6a+20080530/plwm/mixer.py0000644000175000017500000001372210765600750016133 0ustar stewstew# # mw_mixer.py -- control and view volume # # Copyright (C) 2002 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import types import re from plwm import event, wmanager MixerIOEventType = event.new_event_type() MixerTimeoutEventType = event.new_event_type() class ReximaInterface: dev_re = re.compile('^([a-z0-9]+)\s+(\d+)(, (\d+))?', re.MULTILINE) def get_cmd(self): return 'rexima -v' def set_cmd(self, dev, val1, val2): if val2 is None: return 'rexima %s %d' % (dev, val1) else: return 'rexima %s %d,%d' % (dev, val1, val2) def parse_output(self, data): groups = self.dev_re.findall(data) devs = [] for dev, ls1, skip, ls2 in groups: if not ls1: continue left = int(ls1) if not ls2: devs.append((dev, left)) else: right = int(ls2) devs.append((dev, (left, right))) return devs # wm mixin class Mixer: mixer_interface = ReximaInterface() def __wm_init__(self): self.dispatch.add_handler(MixerIOEventType, self.mixer_io_event) self.dispatch.add_handler(MixerTimeoutEventType, self.mixer_timout) self.mixer_devs = {} self.mixer_mute_devs = {} self.mixer_status_msg = None self.mixer_timer_event = None self.mixer_cmd_out = None self.mixer_update_settings() def mixer_update_settings(self): if self.mixer_cmd_out is not None: wmanager.debug('mixer', 'previous update command has not finished yet') return pipes = self.system(self.mixer_interface.get_cmd(), redirect = 1) self.mixer_cmd_out = pipes[1] self.mixer_cmd_data = '' self.mixer_cmd_event = event.FileEvent(MixerIOEventType, self.mixer_cmd_out, event.FileEvent.READ) self.events.add_file(self.mixer_cmd_event) def mixer_io_event(self, evt): d = self.mixer_cmd_out.read() if d: self.mixer_cmd_data = self.mixer_cmd_data + d return # command finished, update values self.mixer_cmd_event.cancel() self.mixer_cmd_event = None self.mixer_cmd_out.close() self.mixer_cmd_out = None devs = self.mixer_interface.parse_output(self.mixer_cmd_data) for dev, val in devs: self.mixer_devs[dev] = val def mixer_timout(self, evt): if self.mixer_status_msg is not None: self.mixer_status_msg.pop() self.mixer_status_msg = None self.mixer_timer_event = None def mixer_status_view(self, devs = None, stereo = 0, timeout = 5): if devs is None: devs = self.mixer_devs.keys() msg = '' for dev in devs: try: val = self.mixer_mute_devs[dev] valstr = 'MUTE:%s' % val except KeyError: try: values = self.mixer_devs[dev] if type(values) is types.TupleType: if stereo: valstr = '%s:%s' % values else: valstr = str(values[0]) else: valstr = str(values) except KeyError: valstr = 'N/A' msg = '%s [%s %s]' % (msg, dev, valstr) if self.mixer_status_msg is None: self.mixer_status_msg = self.current_screen.modestatus_new(msg) else: self.mixer_status_msg.set(msg) if self.mixer_timer_event is not None: self.mixer_timer_event.cancel() self.mixer_timer_event = event.TimerEvent(MixerTimeoutEventType, after = timeout) self.events.add_timer(self.mixer_timer_event) def mixer_get(self, dev, stereo = 0): try: values = self.mixer_devs[dev] if type(values) is types.TupleType: return values[0] else: return values except KeyError: return None def mixer_set(self, dev, val1, val2 = None): try: values = self.mixer_devs[dev] except KeyError: return assert val1 >= 0 and val1 <= 100 assert val2 is None or (val2 >= 0 and val2 <= 100) if type(values) is types.TupleType: if val2 is None: val2 = val1 self.mixer_devs[dev] = (val1, val2) cmd = self.mixer_interface.set_cmd(dev, val1, val2) else: self.mixer_devs[dev] = val1 cmd = self.mixer_interface.set_cmd(dev, val1, None) self.system(cmd, fg = 1) try: del self.mixer_mute_devs[dev] except KeyError: pass def mixer_mute(self, dev): """Toggle muteness of DEV.""" if self.mixer_mute_devs.has_key(dev): old = self.mixer_mute_devs[dev] self.mixer_set(dev, old) else: old = self.mixer_get(dev) self.mixer_set(dev, 0) self.mixer_mute_devs[dev] = old python-plwm-2.6a+20080530/plwm/keys.py0000644000175000017500000003037711004403620015747 0ustar stewstew# # keys.py -- Base keypress handlers # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from Xlib import X, XK from Xlib.keysymdef import latin1 import string import wmanager import event import time import sys error = 'keys.error' ReleaseModifier = X.AnyModifier << 1 modifiers = { 's': X.ShiftMask, 'S': X.ShiftMask, 'c': X.ControlMask, 'C': X.ControlMask, 'm': X.Mod1Mask, 'M': X.Mod1Mask, 'M1': X.Mod1Mask, 'M2': X.Mod2Mask, 'M3': X.Mod3Mask, 'M4': X.Mod4Mask, 'M5': X.Mod5Mask, 'Any': X.AnyModifier, 'R': ReleaseModifier, 'None': 0, } def hash_keycode(code, modifiers): return modifiers << 8 | code; # Screen mixin, still here for backward compitability class KeyGrabber: """Keeps track of all grabbed keys on a window. """ def __screen_init__(self): sys.stderr.write('%s: warning: Screen mixin KeyGrabber is deprecated, and will be removed in future releases.\n' % sys.argv[0]) # This is now the real grabbing class class KeygrabManager: def __init__(self, wm, window): self.wm = wm self.window = window self.grabs = {} def ungrab_keys(self, keylist): """Ungrab some keys. KEYLIST is a list of (keycode, modifier) tuples. """ for keycode, modifiers in keylist: h = hash_keycode(keycode, modifiers) c = self.grabs.get(h, 0) if c > 0: if c == 1: del self.grabs[h] self.window.ungrab_key(keycode, modifiers & ~ReleaseModifier) else: self.grabs[h] = self.grabs[h] - 1 def grab_keys(self, keylist): """Grab some keys. KEYLIST is a list of (keycode, modifier) tuples. """ for keycode, modifiers in keylist: h = hash_keycode(keycode, modifiers) c = self.grabs.get(h, 0) if c == 0: self.window.grab_key(keycode, modifiers & ~ReleaseModifier, 1, X.GrabModeAsync, X.GrabModeAsync) self.grabs[h] = 1 else: self.grabs[h] = self.grabs[h] + 1 def grab_keyboard(self, time): s = self.window.grab_keyboard(0, X.GrabModeAsync, X.GrabModeAsync, time) if s != X.GrabSuccess: raise error, s def ungrab_keyboard(self): self.wm.display.ungrab_keyboard(X.CurrentTime) class KeyHandler: """Class handling key events. Inherit this class and implement member methods for the various keysym. The methods' names must have the following syntax: keymethod :== (modifier)* keysym modifier :== ('s' | 'c' | 'm') '_' The order of the modifiers are not importart, neither is their case. s means shift c means control m means meta (modifier 1) Examples: Return C_Right C_M_F1 The methods must accept two arguments: the event object and the event handler group. Use a KeyHandler object as event handler for KeyPress and KeyRelease events. """ propagate_keys = 1 timeout = None def __init__(self, obj, deprecated_arg = None): """Instantiate a KeyHandler object. """ # Warn if the deprecated and unused former "dispatch" argument # is used. if deprecated_arg is not None: sys.stderr.write("%s: warning: using deprecated KeyHandler __init__ interface\n" % sys.argv[0]) wmanager.debug('mem', 'Initing keyhandler %s for %s', self, obj) # Figure out if we have been added to a WindowManager, Screen # or Client object. Set up KeygrabManager objects if not # already done on screens or clients. if isinstance(obj, wmanager.WindowManager): wm = obj grabmgrs = [] for s in obj.screens: if not hasattr(s, 'keygrab_mgr'): s.keygrab_mgr = KeygrabManager(obj, s.root) grabmgrs.append(s.keygrab_mgr) elif isinstance(obj, wmanager.Screen): wm = obj.wm if not hasattr(obj, 'keygrab_mgr'): obj.keygrab_mgr = KeygrabManager(obj.wm, obj.root) grabmgrs = [obj.keygrab_mgr] elif isinstance(obj, wmanager.Window): wm = obj.wm if not hasattr(obj, 'keygrab_mgr'): obj.keygrab_mgr = KeygrabManager(obj.wm, obj.window) grabmgrs = [obj.keygrab_mgr] else: raise TypeError('expected WindowManager, Screen or Client object') # Dig through all names in this object, ignoring those beginning with # an underscore. # First collect all method names in this and it's base classes names = {} c = [self.__class__] while len(c): names.update(c[0].__dict__) c = c + list(c[0].__bases__) del c[0] names.update (self.__dict__) # And now parse the names rawbinds = [] for name in names.keys(): if name[0] != '_': # Find modifiers in name mask = 0 parts = string.split(name, '_') while len(parts) >= 2 and modifiers.has_key(parts[0]): mask = mask | modifiers[parts[0]] del parts[0] # Find name keysym rest = string.join(parts, '_') keysym = XK.string_to_keysym(rest) if keysym != X.NoSymbol: rawbinds.append((keysym, mask, getattr(self, name))) self.wm = wm self.dispatch = obj.dispatch self.grabmgrs = grabmgrs self.rawbindings = rawbinds self.grabs = [] # Add handlers if self.propagate_keys: self.dispatch.add_handler(X.KeyPress, self._keyevent, handlerid = self) self.dispatch.add_handler(X.KeyRelease, self._keyevent, handlerid = self) else: self.dispatch.add_grab_handler(X.KeyPress, self._keyevent, handlerid = self) self.dispatch.add_grab_handler(X.KeyRelease, self._keyevent, handlerid = self) # Okay, so we will remap and regrab once for every # screen, but I'm not worried. xmodmap isn't the most # frequently used command... self.dispatch.add_handler(X.MappingNotify, self._mappingnotify, handlerid = self) if self.timeout: self.last_key_time = None self.timer_id = event.new_event_type() self.timer = event.TimerEvent(self.timer_id, after = self.timeout) self.wm.events.add_timer(self.timer) self.dispatch.add_handler(self.timer_id, self._internal_timeout, handlerid = self) self._buildmap() def __del__(self): wmanager.debug('mem', 'Freeing keyhandler %s', self) self._cleanup() def _cleanup(self): # Remove all our event handlers self.dispatch.remove_handler(self) # Ungrab keys self._ungrab() # Clear the bindings: essential as elements of this list refers # to bound methods of this object, i.e. circular references. self.rawbindings = None self.bindings = None # Unscedule any pending timeout if self.timeout: self.timer.cancel() def _grab(self): for g in self.grabmgrs: g.grab_keys(self.grabs) def _ungrab(self): for g in self.grabmgrs: g.ungrab_keys(self.grabs) self.grabs = [] def _buildmap(self): """Build key bindings mapping. Also sets passive grabs for the key bindings. """ # First ungrab the grabs we already have self._ungrab() # Build up new list of bindings self.bindings = {} for keysym, modifiers, func in self.rawbindings: keycodes = self.wm.display.keysym_to_keycodes(keysym) for code, index in keycodes: # Don't deal with modeswitching, not yet if index > 1: continue # If AnyModifier is set, we only grab to those keycodes which # have keysym as their primary binding if modifiers & X.AnyModifier: if index != 0: continue # Add shift if necessary if index == 1: mods = modifiers | X.ShiftMask else: mods = modifiers self.bindings[hash_keycode(code, mods)] = func self.grabs.append((code, mods)) # Install the new grabs self._grab() def _mappingnotify(self, event): """Pass as handler for MappingNotify events to rebuild the key bindings. """ self._buildmap() def _internal_timeout(self, ev): """Called when the timer event times out. Don't override this, override _timeout instead. """ # Call _timeout if it has been at least self.timeout # seconds since the last keypress if self.last_key_time is None \ or ev.time - self.last_key_time >= self.timeout: wmanager.debug('keys', 'timeout, last_key = %s, now = %s', self.last_key_time, ev.time) self._timeout(ev) # If not: reschedule a timeout else: wmanager.debug('keys', 'rescheduling timeout at %s', self.last_key_time + self.timeout) self.timer = event.TimerEvent(self.timer_id, at = self.last_key_time + self.timeout) self.wm.events.add_timer(self.timer) def _timeout(self, event): """Called when we really timeout. """ pass def _keyevent(self, event): # Store key press time (approximate to the current time # as the X event.time isn't synced with that) self.last_key_time = time.time() wmanager.debug('keys', '%s %d %d, keyhandler %s', event.__class__.__name__, event.detail, event.state, self) # Add in our release "modifier" if event.type == X.KeyRelease: extrastate = ReleaseModifier else: extrastate = 0 # First check for an exact modifier match match = hash_keycode(event.detail, event.state | extrastate) if self.bindings.has_key(match): self.bindings[match](event) # else, check for an AnyModifier key else: match = hash_keycode(event.detail, X.AnyModifier | extrastate) if self.bindings.has_key(match): self.bindings[match](event) class KeyGrabKeyboard(KeyHandler): propagate_keys = 0 timeout = 10 def __init__(self, obj, time, deprecated_arg = None): # Warn about using the old interface, and convert to new if deprecated_arg is not None: sys.stderr.write("%s: warning: using deprecated KeyGrabKeyboard.__init__ interface\n" % sys.argv[0]) time = deprecated_arg self._grab_time = time KeyHandler.__init__(self, obj) def _grab(self): for g in self.grabmgrs: g.grab_keyboard(self._grab_time) def _ungrab(self): for g in self.grabmgrs: g.ungrab_keyboard() def _timeout(self, evt): self._cleanup() def allmap(klass, method): """Map all printing keys to klass.method in keyhandler klass.""" for c in dir(latin1): if len(c) > 3 and c[:3] == 'XK_': setattr(klass, c[3:], method) python-plwm-2.6a+20080530/plwm/mw_clock.py0000644000175000017500000000407710765600750016610 0ustar stewstew# # mw_clock.py -- display the time in a ModeWindow # # Copyright (C) 2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from plwm import modewindow, event, wmanager import time ClockEventType = event.new_event_type() # wm mixin class ModeWindowClock: mw_clock_position = 1.0 mw_clock_justification = modewindow.RIGHT def __wm_init__(self): self.mw_clock_format = self.rdb_get('.modewindow.clock.format', '.ModeWindow.Clock.Format', '%H:%M') self.mw_clock_message = modewindow.Message(self.mw_clock_position, self.mw_clock_justification) for s in self.screens: s.modewindow_add_message(self.mw_clock_message) self.dispatch.add_handler(ClockEventType, self.mw_clock_tick) self.mw_clock_update() def mw_clock_update(self): t = time.localtime(time.time()) s = time.strftime(self.mw_clock_format, t) self.mw_clock_message.set_text(s) wmanager.debug('clock', 'updated to "%s", rescheduling in %d seconds', s, 61 - t[5]) # Trig a timer when minute change self.events.add_timer(event.TimerEvent(ClockEventType, after = 61 - t[5])) def mw_clock_tick(self, evt): self.mw_clock_update() python-plwm-2.6a+20080530/plwm/moveresize.py0000644000175000017500000002527110765600750017201 0ustar stewstew# # moveresize.py -- Move and resize clients # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from Xlib import X import keys import deltamove import wmanager class MoveResize: def __init__(self, client, delta = deltamove.DeltaMove()): """Start to move/resize CLIENT. If moveunits() will be used DELTA should be a DeltaMove object to use for getting lengths. If not provided, a default DeltaMove will be used. """ self.client = client self.delta = delta (self.x, self.y, self.width, self.height, self.borderwidth) = self.client.geometry() # Handle windows who want incremental resizing self.req_width = self.width self.req_height = self.height self.inc_width, self.inc_height = self.client.resize_increment() # Capture the pointer position ptrpos = self.client.pointer_position() if ptrpos: self.ptrx, self.ptry = ptrpos if self.ptrx < -self.borderwidth or self.ptry < -self.borderwidth \ or self.ptrx >= self.width + self.borderwidth \ or self.ptry >= self.height + self.borderwidth: self.ptrx, self.ptry = None, None else: self.ptrx, self.ptry = None, None self.client.wm.events.put_event(MoveResizeStart(self.client)) def setposition(self,x,y): # Sets the position to an absolute location, not relative self.x, self.y = x,y self.adjust() self.do() def move(self, x, y): self.x = self.x + x self.y = self.y + y self.adjust() self.do() def resize(self, x, y, width, height): """Resize and move client pixelbased.""" self.x = self.x + x self.y = self.y + y self.req_width = self.req_width + width self.req_height = self.req_height + height self.adjust() self.do() def resizeunits(self, x, y, width, height, time = X.CurrentTime): """Resize and move client based on units.""" d = None if x or width: if self.inc_width and self.inc_width > 1: self.x = self.x + x * self.inc_width self.req_width = self.width + width * self.inc_width else: d = self.delta.get(time) self.x = self.x + x * d self.req_width = self.req_width + width * d if y or height: if self.inc_height and self.inc_height > 1: self.y = self.y + y * self.inc_height self.req_height = self.height + height * self.inc_height else: if d is None: d = self.delta.get(time) self.y = self.y + y * d self.req_height = self.req_height + height * d self.adjust() self.do() def adjust(self): self.width, self.height = \ self.client.follow_size_hints(self.req_width, self.req_height) self.x, self.y, self.width, self.height = \ self.client.keep_on_screen(self.x, self.y, self.width, self.height) def do(self): self.client.wm.events.put_event(MoveResizeDo(self.client, self.x, self.y, self.width, self.height)) def end(self): if self.ptrx is not None and self.ptrx < self.width + self.borderwidth \ and self.ptry < self.height + self.borderwidth: self.client.warppointer(self.ptrx, self.ptry) self.client.wm.events.put_event(MoveResizeEnd(self.client)) self.client = None def abort(self): self.client.wm.events.put_event(MoveResizeAbort(self.client)) self.client = None class MoveResizeOpaque(MoveResize): def do(self): MoveResize.do(self) self.client.moveresize(self.x, self.y, self.width, self.height) class MoveResizeOutline(MoveResize): def __init__(self, client, delta = deltamove.DeltaMove()): MoveResize.__init__(self, client, delta) self.outline_show() def outline_show(self): w = self.width + 2 * self.borderwidth h = self.height + 2 * self.borderwidth self.client.outline_show(self.x, self.y, w, h) def do(self): MoveResize.do(self) self.outline_show() def end(self): self.client.outline_hide() self.client.moveresize(self.x, self.y, self.width, self.height) MoveResize.end(self) def abort(self): self.client.outline_hide() MoveResize.abort(self) # # Events for resizing etc # class MoveResizeStart: def __init__(self, client): self.type = MoveResizeStart self.client = client class MoveResizeDo: def __init__(self, client, x, y, w, h): self.type = MoveResizeDo self.client = client self.x = x self.y = y self.width = w self.height = h class MoveResizeEnd: def __init__(self, client): self.type = MoveResizeEnd self.client = client class MoveResizeAbort: def __init__(self, client): self.type = MoveResizeAbort self.client = client # # KeyHandler template for moving/resizing # class MoveResizeKeys(keys.KeyGrabKeyboard): """MoveResizeKeys is a template keyhandler for moving/resizing clients. You should subclass it to define your own keybindings. MoveResizeKeys defines a number of event handler methods: _move_X Move the client in direction X _enlarge_X Enlarge the client in direction X _shrink_X Shrink the client from direction X The direction is one of eight combinations of the four cardinal points: e, ne, n, nw, w, sw, s and se Additionally theres two methods for finishing the moveresize: _moveresize_end Finish, actually moving and resizing the client _moveresize_abort Abort, leaving client with its old geometry By default outline moveresizing is used with the MoveResizeOutline class. This can be changed by redefining the attribute _moveresize_class to any subclass of MoveResize. A small MoveResizeKeys subclass example: class MyMRKeys(MoveResizeKeys): _moveresize_class = MoveResizeOpaque KP_Left = MoveResizeKeys._move_w KP_Right = MoveResizeKeys._move_e KP_Up = MoveResizeKeys._move_n KP_Down = MoveResizeKeys._move_s KP_Begin = MoveResizeKeys._moveresize_end Escape = MoveResizeKeys._moveresize_abort To activate moveresize, write a keyhandler event method like this in your basic keyhandler: def KP_Begin(self, evt): MyMRKeys(self, evt) """ propagate_keys = 0 timeout = 20 _moveresize_class = MoveResizeOutline def __init__(self, keyhandler, event): # Always initialize the keyhandler, otherwise # we'll get problems in the __del__ method. try: keys.KeyGrabKeyboard.__init__(self, keyhandler.wm, event.time) except keys.error, status: wmanager.debug('keys', 'Grabbing keyboard failed: %d', status) # But: if no client is focused, clean up immediately. This # drops the grab and all event handlers if keyhandler.wm.current_client is None: self._cleanup() # Otherwise initialise a MoveResize object for the client else: wmanager.debug('keys', 'Entering move-resize mode') self.delta = deltamove.DeltaMove() self.mv = self._moveresize_class(keyhandler.wm.current_client, self.delta) def _move_w(self, event): self.mv.move(-self.delta.get(event.time), 0) def _move_e(self, event): self.mv.move(self.delta.get(event.time), 0) def _move_n(self, event): self.mv.move(0, -self.delta.get(event.time)) def _move_s(self, event): self.mv.move(0, self.delta.get(event.time)) def _move_nw(self, event): d = self.delta.get(event.time) self.mv.move(-d, -d) def _move_sw(self, event): d = self.delta.get(event.time) self.mv.move(-d, d) def _move_ne(self, event): d = self.delta.get(event.time) self.mv.move(d, -d) def _move_se(self, event): d = self.delta.get(event.time) self.mv.move(d, d) def _enlarge_w(self, event): self.mv.resizeunits(-1, 0, 1, 0, event.time) def _enlarge_e(self, event): self.mv.resizeunits(0, 0, 1, 0, event.time) def _enlarge_n(self, event): self.mv.resizeunits(0, -1, 0, 1, event.time) def _enlarge_s(self, event): self.mv.resizeunits(0, 0, 0, 1, event.time) def _enlarge_nw(self, event): self.mv.resizeunits(-1, -1, 1, 1, event.time) def _enlarge_sw(self, event): self.mv.resizeunits(-1, 0, 1, 1, event.time) def _enlarge_ne(self, event): self.mv.resizeunits(0, -1, 1, 1, event.time) def _enlarge_se(self, event): self.mv.resizeunits(0, 0, 1, 1, event.time) def _shrink_w(self, event): self.mv.resizeunits(1, 0, -1, 0, event.time) def _shrink_e(self, event): self.mv.resizeunits(0, 0, -1, 0, event.time) def _shrink_n(self, event): self.mv.resizeunits(0, 1, 0, -1, event.time) def _shrink_s(self, event): self.mv.resizeunits(0, 0, 0, -1, event.time) def _shrink_nw(self, event): self.mv.resizeunits(1, 1, -1, -1, event.time) def _shrink_sw(self, event): self.mv.resizeunits(1, 0, -1, -1, event.time) def _shrink_ne(self, event): self.mv.resizeunits(0, 1, -1, -1, event.time) def _shrink_se(self, event): self.mv.resizeunits(0, 0, -1, -1, event.time) def _moveresize_end(self, event): wmanager.debug('keys', 'Leaving move-resize mode') self.mv.end() self._cleanup() def _moveresize_abort(self, event): wmanager.debug('keys', 'Aborting move-resize mode') self.mv.abort() self._cleanup() _timeout = _moveresize_abort python-plwm-2.6a+20080530/plwm/wmanager.py0000644000175000017500000017501711017350020016575 0ustar stewstew# # wmanager.py -- core window manager functionality # # Copyright (C) 1999-2002 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys import os import signal import errno import array import traceback import re import string import types from Xlib import display, X, Xutil, Xatom, rdb, error import Xlib.protocol.event import plwm import event import wmevents import cfilter # Minimum Xlib version required_xlib_version = (0, 8) # Errors class UnavailableScreenError(Exception): pass class NoUnmanagedScreensError(Exception): pass class BadXlibVersion(Exception): pass # Debugging debug_file = sys.stderr debug_what = [] def do_debug(what, fmt, *args): if not debug_what or what in debug_what: debug_file.write(what + ': ' + (fmt % args) + '\n') def dont_debug(*args): pass debug = dont_debug class Window: """Class representing any window object on a screen, either internal or client windows. """ full_screen_windows = cfilter.none def __init__(self, screen, window): "Create a window object managing WINDOW." self.screen = screen self.window = window self.wm = screen.wm # So we can pass Windows to KeyHandlers. self.withdrawn = 0 self.delayed_moveresize = 0 self.current = 0 self.focused = 0 self.force_iconified = 0 self.dispatch = event.WindowDispatcher(self.window) # Initial mapped state and geometry a = window.get_attributes() self.mapped = a.map_state != X.IsUnmapped r = self.window.get_geometry() self.x = r.x self.y = r.y self.width = r.width self.height = r.height self.border_width = r.border_width def __str__(self): return '<%s %s>' % (self.__class__, self.window) def __repr__(self): return self.__str__() # # Internal methods # def withdraw(self, destroyed = 0): """Window has been withdrawn. If DESTROYED is true the window doesn't exist anymore.""" if self.withdrawn: return self.mapped = 0 self.withdrawn = 1 # Clear the dispatcher to avoid cirkular references self.dispatch = event.SlaveDispatcher([]) def handle_event(self, event, grabbed = 0): """Handle EVENT for this client by calling its dispatch table. """ self.dispatch.handle_event(event, grabbed) def get_focus(self, time): debug('focus', 'client gets focus: %s', self) self.focused = 1 self.window.set_input_focus(X.RevertToPointerRoot, time) def lose_focus(self): debug('focus', 'client loses focus: %s', self) self.focused = 0 # # External methods # def configure(self, **keys): if self.withdrawn: return for i in ['x', 'y', 'width', 'height', 'border_width']: setattr(self, i, keys.get(i, getattr(self, i))) apply(self.window.configure, (), keys) def is_mapped(self): return self.mapped def valid_window(self): """Return true if the window still exists. """ if self.withdrawn: return 0 # Check for an invalid window by trigging BadDrawable # on a simple request try: r = self.window.get_geometry() except error.BadDrawable: return 0 else: return 1 def resize(self, width, height): if self.withdrawn: return self.window.configure(width = width, height = height) self.width = width self.height = height def move(self, x, y): if self.withdrawn: return self.window.configure(x = x, y = y) self.x = x self.y = y def moveresize(self, x, y, width, height, delayed = 0): if self.withdrawn: return # If client is iconified and delayed is true, don't actually # resize the window now but postpone it until deiconifying if self.mapped or not delayed: self.window.configure(x = x, y = y, width = width, height = height) self.delayed_moveresize = 0 else: self.delayed_moveresize = 1 self.x = x self.y = y self.width = width self.height = height def setborderwidth(self, width): if self.withdrawn: return self.window.configure(border_width = width) self.border_width = width def set_border_color(self, color): if self.withdrawn: return self.window.change_attributes(border_pixel = color) def keep_on_screen(self, x, y, width, height): """Return X, Y, WIDTH, HEIGHT after adjusting so the entire window is visible on the screen, including the border. """ if self.full_screen_windows(self): root_x = 0 root_y = 0 root_width = self.screen.root_full_width root_height = self.screen.root_full_height else: root_x = self.screen.root_x root_y = self.screen.root_y root_width = self.screen.root_width root_height = self.screen.root_height # negative sizes is impossible if width < 0: width = 0 if height < 0: height = 0 # width and height must not be larger than the screen area if width + 2 * self.border_width > root_width: width = root_width - 2 * self.border_width if height + 2 * self.border_width > root_height: height = root_height - 2 * self.border_width # Move window if right/bottom edge is outside screen if (x + width + 2 * self.border_width > root_x + root_width): x = (root_x + root_width - width - 2 * self.border_width) if (y + height + 2 * self.border_width > root_y + root_height): y = (root_y + root_height - height - 2 * self.border_width) # Move window if left/top edge is outside screen if x < root_x: x = root_x if y < root_y: y = root_y return x, y, width, height def geometry(self): return self.x, self.y, self.width, self.height, self.border_width def get_top_edge(self): """Return the y coordinate of the top edge of the client.""" return self.y def get_bottom_edge(self): """Return the y coordinate of the bottom edge of the client.""" return self.y + self.height + 2 * self.border_width def get_left_edge(self): """Return the x coordinate of the left edge of the client.""" return self.x def get_right_edge(self): """Return the x coordinate of the right edge of the client.""" return self.x + self.width + 2 * self.border_width def pointer_position(self): """Return the pointer x and y position relative to the window origin. Return None if the pointer is on another screen. """ if self.withdrawn: return None # Handle destroyed windows gracefully, registering the error # so the window is withdrawn from managing. try: r = self.window.query_pointer() except error.BadWindow, e: self.wm.events.put_event(e) return None if r.same_screen: return r.win_x, r.win_y else: return None # # Window ops # def map(self): if self.withdrawn: return None # Perform delayed resizing if self.delayed_moveresize: self.window.configure(x = self.x, y = self.y, width = self.width, height = self.height) self.delayed_moveresize = 0 self.window.map() def unmap(self): if self.withdrawn: return None self.window.unmap() def clear_area(self, *args, **keys): if self.withdrawn: return None apply(self.window.clear_area, args, keys) def fill_rectangle(self, gc, x, y, width, height, onerror = None): if self.withdrawn: return None self.window.fill_rectangle(gc, x, y, width, height, onerror) def image_text(self, gc, x, y, string, onerror = None): if self.withdrawn: return None self.window.image_text(gc, x, y, string, onerror) def draw_text(self, gc, x, y, text, onerror = None): if self.withdrawn: return None self.window.draw_text(gc, x, y, text, onerror) def convert_selection(self, selection, target, property, time, onerror = None): if self.withdrawn: return None self.window.convert_selection(selection, target, property, time, onerror) def destroy(self): if self.withdrawn: return self.screen.remove_window(self.window) self.window.destroy() def raisewindow(self): if self.withdrawn: return self.window.configure(stack_mode = X.Above) def lowerwindow(self): if self.withdrawn: return self.window.configure(stack_mode = X.Below) def raiselower(self): if self.withdrawn: return self.window.configure(stack_mode = X.Opposite) class InternalWindow(Window): pass class Client(Window): "Container for clients of the window manager." start_iconified_clients = cfilter.none default_pointer_pos = {} client_maxsize = {} needs_reparent_clients = cfilter.name('AWTapp') window_proxy_class = None def __init__(self, screen, window, maprequest): """Create a client object managing WINDOW. If MAPPED is true, this client has been created because of a MapRequest event. If false, it was created when the window manager started and scanned available windows. """ # Let any window proxy in on the fun if self.window_proxy_class is not None: window = self.window_proxy_class(screen, window) Window.__init__(self, screen, window) # Let proxy register event handlers now that we have a dispatcher if self.window_proxy_class is not None: window.__proxy_event_init__(self) self.window.change_save_set(X.SetModeInsert) self.from_maprequest = maprequest # Set up the system event handlers self.dispatch.add_system_handler(X.DestroyNotify, self.handle_destroy_notify) self.dispatch.add_system_handler(X.UnmapNotify, self.handle_unmap_notify) self.dispatch.add_system_handler(X.PropertyNotify, self.handle_property_notify) # Fetch WM hints self.wmhints = self.window.get_wm_hints() self.sizehints = self.window.get_wm_normal_hints() self.protocols = self.window.get_wm_protocols() hint = self.window.get_wm_class() if hint: self.res_name, self.res_class = hint else: self.res_name = self.res_class = None # Some broken widget sets (e.g. Java AWT) checks whether a # window manager is running, and if so, will do nothing until # they receive the ReparentNotify that indicates that their # window has now been put into a frame by the wm. Because # PLWM does not have frame windows, this breaks horribly. # To cater for AWT and its demented cousins, we reparent the # window to be at the same position in the root window. This # is effectively a no-op, but will generate the # ReparentNotify. It can also generates an UnmapNotify, so # block that event. if self.needs_reparent_clients(self): debug('client', 'reparenting incompetent window') self.dispatch.block_masks(X.StructureNotifyMask) self.screen.dispatch.block_masks(X.SubstructureNotifyMask) self.window.reparent(self.screen.root, self.x, self.y) self.screen.dispatch.unblock_masks(X.SubstructureNotifyMask) self.dispatch.unblock_masks(X.StructureNotifyMask) # Detect client focus model # Always call set_input_focus, unless the client has set input # hint to false if self.wmhints is not None \ and self.wmhints.flags & Xutil.InputHint \ and not self.wmhints.input: self.do_set_focus = 0 else: self.do_set_focus = 1 # We send message if client supports the WM_TAKE_FOCUS protocol if self.wm.WM_TAKE_FOCUS in self.protocols: self.do_send_focus_msg = 1 # However: we always set focus of globally active windows, # to avoid having to track FocusIn/FocusOut. if not self.do_set_focus: self.do_set_focus = 1 else: self.do_send_focus_msg = 0 # Figure out if we should start iconified or not # First: if the window is mapped from a withdrawn state # use the WM hints state hint if present if maprequest and self.wmhints and self.wmhints.flags & Xutil.StateHint \ and self.wmhints.initial_state == Xutil.IconicState: self.start_iconified = 1 # Second: start iconified if the client already is iconic elif self.get_wm_state() == Xutil.IconicState: self.start_iconified = 1 # Third : start iconified if the clients matches # start_iconified_clients elif self.start_iconified_clients(self): self.start_iconified = 1 # Otherwise start mapped, although this can be overridden else: self.start_iconified = 0 # Now find and call all __client_init__ methods call_inits(self.__class__, '__client_init__', self) def __del__(self): # Just call all __client_del__ methods call_inits(self.__class__, '__client_del__', self) # # Internal methods # def withdraw(self, destroyed = 0): """Window has been withdrawn. If DESTROYED is true the window doesn't exist anymore.""" if self.withdrawn: return Window.withdraw(self, destroyed) if not destroyed: self.window.change_save_set(X.SetModeDelete) self.window.delete_property(self.wm.WM_STATE) # Pass note to proxy, if any, that the window is gone if self.window_proxy_class is not None: self.window._proxy_withdraw() def handle_property_notify(self, event): """Called when a property has changed on the client window. EVENT is a PropertyEvent object. """ if self.withdrawn: return # The only ICCCM property we should follow is WM_NORMAL_HINTS, # as that one can change e.g. when changing font in an Emacs. # The other properties should be set before the window is # mapped, and mostly affect initial state anyway. if event.atom == Xatom.WM_NORMAL_HINTS: # Handle destroyed windows gracefully, registering the error # so the window is withdrawn from managing. try: self.sizehints = self.window.get_wm_normal_hints() except error.BadWindow, e: self.wm.events.put_event(e) def handle_destroy_notify(self, event): debug('clientnotify', 'Destroy of window %s', event.window) self.wm.remove_window(event.window, 1) def handle_unmap_notify(self, event): debug('clientnotify', 'Unmapping window %s', event.window) self.wm.remove_window(event.window) def initial_map(self): """Called after creating a client to map its window.""" debug('client', 'start_iconified: %d' % self.start_iconified) if self.start_iconified: self.iconify() else: self.deiconify() def get_focus(self, time): debug('focus', 'client gets focus: %s', self) self.focused = 1 self.wm.events.put_event(wmevents.ClientFocusIn(self)) if self.do_set_focus: self.window.set_input_focus(X.RevertToPointerRoot, time) if self.do_send_focus_msg: self.send_message(self.wm.WM_TAKE_FOCUS, time) def lose_focus(self): debug('focus', 'client loses focus: %s', self) self.wm.events.put_event(wmevents.ClientFocusOut(self)) self.focused = 0 # # External methods # def configure(self, **keys): if self.client_maxsize.has_key(self.res_name): maxsize = self.client_maxsize[self.res_name] elif self.client_maxsize.has_key(self.res_class): maxsize = self.client_maxsize[self.res_class] else: maxsize = None if keys.has_key('width') and maxsize: keys['width'] = min(keys['width'], maxsize[0]) if keys.has_key('height') and maxsize: keys['height'] = min(keys['height'], maxsize[1]) apply(Window.configure, (self, ), keys) def iconify(self): if self.withdrawn: return debug('client', 'iconify') # Prevent us from recieving UnmapNotify events self.dispatch.block_masks(X.StructureNotifyMask) self.screen.dispatch.block_masks(X.SubstructureNotifyMask) self.window.unmap() debug('client', 'iconify') # Prevent us from recieving UnmapNotify events self.dispatch.block_masks(X.StructureNotifyMask) self.screen.dispatch.block_masks(X.SubstructureNotifyMask) self.unmap() self.screen.dispatch.unblock_masks(X.SubstructureNotifyMask) self.dispatch.unblock_masks(X.StructureNotifyMask) self.mapped = 0 self.window.set_wm_state(state = Xutil.IconicState, icon = 0) self.wm.events.put_event(wmevents.ClientIconified(self)) def deiconify(self): if self.withdrawn: return if self.force_iconified: return if self.force_iconified: return debug('client', 'deiconify') self.map() self.mapped = 1 self.window.set_wm_state(state = Xutil.NormalState, icon = 0) self.wm.events.put_event(wmevents.ClientDeiconified(self)) def delete(self, destroy = 0): if self.withdrawn: return if self.wm.WM_DELETE_WINDOW in self.protocols: self.send_message(self.wm.WM_DELETE_WINDOW) elif destroy: self.destroy() def activate(self): """Make this client active by raising it's window and moving the pointer into it. """ if self.withdrawn: return if not self.mapped: self.deiconify() ### FIXME: This really could be done so much prettier to ### avoid having to upset the window stacking order. self.raisewindow() self.warppointer() # Explicitly set focus, in case we use a non-pointer-based # focus method self.wm.set_current_client(self) def send_message(self, atom, time = X.CurrentTime, args = []): if self.withdrawn: return if len(args) > 3: args = args[:3] elif len(args) < 3: args = args + [0] * (3 - len(args)) ev = Xlib.protocol.event.ClientMessage(window = self.window, client_type = self.wm.WM_PROTOCOLS, data = (32, ([atom, time] + args))) self.window.send_event(ev) def resize_increment(self): if self.sizehints and self.sizehints.flags & Xutil.PResizeInc: return self.sizehints.width_inc, self.sizehints.height_inc else: return 0, 0 def base_size(self): if self.sizehints and self.sizehints.flags & Xutil.PBaseSize: return self.sizehints.base_width, self.sizehints.base_height else: return 0, 0 def follow_size_hints(self, width, height): w = width h = height if self.sizehints: sh = self.sizehints # Fix resize increment stuff if sh.flags & Xutil.PResizeInc: # Find base size if sh.flags & Xutil.PBaseSize: base_width = sh.base_width base_height = sh.base_height # If no base size is provided, the minimum size should # be used instead, it that is provided elif sh.flags & Xutil.PMinSize: base_width = sh.min_width base_height = sh.min_height # Else no base size at all else: base_width = 0 base_height = 0 w = width - base_width h = height - base_height wi = w % sh.width_inc if wi != 0: if 2 * wi >= sh.width_inc: w = w + sh.width_inc - wi else: w = w - wi hi = h % sh.height_inc if hi != 0: if 2 * hi >= sh.height_inc: h = h + sh.height_inc - hi else: h = h - hi if self.full_screen_windows(self): root_width = self.screen.root_full_width root_height = self.screen.root_full_height else: root_width = self.screen.root_width root_height = self.screen.root_height # Make sure the window fits on screen rw = root_width - base_width - 2 * self.border_width rh = root_height - base_height - 2 * self.border_width if w > rw: w = rw - rw % sh.width_inc if h > rh: h = rh - rh % sh.height_inc w = w + base_width h = h + base_height # Use minimum size if provided if sh.flags & Xutil.PMinSize: if w < sh.min_width: w = sh.min_width if h < sh.min_height: h = sh.min_height # Fall back to base size if that is provided elif sh.flags & Xutil.PBaseSize: if w < sh.base_width: w = sh.base_width if h < sh.base_height: h = sh.base_height # Use maximum size, but there's no fallback if sh.flags & Xutil.PMaxSize: if w > sh.max_width: w = sh.max_width if h > sh.max_height: h = sh.max_height # Fixing aspect ratio? Nah. Not now. return w, h def warppointer(self, x = None, y = None): """Warp the pointer to the coordinate (X, Y) in this window. X and Y are the coordinates to move the pointer to. Positive values count from top/left edges, negative values from the bottom/right edges (the "negative" origo is (-1, -1)). If X and Y is omitted, warp to the default position (which normally is the middle of the window. A different default position can be stored in the mapping Client.default_pointer_pos. The key is the res_name or res_class of the window, the value a tuple of two integers: the x and y coordinates. """ if self.withdrawn: return # Get default position, if needed if x == None or y == None: if self.default_pointer_pos.has_key(self.res_name): x, y = self.default_pointer_pos[self.res_name] elif self.default_pointer_pos.has_key(self.res_class): x, y = self.default_pointer_pos[self.res_class] else: x = self.width / 2 y = self.height / 2 # Handle negative positions if x < 0: x = self.width + x - 1 if y < 0: y = self.height + y - 1 # Make sure that the pointer is inside the window if x < 0: x = 0 elif x >= self.width: x = self.width - 1 if y < 0: y = 0 elif y >= self.height: y = self.height - 1 self.window.warp_pointer(x, y) def fetch_name(self): if self.withdrawn: return None # Handle destroyed windows gracefully, registering the error # so the window is withdrawn from managing. try: return self.window.get_wm_name() except error.BadWindow, e: self.wm.events.put_event(e) return None def get_title(self): """Return an appropriate title for this client. """ name = self.fetch_name() if not name: if self.res_name: name = self.res_name else: name = '' return name def get_wm_state(self): if self.withdrawn: return Xutil.WithdrawnState try: r = self.window.get_wm_state() except error.BadWindow, e: self.wm.events.put_event(e) return None if r: return r.state else: return None class Screen: allow_self_changes = cfilter.all def __init__(self, wm, screenno): self.wm = wm self.number = screenno self.info = self.wm.display.screen(screenno) # Fetch root window and important values self.root = self.wm.display.screen(self.number).root g = self.root.get_geometry() self.root_x = 0 self.root_y = 0 self.root_width = g.width self.root_height = g.height self.root_full_width = g.width self.root_full_height = g.height self.windows = {} # Map proxy windows to actual windows self.proxy_windows = {} # Set up event handler for this screen self.dispatch = event.WindowDispatcher(self.root) ec = error.CatchError(error.BadAccess) self.dispatch.set_masks(X.SubstructureRedirectMask, onerror = ec) # And sync here, so we catch any errors caused by # failing to set SubstructureRedirectMask self.wm.display.sync() err = ec.get_error() if err: # Another wm already manages this screen: cancel raise UnavailableScreenError(err) # Fix a DISPLAY string for this screen by replacing the # screen number in the DISPLAY with this screen's number dstr = self.wm.display.get_display_name() m = re.search(r'(:\d+)(\.\d+)?$', dstr) if m: self.displaystring = dstr[:m.end(1)] + '.' + str(self.number) else: self.displaystring = dstr # Set up all the system handlers. # Handlers for the redirect events. We don't want to update the masks # as we explicitly set those masks above. self.dispatch.add_system_handler(X.MapRequest, self.handle_map_request, masks = ()) self.dispatch.add_system_handler(X.ConfigureRequest, self.handle_configure_request, masks = ()) self.dispatch.add_system_handler(X.CirculateRequest, self.handle_circulate_request, masks = ()) self.dispatch.add_system_handler(X.ClientMessage, self.handle_client_message) # Track screen changes self.dispatch.add_system_handler(X.EnterNotify, self.handle_screen_enter) self.dispatch.add_system_handler(X.ConfigureNotify, self.handle_screen_change) call_inits(self.__class__, '__screen_client_init__', self) # Find all clients, ignoring transient windows (override_redirect). r = self.root.query_tree() wins = r.children # Weed out icon windows (thanks to ctwm for the idea...) for w in wins[:]: wmh = w.get_wm_hints() if wmh and wmh.flags & Xutil.IconWindowHint: try: wins.remove(wmh.icon_window) except ValueError: pass # Then add any mapped window, or windows with non-withdrawn # WM_STATE property, unless it has override_redirect set. # Skip internal windows. for w in wins: a = w.get_attributes() r = w.get_wm_state() if (a.map_state != X.IsUnmapped or (r and r.state in (Xutil.NormalState, Xutil.IconicState))) \ and not a.override_redirect \ and not self.is_internal_window(w): self.add_client(w, 0) call_inits(self.__class__, '__screen_init__', self) def __del__(self): # Just call all __screen_del__ methods call_inits(self.__class__, '__screen_del__', self) def add_client(self, window, maprequest): """Add a client managing WINDOW. Returns 1 if a client was added, 0 if it was already managed. """ if self.is_client(window): return 0 else: debug('clients', 'Adding client for %s', window) client = self.wm.client_class(self, window, maprequest) # Use what client think its window is, to handle proxy windows self.windows[client.window] = client client.initial_map() self.wm.events.put_event(wmevents.AddClient(client)) return 1 def remove_window(self, window, destroyed = 0): """Remove the Window objet handling WINDOW. If DESTROYED is true, WINDOW doesn't exist anymore. """ debug('window', 'Removing window for %s', window) wobj = self.get_window(window) if wobj is None: return # If it is a Client object, post a RemoveClient event. As # that will not reach the client object, dispatch it manually. # Also, this must be done before withdrawing the window, as # that kills the event dispatcher. if isinstance(wobj, Client): debug('clientnotify', 'Sending RemoveClient for %s', window) evt = wmevents.RemoveClient(wobj) self.wm.events.put_event(evt) wobj.handle_event(evt) wobj.withdraw(destroyed) del self.windows[window] def add_internal_window(self, window): self.windows[window] = InternalWindow(self, window) return self.windows[window] def get_client(self, window): c = self.get_window(window) if isinstance(c, Client): return c else: return None def get_window(self, window): """Translate an Xlib window object into its controlling Window or Client object. Returns None if the Xlib window isn't known. """ while window is not None: try: return self.windows[window] except KeyError: # Try to translate via proxy window window = self.proxy_windows.get(window, None) return None def is_client(self, window): return isinstance(self.get_window(window), Client) def is_internal_window(self, window): return isinstance(self.get_window(window), InternalWindow) def add_proxy_window(self, proxy, window): """Add a proxy window, so that get_window() on PROXY leads on to WINDOW to find the actual Window object. """ self.proxy_windows[proxy] = window def remove_proxy_window(self, proxy): """Remove a proxy window previously registered with add_proxy_window(). """ del self.proxy_windows[proxy] def query_clients(self, client_filter = cfilter.all, stackorder = 0): """Return a list of clients on this screen matching CLIENT_FILTER. By default, all clients are returned in no particular order. But if STACKORDER is true, the clients will be returned in their current stacking order, lowest client first. """ if stackorder: wins = self.root.query_tree().children clients = [] for w in wins: c = self.get_client(w) if c and client_filter(c): clients.append(c) return clients else: # Use the internal filter function, but filter # out non-client windows by augmenting the client # filter return filter(cfilter.And(cfilter.is_client, client_filter), self.windows.values()) def alloc_border(self, edge, size): """Allocate a part of the outmost area of the root to display wm info in. Clients will not infringe on this area. EDGE is one of 'top', 'bottom', 'left', or 'right', indicating on which edge of the root the area should be placed against. SIZE is the height or width of the area, depending on EDGE. The return value is a tuple, giving the coordinates of the allocated area: (x, y, width, height) """ if edge == 'top': assert size < self.root_height c = (self.root_x, self.root_y, self.root_width, size) self.root_height = self.root_height - size self.root_y = self.root_y + size return c elif edge == 'bottom': assert size < self.root_height self.root_height = self.root_height - size return (self.root_x, self.root_y + self.root_height, self.root_width, size) elif edge == 'left': assert size < self.root_width c = (self.root_x, self.root_y, size, self.root_height) self.root_width = self.root_width - size self.root_x = self.root_x + size return c elif edge == 'right': assert size < self.root_height self.root_width = self.root_width - size return (self.root_x + self.root_width, self.root_y, size, self.root_height) else: raise TypeError('bad edge value: %s' % edge) def handle_event(self, event, window = None, grabbed = 0): grabbed = self.dispatch.handle_event(event, grabbed) if window: window.handle_event(event, grabbed) def handle_map_request(self, event): debug('redirect', 'Maprequest for client %s', event.window) # add the client, unless it already is managed if not self.add_client(event.window, 1): # already mapped window, map it if the user allows it w = self.get_client(event.window) if self.allow_self_changes(w): w.map() def event_to_change(self, event): change = {} if event.value_mask & X.CWX: change['x'] = event.x if event.value_mask & X.CWY: change['y'] = event.y if event.value_mask & X.CWWidth: change['width'] = event.width if event.value_mask & X.CWHeight: change['height'] = event.height w = self.get_window(event.window) if w and self.allow_self_changes(w): if event.value_mask & X.CWSibling: change['sibling'] = event.above if event.value_mask & X.CWStackMode: change['stack_mode'] = event.stack_mode return change def handle_configure_request(self, event): w = self.get_window(event.window) if w: debug('redirect', 'ConfigureRequest for window %s', event.window) apply(w.configure, (), self.event_to_change(event)) else: debug('redirect', 'ConfigureRequest for unmanaged window %s', event.window) apply(event.window.configure, (), self.event_to_change(event)) def handle_circulate_request(self, event): debug('redirect', 'CirculateRequest for %s', event.window) w = self.get_window(event.window) if not (w and self.allow_self_changes(w)): return if event.place == X.PlaceOnTop: if w: w.raisewindow() else: event.window.configure(stack_mode = X.Above) elif event.place == X.PlaceOnBottom: if w: w.lowerwindow() else: event.window.configure(stack_mode = X.Below) def handle_client_message(self, event): debug('client', 'client sent us a message: %s', event) w = self.get_client(event.window) if w is None: return if event.client_type == self.wm.WM_CHANGE_STATE and \ event.data[1][0] == Xutil.IconicState and \ self.allow_self_changes(w): w.iconify() def handle_screen_enter(self, event): # Mouse has entered this screen from another screen # iff these conditions are true: if event.window == self.root \ and event.detail in (X.NotifyNonlinear, X.NotifyNonlinearVirtual): self.wm.current_screen = self def handle_screen_change(self, event): """The screen changed geometry. Cool.""" if event.window == self.root and \ (self.root_full_width != event.width or \ self.root_full_height != event.height): self.root_full_width = self.root_width = event.width self.root_full_height = self.root_height = event.height self.wm.handle_screen_resize(event) def system(self, cmd, fg = 0, evt = None, redirect = None): """Run the shell command CMD, with DISPLAY set to this screen. CMD is run in the background by default, but is FG is true this call will block until it has finished. If EVT is not None, it should be a CommandEvent or subclass. This event will be put on the event queue when CMD exits. EVT is ignored though if FG is true. If REDIRECT is given, it should be either a single integer 0 to 2, or a tuple of them. These file descriptors will be redirected into pipes allowing the caller to capture the data. The pipes are returned as file objects in a three-tuple, with index N corresponding the the redirected file descriptor N. REDIRECT implies background execution. Returns None if CMD is run in the background and REDIRECT is None, if it is run in the foreground the exit status of CMD is returned as encoded by system(). """ # purge collected exit statuses of non-event children del self.wm.children_exit_status[:] if redirect is not None: if type(redirect) is not types.TupleType: redirect = (redirect, ) fg = 0 pipes = [None, None, None] for fd in redirect: if fd < 0 or fd > 2: raise ValueError('redirect file descriptor should be 0-2: %s' % fd) if pipes[fd]: raise ValueError('redirect file descriptor should not be repeated: %s' % redirect) pipes[fd] = os.pipe() pid = os.fork() if pid == 0: # Create new process group for the child, so it doesn't # get the signals sent to the parent process os.setpgid(0, 0) # Child process, run the command with a little help from sh os.environ['DISPLAY'] = self.displaystring try: # dup and close pipes on the child end if redirect: if pipes[0]: r, w = pipes[0] os.close(w) os.dup2(r, 0) os.close(r) if pipes[1]: r, w = pipes[1] os.close(r) os.dup2(w, 1) os.close(w) if pipes[2]: r, w = pipes[2] os.close(r) os.dup2(w, 2) os.close(w) os.execlp('sh', 'sh', '-c', cmd) except os.error, msg: # Failed to run the program sys.stderr.write('%s: %s: %s\n' % (sys.argv[0], cmd, str(msg))) os._exit(127) else: # Parent, should we block here? if fg: # check if child still exists try: p2, status = os.waitpid(pid, os.WNOHANG) if p2 == pid: return status except os.error, val: if val.errno != errno.ECHILD: raise val # it is gone, grab error from collected exit # statuses for p2, status in self.wm.children_exit_status: if pid == p2: return status # odd, the exit status have disappeared raise RuntimeError("foreground children exit status has gone, this can't happen!") # otherwise we must loop until the children exits while 1: # We must catch EINTR errors to avoid exceptions # when recieving SIGCHLD. try: signal.pause() except os.error, val: raise val if val.errno not in (errno.EINTR, errno.ECHILD): raise val for p2, status in self.wm.children_exit_status: if pid == p2: return status else: # FIXME: possible race condition here, should handle through # children_exit_status as above but can't be bothered # until something actually uses child events. if evt: self.wm.add_command_event(pid, evt) if redirect: # close pipes and create file objects # on the parent end if pipes[0]: r, w = pipes[0] os.close(r) pipes[0] = os.fdopen(w, 'w') if pipes[1]: r, w = pipes[1] os.close(w) pipes[1] = os.fdopen(r, 'r') if pipes[2]: r, w = pipes[2] os.close(w) pipes[2] = os.fdopen(r, 'r') return pipes else: return None # # Cyclops circular reference detecting support # def _register_cycle_roots(self, cyclefinder): """Called by the corresponding function in WindowManager. """ # We just add all the clients for c in self.clients.values(): cyclefinder.register(c) def _cleanup_cycle_roots(self): """Called by the corresponding function in WindowManager. """ for c in self.clients.values(): self.remove_client(c.window, 1) class WindowManager: client_class = Client screen_class = Screen appclass = 'Plwm' def __init__(self, disp, appname, db): """WindowManager(display, appname, rdb) Create a WindowManager object. """ self.display = disp self.appname = appname self.rdb = db # Set up some atoms not defined in Xatom self.WM_DELETE_WINDOW = self.display.intern_atom('WM_DELETE_WINDOW') self.WM_PROTOCOLS = self.display.intern_atom('WM_PROTOCOLS') self.WM_STATE = self.display.intern_atom('WM_STATE') self.WM_TAKE_FOCUS = self.display.intern_atom('WM_TAKE_FOCUS') self.WM_CHANGE_STATE = self.display.intern_atom('WM_CHANGE_STATE') debug('atoms', 'DELETE_WINDOW: %s\tPROTOCOLS: %s\tSTATE: %s\t' \ 'TAKE_FOCUS: %s\t, CHANGE_STATE: %s', self.WM_DELETE_WINDOW, self.WM_PROTOCOLS, self.WM_STATE, self.WM_TAKE_FOCUS, self.WM_CHANGE_STATE) if display is not None: os.environ['DISPLAY'] = self.display.get_display_name() # Set up the event handling. self.events = event.EventFetcher(self.display) # Install handlers for child processes self.child_events = {} self.children_exit_status = [] self.old_sigchld_handler = signal.signal(signal.SIGCHLD, self.sigchld_handler) if self.old_sigchld_handler in (signal.SIG_IGN, signal.SIG_DFL): self.old_sigchld_handler = None # current_client is the client which currently contains the # pointer, and on which window operations should take placce. # focus_client is the client which actually holds the input # focus. self.current_client = None self.focus_client = None # Setup a default focus policy, which can be overridden later self.display.set_input_focus(X.NONE, X.PointerRoot, X.RevertToPointerRoot) # Set up a screen-indepentend event handler self.dispatch = event.SlaveDispatcher([]) # Call mixin initialisation needed before adding screens call_inits(self.__class__, '__wm_screen_init__', self) # Find all the screens self.screens = [] self.screen_nums = {} self.screen_roots = {} for sno in range(0, self.display.screen_count()): try: s = self.screen_class(self, sno) self.screens.append(s) self.screen_nums[sno] = s self.screen_roots[s.root] = s self.dispatch.add_master(s.dispatch) # This screen is already managed by some other # window manager, print a message and skip it except UnavailableScreenError: sys.stderr.write('%s: Screen %d already managed by some other window manager\n' % (sys.argv[0], sno)) # If we cant find any screens, abort now if not self.screens: raise NoUnmanagedScreensError('another window manager already running?') try: self.default_screen = self.screen_nums[self.display.get_default_screen()] except KeyError: self.default_screen = self.screens[0] # Find the current screen self.current_screen = self.default_screen for s in self.screens: if s.root.query_pointer().same_screen: self.current_screen = s break # Handle to refresh the keyboard mapping information self.dispatch.add_system_handler(X.MappingNotify, self.handle_mapping_notify) # Handle errors caused by destroyed windows self.display.set_error_handler(self.x_error_handler) # Call all final mixin constructors call_inits(self.__class__, '__wm_init__', self) def __del__(self): # Just call all __wm_del__ methods call_inits(self.__class__, '__wm_del__', self) def loop(self): """Loop indefinitely, handling events. """ while 1: event = self.events.next_event() self.handle_event(event) if event.type == wmevents.QuitWindowManager: self.display.sync() return def brave_loop(self, max_exc = 10): """Loop indefinitely, handling events. If an exception occur, catch it and print a traceback. Contiune processing events while less than MAX_EXC exceptions has occured. """ exc = 0 while 1: try: event = self.events.next_event() self.handle_event(event) if event.type == wmevents.QuitWindowManager: self.display.sync() return # Pass on keyboardinterrupt, exiting loop except KeyboardInterrupt: raise # Print all other exceptions and continue except: if exc < max_exc: apply(traceback.print_exception, sys.exc_info()) exc = exc + 1 else: raise sys.exc_info()[0], sys.exc_info()[1] def quit(self): """Quit PLWM, or at least return to caller of loop() or brave_loop(). """ self.events.put_event(wmevents.QuitWindowManager()) def handle_events(self): """Handle all the events on the queue. """ while 1: event = self.events.next_event(timeout = 0) if event is None: return self.handle_event(event) def remove_window(self, window, destroyed = 0): """Remove the Window object of WINDOW. If DESTROYED is true, the window doesn't exist anymore. """ w = self.get_window(window) if w is not None: w.screen.remove_window(window, destroyed) def get_client(self, window): for s in self.screens: c = s.get_client(window) if c: return c return None def get_window(self, window): for s in self.screens: w = s.get_window(window) if w: return w return None def is_client(self, window): for s in self.screens: if s.is_client(window): return 1 return 0 def is_internal_window(self, window): for s in self.screens: if s.is_internal_window(window): return 1 return 0 def query_clients(self, client_filter = cfilter.all, stackorder = 0): """Return a list of clients on all screens, matching CLIENT_FILTER. By default, all clients are returned sorted by their screen number, but with no additional sorting. If STACKORDER is true, the clients will be returned in their current stacking order, lowest client first. """ clients = [] for s in self.screens: clients = clients + s.query_clients(client_filter, stackorder) return clients def x_error_handler(self, err, request): """Handle window errors by telling the client than it is withdrawn. """ sys.stderr.write('X protocol error:\n%s\n' % err) if isinstance(err, error.BadWindow) or isinstance(err, error.BadDrawable): # The error can't be handled immediately, as the error # handler must not call any Xlib methods. Solve this by # queing up the error object on the event queue and # checking for this in the event handler just below. # The event queue doesn't care about us putting an XError # there, it just returns it to us. self.events.put_event(err) def handle_event(self, event): """Handle EVENT by dispatching to registered handlers. First handlers for the WindowManager as whole is called, followed by the handlers for each of the managed screen's root window. Then if the events corresponds to a client, call its handlers. A grab handler for the window manager prevents any grab and normal handlers for the client to be called. """ debug('verbose_event', 'handling %s', event) # Continuation of the error handling hack: check if this is a # window error and if so remove it if isinstance(event, error.BadWindow) or isinstance(event, error.BadDrawable): self.remove_window(event.resource_id, 1) return grabbed = self.dispatch.handle_event(event) if hasattr(event, 'client') and event.client: event.client.screen.handle_event(event, event.client, grabbed) return if hasattr(event, 'window'): window = event.window # Find the correct screen. # First check if the event window is # the root window or is an already handled client for # some screen for s in self.screens: if window == s.root: s.handle_event(event, None, grabbed) return w = s.get_window(window) if w: s.handle_event(event, w, grabbed) return # Unknown window, ask for it's root window if window: try: root = window.get_geometry().root s = self.screen_roots[root] # Bad window, or unmanaged screen, just abort except (error.BadDrawable, KeyError): pass else: s.handle_event(event, None, grabbed) return # Event destined for specific screen if hasattr(event, 'screen') and event.screen: event.screen.handle_event(event, None, grabbed) # Unmanaged screen, ignore return def handle_mapping_notify(self, event): debug('keys', 'MappingNotify event') self.display.refresh_keyboard_mapping(event) def handle_screen_resize(self, event): # The default window manager does nada; but let mixins know call_inits(self.__class__, '__wm_screen_resize__', self) def set_current_client(self, client, time = X.CurrentTime, force_focus = 0): """Set the current client to CLIENT, occuring at the X event TIME. This will update the WindowManager.current_client and Client.current of the affected clients. A CurrentClientChange event will be sent with the new current client. CLIENT can also be None, meaning that now no client is the current one. If the client accepts focus, or FORCE_FOCUS is true, CLIENT will get the focus, and WindowManager.focus_client and Client.focused will be changed. ClientFocusOut and ClientFocusIn events will be sent. """ # Will the new current client also get focus? # If client is None, we treat that as getting focus if client: getfocus = (client.do_set_focus or force_focus) else: getfocus = 1 # If focus will change, drop focus from the old focused client if getfocus and self.focus_client and self.focus_client != client: self.focus_client.lose_focus() self.focus_client = None # If current client changes, update that if self.current_client != client: if self.current_client: self.current_client.current = 0 screen = self.current_client.screen else: screen = None if client: client.current = 1 self.current_client = client self.events.put_event(wmevents.CurrentClientChange(screen, client)) debug('focus', 'current client: %s', self.current_client) # Finally, give the client focus if it should have it if client: if getfocus and self.focus_client != client: self.focus_client = client client.get_focus(time) # No client, reset focus else: self.display.set_input_focus(X.PointerRoot, X.RevertToPointerRoot, time) def rdb_get(self, res, cls, default = None): """rdb_get(res, cls, default = None) Get a value from the resource database. RES and CLS should omit the first component, and thus start with periods '.'. This is because the application name (fetched from sys.argv[0]) will be prepended to RES, and self.appclass will be prepended to CLS. DEFAULT is returned if the resource can't be found. """ return self.rdb.get(self.appname + res, self.appclass + cls, default) def system(self, cmd, fg = 0, evt = None, redirect = None): """Run CMD on the current screen. """ return self.current_screen.system(cmd, fg = fg, evt = evt, redirect = redirect) def add_command_event(self, pid, evt): """Add PID to the list of child processes to wait for, inserting EVT in the event queue when it does. """ self.child_events[pid] = evt def sigchld_handler(self, sig, frame): """Signal handler for SIGCHLD. """ while 1: try: pid, status = os.waitpid(-1, os.WNOHANG) if pid == 0: break # child with event handler if self.child_events.has_key(pid): evt = self.child_events[pid] del self.child_events[pid] evt.status = status self.events.put_event(evt) # unmanaged child, we might be interested in it anyway # so record it. else: self.children_exit_status.append((pid, status)) except os.error, val: if val.errno == errno.EINTR: continue if val.errno == errno.ECHILD: break raise val # Call the old sigchld handler, if any if self.old_sigchld_handler: self.old_sigchld_handler(sig, frame) def fake_button_click(self, button): self.display.xtest_fake_input(X.ButtonPress, button, 0) self.display.xtest_fake_input(X.ButtonRelease, button, 5) self.display.flush() # # Cyclops circular reference detecting support # def _register_cycle_roots(self, cyclefinder): """Call this function after having initialised the WindowManager. It will register all dynamically added objects, mainly Clients. Then call loop or brave_loop with the run method of the cyclefinder object. """ # Currently, the WindowManager has no dynamic objects, # only static ones like Screen. So ask them. for s in self.screens: s._register_cycle_roots(cyclefinder) def _cleanup_cycle_roots(self): """Free all dynamically added objects. """ # WindowManager only has this link to any Clients self.focus_client = None # But the screens have more... for s in self.screens: s._cleanup_cycle_roots() def call_inits(cls, method, obj): """Call constructors for all mixin classes. If CLS has a method named METHOD, it will be called with OBJ as an argument. Otherwise call_inits will be called recursively for each base class of CLS. """ if cls.__dict__.has_key(method): cls.__dict__[method](obj) else: for c in cls.__bases__: call_inits(c, method, obj) class WindowProxyBase: """Base class for proxy objects for the real Xlib window objects. These proxies can override the Xlib window object methods when necessary. """ def __init__(self, screen, window): self._screen = screen self._window = window self._wm = screen.wm # These Xlib casts allows the proxy object to be passed to any # Xlib method expecting one of the following objects self.__resource__ = window.__resource__ self.__drawable__ = window.__resource__ self.__window__ = window.__resource__ # And these allows the proxy, for dictionary purposes, to # pretend to be the actual window object. self.__cmp__ = window.__cmp__ self.__hash__ = window.__hash__ def __proxy_event_init__(self, client): # Client can now register event handlers self._client = client def __getattr__(self, attr): # Unknown attribute in the proxy, grab it from the real window instead v = getattr(self._window, attr) # And avoid this getting called again for this attr setattr(self, attr, v) return v def _proxy_withdraw(self): """Called by Client when the proxied window is gone, to allow any cleanup. """ pass def __str__(self): return '<%s for %s 0x%08x>' % ( self.__class__, self._window.__class__, self._window.id) def __repr__(self): return self.__str__() # main function, parsing some common options and doing # all that typical initialization. wm_options = rdb.stdopts.copy() wm_options['-debug'] = rdb.SepArg('.debug') wm_options['-version'] = rdb.IsArg('.version') def main(wmclass, extra_options = {}, xlib_version = required_xlib_version): global debug, debug_what # Check Xlib version if Xlib.__version__ < xlib_version: raise BadXlibVersion('requires Xlib %s, found %s' % (string.join(map(str, xlib_version), '.'), string.join(map(str, Xlib.__version__), '.'))) opts = wm_options.copy() opts.update(extra_options) disp, name, db, args = rdb.get_display_opts(opts) # Print PLWM version, if set version = db.get(name + '.version', 'PLWM.Version', None) if version == '-version': try: sys.stderr.write('PLWM version: %s\n' % plwm.__version__) except AttributeError: sys.stderr.write('PLWM version: unknown (probably in build tree)\n') # Get debug info dbg = db.get(name + '.debug', 'PLWM.Debug', None) # Parse debugstring, separated by commas, and remove any whitespace and # empty elements if dbg is not None: dbg = string.split(dbg, ',') dbg = map(string.strip, dbg) dbg = filter(None, dbg) debug_what = dbg debug = do_debug try: wm = wmclass(disp, name, db) except NoUnmanagedScreensError: sys.stderr.write(sys.argv[0] + ': Another window manager already running?\n') sys.exit(1) try: wm.brave_loop() # Exit gracefully on Ctrl-C except KeyboardInterrupt: sys.exit(0) python-plwm-2.6a+20080530/plwm/modestatus.py0000644000175000017500000001346110765600750017177 0ustar stewstew# # modestatus.py -- display various status information in xmodewin # # Copyright (C) 2000-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import modewindow, wmanager, moveresize, wmevents from Xlib import X, Xatom # Screen mixin, requires ModeWindow class ModeStatus: def __screen_client_init__(self): self.modestatus_modes = [ModeText(self, '')] self.modestatus_message = modewindow.Message(.5, modewindow.CENTER) self.modewindow_add_message(self.modestatus_message) def modestatus_new(self, text = ''): """Creates and returns a new ModeText object. The object will be put on top of the display stack. TEXT is the initial message of the object, an empty string if omitted. """ mt = ModeText(self, text) self.modestatus_modes.append(mt) self.modestatus_message.set_text(text) return mt def modestatus_set_default(self, text): """Set the default status message to TEXT. """ self.modestatus_modes[0].set(text) ### Internal functions below def modestatus_update(self, modetext): """Internal function, called by MODETEXT when it has changed. """ if modetext is self.modestatus_modes[-1]: self.modestatus_message.set_text(modetext.text) def modestatus_remove(self, modetext): """Internal function, called by MODETEXT when it is popped. """ try: self.modestatus_modes.remove(modetext) except ValueError: pass self.modestatus_message.set_text(self.modestatus_modes[-1].text) class ModeText: """Class representing a mode status message. Don't instantiate it directly, use the modestatus_new() method of the screen instead. """ def __init__(self, screen, text): self.screen = screen self.text = text def set(self, text): """Change the message to TEXT. """ self.text = text self.screen.modestatus_update(self) def pop(self): """Remove this message from the display stack. """ self.screen.modestatus_remove(self) # # Various mode functions # # Client mixin class ModeFocusedTitleClient: def __client_init__(self): self.dispatch.add_handler(X.PropertyNotify, self.modefocusedtitle_property_notify) def modefocusedtitle_property_notify(self, event): if self.current and event.atom == Xatom.WM_NAME: self.screen.modestatus_set_default(self.get_title()) # Screen mixin class ModeFocusedTitleScreen: def __screen_init__(self): self.dispatch.add_handler(wmevents.CurrentClientChange, self.modefocusedtitle_change) def modefocusedtitle_change(self, evt): if evt.client: # Reset modewindow on other screen if evt.screen and evt.screen != evt.client: evt.screen.modestatus_set_default('') # Set modewindow on this screen self.modestatus_set_default(evt.client.get_title()) # Reset this modewindow else: self.modestatus_set_default('') # Screen mixin class ModeMoveResize: def __screen_init__(self): res = '.moveResize.modeFormat' cls = '.MoveResize.ModeFormat' default = '%(title)s [%(geometry)s]' self.modemoveresize_format = self.wm.rdb_get(res, cls, default) self.modemoveresize_text = None self.modemoveresize_tags = {} self.modemoveresize_hints = None self.dispatch.add_handler(moveresize.MoveResizeStart, self.modemoveresize_start) self.dispatch.add_handler(moveresize.MoveResizeDo, self.modemoveresize_do) self.dispatch.add_handler(moveresize.MoveResizeEnd, self.modemoveresize_end) self.dispatch.add_handler(moveresize.MoveResizeAbort, self.modemoveresize_end) def modemoveresize_start(self, ev): self.modemoveresize_tags['title'] = ev.client.get_title() self.modemoveresize_hints = ev.client.resize_increment() + ev.client.base_size() x, y, w, h = ev.client.geometry()[0:4] msg = self.modemoveresize_format_text(x, y, w, h) self.modemoveresize_text = self.modestatus_new(msg) def modemoveresize_do(self, ev): if self.modemoveresize_text: msg = self.modemoveresize_format_text(ev.x, ev.y, ev.width, ev.height) self.modemoveresize_text.set(msg) def modemoveresize_end(self, ev): if self.modemoveresize_text: self.modemoveresize_text.pop() self.modemoveresize_text = None self.modemoveresize_title = None self.modemoveresize_hints = None def modemoveresize_format_text(self, x, y, w, h): wi, hi, wb, hb = self.modemoveresize_hints if wi and wi > 1: w = (w - wb) / wi if hi and hi > 1: h = (h - hb) / hi self.modemoveresize_tags['geometry'] = '%dx%d+%d+%d' % (w, h, x, y) return self.modemoveresize_format % self.modemoveresize_tags python-plwm-2.6a+20080530/plwm/mw_acpi.py0000644000175000017500000004163411004403603016412 0ustar stewstew# -*- coding: iso-8859-1 -*- # # mw_acpi.py -- display ACPI AC/battery status in a modewindow # # Copyright (C) 2004 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # from plwm import modewindow, event, wmanager import sys import os import time import re import socket TimerEventType = event.new_event_type() ACPIEventType = event.new_event_type() # ACPI interface: # probe: check if interface is present # get_event_socket: return an event socket, if any # handle_event_socket: process data read from event socket, return # pair with new status: (status, numbeeps), or None if no change. # poll: check all data, return pair (status, numbeeps) or None as above class BadInfo(Exception): pass class LinuxProcACPI: acpid_socket = '/var/run/acpid.socket' event_re = re.compile(r'^(\S+) (\S+) ') class AcAdapter: EVENT = 'ac_adapter' PROC_DIR = '/proc/acpi/ac_adapter' STATE = 'state' ONLINE = 'on-line' def __init__(self, id, path): self.id = id self.path = path self.messsage = '' self.state = None self.update() def update(self): state = linux_proc_read_state(self.path) if self.state != state[self.STATE]: self.state = state[self.STATE] if self.state == self.ONLINE: self.message = 'AC' else: self.message = '' return 1 else: return 0 class Battery: EVENT = 'battery' PROC_DIR = '/proc/acpi/battery' PRESENT = 'present' MAX = 'design capacity' STATE = 'charging state' RATE = 'present rate' REMAIN = 'remaining capacity' mW_re = re.compile(r'^(\d+) m[AW]$') mWh_re = re.compile(r'^(\d+) m[AW]h$') def __init__(self, id, path): self.id = id self.path = path self.messsage = '' self.state = None self.rate = None self.remain = None info = linux_proc_read_info(self.path) try: # Fetch all values from state if info[self.PRESENT] != 'yes': raise BadInfo('battery %s not present (value: %s)' % (self.id, info[self.PRESENT])) m = self.mWh_re.match(info[self.MAX]) if m is None: raise BadInfo('expected mWh value: %s' % info[self.MAX]) self.max = int(m.group(1)) self.update() except KeyError, e: raise BadInfo('battery %s missing required info: %s' % (self.id, e)) def update(self): state = linux_proc_read_state(self.path) new_state = state[self.STATE] new_rate = state[self.RATE] new_remain = state[self.REMAIN] # Check values if new_state not in ('charged', 'unknown', 'discharging', 'charging'): raise BadInfo('unknown charging state: %s' % new_state) if new_rate != 'unknown': m = self.mW_re.match(new_rate) if m is None: raise BadInfo('expected mW value: %s' % new_rate) new_rate = int(m.group(1)) else: new_rate = None m = self.mWh_re.match(new_remain) if m is None: raise BadInfo('expected mWh value: %s' % new_remain) new_remain = int(m.group(1)) updated = (new_state != self.state or new_rate != self.rate or new_remain != self.remain) if updated: self.state = new_state self.rate = new_rate self.remain = new_remain # Calculate percentage and remaning time self.percent = int(100.0 * self.remain / self.max) remaining = 0 if self.rate is not None: if self.state == 'charging': remaining = self.max - self.remain elif self.state == 'discharging': remaining = self.remain if remaining > 0: self.seconds_left = (3600 * remaining) / self.rate secstr = ' (%d:%02d:%02d)' % (self.seconds_left / 3600, (self.seconds_left / 60) % 60, self.seconds_left % 60) else: self.seconds_left = None secstr = '' if self.state == 'discharging': self.message = '%d%%%s' % (self.percent, secstr) elif self.state == 'charging': self.message = 'charging %d%%%s' % (self.percent, secstr) else: self.message = '' return updated class Processor: EVENT = 'processor' PROC_DIR = '/proc/acpi/processor' PERFORMANCE_MANAGEMENT = 'performance management' THROTTLING_CONTROL = 'throttling control' ACTIVE_STATE = 'active state' FREQ_RE = re.compile(r'^(\d+) MHz') PERCENT_RE = re.compile(r'^(\d+)%') def __init__(self, id, path): self.id = id self.path = path self.performance_file = os.path.join(path, 'performance') self.throttling_file = os.path.join(path, 'throttling') self.messsage = '' info = linux_proc_read_info(path) self.has_performance = info.get(self.PERFORMANCE_MANAGEMENT) == 'yes' self.has_throttling = info.get(self.THROTTLING_CONTROL) == 'yes' if not self.has_performance and not self.has_throttling: raise BadInfo('neither performance nor throttling control, nothing to display') self.performance_state = None self.performance_freq = None self.throttling_state = None self.throttling_percent = None self.update() def update(self): change = 0 if self.has_performance: perf = linux_proc_read_values(self.performance_file) if self.performance_state != perf[self.ACTIVE_STATE]: self.performance_state = perf[self.ACTIVE_STATE] try: freq = perf['*%s' % self.performance_state] except KeyError: raise BadInfo('unknown performance state: %s' % self.performance_state) m = self.FREQ_RE.match(freq) if not m: raise BadInfo('unknown performance info: %s' % freq) self.performance_freq = int(m.group(1)) change = 1 if self.has_throttling: thr = linux_proc_read_values(self.throttling_file) if self.throttling_state != thr[self.ACTIVE_STATE]: self.throttling_state = thr[self.ACTIVE_STATE] try: percent = thr['*%s' % self.throttling_state] except KeyError: print thr raise BadInfo('unknown throttling state: %s' % self.throttling_state) m = self.PERCENT_RE.match(percent) if not m: raise BadInfo('unknown throttling info: %s' % percent) self.throttling_percent = int(m.group(1)) change = 1 if change: msg = [] if self.has_performance: msg.append('%dMHz' % self.performance_freq) if self.has_throttling and self.throttling_percent > 0: msg.append('T%d%%' % self.throttling_percent) self.message = ' '.join(msg) return 1 else: return 0 class ThermalZone: EVENT = None PROC_DIR = '/proc/acpi/thermal_zone' STATE = 'state' TEMPERATURE = 'temperature' DEGREE_RE = re.compile(r'^(\d+) C$') def __init__(self, id, path): self.id = id self.path = path self.temperature_file = os.path.join(path, 'temperature') self.messsage = '' self.degrees = None self.update() def update(self): temps = linux_proc_read_values(self.temperature_file) m = self.DEGREE_RE.match(temps[self.TEMPERATURE]) if not m: raise BadInfo('bad temperature: %s' % temps[self.TEMPERATURE]) new_degrees = int(m.group(1)) if self.degrees != new_degrees: self.degrees = new_degrees self.message = '%d°C' % self.degrees return 1 else: return 0 # Not really ACPI, this one. But on Linux 2.6 kernels, the CPU frequency isn't # displayed in the ACPI interface, but from the cpu-freq module. class CpuFreqScaling: EVENT = None PROC_DIR = '/sys/devices/system/cpu' SCALING_CPU_FREQ = '%s/cpufreq/scaling_cur_freq' def __init__(self, id, path): self.id = id self.path = path self.scaling_cpu_freq = self.SCALING_CPU_FREQ % (path, ) self.messsage = '' # frequency in kHz self.freq = None self.update() def update(self): try: new_freq = int(open(self.scaling_cpu_freq).read(50)) except (IOError, ValueError), e: raise BadInfo('bad cpufreq: %s: %s' % (self.scaling_cpu_freq, e)) if self.freq != new_freq: self.freq = new_freq self.message = '%d MHz' % (self.freq / 1000) return 1 else: return 0 def __init__(self): self.socket = None self.event_data = '' self.status_changed = 0 self.infos = [] # Map of (class, unit) -> object self.units = {} def add_units(self, cls): for unit, path in self.find_units(cls.PROC_DIR): try: b = cls(unit, path) self.infos.append(b) self.units[(cls.EVENT, unit)] = b except (IOError, BadInfo), e: wmanager.debug('acpi', 'could not add %s %s: %s', cls.__name__, unit, e) def find_units(self, path): try: subdirs = os.listdir(path) except OSError, e: wmanager.debug('acpi', 'no such dir: %s', path) return () subdirs.sort() return [ (d, os.path.join(path, d)) for d in subdirs if os.path.isdir(os.path.join(path, d)) ] def handle_event(self, eventstr): m = self.event_re.match(eventstr) if not m: wmanager.debug('acpi', 'could parse event, closing socket:', eventstr) self.socket.close() return event = m.group(1).replace('/', '_') arg = m.group(2) try: unit = self.units[(event, arg)] except KeyError: wmanager.debug('acpi', 'unknown event: %s', eventstr) return if unit.update(): self.status_changed = 1 # Interface functions below def probe(self): if os.path.isdir('/proc/acpi'): wmanager.debug('acpi', 'using Linux proc interface') # See if there's also an acpid demon try: self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.socket.connect(self.acpid_socket) except socket.error: wmanager.debug('acpi', 'could not open %s, relying solely on polling', self.acpid_socket) self.socket = None self.add_units(self.AcAdapter) self.add_units(self.Battery) self.add_units(self.Processor) self.add_units(self.CpuFreqScaling) self.add_units(self.ThermalZone) return 1 else: return 0 def get_event_socket(self): return self.socket def handle_event_socket(self, data): self.event_data += data self.status_changed = 0 while 1: lines = self.event_data.split('\n', 1) # Not a complete line, stop if len(lines) == 1: break event = lines[0] self.event_data = lines[1] wmanager.debug('acpi', 'got event: %s', event) self.handle_event(event) if self.status_changed: return (' '.join([i.message for i in self.infos if i.message]), 0) else: return None def poll(self, force): self.status_changed = 0 for i in self.infos: if i.update(): self.status_changed = 1 if self.status_changed or force: return (' '.join([i.message for i in self.infos if i.message]), 0) else: return None LINUX_PROC_VALUE_RE = re.compile(r'^\s*([^:]+):' '[ \t]' r'*(.*\S)\s*$', re.MULTILINE) def linux_proc_read_values(file): """Read a key: value file, returning a map of strings""" data = open(file).read() values = LINUX_PROC_VALUE_RE.findall(data) return dict(values) def linux_proc_read_state(dir): return linux_proc_read_values(os.path.join(dir, 'state')) def linux_proc_read_info(dir): return linux_proc_read_values(os.path.join(dir, 'info')) acpi_interfaces = [ LinuxProcACPI(), ] # wm mixin class ModeWindowACPI: mw_acpi_position = 0.2 mw_acpi_justification = modewindow.RIGHT mw_acpi_degree_symbol = "°" def __wm_init__(self): # Figure out which ACPI interface to use for ai in acpi_interfaces: if ai.probe(): self.mw_acpi_interface = ai break else: # No matching ACPI interface, skip rest of our installation sys.stderr.write('%s: failed to find a parsable ACPI interface, disabling mw_acpi' % sys.argv[0]) return self.mw_acpi_message = modewindow.Message(self.mw_acpi_position, self.mw_acpi_justification) for s in self.screens: s.modewindow_add_message(self.mw_acpi_message) self.dispatch.add_handler(TimerEventType, self.mw_acpi_tick) self.mw_acpi_socket = ai.get_event_socket() if self.mw_acpi_socket: self.dispatch.add_handler(ACPIEventType, self.mw_acpi_handle_socket_event) self.mw_acpi_socket_event = event.FileEvent(ACPIEventType, self.mw_acpi_socket, event.FileEvent.READ) self.events.add_file(self.mw_acpi_socket_event) self.mw_acpi_poll(1) def mw_acpi_update(self, newstatus): if newstatus is not None: msg, beeps = newstatus self.mw_acpi_message.set_text(msg.replace("°", self.mw_acpi_degree_symbol)) # Beep if status have changed if beeps: for i in range(beeps): self.display.bell(100) def mw_acpi_tick(self, evt): self.mw_acpi_poll() def mw_acpi_poll(self, force = 0): newstatus = self.mw_acpi_interface.poll(force) self.mw_acpi_update(newstatus) # Recheck in 30 seconds self.events.add_timer(event.TimerEvent(TimerEventType, after = 30)) def mw_acpi_handle_socket_event(self, evt): if evt.state & event.FileEvent.READ: try: data = self.mw_acpi_socket.recv(200) except socket.error, e: wmanager.debug('acpi', 'failed to receive from event socket: %s', e) data = '' if data: newstatus = self.mw_acpi_interface.handle_event_socket(data) self.mw_acpi_update(newstatus) else: wmanager.debug('acpi', 'event socket closed') self.mw_acpi_socket.close() self.mw_acpi_socket = None self.mw_acpi_socket_event.cancel() python-plwm-2.6a+20080530/plwm/__init__.py0000644000175000017500000000342411010153410016520 0ustar stewstew# # __init__.py for package plwm # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """PLWM - The Pointless Window Manager This module provides various window manager components, allowing you to build your own window manager. """ __version_number__ = (2, 6) __version_extra__ = 'a' __version__ = '.'.join(map(str, __version_number__)) + __version_extra__ __all__ = [ 'border', 'cfilter', 'color', 'composite', 'cycle', 'deltamove', 'event', 'focus', 'font', 'frame', 'input', 'inspect', 'keys', 'menu', 'message', 'misc', 'modestatus', 'modewindow', 'moveresize', 'mw_acpi', 'mw_apm', 'mw_biff', 'mw_clock', 'mw_load', 'mw_watchfiles', 'outline', 'panes', 'views', 'wmanager', 'wmevents', ] python-plwm-2.6a+20080530/plwm/panes.py0000644000175000017500000003540111003503177016102 0ustar stewstew# # panes.py -- Handle panes (sometimes known as "frames") # # Copyright (C) 2001 Mike Meyer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Panes - provide panes to put plwm windows in. The idea is that each screen is completely covered by panes. Each pixel on the screen must belong to one and only one pane. Focus ignores the mouse, but is moved from pane to pane via the keyboard. Windows open in the current pane, and are told to resize themselves to fit that pane. The rest of the UI - well, that's up to you.""" from Xlib import X, Xutil, Xatom from plwm import wmanager, wmevents, modewindow, cfilter WM_TRANSIENT_FOR = None class panesManager: "panesManager - pane mixin for window manager." panes_window_gravity = X.CenterGravity panes_maxsize_gravity = X.CenterGravity panes_transient_gravity = X.CenterGravity def __wm_screen_init__(self): "Create the list of panes with no current pane." global WM_TRANSIENT_FOR wmanager.debug('panesManager', 'inited') # Warning - if we manage more than one display, this breaks! if not WM_TRANSIENT_FOR: WM_TRANSIENT_FOR = self.display.intern_atom("WM_TRANSIENT_FOR") self.panes_list = [] self.panes_current = None def __wm_init__(self): "Enable activation, then activate the first pane." Pane.activate = Pane.do_activate self.panes_list[self.panes_current].activate() def panes_add(self, pane): "Add the given pane to the list of all panes." wmanager.debug('panesManager', 'added pane %s', `pane`) self.panes_list.append(pane) if self.panes_current is None: self.panes_current = 0 def panes_remove(self, test): "Remove panes that match the filter." old = self.panes_list[self.panes_current] self.panes_list = filter(cfilter.Not(test), self.panes_list) try: self.panes_current = self.panes_list.index(old) except ValueError: self.panes_current = 0 def panes_goto(self, new): "Go to the given pane." if new == self.panes_current: return if 0 <= new < len(self.panes_list): self.panes_list[self.panes_current].deactivate() self.panes_current = new self.panes_list[new].activate() def panes_activate(self, pane): "Make the given pane the current pane." self.panes_goto(self.panes_list.index(pane)) def panes_next(self): "Move to the next pane." if len(self.panes_list) > 1: wmanager.debug('panesManager', 'next pane') self.panes_list[self.panes_current].deactivate() self.panes_current = self.panes_current + 1 if self.panes_current == len(self.panes_list): self.panes_current = 0 self.panes_list[self.panes_current].activate() def panes_prev(self): "Move to the previous pane." if len(self.panes_list) > 1: wmanager.debug('panesManager', 'previous pane') self.panes_list[self.panes_current].deactivate() self.panes_current = self.panes_current - 1 if self.panes_current < 0: self.panes_current = len(self.panes_list) - 1 self.panes_list[self.panes_current].activate() def panes_number(self, number): "Reorder the panes list so the current pane has the given number." self.panes_list[self.panes_current], self.panes_list[number] = \ self.panes_list[number], \ self.panes_list[self.panes_current] self.panes_current = number # The intent is that if the user has a favorite configuration of panes # and windows, that gets set up and then panes_save gets invoked. Later, # the windows can be put back in the same panes by invoking panes_restore. # I recommend a script that recreates the pane configuration then # calls panes_restore be in the config file. panes_saved = {} def panes_save(self): "Record which pane all the windows are in." self.panes_saved.clear() for client in self.query_clients(): if client.panes_pane: self.panes_saved[client] = self.panes_list.index(client.panes_pane) def panes_restore(self): "Put the clients back in the saved panes." clients = self.query_clients() for client in clients: pane = self.panes_saved.get(client, None) if pane is not None and 0 <= pane < len(self.panes_list) and \ self.panes_list[pane].screen == client.screen: self.panes_list[pane].add_window(client) for client in clients: if not client.panes_pane: client.iconify() class panesScreen: "paneScreen - pane mixin for Screens." def __screen_client_init__(self): "Create the initial pane object for this screen." wmanager.debug('panesScreen', 'Initializing screen %d', self.number) self.dispatch.add_handler(X.ConfigureRequest, self.panes_configure) pane = Pane(self, 0, 0, self.root_width, self.root_height) self.panes_fullscreen(pane) self.wm.panes_add(pane) def panes_fullscreen(self, pane): "Make the pane use the all the available screen." pane.width = self.root_width pane.x = 0 pane.height = self.root_height pane.y = 0 def panes_configure(self, event): "A window changed, so pass it on to my pane." w = self.get_window(event.window) if w and w.panes_pane: if event.value_mask & (X.CWX | X.CWY | X.CWWidth | X.CWHeight): w.panes_pane.place_window(w) if event.value_mask & X.CWStackMode and event.stack_mode == X.Above \ and self.allow_self_changes(w): w.panes_pane.add_window(w) class panesClient: """panesClient - pane mixin for clients Note that this needs to be mixed in *after* any mixins that affect window geometry, such as border.""" def __client_init__(self): "Arrange to open in the current pane." wmanager.debug('Pane', 'Initing client %s', self) # Set this clients gravity if self.window.get_property(WM_TRANSIENT_FOR, Xatom.WINDOW, 0, 1) is not None: self.panes_gravity = self.wm.panes_transient_gravity elif self.sizehints and self.sizehints.flags & Xutil.PMaxSize: self.panes_gravity = self.wm.panes_maxsize_gravity else: self.panes_gravity = self.wm.panes_window_gravity self.panes_pointer_pos = self.panes_pane = None pane = self.wm.panes_list[self.wm.panes_current] if pane.screen != self.screen: pane = filter(lambda p, m=self.screen: p.screen == m, self.wm.panes_list)[0] pane.add_window(self) self.dispatch.add_handler(X.UnmapNotify, self.panes_unmap) self.dispatch.add_handler(X.DestroyNotify, self.panes_unmap) def panes_unmap(self, event): "The window is going away or gone - make sure it's not taking up a pane" if self.panes_pane: self.panes_pane.remove_window(self) class Pane: "Pane - the object(s) that manages windows on the screen." def __init__(self, screen, x, y, width, height): "Initialize a pane of the given size on the given screen." self.screen, self.x, self.y, self.width, self.height = screen, x, y, width, height self.wm = screen.wm self.window = None def add_window(self, window): "Add a window to this pane." wmanager.debug('Pane', 'Adding window %s to pane %s', window, self) if window == self.window: return old = window.panes_pane if old != self: if old: old.remove_window(window) self.place_window(window) window.panes_pane = self if self.window: self.deactivate() self.window = window self.activate() def iconify_window(self): "Iconify my window, if any." if self.window: self.window.iconify() self.remove_window(self.window) def remove_window(self, window): "Tag a window as not belonging to me." wmanager.debug('Pane', 'Removing window %s from pane %s' % (window, self)) window.panes_pane = None if self.window == window: self.deactivate() clients = self.screen.query_clients(panefilter(self), 1) if not clients: self.window = None else: self.window = clients[len(clients) - 1] if self.wm.panes_list[self.wm.panes_current] == self: self.activate() def place_window(self, window = None): "Figure out where the window should be put." if not window: window = self.window wmanager.debug('Pane', 'Placing window %s for pane %s' % (window, self)) width, height = window.follow_size_hints(self.width - 2 * window.border_width, self.height - 2 * window.border_width) # If it doesn't fit, just force it. if width > self.width - 2 * window.border_width: width = self.width - 2 * window.border_width if height > self.height - 2 * window.border_width: height = self.height - 2 * window.border_width if window.panes_gravity in (X.NorthEastGravity, X.EastGravity, X.SouthEastGravity): x = self.x elif window.panes_gravity in (X.NorthGravity, X.CenterGravity, X.SouthGravity): x = self.x + (self.width - width) / 2 - window.border_width else: x = self.x + self.width - width - (2 * window.border_width) if window.panes_gravity in (X.NorthEastGravity, X.NorthGravity, X.NorthWestGravity): y = self.y elif window.panes_gravity in (X.EastGravity, X.CenterGravity, X.WestGravity): y = self.y + (self.height - height) / 2 - window.border_width else: y = self.y + self.height - height - (2 * window.border_width) x, y, width, height = window.keep_on_screen(x, y, width, height) wmanager.debug('Pane-configure', 'Resizing window from %d, %d to %d, %d' % (window.width, window.height, width, height)) window.moveresize(x, y, width, height) def force_window(self): "Try and force an application to notice what size it's window is." if not self.window: return self.window.resize(self.width / 2, self.height / 2) self.wm.display.flush() self.place_window() def next_window(self): "Move to the next window in this pane." wmanager.debug('Pane', 'next window') clients = self.screen.query_clients(panefilter(self), 1) if len(clients) > 1: self.deactivate() self.window = clients[0] self.activate() def prev_window(self): "Move to the previous window in this pane." wmanager.debug('Pane', 'previous window') clients = self.screen.query_clients(panefilter(self), 1) if len(clients) > 1: self.deactivate() # Lower the old window to make it the "next" window. self.window.lowerwindow() self.window = clients[len(clients) - 2] self.activate() def deactivate(self): "A place to do anything appropriate for us when losing the focus." if self.window and not self.window.withdrawn: self.window.panes_pointer_pos = self.window.pointer_position() if self.wm.panes_list[self.wm.panes_current] == self: self.wm.set_current_client(None) def activate(self): "Dummy function, reset to do_activate after all windows are opened." def do_activate(self): "Activate whatever is currently my window." self.wm.current_screen = self.screen if self.window and not self.window.withdrawn: wmanager.debug('Pane', 'Activating window %s in pane %s' % (self.window, self)) # Will raise window and give focus self.window.activate() pos = self.window.panes_pointer_pos if pos: self.window.warppointer(pos[0], pos[1]) def horizontal_split(self, frac = .5): "Split the pane horizontally, taking frac off the bottom." if frac <= 0 or 1 <= frac: raise ValueError, "Pane splits must be between 0 and 1." new_height = int(self.height * frac) self.height = self.height - new_height new_y = self.y + self.height map(self.place_window, self.screen.query_clients(panefilter(self))) new_pane = Pane(self.screen, self.x, new_y, self.width, new_height) self.wm.panes_add(new_pane) self.wm.panes_activate(new_pane) def vertical_split(self, frac = .5): "Split the pane vertically, taking frac off the right." if frac <= 0 or 1 <= frac: raise ValueError, "Pane splits must be between 0 and 1." new_width = int(self.width * frac) self.width = self.width - new_width new_x = self.x + self.width map(self.place_window, self.screen.query_clients(panefilter(self))) new_pane = Pane(self.screen, new_x, self.y, new_width, self.height) self.wm.panes_add(new_pane) self.wm.panes_activate(new_pane) def maximize(self): "Make me the only pane on my screen." self.wm.panes_remove(lambda x, s = self.screen, m = self: x.screen == s and x != m) self.screen.panes_fullscreen(self) for window in self.screen.query_clients(): window.panes_pane = self self.place_window(window) self.activate() class panefilter: "Filter for windows mapped in the current pane." def __init__(self, pane): "Set the pane we're active in." self.pane = pane def __call__(self, window): "Check to see if this is our pane." return self.pane == window.panes_pane and not cfilter.iconified(window) python-plwm-2.6a+20080530/plwm/input.py0000644000175000017500000002363710767527561016166 0ustar stewstew# # input.py -- input editing for PLWM # # Copyright (C) 2001 Mike Meyer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA "input - a tool for getting input from the user." from Xlib import X, Xatom from keys import KeyGrabKeyboard, allmap from wmanager import Window class InputKeyHandler(KeyGrabKeyboard): """Template for handling user input. InputKeyHandler defines the following event handler methods: _insert - insert the character typed. The latin1 character set is bound to this by default. _forw, _back - move the cursor forward or backward in the input _delforw, _delback - delete the character forward or backward in the input _end, _begin - move the cursor to the end or beginning of the input _deltoend - remove all characters to the end of the input _paste - paste the current selection into the intput. The handler must be an instance of wmanager.Window for this to work _done - run action on the current string _abort - exit without doing anything _history_up - scroll to newer events _history_down - scroll to older events""" timeout = None def __init__(self, handler, display, history): """Init with a handler and display. handler is a handler object appropriate for KeyGrabKeyboard. display has show(left, right), do(string) and abort() methods. both abort() and do() should clean up the object. history is a list of strings we let the user scroll through.""" KeyGrabKeyboard.__init__(self, handler, X.CurrentTime) self.display = display self.handler = handler self.history = history self.history_index = len(history) self.left = "" self.right = "" if isinstance(handler, Window): self.selection = self.wm.display.intern_atom("SELECTION") self.wm.dispatch.add_handler(X.SelectionNotify, self._paste_selection, handlerid = self) display.show(self.left, self.right) def _paste_selection(self, event): if event.property: sel = self.handler.window.get_full_property(self.selection, Xatom.STRING) if sel and sel.format == 8: self.left = self.left + sel.value self.display.show(self.left, self.right) def _paste(self, event): if isinstance(self.handler, Window): self.handler.window.convert_selection(Xatom.PRIMARY, Xatom.STRING, self.selection, X.CurrentTime) def _insert(self, event): if event.type != X.KeyPress: return sym = self.wm.display.keycode_to_keysym(event.detail, event.state & X.ShiftMask != 0) chr = self.wm.display.lookup_string(sym) if chr: self.left = self.left + chr self.display.show(self.left, self.right) def _forw(self, event): if self.right: self.left = self.left + self.right[0] self.right = self.right[1:] self.display.show(self.left, self.right) def _back(self, event): if self.left: self.right = self.left[-1] + self.right self.left = self.left[:-1] self.display.show(self.left, self.right) def _delforw(self, event): if self.right: self.right = self.right[1:] self.display.show(self.left, self.right) def _delback(self, event): if self.left: self.left = self.left[:-1] self.display.show(self.left, self.right) def _deltoend(self, event): self.right = "" self.display.show(self.left, self.right) def _end(self, event): self.left = self.left + self.right self.right = "" self.display.show(self.left, self.right) def _begin(self, event): self.right = self.left + self.right self.left = "" self.display.show(self.left, self.right) def _done(self, event): res = self.left + self.right self.history.append(res) self.display.do(res) self.wm.dispatch.remove_handler(self) self._cleanup() def _abort(self, event): self.display.abort() self.wm.dispatch.remove_handler(self) self._cleanup() def _history_up(self, event): if len(self.history): if self.history_index > 0: self.history_index -= 1 self.left = self.history[self.history_index] self.right = "" self.display.show(self.left, self.right) def _history_down(self, event): if len(self.history): if self.history_index <(len(self.history)-1): self.history_index += 1 self.left = self.history[self.history_index] self.right = "" self.display.show(self.left, self.right) allmap(InputKeyHandler, InputKeyHandler._insert) class inputWindow: "Class to get a line of user input in a window." fontname= "9x15" foreground = "black" background = "white" borderwidth = 3 bordercolor = "black" history = [] def __init__(self, prompt, screen, length=30): if not prompt: prompt = ' ' # We have problems if there's no prompt, so add one. self.string = self.prompt = prompt self.offset = len(self.prompt) self.length = length + self.offset self.start = 0 fg = screen.get_color(self.foreground) bg = screen.get_color(self.background) bc = screen.get_color(self.bordercolor) font = screen.wm.get_font(self.fontname, 'fixed') size = font.query() self.height = size.font_ascent + size.font_descent + 1 self.width = font.query_text_extents(prompt).overall_width + \ font.query_text_extents(length * 'm').overall_width self.baseline = size.font_ascent + 1 window = screen.root.create_window(0, 0, self.width, self.height, self.borderwidth, X.CopyFromParent, X.InputOutput, X.CopyFromParent, background_pixel = bg, border_pixel = bc, event_mask = (X.VisibilityChangeMask | X.ExposureMask)) self.gc = window.create_gc(font = font, function = X.GXinvert, foreground = fg, background = bg) self.font = font self.window = screen.add_internal_window(window) self.window.dispatch.add_handler(X.VisibilityNotify, self.raisewindow) self.window.dispatch.add_handler(X.Expose, self.redraw) def read(self, action, handlertype, x=0, y=0): "Open the window at x, y, using handlertype, and doing action." self.action = action x, y, width, height = self.window.keep_on_screen(x, y, self.width, self.height) self.window.configure(x = x, y = y, width = width, height = height) self.window.map() self.window.get_focus(X.CurrentTime) handlertype(self.window, self, self.history) def raisewindow(self, event): self.window.raisewindow() def redraw(self, event = None): length = len(self.string) if self.offset < length: wide = self.font.query_text_extents(self.string[self.offset]).overall_width else: wide = self.font.query_text_extents(' ').overall_width if self.start >= self.offset: self.start = self.offset - 1 left = self.font.query_text_extents(self.string[self.start:self.offset]).overall_width if left + wide >= self.width: self.start = self.offset - self.length + 1 left = self.font.query_text_extents(self.string[self.start:self.offset]).overall_width self.window.clear_area(width = self.width, height = self.height) self.window.image_text(self.gc, 0, self.baseline, self.string[self.start:]) self.window.fill_rectangle(self.gc, left, 0, wide, self.height) def show(self, left, right): if left: self.string = self.prompt + left else: # Display the prompt in this case. self.string = self.prompt self.start = 0 self.offset = len(self.string) self.string = self.string + right self.redraw() def do(self, string): self.action(string) self.window.destroy() def abort(self): self.window.destroy() class modeInput: "Class to get input via the modewindow." history = [] def __init__(self, prompt, screen, length = None): # ignore length argument self.prompt = prompt self.screen = screen def read(self, action, handlertype, x = 0, y = 0): self.action = action self.status_msg = self.screen.modestatus_new(self.prompt + "_") handlertype(self.screen.modewindow_mw.window, self, self.history) def show(self, left, right): self.status_msg.set("%s%s_%s" % (self.prompt, left, right)) def do(self, string): self.action(string) self.status_msg.pop() def abort(self): self.status_msg.pop() python-plwm-2.6a+20080530/plwm/mw_xmms.py0000644000175000017500000001206710765600750016477 0ustar stewstew# # mw_xmms.py -- display xmms track title in the mode window # # Copyright (C) 2004 Mark Tigges mtigges@gmail.com # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # This file provides two mixins. The first is a mode window display for # the currently playing XMMS track. The second is a keyboard controllor # for controlling the XMMS player. # # Of course for any of this to be relevant you must have XMMS, and be # using it. Further, you have to have the PyXMMS module. If you # don't this module won't cause your WM to crash if you use it, it # just won't do anything. # # This has been developed using PyXMMS-2.03 # http://people.via.ecp.fr/~flo/ from plwm import modewindow, event, wmanager, keys try: import xmms XMMSTimerEvent = event.new_event_type() class ModeWindowXMMS: # These values are so that the XMMS position appears just # to the right of the default output of the mw_load mixin. mw_xmms_position = 0.070 mw_xmms_justification = modewindow.LEFT def __wm_init__(self): self.wm_xmms_message = modewindow.Message(self.mw_xmms_position, self.mw_xmms_justification) for s in self.screens: s.modewindow_add_message(self.wm_xmms_message) self.dispatch.add_handler(XMMSTimerEvent,self.mw_xmms_tick) self.mw_xmms_update() def mw_xmms_update(self): try: stime = xmms.get_output_time() stitle = xmms.get_playlist_title(xmms.get_playlist_pos()) if stitle: minutes = stime/1000.0/60 seconds = (minutes-int(minutes))*60 # Format the message self.wm_xmms_message.set_text('%s %d:%02d' % (stitle, minutes,seconds)) self.events.add_timer(event.TimerEvent(XMMSTimerEvent, after = 1)) except: # There was some problem, so we'll try again in 20 seconds. self.events.add_timer(event.TimerEvent(XMMSTimerEvent, after = 20)) import traceback traceback.print_exc() def mw_xmms_tick(self,evt): self.mw_xmms_update() class XMMSKeys(keys.KeyGrabKeyboard): # This class provides the keyboard control of xmms # I add the followin in my main keyboard class in my window # manager: # # def M_m(self, evt): # XMMSKeys(self,evt) # # With that function the following key controls affect XMMS # # Alt+M,P Pause/Play # Alt+M,S Stop # Alt+M,Right Next track # Alt+M,Left Previous track # Alt+M,Up* Increase volume # Alt+M,Down* Decrease volume # # With the volume changers the grab does not end, so you can # repeatedly press Up (or hold it down). Pressing Return when # you are done will release the keyboard grab. # propagate_keys = 0 timeout = 4 def __init__(self, keyhandler, evt): keys.KeyGrabKeyboard.__init__(self, keyhandler.wm, evt.time) def done(self): self._cleanup() def M_p(self,evt): if xmms.is_playing(): xmms.pause() else: xmms.play() self.done() def M_s(self,evt): xmms.stop() self.done() def M_Right(self,evt): xmms.playlist_next() self.done() def M_Left(self,evt): xmms.playlist_prev() self.done() def M_Up(self,evt): xmms.set_main_volume(xmms.get_main_volume()+1) def M_Down(self,evt): xmms.set_main_volume(xmms.get_main_volume()-1) def Return(self,evt): self.done() def _timeout(self, evt): self.wm.display.bell(100) self._cleanup() Any_g = _timeout Any_Escape = _timeout except: import sys sys.stderr.write('mw_xmms: could not load XMMS python module\n') class ModeWindowXMMS: pass class XMMSKeys: pass python-plwm-2.6a+20080530/plwm/mw_xmms2.py0000644000175000017500000001264211007513661016552 0ustar stewstew# mw_xmms2.py -- display xmms2 track title in the mode window # # Copyright (C) 2008 David H. Bronke # Based on mw_xmms.py, Copyright (C) 2004 Mark Tigges # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """mw_xmms2.py - Display xmms2 track title in the mode window. This requires xmms2's Python bindings, which live in the xmmsclient module. """ from plwm import modewindow, event try: import xmmsclient, os XMMS2TimerEvent = event.new_event_type() XMMS2ReconnectEvent = event.new_event_type() class ModeWindowXMMS2: """WM mixin providing an XMMS2 status display in the ModeWindow. """ mw_xmms2_position = 0.3 mw_xmms2_justification = modewindow.LEFT def __wm_init__(self): self.wm_xmms2_message = modewindow.Message(self.mw_xmms2_position, self.mw_xmms2_justification) self.mw_xmms2_connection = xmmsclient.XMMS("mw_xmms2") self.mw_xmms2_connect () for s in self.screens: s.modewindow_add_message(self.wm_xmms2_message) self.dispatch.add_handler(XMMS2TimerEvent, self.mw_xmms2_tick) self.dispatch.add_handler(XMMS2ReconnectEvent, self.mw_xmms2_reconnect) self.mw_xmms2_update() def mw_xmms2_connect(self): try: self.mw_xmms2_connection.connect(os.getenv("XMMS_PATH")) except IOError, detail: print "mw_xmms2: Connection failed:", detail def mw_xmms2_ms_to_time(self, ms): minutes = ms/1000.0/60 seconds = (minutes-int(minutes))*60 return '%d:%02d' % (minutes, seconds) def mw_xmms2_update(self): """Get the artist and title of the currently-playing track. """ if self.mw_xmms2_connection.get_fd() == -1: self.mw_xmms2_connect() else: result = self.mw_xmms2_connection.playback_current_id() result.wait() if result.iserror(): print "mw_xmms2: playback current id returns error, %s" % result.get_error() self.wm_xmms2_message.set_text("XMMS2 Error: %s" % result.get_error()) # There was some problem, so we'll try again in 20 seconds. self.events.add_timer(event.TimerEvent(XMMS2ReconnectEvent, after = 20)) if result.value() == 0: curstatus = "Stopped" else: result = self.mw_xmms2_connection.medialib_get_info(result.value()) result.wait() if result.iserror(): print "mw_xmms2: medialib get info returns error, %s" % result.get_error() self.wm_xmms2_message.set_text("XMMS2 Error: %s" % result.get_error()) # There was some problem, so we'll try again in 20 seconds. self.events.add_timer(event.TimerEvent(XMMS2ReconnectEvent, after = 20)) minfo = result.value() try: duration = self.mw_xmms2_ms_to_time(minfo["duration"]) except KeyError: duration = "?:??" try: artist = minfo["artist"] except KeyError: artist = "No artist" try: title = minfo["title"] except KeyError: title = "No title" if artist == "No artist" and title == "No title": try: stitle = minfo["file"] except KeyError: stitle = "No file" else: stitle = "%s - %s" % (title, artist) result = self.mw_xmms2_connection.playback_playtime() result.wait() if result.iserror(): print "mw_xmms2: playback playtime returns error, %s" % result.get_error() self.wm_xmms2_message.set_text("XMMS2 Error: %s" % result.get_error()) # There was some problem, so we'll try again in 20 seconds. self.events.add_timer(event.TimerEvent(XMMS2ReconnectEvent, after = 20)) stime = result.value() elapsed = self.mw_xmms2_ms_to_time(stime) result = self.mw_xmms2_connection.playback_status() result.wait() if result.iserror(): print "mw_xmms2: playback status returns error, %s" % result.get_error() self.wm_xmms2_message.set_text("XMMS2 Error: %s" % result.get_error()) # There was some problem, so we'll try again in 20 seconds. self.events.add_timer(event.TimerEvent(XMMS2ReconnectEvent, after = 20)) status = "Unknown" statnum = result.value() if statnum == xmmsclient.PLAYBACK_STATUS_STOP: status = "Stopped" elif statnum == xmmsclient.PLAYBACK_STATUS_PLAY: status = "Playing" elif statnum == xmmsclient.PLAYBACK_STATUS_PAUSE: status = "Paused" # Format the message if status == "Stopped": curstatus = '%s <%s>' % (status, stitle) else: curstatus = '%s <%s> [%s/%s]' % (status, stitle, elapsed, duration) self.events.add_timer(event.TimerEvent(XMMS2TimerEvent, after = 1)) self.wm_xmms2_message.set_text(str(curstatus)) def mw_xmms2_reconnect(self, evt): self.mw_xmms2_connect() self.mw_xmms2_update() def mw_xmms2_tick(self, evt): self.mw_xmms2_update() except: import sys sys.stderr.write('mw_xmms2: could not load xmmsclient python module\n') class ModeWindowXMMS2: pass python-plwm-2.6a+20080530/examples/0000755000175000017500000000000011020017212015243 5ustar stewstewpython-plwm-2.6a+20080530/examples/hrwwm.py0000755000175000017500000002614310761724253017017 0ustar stewstew#!/usr/bin/env python # # hrwwm.py -- Example PLWM window manager "configuration" # # Copyright (C) 1999,2000 Peter Liljenberg # Henrik Rindlöw # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys import os ###SETUP PATH sys.path[1:1] = [os.path.join(sys.path[0], '..')] ###END SETUP PATH from plxlib import plxlib, X from plwm import wmanager, focus, keys, \ moveresize, deltamove, \ border, color, font, cycle, views, \ modewinctl, modetitle delta = deltamove.DeltaMove() class MyClient(wmanager.Client, focus.FocusClient, border.BorderClient, modetitle.ModeTitleClient): no_border_clients = ['XClock', 'XBiff', 'CDStatus', 'XModeWindow'] start_iconified_clients = ['WMManager'] move_focus_ignore_clients = no_border_clients default_pointer_pos = {'Emacs': (-1, 0), 'XTerm': (-1, 0)} class ScreenConfig: pass # def __screen_client_init__(self): # self.set_color('BorderFocusColor', 'grey60') class MyScreen(wmanager.Screen, color.Color, modewinctl.ModeClientControl, views.XMW_ViewHandler, keys.KeyGrabber, ScreenConfig): view_always_visible_clients = ['XClock', 'XBiff', 'CDStatus', 'XModeWindow'] class WMConfig: # def __wm_screen_init__(self): # self.set_font('OutlineNameFont', '-*-lucida-bold-r-*-sans-20-*-*-*-*-*-*-*') def __wm_init__(self): BasicKeys(self, self.dispatch) class PLWM(wmanager.WindowManager, focus.SloppyFocus, font.Font, WMConfig): client_class = MyClient screen_class = MyScreen class BasicKeys(keys.KeyHandler): # def F5(self, event): # wins = self.wm.root.QueryTree()[2] # for w in wins: # c = self.wm.get_client(w) # if c and c.is_mapped(): # c.raisewindow() # c.warppointer() # return def F1(self, event): self.wm.current_screen.view_find_with_client('XTerm') def S_F1(self, event): self.wm.system('xterm -geometry 80x50+200+100') def C_S_F1(self, event): self.wm.current_screen.view_new() self.wm.system('xterm -geometry 80x50+200+100') def F2(self, event): self.wm.current_screen.view_find_with_client('Emacs') def S_F2(self, event): self.wm.system('emacs') def C_S_F2(self, event): self.wm.current_screen.view_new() self.wm.system('emacs') def F3(self, event): self.wm.current_screen.view_find_with_client('Netscape') def S_F3(self, event): self.wm.system('netscape') def C_S_F3(self, event): self.wm.current_screen.view_new() self.wm.system('netscape') def F4(self, event): self.wm.current_screen.view_find_with_client('applix') def KP_Begin(self, event): wmanager.debug('keys', 'Entering move-resize mode') if self.wm.focus_client: try: mv = MoveResizeKeys(self.wm, self.dispatch, self.wm.focus_client, event.time) except keys.error, status: wmanager.debug('keys', 'Grabbing keyboard failed: %d', status) KP_5 = KP_Begin def C_Tab(self, event): wmanager.debug('keys', 'Into CycleUnmapped mode') try: mv = CycleUMKeys(self.wm, self.dispatch, event.time) except keys.error, status: wmanager.debug('keys', 'Grabbing keyboard failed: %d', status) def KP_Insert(self, event): wmanager.debug('keys', 'Iconifying') if self.wm.focus_client: self.wm.focus_client.iconify() KP_0 = KP_Insert def KP_Subtract(self, event): wmanager.debug('keys', 'Prev view') self.wm.current_screen.view_prev() def KP_Add(self, event): wmanager.debug('keys', 'Next view') self.wm.current_screen.view_next() def C_KP_Add(self, event): wmanager.debug('keys', 'New view') self.wm.current_screen.view_new() def KP_Left(self, event): self.wm.display.WarpPointer(-delta.get(event.time), 0) KP_4 = KP_Left def KP_Right(self, event): self.wm.display.WarpPointer(delta.get(event.time), 0) KP_6 = KP_Right def KP_Up(self, event): self.wm.display.WarpPointer(0, -delta.get(event.time)) KP_8 = KP_Up def KP_Down(self, event): self.wm.display.WarpPointer(0, delta.get(event.time)) KP_2 = KP_Down def KP_Home(self, event): d = delta.get(event.time) self.wm.display.WarpPointer(-d, -d) KP_7 = KP_Home def KP_End(self, event): d = delta.get(event.time) self.wm.display.WarpPointer(-d, d) KP_1 = KP_End def KP_Prior(self, event): d = delta.get(event.time) self.wm.display.WarpPointer(d, -d) KP_9 = KP_Prior def KP_Next(self, event): d = delta.get(event.time) self.wm.display.WarpPointer(d, d) KP_3 = KP_Next def KP_Enter(self, event): if self.wm.focus_client: self.wm.focus_client.configure({'stack_mode': X.Opposite}) def C_KP_Subtract(self, event): self.wm.system('xlock -mode blank') def C_M_Escape(self, event): raise 'PLWMEscape', 'Escaping window manager' def C_KP_Delete(self, event): if self.wm.focus_client: self.wm.focus_client.delete(1) C_KP_Separator= C_KP_Delete def C_S_KP_Delete(self, event): if self.wm.focus_client: self.wm.focus_client.destroy() C_S_KP_Separator= C_S_KP_Delete def C_KP_Left(self, event): self.wm.move_focus(focus.MOVE_LEFT) C_KP_ = C_KP_Left def C_KP_Right(self, event): self.wm.move_focus(focus.MOVE_RIGHT) C_KP_6 = C_KP_Right def C_KP_Up(self, event): self.wm.move_focus(focus.MOVE_UP) C_KP_8 = C_KP_Up def C_KP_Down(self, event): self.wm.move_focus(focus.MOVE_DOWN) C_KP_2 = C_KP_Down def C_less(self, event): self.wm.move_focus(focus.MOVE_LEFT) def C_S_less(self, event): self.wm.move_focus(focus.MOVE_RIGHT) class MoveResizeKeys(keys.KeyGrabKeyboard): propagate_keys = 0 timeout = 20 def __init__(self, wm, dispatch, client, time): keys.KeyGrabKeyboard.__init__(self, wm, dispatch, time) self.mv = moveresize.MoveResizeOutline(client, delta) def KP_Left(self, event): self.mv.move(-delta.get(event.time), 0) KP_4 = KP_Left def KP_Right(self, event): self.mv.move(delta.get(event.time), 0) KP_6 = KP_Right def KP_Up(self, event): self.mv.move(0, -delta.get(event.time)) KP_8 = KP_Up def KP_Down(self, event): self.mv.move(0, delta.get(event.time)) KP_2 = KP_Down def KP_Home(self, event): d = delta.get(event.time) self.mv.move(-d, -d) KP_7 = KP_Home def KP_End(self, event): d = delta.get(event.time) self.mv.move(-d, d) KP_1 = KP_End def KP_Prior(self, event): d = delta.get(event.time) self.mv.move(d, -d) KP_9 = KP_Prior def KP_Next(self, event): d = delta.get(event.time) self.mv.move(d, d) KP_3 = KP_Next def S_KP_Left(self, event): self.mv.resizeunits(-1, 0, 1, 0, event.time) S_KP_4 = S_KP_Left def S_KP_Right(self, event): self.mv.resizeunits(0, 0, 1, 0, event.time) S_KP_6 = S_KP_Right def S_KP_Up(self, event): self.mv.resizeunits(0, -1, 0, 1, event.time) S_KP_8 = S_KP_Up def S_KP_Down(self, event): self.mv.resizeunits(0, 0, 0, 1, event.time) S_KP_2 = S_KP_Down def S_KP_Home(self, event): self.mv.resizeunits(-1, -1, 1, 1, event.time) S_KP_7 = S_KP_Home def S_KP_End(self, event): self.mv.resizeunits(-1, 0, 1, 1, event.time) S_KP_1 = S_KP_End def S_KP_Prior(self, event): self.mv.resizeunits(0, -1, 1, 1, event.time) S_KP_9 = S_KP_Prior def S_KP_Next(self, event): self.mv.resizeunits(0, 0, 1, 1, event.time) S_KP_3 = S_KP_Next def C_KP_Left(self, event): self.mv.resizeunits(1, 0, -1, 0, event.time) C_KP_4 = C_KP_Left def C_KP_Right(self, event): self.mv.resizeunits(0, 0, -1, 0, event.time) C_KP_6 = C_KP_Right def C_KP_Up(self, event): self.mv.resizeunits(0, 1, 0, -1, event.time) C_KP_8 = C_KP_Up def C_KP_Down(self, event): self.mv.resizeunits(0, 0, 0, -1, event.time) C_KP_2 = C_KP_Down def C_KP_Home(self, event): self.mv.resizeunits(1, 1, -1, -1, event.time) C_KP_7 = C_KP_Home def C_KP_End(self, event): self.mv.resizeunits(1, 0, -1, -1, event.time) C_KP_1 = C_KP_End def C_KP_Prior(self, event): self.mv.resizeunits(0, 1, -1, -1, event.time) C_KP_9 = C_KP_Prior def C_KP_Next(self, event): self.mv.resizeunits(0, 0, -1, -1, event.time) C_KP_3 = C_KP_Next def KP_Begin(self, event): wmanager.debug('keys', 'Leaving move-resize mode') self.mv.end() self._cleanup() KP_5 = KP_Begin S_KP_Begin = KP_Begin C_KP_Begin = KP_Begin def Escape(self, event): wmanager.debug('keys', 'Aborting move-resize mode') self.mv.abort() self._cleanup() _timeout = Escape KP_Delete = Escape class CycleUMKeys(keys.KeyGrabKeyboard): propagate_keys = 0 timeout = 10 def __init__(self, wm, dispatch, time): keys.KeyGrabKeyboard.__init__(self, wm, dispatch, time) self.cy = cycle.CycleUnmapped(wm.current_screen, 1) def Tab(self, event): self.cy.next() C_Tab = Tab def S_Tab(self, event): self.cy.previous() def Return(self, event): wmanager.debug('keys', 'Escaping CycleMapped mode') self._cleanup() self.cy.end() def Escape(self, event): wmanager.debug('keys', 'Aborting CycleMapped mode') self._cleanup() self.cy.abort() _timeout = Escape if __name__ == '__main__': sync = 0 while len(sys.argv) > 1: if sys.argv[1] == '-d': wmanager.debug = wmanager.do_debug elif sys.argv[1] == '-s': sync = 1 del sys.argv[1] try: p = PLWM() except wmanager.error_no_unmanaged_screens: sys.stderr.write(sys.argv[0] + ': Another window manager already running?\n') sys.exit(1) if sync: p.display.Synchronize(1) p.brave_loop() python-plwm-2.6a+20080530/examples/plpwmrc.xml0000644000175000017500000001237310646112446017501 0ustar stewstew python-plwm-2.6a+20080530/examples/README.fr.examplewm0000644000175000017500000000342507233270312020550 0ustar stewstew** Comment utiliser pointless window manager petliwm: ** (Ecrit par Kamel Sehil ) Le pointeur de la souris peut etre deplaces avec les touches du paves numeriques(KP_1 a KP_9) Ctrl+{KP_Left, KP_Right, KP_Up, KP_Down} change le focus a la prochaine fenetre qui se trouve a peu pres a gauche , droite , en haut , en bas de la fenetre en focus actuellement, comme Ctrl-< resp Ctrl-Shift->. KP_5 permet de passer en mode "deplacer-retailler" pour la fenetre en focus. La fenetre peut etre deplacer en se servant des touches KP_1 a KP_9, agrandir avec Shift + KP_*, reduire avec Ctrl + KP_*. valider la nouvelle position en appuyant de nouveau sur KP_5, ou Escape pour annuler. Cacher ou Montrer une fenetre (par rapport a une autre) avec KP_Enter. Iconifier une fenetre avec KP_0 (KP_Insert) Deiconifier une fenetre avec Ctrl+Tab. Appuyer sur Tab (or Ctrl+Tab) pour passer a la fentre iconifier suivante,et Shift+Tab pour la precedente. Appuyer sur Return pour selectionner et deiconifier une fenetre ou Escape pour annuler . Effacer une fenetre avec Ctrl+KP_Delete. Detruire une fenetre avec Ctrl+Shift+Delete. Lock l'ecran avec Ctrl+KP_Subtract. (necessite xlock) Creer une nouvelle Vue (un espace de travail (workspace)) avec Ctrl+KP_Add. Passer a la Vue prochaine avec KP_Add, a la precedente Vue avec KP_Subtract. Une Vue est effacee si elle est vide quand on la quitte. F1 passe la prochaine Vue contenant un xterm, F2 un emacs, F3 un netscape. Shift+F{1,2,3} lance un nouvel xterm , emacs ou netscape et Ctrl+Shift+F{1,2,3} cree une nouvelle Vue et lance le programme. Shift+F5 and Shift+F6 tag les Vue avec les touches "F5" and "F6", respectivement. F5 and F6 vous emmeneront a la vue taguees avec les touches respectives Quittez le window manager avec Ctrl+Meta+Escape. python-plwm-2.6a+20080530/examples/petliwm.py0000755000175000017500000005456711007513675017345 0ustar stewstew#!/usr/bin/env python # -*- coding: iso-8859-1 -*- # # petliwm.py -- My PLWM "configuration" # # Copyright (C) 1999-2002 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys import os ###SETUP PATH sys.path[1:1] = [os.path.join(sys.path[0], '..')] ###END SETUP PATH import time from Xlib import X, XK, Xatom from plwm import wmanager, wmevents, \ focus, keys, \ deltamove, outline, \ border, color, font, views, \ modewindow, modestatus, \ mw_clock, mw_acpi, \ mw_watchfiles, \ inspect, misc, input, \ composite, mixer from plwm.cycle import CycleKeys, CycleActivate from plwm.moveresize import MoveResizeKeys from plwm.cfilter import * delta = deltamove.DeltaMove() class Forcefull: def __client_init__(self): if self.res_class == 'Tk': x, y, w, h = self.keep_on_screen(self.screen.root_x, self.screen.root_y, self.screen.root_width, self.screen.root_height) w, h = self.follow_size_hints(w, h) self.configure(x = x, y = y, width = w, height = h) class TraceIM: """Class for TraceIMClient.traceim_filters objects. """ def __init__(self, enable, unseen, message): """Create a TraceIM filter. enable is a client filter selecting the clients that should be traced. It is checked when the client is created. unseen is a client filter which is true when there are unseen IMs for this client, and then message is displayed in the mode window. """ self.enable = enable self.unseen = unseen self.message = message class TraceIMClient: """Trace IM clients which alter their window title when there's new/unread messages. Do this by reacting to WM_NAME and iconification changes, applying a filter each time. """ # A list of TraceIM objects traceim_filters = () def __client_init__(self): self.traceim_message = None filters = None for f in self.traceim_filters: if f.enable(self): if filters is None: wmanager.debug('traceim', 'Enabling IM tracing for %s' % self.get_title()) self.dispatch.add_handler(X.PropertyNotify, self.traceim_handle_property) self.dispatch.add_handler(wmevents.ClientIconified, self.traceim_handle_iconified) self.dispatch.add_handler(wmevents.ClientDeiconified, self.traceim_handle_iconified) self.dispatch.add_handler(wmevents.RemoveClient, self.traceim_handle_removed) filters = [f] else: filters.append(f) if filters: self.traceim_filters = filters self.traceim_message = None self.traceim_update() def traceim_handle_removed(self, evt): wmanager.debug('traceim', 'IM tracing window removed') del self.traceim_filters[:] self.traceim_message = None self.wm.traceim_remove_message(self) def traceim_handle_property(self, evt): if evt.atom == Xatom.WM_NAME: self.traceim_update() def traceim_handle_iconified(self, evt): self.traceim_update() def traceim_update(self): m = [] for f in self.traceim_filters: if f.unseen(self): m.append(f.message) if m: m = ' '.join(m) if m != self.traceim_message: wmanager.debug('traceim', 'Unseen IM for %s: %s', self.get_title(), m) self.traceim_message = m self.wm.traceim_add_message(self, m) else: if self.traceim_message: wmanager.debug('traceim', 'No longer unseen IM for %s', self.get_title()) self.traceim_message = None self.wm.traceim_remove_message(self) class ModeWindowTraceIM: mw_traceim_position = 0.1 mw_traceim_justification = modewindow.LEFT def __wm_screen_init__(self): self.mw_traceim_unseen_clients = {} self.mw_traceim_message = modewindow.Message(self.mw_traceim_position, self.mw_traceim_justification) def __wm_init__(self): for s in self.screens: s.modewindow_add_message(self.mw_traceim_message) def traceim_add_message(self, client, message): self.mw_traceim_unseen_clients[client] = message self.mw_traceim_message.set_text(' '.join(self.mw_traceim_unseen_clients.values())) def traceim_remove_message(self, client): try: del self.mw_traceim_unseen_clients[client] except KeyError: return self.mw_traceim_message.set_text(' '.join(self.mw_traceim_unseen_clients.values())) class MyClient(wmanager.Client, outline.XorOutlineClient, border.BorderClient, modestatus.ModeFocusedTitleClient, misc.InitialKeepOnScreenClient, focus.JumpstartClient, TraceIMClient, ): window_proxy_class = composite.CompositeProxy no_border_clients = name('MPlayer') full_screen_windows = name('MPlayer') start_iconified_clients = name('WMManager') default_pointer_pos = {'Emacs': (-1, 0), 'XTerm': (-1, 0)} # Use title-based border colors for xterms, but fixed greys for # all other windows border_colors = [(name('XTerm'), border.TitleBorderColor(0.3, 0.7, 1.0, 0.7)) ] border_default_color = border.FixedBorderColor('grey20', 'grey60') traceim_filters = [ TraceIM(name('Emacs'), # KOM runs in Emacs And(iconified, re_title('Olästa')), # only when iconified and unread 'Olästa'), TraceIM(name('Emacs'), # TNT runs in Emacs re_title(r'\(IM\)'), # always when unacknowledged IM event 'IM'), TraceIM(name('Firefox-bin'), re_title(r'QX.*ol.st'), 'QX:brev'), TraceIM(name('Firefox-bin'), re_title(r'QX.*favoriter'), 'QX:fav'), ] class MyScreen(wmanager.Screen, color.Color, modewindow.ModeWindowScreen, modestatus.ModeStatus, modestatus.ModeMoveResize, views.XMW_ViewHandler, modestatus.ModeFocusedTitleScreen): view_always_visible_clients = none view_reorder_views = 1 view_reorder_delay = 2.0 #allow_self_changes = none class WMConfig: def __wm_init__(self): BasicKeys(self) class PLWM(wmanager.WindowManager, focus.SloppyFocus, focus.MoveFocus, font.Font, mw_clock.ModeWindowClock, mw_acpi.ModeWindowACPI, mw_watchfiles.ModeWindowWatchFiles, inspect.InspectServer, composite.CompositionManager, mixer.Mixer, ModeWindowTraceIM, WMConfig): mw_acpi_position = 0 mw_acpi_justification = modewindow.LEFT mw_watchfiles_position = 0.85 mw_watchfiles_justification = modewindow.RIGHT mw_watchfiles = (mw_watchfiles.WatchedFile('/var/run/laptop-mode-enabled', present_msg = '', missing_msg = 'LTM OFF'), mw_watchfiles.WatchedFile('/var/run/tre-network', present_msg = '3|%s', format_content = 1) ) mw_watchfiles_interval = 5 client_class = MyClient screen_class = MyScreen class BasicKeys(keys.KeyHandler): # WM control def M5_z(self, evt): self.wm.system('xlock -mode blank') def M5_x(self, evt): wmanager.debug('keys', 'installing quit keys') QuitKeys(self, evt) def M5_e(self, evt): wmanager.debug('keys', 'running command') # misc.RunKeys(self, evt) Runcommand(self.wm.current_screen) def F12(self, evt): self.wm.inspect_toggle() def S_F12(self, evt): self.wm.inspect_toggle(force = 1) # Drop all keygrabs until Scroll_Lock is pressed again, to allow # clients to recieve keys used by plwm def S_Pause(self, evt): wmanager.debug('keys', 'dropping keygrabs temporarily') # First release all our grabs. They will be reinstalled # by BypassHandler when it exits self._ungrab() BypassHandler(self) # Window control def M5_u(self, evt): if self.wm.current_client: self.wm.current_client.raiselower() def M5_i(self, evt): wmanager.debug('keys', 'Iconifying') if self.wm.current_client: self.wm.current_client.iconify() def M5_o(self, evt): self.wm.move_focus(focus.MOVE_LEFT) def M5_l(self, evt): if self.wm.current_client: self.wm.current_client.warppointer() def M5_k(self, evt): MyMoveResizeKeys(self, evt) def M5_m(self, evt): CycleUMKeys(self, evt) def M5_plus(self, evt): c = self.wm.current_client if c: x, y, w, h = c.keep_on_screen(c.screen.root_x, c.screen.root_y, c.screen.root_width, c.screen.root_height) w, h = c.follow_size_hints(w, h) c.configure(x = x, y = y, width = w, height = h) # def M_Tab(self, evt): # CycleMKeys(self, evt) def M5_S_minus(self, evt): if self.wm.current_client: self.wm.current_client.delete(1) def M5_S_C_minus(self, evt): if self.wm.current_client: self.wm.current_client.destroy() # View control def F1(self, evt): self.wm.current_screen.view_find_with_client(name('XTerm')) def S_F1(self, evt): self.wm.system('xterm') def C_S_F1(self, evt): self.wm.current_screen.view_new() self.wm.system('xterm') def F2(self, evt): self.wm.current_screen.view_find_with_client(name('Emacs')) def S_F2(self, evt): self.wm.system('emacs') def C_S_F2(self, evt): self.wm.current_screen.view_new() self.wm.system('emacs') def F3(self, evt): self.wm.current_screen.view_find_with_client(Or(name('Firefox-bin'), name('Mozilla'), name('Netscape'), name('Mozilla-bin'))) def S_F3(self, evt): self.wm.system('firefox') def C_S_F3(self, evt): self.wm.current_screen.view_new() self.wm.system('firefox') def F4(self, evt): self.wm.current_screen.view_find_with_client(Or(name('xpdf'), name('soffice'), name('OpenOffice.org 2.0'), name('AcroRead'))) def F7(self, evt): self.wm.current_screen.view_find_with_client(name('Ccm')) def F8(self, evt): self.wm.current_screen.view_find_with_client(Or(name('Vmware'), name('Vmplayer'))) def F5(self, evt): self.wm.current_screen.view_find_tag('F5') def S_F5(self, evt): self.wm.current_screen.view_tag('F5') def F6(self, evt): self.wm.current_screen.view_find_tag('F6') def S_F6(self, evt): self.wm.current_screen.view_tag('F6') def M5_Prior(self, evt): wmanager.debug('keys', 'Prev view') self.wm.current_screen.view_prev() def M5_Next(self, evt): wmanager.debug('keys', 'Next view') self.wm.current_screen.view_next() def C_M5_Next(self, evt): wmanager.debug('keys', 'New view') self.wm.current_screen.view_new() def M5_n(self, evt): wmanager.debug('keys', 'Moving window to new view') if self.wm.current_client: c = self.wm.current_client c.iconify() self.wm.current_screen.view_new() c.deiconify() # Pointer movements def M5_Left(self, evt): self.wm.display.warp_pointer(-delta.get(evt.time), 0) def M5_Right(self, evt): self.wm.display.warp_pointer(delta.get(evt.time), 0) def M5_Up(self, evt): self.wm.display.warp_pointer(0, -delta.get(evt.time)) def M5_Down(self, evt): self.wm.display.warp_pointer(0, delta.get(evt.time)) # Simulate mouse clicks def Any_F9(self, evt): self.wm.fake_button_click(1) def Any_F10(self, evt): self.wm.fake_button_click(2) def Any_F11(self, evt): self.wm.fake_button_click(3) # # Composition effects # def M5_S_b(self, evt): if self.wm.current_client: self.wm.comp_change_brightness(self.wm.current_client, 16) def M5_b(self, evt): if self.wm.current_client: self.wm.comp_change_brightness(self.wm.current_client, -16) def M5_C_b(self, evt): if self.wm.current_client: self.wm.comp_set_brightness(self.wm.current_client, 0) # # Mixer control # def M5_Delete(self, evt): # lower volume. First main to 50, then pcm to 0 main = self.wm.mixer_get('vol') if main > 50: self.wm.mixer_set('vol', max(main - 10, 50)) else: pcm = self.wm.mixer_get('pcm') if pcm > 0: self.wm.mixer_set('pcm', max(pcm - 10, 0)) else: self.wm.display.bell(100) self.wm.mixer_status_view(devs = ('vol', 'pcm')) def M5_Insert(self, evt): # raise volume. First pcm to 100, then main to 100 pcm = self.wm.mixer_get('pcm') if pcm < 100: self.wm.mixer_set('pcm', min(pcm + 10, 100)) else: main = self.wm.mixer_get('vol') if main < 100: self.wm.mixer_set('vol', min(main + 10, 100)) else: self.wm.display.bell(-50) self.wm.mixer_status_view(devs = ('vol', 'pcm')) def M5_End(self, evt): # toggle mute self.wm.mixer_mute('pcm') self.wm.mixer_status_view(devs = ('vol', 'pcm')) class BypassHandler(keys.KeyHandler): propagate_keys = 0 def __init__(self, keyhandler): keys.KeyHandler.__init__(self, keyhandler.wm) self._keyhandler = keyhandler self._message = modewindow.Message(.1, modewindow.LEFT, 0, '[Bypassing]') self._screen = keyhandler.wm.current_screen self._screen.modewindow_add_message(self._message) def Pause(self, evt): wmanager.debug('keys', 'reinstalling keygrabs') self._screen.modewindow_remove_message(self._message) # Delete ourself, and reinstall the callee grabs self._cleanup() self._keyhandler._buildmap() # Remove it, just to be sure there are no circular references del self._keyhandler del self._screen class QuitKeys(keys.KeyGrabKeyboard): propagate_keys = 0 timeout = 4 def __init__(self, keyhandler, evt): keys.KeyGrabKeyboard.__init__(self, keyhandler.wm, evt.time) def M5_c(self, evt): wmanager.debug('keys', 'quitting PLWM') self.wm.quit() def _timeout(self, evt): wmanager.debug('keys', 'cancelling quit keys') self.wm.display.bell(100) self._cleanup() Any_g = _timeout Any_Escape = _timeout class MyMoveResizeKeys(MoveResizeKeys): j = MoveResizeKeys._move_w l = MoveResizeKeys._move_e i = MoveResizeKeys._move_n comma = MoveResizeKeys._move_s u = MoveResizeKeys._move_nw m = MoveResizeKeys._move_sw o = MoveResizeKeys._move_ne period = MoveResizeKeys._move_se M5_j = MoveResizeKeys._move_w M5_l = MoveResizeKeys._move_e M5_i = MoveResizeKeys._move_n M5_comma = MoveResizeKeys._move_s M5_u = MoveResizeKeys._move_nw M5_m = MoveResizeKeys._move_sw M5_o = MoveResizeKeys._move_ne M5_period = MoveResizeKeys._move_se S_j = MoveResizeKeys._enlarge_w S_l = MoveResizeKeys._enlarge_e S_i = MoveResizeKeys._enlarge_n S_comma = MoveResizeKeys._enlarge_s S_u = MoveResizeKeys._enlarge_nw S_m = MoveResizeKeys._enlarge_sw S_o = MoveResizeKeys._enlarge_ne S_period = MoveResizeKeys._enlarge_se C_j = MoveResizeKeys._shrink_w C_l = MoveResizeKeys._shrink_e C_i = MoveResizeKeys._shrink_n C_comma = MoveResizeKeys._shrink_s C_u = MoveResizeKeys._shrink_nw C_m = MoveResizeKeys._shrink_sw C_o = MoveResizeKeys._shrink_ne C_period = MoveResizeKeys._shrink_se k = MoveResizeKeys._moveresize_end M5_k = MoveResizeKeys._moveresize_end g = MoveResizeKeys._moveresize_abort M5_g = MoveResizeKeys._moveresize_abort class CycleUMKeys(CycleKeys): _cycle_filter = iconified Any_m = CycleKeys._cycle_next Any_n = CycleKeys._cycle_previous R_M5_Super_L = CycleKeys._cycle_end R_M5_Super_R = CycleKeys._cycle_end Any_g = CycleKeys._cycle_abort Any_Escape = CycleKeys._cycle_abort class CycleMKeys(CycleKeys): _cycle_filter = mapped # _cycle_class = CycleActivate M_Tab = CycleKeys._cycle_next S_M_Tab = CycleKeys._cycle_previous R_M_Alt_L = CycleKeys._cycle_end R_M_Alt_R = CycleKeys._cycle_end Any_Escape = CycleKeys._cycle_abort class MyEditHandler(input.InputKeyHandler): Any_Escape = C_g = input.InputKeyHandler._abort Any_Return = input.InputKeyHandler._done Any_BackSpace = C_h = input.InputKeyHandler._delback C_d = input.InputKeyHandler._delforw C_b = input.InputKeyHandler._back C_f = input.InputKeyHandler._forw C_k = input.InputKeyHandler._deltoend C_a = input.InputKeyHandler._begin C_e = input.InputKeyHandler._end C_y = input.InputKeyHandler._paste class Runcommand: "Read a string from the user, and run it." def __init__(self, screen): self.screen = screen window = input.modeInput("$ ", self.screen) window.read(self, MyEditHandler, 0, 0) def __call__(self, string): self.screen.system(string) # Support for using Cyclops to detect circular references # There are two kinds of circular references in PLWM: static and # dynamic. The static chains are created at startup, and contains # the WindowManager object, all the Screen objects and the basic # keyhandler. # The dynamic chains are primarily the Client objects and temporary # KeyHandlers. These objects have several circular references which # must be broken when the object isn't needed any longer. If these chains # aren't broken by properly destroying the objects (e.g. by calling # Client.withdraw or KeyHandler._cleanup) they will be left in memory, # causing a memory leak. # The dynamic chains are the problem, and new chains must be found to fix # memory problems. This makes using Cyclops slightly difficult: we must # filter out the static chains to be able to focus on the dynamic chains. # We have currently two different ways to find out which chains # to ignore, hopefully they can complement each other in finding new # chains. # Method 1: # # Use Cyclops to find all objects created at startup. # Then filter out all cycles involving these objects. class cycle_filter1: def __init__(self, rootset): self.rootobjs = {} for rc, cyclic, obj in rootset: if rc != 0: self.rootobjs[id(obj)] = 1 print 'Length of rootset', len(rootset) print 'Length of rootobjs', len(self.rootobjs) # Ignore cycles which include objects in the startup rootset def __call__(self, cycle): for obj, index in cycle: if self.rootobjs.has_key(id(obj)): return 0 return 1 def cycle_detect1(): import Cyclops z = Cyclops.CycleFinder() # Create PLWM inside cyclop to find all objects created at startup p = z.run(PLWM) # Get the rootset (== all objects) and reset the cyclop rootset = z.get_rootset() z.clear() # Add a filter for the rootset z.install_cycle_filter(cycle_filter1(rootset)) # Finally, run the loop z.run(p.brave_loop) z.find_cycles() # Print info z.show_stats() z.show_cycles() z.show_arcs() # Method 2: # # Ask the WindowManager about all dynamic objects it added during startup, # and then filter out static objects when finding cycles. def cycle_filter2(cycle): for obj, index in cycle: if isinstance(obj, wmanager.WindowManager) or \ isinstance(obj, wmanager.Screen) or \ isinstance(obj, BasicKeys): return 0 return 1 def cycle_detect2(): import Cyclops z = Cyclops.CycleFinder() # Create the window manager and find its dynamic cycle roots p = PLWM() p._register_cycle_roots(z) # Run the event handler z.run(p.brave_loop) # Tell the window manager to do cleanup of all cycle roots p._cleanup_cycle_roots() # We must handle all the events generated by the cleanup p.handle_events() z.install_cycle_filter(cycle_filter2) # Purge relly dead roots, as this will include (hopefully) all # dynamically created objects with no circular references z.find_cycles(1) # Print info z.show_stats() z.show_cycles() z.show_arcs() if __name__ == '__main__': wmanager.main(PLWM) python-plwm-2.6a+20080530/examples/senapwm.py0000755000175000017500000001466610761724253017334 0ustar stewstew#!/usr/bin/env python # # senapwm.py -- Example PLWM window manager "configuration" # # Copyright (C) 1999,2000 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys import os ###SETUP PATH sys.path[1:1] = [os.path.join(sys.path[0], '/home/morgan/hack/plwm/')] ###END SETUP PATH from plxlib import plxlib, X from plwm import wmanager, focus, keys, \ deltamove, \ border, color, font, cycle, views, \ modewinctl, modestatus from plwm.moveresize import MoveResizeKeys # from textwindow import TextWindow delta = deltamove.DeltaMove() class MyClient(wmanager.Client, focus.FocusClient, border.BorderClient, focus.JumpstartClient, modestatus.ModeFocusedTitle): no_border_clients = ['XModeWindow'] start_iconified_clients = ['WMManager'] move_focus_ignore_clients = no_border_clients default_pointer_pos = {'Emacs': (-1, 0), 'XTerm': (-1, 0)} class MyScreen(wmanager.Screen, color.Color, modewinctl.ModeClientControl, modestatus.ModeStatus, modestatus.ModeMoveResize, views.XMW_ViewHandler, keys.KeyGrabber): view_always_visible_clients = ['XModeWindow'] class WMConfig: def __wm_init__(self): BasicKeys(self, self.dispatch) self.dispatch.add_handler('cmdevent', cmdhandler) class PLWM(wmanager.WindowManager, focus.SloppyFocus, font.Font, WMConfig): client_class = MyClient screen_class = MyScreen def cmdhandler(evt): print 'Exit:', evt.exitstatus(), 'Signal:', evt.termsig() class BasicKeys(keys.KeyHandler): def F1(self, event): self.wm.current_screen.view_prev() def F2(self, event): self.wm.current_screen.view_next() def F3(self, event): self.wm.move_focus(focus.MOVE_LEFT) def F4(self, event): self.wm.move_focus(focus.MOVE_RIGHT) def KP_End(self, event): self.wm.current_screen.view_prev() def KP_Down(self, event): self.wm.current_screen.view_next() def KP_Insert(self, event): wmanager.debug('keys', 'New view') self.wm.current_screen.view_new() def KP_Divide(self, event): #up self.wm.move_focus(focus.MOVE_UP) def KP_Delete(self, event): #down self.wm.move_focus(focus.MOVE_DOWN) def KP_Next(self, event): #left self.wm.move_focus(focus.MOVE_LEFT) def KP_Add(self, event): #right self.wm.move_focus(focus.MOVE_RIGHT) def S_KP_Divide(self, event): #up d = delta.get(event.time) self.wm.display.WarpPointer(0, -d) def S_KP_Delete(self, event): #down d = delta.get(event.time) self.wm.display.WarpPointer(0, d) def S_KP_Next(self, event): #left d = delta.get(event.time) self.wm.display.WarpPointer(-d, 0) def S_KP_Add(self, event): #right d = delta.get(event.time) self.wm.display.WarpPointer(d, 0) def KP_Subtract(self, event): MyMoveResizeKeys(self, event) def Pause(self, event): self.wm.system('xlock -mode blank') def C_M_Escape(self, event): raise 'PLWMEscape', 'Escaping window manager' def C_KP_Delete(self, event): if self.wm.focus_client: self.wm.focus_client.delete(1) def C_S_KP_Delete(self, event): if self.wm.focus_client: self.wm.focus_client.destroy() def M_section(self, event): self.wm.system('xterm') def M_F9(self, event): self.wm.system('xterm -title Shell -sb -sl 1024 -fn fixed -bg grey70 -fg black') def M_F10(self, event): self.wm.system('emacs -bg grey70 -fg black') def M_F11(self, event): self.wm.system('netscape&') # # Use the keymap for moving and resizing windows. # class MyMoveResizeKeys(MoveResizeKeys): KP_Next = MoveResizeKeys._move_w KP_Add = MoveResizeKeys._move_e KP_Divide = MoveResizeKeys._move_n KP_Delete = MoveResizeKeys._move_s C_KP_Next = MoveResizeKeys._enlarge_w C_KP_Add = MoveResizeKeys._enlarge_e C_KP_Divide = MoveResizeKeys._enlarge_n C_KP_Delete = MoveResizeKeys._enlarge_s M_KP_Next = MoveResizeKeys._shrink_e M_KP_Add = MoveResizeKeys._shrink_w M_KP_Divide = MoveResizeKeys._shrink_s M_KP_Delete = MoveResizeKeys._shrink_n KP_Subtract = MoveResizeKeys._moveresize_end S_KP_Subtract = MoveResizeKeys._moveresize_end C_KP_Subtract = MoveResizeKeys._moveresize_end class CycleUMKeys(keys.KeyGrabKeyboard): propagate_keys = 0 timeout = 10 def __init__(self, wm, dispatch, time): keys.KeyGrabKeyboard.__init__(self, wm, dispatch, time) self.cy = cycle.CycleUnmapped(wm.current_screen, 1) def Tab(self, event): self.cy.next() C_Tab = Tab def S_Tab(self, event): self.cy.previous() def Return(self, event): wmanager.debug('keys', 'Escaping CycleMapped mode') self._cleanup() self.cy.end() def Escape(self, event): wmanager.debug('keys', 'Aborting CycleMapped mode') self._cleanup() self.cy.abort() _timeout = Escape if __name__ == '__main__': sync = 0 while len(sys.argv) > 1: if sys.argv[1] == '-d': wmanager.debug = wmanager.do_debug elif sys.argv[1] == '-s': sync = 1 del sys.argv[1] try: p = PLWM() except wmanager.error_no_unmanaged_screens: sys.stderr.write(sys.argv[0] + ': Another window manager already running?\n') sys.exit(1) if sync: p.display.Synchronize(1) p.brave_loop() python-plwm-2.6a+20080530/examples/README.examplewm0000644000175000017500000000361407233270312020142 0ustar stewstew** How to use the pointless window manager petliwm: The mouse pointer can be moved with the numeric keyboard (KP_1 to KP_9). Mouse clicks can be simulated by pressing F9, F10 or F11, for button 1, 2 or 3, respectively. Ctrl+{KP_Left, KP_Right, KP_Up, KP_Down} will move the focus to the next window approximately to the left, right, up or down of the currently focused window, as will Ctrl-< resp Ctrl-Shift->. KP_5 switches to outlined move-resize mode for the focused window. The window is moved by KP_1 to KP_9, enlarged with Shift + KP_*, and shrinked with Ctrl + KP_*. Use the new position by pressing KP_5 again, or abort with Escape. Raise or lower a window with KP_Enter. Iconify windows with KP_0 (KP_Insert). Deiconify windows with Ctrl+Tab. Press Tab (or Ctrl+Tab) to step to the next iconified window, and Shift+Tab to the previous. Press Return to select and deiconify a window, or Escape to cancel. Delete a window with Ctrl+KP_Delete. Destroy a window with Ctrl+Shift+Delete. Lock the screen with Ctrl+KP_Subtract. Create a new View (a "workspace" with extra bells and whistles) with Ctrl+KP_Add. Jump to the next view with KP_Add, to the previous view with KP_Subtract. A view is removed if there are no more visible windows when you leave it. F1 jumps to the next view containing an xterm, F2 to an Emacs, and F3 to a Netscape. Shift+F{1,2,3} starts a new xterm, Emacs or Netscape, and Ctrl+Shift+F{1,2,3} creates a new view and then starts the program. Shift+F5 and Shift+F6 tags that view with the strings "F5" and "F6", respectively. F5 and F6 will then jump to next view which is tagged with the corresponding string. Quit the window manager with Ctrl+Meta+Escape. To let clients recieve key events normally catched by PLWM, press Shift-Pause. This will bypass all key event handling in PLWM until Pause is pressed. This state is indicated by the message [Bypassing] in the modewindow.python-plwm-2.6a+20080530/examples/ChangeLog0000644000175000017500000000114507341502320017030 0ustar stewstew2001-08-24 Peter Liljenberg * petliwm.py (MozillaKeys.__init__): (MozillaKeys.R_Any_BackSpace): For Netscape 6.1, backspace now scrolls the page up again. Wed Feb 14 12:00:50 2001 Peter Liljenberg * petliwm.py (BasicKeys.M5_l): Move pointer to the canonical position for this window (typically out of the way). Thu Jan 11 18:34:58 2001 Peter Liljenberg * petliwm.py (FixaBalansHelvetetFulpatch.__client_init__): Block UnmapNotify events while reparenting JVM AWT windows, to avoid loosing them immediately afterwards. python-plwm-2.6a+20080530/examples/plpwm.py0000755000175000017500000003146310767527561017024 0ustar stewstew#!/usr/bin/env python # # plpwm.py -- Example PLWM window manager configuration with panes. # # Copyright (C) 2001 Mike Meyer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """plpwm.py - Pointer Less Paned Window Manager Example PLWM window manager configuration with panes.""" import sys, os, string ###SETUP PATH sys.path[1:1] = [os.path.join(sys.path[0], '..')] ###END SETUP PATH from time import sleep from string import digits from Xlib import X, XK from plwm import wmanager, keys, inspect, \ border, color, font, menu, panes, cfilter, input, message from plwm.pane_utilities import appmenu, codemenu, windowmenu, panesmenu, \ runcommand, splitpane, numberpane, pullwindow, gotowindow, websearch, \ split_pane, getapp, view_menu from xmlcontrol import XML_controller, load_menus class MyMenuHandler(menu.MenuCharSelecter): Any_Escape = C_g = menu.MenuKeyHandler._abort Any_Return = menu.MenuKeyHandler._do Any_space = Any_Down = C_n = menu.MenuKeyHandler._down Any_BackSpace = Any_Up = C_p = menu.MenuKeyHandler._up class MyEditHandler(input.InputKeyHandler): Any_Escape = C_g = input.InputKeyHandler._abort Any_Return = input.InputKeyHandler._done Any_Delete = Any_BackSpace = C_h = input.InputKeyHandler._delback C_d = input.InputKeyHandler._delforw C_b = input.InputKeyHandler._back C_f = input.InputKeyHandler._forw C_k = input.InputKeyHandler._deltoend C_a = input.InputKeyHandler._begin C_e = input.InputKeyHandler._end C_y = input.InputKeyHandler._paste c_p = input.InputKeyHandler._history_up c_n = input.InputKeyHandler._history_down class MyClient(wmanager.Client, border.BorderClient, panes.panesClient): "Put the clients in panes, with a border." border_default_width = 5 border_color_name = 'IndianRed4' border_focuscolor_name = 'Lawn Green' class MyScreen(wmanager.Screen, color.Color, message.screenMessage, panes.panesScreen, menu.screenMenu): "And panes on the screen, and I'm going to want some menus." allow_self_changes = cfilter.title('FXTV') menu_handler = MyMenuHandler menu_bordercolor = "Red" message_bordercolor = "Blue" class WMConfig: def __wm_init__(self): "install the panes key map, menus, and try to restore my config." self.panekeys = PaneKeys(self) auto_restore(self, self.panekeys.menus.find('paneconfigmenus')) class PLPWM(wmanager.WindowManager, font.Font, panes.panesManager, inspect.InspectServer, WMConfig): "Set up my window manager." client_class = MyClient screen_class = MyScreen panes_maxsize_gravity = X.NorthWestGravity def __wm_screen_resize__(self): "install the panes key map, menus, and try to restore my config." auto_restore(self, self.panekeys.menus.find('paneconfigmenus')) class paneWindow(input.inputWindow): bordercolor = "Goldenrod" editHandler = MyEditHandler class screenWindow(input.inputWindow): borderwidth = 5 bordercolor = "Orange" editHandler = MyEditHandler class PaneKeys(keys.KeyHandler): "The pane control keys." menu_file = os.path.expanduser('~/.plpwmrc.xml') def __init__(self, obj): keys.KeyHandler.__init__(self, obj) self.menus = load_menus(self.menu_file) # Commands for navigating and manipulating panes def C_0(self, event): "Go to the pane numbered by the keycode." self.wm.panes_goto(self.wm.display.keycode_to_keysym(event.detail, 0) - XK.XK_0) C_1 = C_2 = C_3 = C_4 = C_5 = C_6 = C_7 = C_8 = C_9 = C_0 def M1_Tab(self, event): self.wm.panes_next() def S_M1_Tab(self, event): self.wm.panes_prev() def M1_0(self, event): pane = self.wm.panes_list[self.wm.panes_current] splitpane(pane, pane.horizontal_split, paneWindow) def S_M1_0(self, event): pane = self.wm.panes_list[self.wm.panes_current] splitpane(pane, pane.vertical_split, paneWindow) def M1_1(self, event): self.wm.panes_list[self.wm.panes_current].maximize() def M1_2(self, event): split_pane(self.wm.display.keycode_to_keysym(event.detail, 0) - XK.XK_0, self.wm.panes_list[self.wm.panes_current].horizontal_split) M1_3 = M1_4 = M1_5 = M1_6 = M1_7 = M1_8 = M1_9 = M1_2 def S_M1_2(self, event): split_pane(self.wm.display.keycode_to_keysym(event.detail, 0) - XK.XK_0, self.wm.panes_list[self.wm.panes_current].vertical_split) S_M1_3 = S_M1_4 = S_M1_5 = S_M1_6 = S_M1_7 = S_M1_8 = S_M1_9 = S_M1_2 def M1_exclam(self, event): runcommand(self.wm.panes_list[self.wm.panes_current], paneWindow) def M1_equal(self, event): numberpane(self.wm.panes_list[self.wm.panes_current], paneWindow) def M1_minus(self, event): """Close all the internal windows. Untested""" for s in self.wm.screens: for w in s.windows: if self.wm.is_internal_window(w): w.destroy() def M1_quoteright(self, event): pullwindow(self.wm.panes_list[self.wm.panes_current], paneWindow) def M1_quotedbl(self, event): gotowindow(self.wm.panes_list[self.wm.panes_current], screenWindow) def M1_space(self, event): self.wm.panes_list[self.wm.panes_current].prev_window() def S_M1_space(self, event): self.wm.panes_list[self.wm.panes_current].next_window() def _getapp(self, node): getapp(self.wm.panes_list[self.wm.panes_current], node.get('title'), node.get('command')) def M1_a(self, event): view_menu(self.wm.panes_list[self.wm.panes_current], XML_controller(self.menus.find('functionmenu'), dict(getapp=self._getapp))) def M1_A(self, event): def run(node): self.wm.system('%s &' % node.get('command')) view_menu(self.wm.panes_list[self.wm.panes_current], XML_controller(self.menus.find('namemenu'), dict(getapp=self._getapp, run=run))) def M1_c(self, event): self.wm.panes_list[self.wm.panes_current].window.delete(1) def M1_l(self, event): self.wm.system('xscreensaver-command -activate') def M1_i(self, event): windowmenu(self.wm.panes_list[self.wm.panes_current], cfilter.iconified) def M1_I(self, event): self.wm.inspect_enable() self.wm.system('xterm -title Inspector -e inspect_plwm &') def M1_k(self, event): self.wm.panes_list[self.wm.panes_current].window.delete(1) def M1_K(self, event): self.wm.panes_list[self.wm.panes_current].window.destroy() def M1_m(self, event): def itunes(node): os.system('itunes "%s" &' % node.get('command')) view_menu(self.wm.panes_list[self.wm.panes_current], XML_controller(self.menus.find('itunesmenu'), dict(itunes=itunes))) def M1_n(self, event): pane = self.wm.panes_list[self.wm.panes_current] width, height = pane.screen.message_make("Current pane %d" % \ self.wm.panes_current) pane.screen.message_display((pane.width - width) / 2 + pane.x, (pane.height - height) / 2 + pane.y) def M1_p(self, event): panesmenu(self.wm.current_screen) def M1_r(self, event): self.wm.panes_list[self.wm.panes_current].force_window() def M1_R(self, event): def reload_menus(): self.menus = load_menus(self.menu_file) auto_restore(self.wm, self.menus.find('paneconfigmenus')) def restore_menu(wm): view_menu(pane, XML_controller(self.menus.find('paneconfigmenus'), dict(paneconfig=lambda x: restore(wm, x)))) pane = self.wm.panes_list[self.wm.panes_current] codemenu(pane, {'1: Restore': (restore_menu, (self.wm,)), '2: Reload': (reload_menus, ()), '3: Restart': (os.execvp, (sys.executable, [sys.executable] + sys.argv)), '4: Quit': (self.wm.quit, ()) }) def M1_s(self, event): def dillo(url): self.wm.system("dillo '%s' &" % url) def dowebsearch(node): if node.get('images'): websearch(pane, node.get('label'), paneWindow, node.get('url'), dillo) else: websearch(pane, node.get('label'), paneWindow, node.get('url')) pane = self.wm.panes_list[self.wm.panes_current] menu = XML_controller(self.menus.find('websearches'), dict(search=dowebsearch)) add_keys(menu) view_menu(pane, menu) def M1_S(self, event): self.wm.panes_save() pane = self.wm.panes_list[self.wm.panes_current] width, height = pane.screen.message_make("Saved panes configuration") pane.screen.message_display((pane.screen.root_width - width) / 2 + pane.screen.root_x, (pane.screen.root_height - height) / 2 + pane.screen.root_y) def M1_w(self, event): windowmenu(self.wm.panes_list[self.wm.panes_current]) def M1_W(self, event): pane = self.wm.panes_list[self.wm.panes_current] windowmenu(pane, panes.panefilter(pane)) def M1_x(self, event): self.wm.panes_list[self.wm.panes_current].iconify_window() def add_keys(menu): """Add shortcuts keys to the menu.""" for label in menu.keys(): node = menu[label] key = node.get('shortcut') if key: nl = '%s: %s' % (key, label) menu[nl] = menu[label] del menu[label] def auto_restore(wm, panes): "Look for and restore an automatic config." geom = '%dx%d' % (wm.screens[0].root_full_width, wm.screens[0].root_full_height) for config in panes: startup = config.get('startup') if (startup == 'match' and config.get('label').endswith(geom)) or \ startup == 'use': restore(wm, config) break def restore(wm, config): "Build my standard work environment." wm.panes_list[0].maximize() # Disconnect all windows from panes to avoid displaying everything # that's about to happen. wm.panes_restore() will put things back. for c in wm.query_clients(): c.panes_pane = None # Disconnect any remaining panes from their windows as well. wm.panes_list[0].window = None places = dict() for node in config: if node.tag == 'vertical': wm.panes_list[wm.panes_current].vertical_split(float(node.get('fraction'))) elif node.tag == 'horizontal': wm.panes_list[wm.panes_current].horizontal_split(float(node.get('fraction'))) elif node.tag == 'newpane': pane = wm.panes_list[int(node.get('pane'))] elif node.tag == 'goto': wm.panes_goto(int(node.get('pane'))) elif node.tag == 'number': wm.panes_number(int(node.get('new'))) elif node.tag == 'placewindow': places[node.get('name')] = int(node.get('pane')) else: raise ValueError, 'Unrecognized pane config element %s' % node # Now fix any windows wired by title panecount = len(wm.panes_list) for s in wm.screens: for c in s.query_clients(cfilter.true, 1): title = c.get_title() if title[-2] == '@' and title[-1] in digits: pane = digits.index(title[-1]) if pane < panecount: wm.panes_list[pane].add_window(c) continue else: for name in places: if name in title: if places[name] < panecount: wm.panes_list[places[name]].add_window(c) break # And now make the world sane wm.panes_goto(0) wm.panes_restore() if __name__ == '__main__': wmanager.main(PLPWM) python-plwm-2.6a+20080530/examples/examplewm.py0000755000175000017500000002301411010153410017617 0ustar stewstew#!/usr/bin/env python # # petliwm.py -- Example PLWM window manager "configuration" # # Copyright (C) 1999-2001 Peter Liljenberg # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys import os ###SETUP PATH sys.path[1:1] = [os.path.join(sys.path[0], '..')] ###END SETUP PATH import time from Xlib import X from plwm import wmanager, focus, keys, \ deltamove, outline, \ frame, color, font, views, \ modewindow, modestatus, \ mw_clock, mw_biff, \ inspect from plwm.cycle import CycleKeys from plwm.moveresize import MoveResizeKeys from plwm.cfilter import * delta = deltamove.DeltaMove() class MyClient(wmanager.Client, outline.XorOutlineClient, modestatus.ModeFocusedTitleClient): # Put a frame around all client windows window_proxy_class = frame.FrameProxy start_iconified_clients = name('WMManager') default_pointer_pos = {'Emacs': (-1, 0), 'XTerm': (-1, 0)} class MyScreen(wmanager.Screen, color.Color, modewindow.ModeWindowScreen, modestatus.ModeStatus, modestatus.ModeMoveResize, views.XMW_ViewHandler, modestatus.ModeFocusedTitleScreen): view_always_visible_clients = Or(name('XClock'), name('XBiff'), name('XModeWindow')) class WMConfig: def __wm_init__(self): # Install the basic key map BasicKeys(self) class PLWM(wmanager.WindowManager, font.Font, focus.SloppyFocus, focus.MoveFocus, mw_clock.ModeWindowClock, mw_biff.ModeWindowBiff, inspect.InspectServer, WMConfig): client_class = MyClient screen_class = MyScreen class BasicKeys(keys.KeyHandler): def F1(self, event): self.wm.current_screen.view_find_with_client(name('XTerm')) def S_F1(self, event): self.wm.system('xterm -geometry 80x50+200+100') def C_S_F1(self, event): self.wm.current_screen.view_new() self.wm.system('xterm -geometry 80x50+200+100') def F2(self, event): self.wm.current_screen.view_find_with_client(name('Emacs')) def S_F2(self, event): self.wm.system('emacs') def C_S_F2(self, event): self.wm.current_screen.view_new() self.wm.system('emacs') def F3(self, event): self.wm.current_screen.view_find_with_client(name('Netscape')) def S_F3(self, event): self.wm.system('netscape') def C_S_F3(self, event): self.wm.current_screen.view_new() self.wm.system('netscape') def F5(self, event): self.wm.current_screen.view_find_tag('F5') def S_F5(self, event): self.wm.current_screen.view_tag('F5') def F6(self, event): self.wm.current_screen.view_find_tag('F6') def S_F6(self, event): self.wm.current_screen.view_tag('F6') def F7(self, event): self.wm.current_screen.view_find_tag('F7') def S_F7(self, event): self.wm.current_screen.view_tag('F7') def F8(self, event): self.wm.current_screen.view_find_tag('F8') def S_F8(self, event): self.wm.current_screen.view_tag('F8') # Simulate mouse clicks def Any_F9(self, evt): self.wm.fake_button_click(1) def Any_F10(self, evt): self.wm.fake_button_click(2) def Any_F11(self, evt): self.wm.fake_button_click(3) def F12(self, evt): self.wm.inspect_toggle() def S_F12(self, evt): self.wm.inspect_toggle(force = 1) # Drop all keygrabs until Scroll_Lock is pressed again, to allow # clients to recieve keys used by plwm def S_Pause(self, evt): wmanager.debug('keys', 'dropping keygrabs temporarily') # First release all our grabs. They will be reinstalled # when BypassHandler exits self._ungrab() BypassHandler(self) def KP_Begin(self, event): MyMoveResizeKeys(self, event) def C_Tab(self, event): CycleUMKeys(self, event) def KP_Insert(self, event): wmanager.debug('keys', 'Iconifying') if self.wm.current_client: self.wm.current_client.iconify() def KP_Subtract(self, event): wmanager.debug('keys', 'Prev view') self.wm.current_screen.view_prev() def KP_Add(self, event): wmanager.debug('keys', 'Next view') self.wm.current_screen.view_next() def C_KP_Add(self, event): wmanager.debug('keys', 'New view') self.wm.current_screen.view_new() def KP_Left(self, event): self.wm.display.warp_pointer(-delta.get(event.time), 0) def KP_Right(self, event): self.wm.display.warp_pointer(delta.get(event.time), 0) def KP_Up(self, event): self.wm.display.warp_pointer(0, -delta.get(event.time)) def KP_Down(self, event): self.wm.display.warp_pointer(0, delta.get(event.time)) def KP_Home(self, event): d = delta.get(event.time) self.wm.display.warp_pointer(-d, -d) def KP_End(self, event): d = delta.get(event.time) self.wm.display.warp_pointer(-d, d) def KP_Prior(self, event): d = delta.get(event.time) self.wm.display.warp_pointer(d, -d) def KP_Next(self, event): d = delta.get(event.time) self.wm.display.warp_pointer(d, d) def KP_Enter(self, event): if self.wm.current_client: self.wm.current_client.raiselower() # For laptop compitability KP_Divide = KP_Enter def C_KP_Subtract(self, event): self.wm.system('xlock -mode blank') def C_M_Escape(self, event): self.wm.quit() def C_KP_Delete(self, event): if self.wm.current_client: self.wm.current_client.delete(1) def C_S_KP_Delete(self, event): if self.wm.current_client: self.wm.current_client.destroy() def C_KP_Left(self, event): self.wm.move_focus(focus.MOVE_LEFT) def C_KP_Right(self, event): self.wm.move_focus(focus.MOVE_RIGHT) def C_KP_Up(self, event): self.wm.move_focus(focus.MOVE_UP) def C_KP_Down(self, event): self.wm.move_focus(focus.MOVE_DOWN) def C_less(self, event): self.wm.move_focus(focus.MOVE_LEFT) def C_S_less(self, event): self.wm.move_focus(focus.MOVE_RIGHT) class BypassHandler(keys.KeyHandler): propagate_keys = 0 def __init__(self, keyhandler): keys.KeyHandler.__init__(self, keyhandler.wm) self._keyhandler = keyhandler self._message = modewindow.Message(.1, modewindow.LEFT, 0, '[Bypassing]') self._screen = keyhandler.wm.current_screen self._screen.modewindow_add_message(self._message) def Pause(self, evt): wmanager.debug('keys', 'reinstalling keygrabs') self._screen.modewindow_remove_message(self._message) # Delete ourself, and reinstall the callee grabs self._cleanup() self._keyhandler._buildmap() # Remove it, just to be sure there are no circular references del self._keyhandler del self._screen # # Use the keymap for moving and resizing windows. # Without any modifiers moves, with Shift enlarges, with Ctrl shrinks # End with KP_5. Abort with Escape or KP_Delete. # class MyMoveResizeKeys(MoveResizeKeys): KP_Left = MoveResizeKeys._move_w KP_Right = MoveResizeKeys._move_e KP_Up = MoveResizeKeys._move_n KP_Down = MoveResizeKeys._move_s KP_Home = MoveResizeKeys._move_nw KP_End = MoveResizeKeys._move_sw KP_Prior = MoveResizeKeys._move_ne KP_Next = MoveResizeKeys._move_se S_KP_Left = MoveResizeKeys._enlarge_w S_KP_Right = MoveResizeKeys._enlarge_e S_KP_Up = MoveResizeKeys._enlarge_n S_KP_Down = MoveResizeKeys._enlarge_s S_KP_Home = MoveResizeKeys._enlarge_nw S_KP_End = MoveResizeKeys._enlarge_sw S_KP_Prior = MoveResizeKeys._enlarge_ne S_KP_Next = MoveResizeKeys._enlarge_se C_KP_Left = MoveResizeKeys._shrink_w C_KP_Right = MoveResizeKeys._shrink_e C_KP_Up = MoveResizeKeys._shrink_n C_KP_Down = MoveResizeKeys._shrink_s C_KP_Home = MoveResizeKeys._shrink_nw C_KP_End = MoveResizeKeys._shrink_sw C_KP_Prior = MoveResizeKeys._shrink_ne C_KP_Next = MoveResizeKeys._shrink_se KP_Begin = MoveResizeKeys._moveresize_end S_KP_Begin = MoveResizeKeys._moveresize_end C_KP_Begin = MoveResizeKeys._moveresize_end Escape = MoveResizeKeys._moveresize_abort KP_Delete = MoveResizeKeys._moveresize_abort class CycleUMKeys(CycleKeys): _cycle_filter = iconified Tab = CycleKeys._cycle_next C_Tab = CycleKeys._cycle_next S_Tab = CycleKeys._cycle_previous S_C_Tab = CycleKeys._cycle_previous Escape = CycleKeys._cycle_abort Return = CycleKeys._cycle_end if __name__ == '__main__': wmanager.main(PLWM) python-plwm-2.6a+20080530/examples/xmlcontrol.py0000644000175000017500000000271710761724253020052 0ustar stewstew from os.path import split, join try: # Python 2.5 bundled elementtree from xml.etree import ElementTree, ElementInclude except ImportError: # Pre-2.5 third party elementtree from elementtree import ElementTree, ElementInclude class XML_controller(dict): """Controller part of the MVC model for a user selection facility.""" def __init__(self, node, actions): """Verify that everything is labeled and has an action.""" for child in node: if not child.get('label'): raise ValueError, 'Element %s has no label' % child.tag if not actions.has_key(child.tag): raise ValueError, 'Element tag %s not in actions' % child.tag self[child.get('label')] = child self.actions = dict(actions) def choose(self, label): """Run the code that goes with labe.""" self.actions[self[label].tag](self[label]) class Loader(object): """Custmom loader for XInclude processing to fix relative addressing.""" def __init__(self, name): self.base = split(name)[0] def __call__(self, href, typ, encoding=None): """Fix relative references and call the default loader.""" return ElementInclude.default_loader(join(self.base, href), typ, encoding) def load_menus(path): """Load the file path.""" menus = ElementTree.parse(path) ElementInclude.include(menus.getroot(), loader=Loader(path)) return menus python-plwm-2.6a+20080530/INSTALL0000644000175000017500000000110210031365063014463 0ustar stewstew Installation of the Pointless Window Manager * Requirements PLWM requires Python 2.0 or newer, which can be downloaded from http://www.python.org/. It has been tested with Python 2.0, 2.1, 2.2 and 2.3. It also requires the Python X Library 0.8 or newer, which can be downloaded from http://python-xlib.sourceforge.net/. * Configuring Configuration is done by the Python distutils package as part of the installation process. * Building No building is required, as there's no C code in PLWM. * Installing As root, just type: python setup.py install python-plwm-2.6a+20080530/COPYING0000644000175000017500000004312707224607241014511 0ustar stewstew GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. python-plwm-2.6a+20080530/doc/0000755000175000017500000000000011020017212014172 5ustar stewstewpython-plwm-2.6a+20080530/doc/plwm.pdf0000644000175000017500000061107010035014302015653 0ustar stewstew%PDF-1.2 7 0 obj << /Type/Encoding /Differences[0/Gamma/Delta/Theta/Lambda/Xi/Pi/Sigma/Upsilon/Phi/Psi/Omega/ff/fi/fl/ffi/ffl/dotlessi/dotlessj/grave/acute/caron/breve/macron/ring/cedilla/germandbls/ae/oe/oslash/AE/OE/Oslash/suppress/exclam/quotedblright/numbersign/dollar/percent/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/exclamdown/equal/questiondown/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/quotedblleft/bracketright/circumflex/dotaccent/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/endash/emdash/hungarumlaut/tilde/dieresis/suppress 160/space/Gamma/Delta/Theta/Lambda/Xi/Pi/Sigma/Upsilon/Phi/Psi 173/Omega/ff/fi/fl/ffi/ffl/dotlessi/dotlessj/grave/acute/caron/breve/macron/ring/cedilla/germandbls/ae/oe/oslash/AE/OE/Oslash/suppress/dieresis] >> endobj 10 0 obj << /Encoding 7 0 R /Type/Font /Subtype/Type1 /Name/F1 /FontDescriptor 9 0 R /BaseFont/HCXOLA+CMBX12 /FirstChar 33 /LastChar 196 /Widths[342.6 581 937.5 562.5 937.5 875 312.5 437.5 437.5 562.5 875 312.5 375 312.5 562.5 562.5 562.5 562.5 562.5 562.5 562.5 562.5 562.5 562.5 562.5 312.5 312.5 342.6 875 531.3 531.3 875 849.5 799.8 812.5 862.3 738.4 707.2 884.3 879.6 419 581 880.8 675.9 1067.1 879.6 844.9 768.5 844.9 839.1 625 782.4 864.6 849.5 1162 849.5 849.5 687.5 312.5 581 312.5 562.5 312.5 312.5 546.9 625 500 625 513.3 343.8 562.5 625 312.5 343.8 593.8 312.5 937.5 625 562.5 625 593.8 459.5 443.8 437.5 625 593.8 812.5 593.8 593.8 500 562.5 1125 562.5 562.5 562.5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 675.9 937.5 875 787 750 879.6 812.5 875 812.5 875 0 0 812.5 656.3 625 625 937.5 937.5 312.5 343.8 562.5 562.5 562.5 562.5 562.5 849.5 500 574.1 812.5 875 562.5 1018.5 1143.5 875 312.5 562.5] >> endobj 12 0 obj << /Filter[/FlateDecode] /Length 193 >> stream xÚMÎ= Â0àÝ_qc2ôÌ5MVñDA0à c­ÔªààŸ·5.y/äyA PÀg,`âÇs‚T ÑàÏ@Ò)?ݳ͊SªÙnÍ™)öê‡fþâaÃ¥`MYsIìQ…û=>Û•õ©éWÏx_çu^„–üf$: ÏH™Ž2p¥Ú_PÁvhF ¥ú63"C§c³Þ î×±*«k¨{óØ‚…¶ør„Î œ!YúÇ AÇÞÙ Ab endstream endobj 14 0 obj << /F1 10 0 R >> endobj 6 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 14 0 R >> endobj 19 0 obj << /Encoding 7 0 R /Type/Font /Subtype/Type1 /Name/F2 /FontDescriptor 18 0 R /BaseFont/DCHSRE+CMR10 /FirstChar 33 /LastChar 196 /Widths[277.8 500 833.3 500 833.3 777.8 277.8 388.9 388.9 500 777.8 277.8 333.3 277.8 500 500 500 500 500 500 500 500 500 500 500 277.8 277.8 277.8 777.8 472.2 472.2 777.8 750 708.3 722.2 763.9 680.6 652.8 784.7 750 361.1 513.9 777.8 625 916.7 750 777.8 680.6 777.8 736.1 555.6 722.2 750 750 1027.8 750 750 611.1 277.8 500 277.8 500 277.8 277.8 500 555.6 444.4 555.6 444.4 305.6 500 555.6 277.8 305.6 527.8 277.8 833.3 555.6 500 555.6 527.8 391.7 394.4 388.9 555.6 527.8 722.2 527.8 527.8 444.4 500 1000 500 500 500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 625 833.3 777.8 694.4 666.7 750 722.2 777.8 722.2 777.8 0 0 722.2 583.3 555.6 555.6 833.3 833.3 277.8 305.6 500 500 500 500 500 750 444.4 500 722.2 777.8 500 902.8 1013.9 777.8 277.8 500] >> endobj 20 0 obj << /Type/Encoding /Differences[0/minus/periodcentered/multiply/asteriskmath/divide/diamondmath/plusminus/minusplus/circleplus/circleminus/circlemultiply/circledivide/circledot/circlecopyrt/openbullet/bullet/equivasymptotic/equivalence/reflexsubset/reflexsuperset/lessequal/greaterequal/precedesequal/followsequal/similar/approxequal/propersubset/propersuperset/lessmuch/greatermuch/precedes/follows/arrowleft/arrowright/arrowup/arrowdown/arrowboth/arrownortheast/arrowsoutheast/similarequal/arrowdblleft/arrowdblright/arrowdblup/arrowdbldown/arrowdblboth/arrownorthwest/arrowsouthwest/proportional/prime/infinity/element/owner/triangle/triangleinv/negationslash/mapsto/universal/existential/logicalnot/emptyset/Rfractur/Ifractur/latticetop/perpendicular/aleph/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/union/intersection/unionmulti/logicaland/logicalor/turnstileleft/turnstileright/floorleft/floorright/ceilingleft/ceilingright/braceleft/braceright/angbracketleft/angbracketright/bar/bardbl/arrowbothv/arrowdblbothv/backslash/wreathproduct/radical/coproduct/nabla/integral/unionsq/intersectionsq/subsetsqequal/supersetsqequal/section/dagger/daggerdbl/paragraph/club/diamond/heart/spade/arrowleft 161/minus/periodcentered/multiply/asteriskmath/divide/diamondmath/plusminus/minusplus/circleplus/circleminus 173/circlemultiply/circledivide/circledot/circlecopyrt/openbullet/bullet/equivasymptotic/equivalence/reflexsubset/reflexsuperset/lessequal/greaterequal/precedesequal/followsequal/similar/approxequal/propersubset/propersuperset/lessmuch/greatermuch/precedes/follows/arrowleft/spade] >> endobj 23 0 obj << /Encoding 20 0 R /Type/Font /Subtype/Type1 /Name/F3 /FontDescriptor 22 0 R /BaseFont/BSZITK+CMSY10 /FirstChar 33 /LastChar 196 /Widths[1000 500 500 1000 1000 1000 777.8 1000 1000 611.1 611.1 1000 1000 1000 777.8 275 1000 666.7 666.7 888.9 888.9 0 0 555.6 555.6 666.7 500 722.2 722.2 777.8 777.8 611.1 798.5 656.8 526.5 771.4 527.8 718.7 594.9 844.5 544.5 677.8 762 689.7 1200.9 820.5 796.1 695.6 816.7 847.5 605.6 544.6 625.8 612.8 987.8 713.3 668.3 724.7 666.7 666.7 666.7 666.7 666.7 611.1 611.1 444.4 444.4 444.4 444.4 500 500 388.9 388.9 277.8 500 500 611.1 500 277.8 833.3 750 833.3 416.7 666.7 666.7 777.8 777.8 444.4 444.4 444.4 611.1 777.8 777.8 777.8 777.8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 777.8 277.8 777.8 500 777.8 500 777.8 777.8 777.8 777.8 0 0 777.8 777.8 777.8 1000 500 500 777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 1000 1000 777.8 777.8 1000 777.8] >> endobj 24 0 obj << /Filter[/FlateDecode] /Length 804 >> stream xÚuUKo›@¾÷W œÖR!Àò¼6m"UQ©î©îa k{ ¬“øßw‹Ž{bfvžß<ð ½­GŸïËòö>ö¢0(#o¹ñ¢Âó“²ÂÌ[~ý-îºÃ".Ä©7ÛvñgùÝK“ NÁŒI©Báí½|÷âË ”žÖXÅYÈJÓPI&^H QY–~†ñ—RЧEœ muÏì£iþêeë…/ý–òð³4Èsϲ JÈö{3 ¦kÁ:• üÝöŠ|X]³ÀvüÝ«g2cNµî¹6ƒíÍúhé!/ì{­¬Ù£$Uw0zà×nãœîÆx{ —‰£jXûÐwèáÅÔçvšê€VDP… ¢”ý€:ÁÐvÖTšiÎRžãÉD›bVü¥M¯f¯Ýs=ä&£¾jÖáÚÆÙ%0Ë«0'QA0'q8‡æ$*¹ªLœXJ x 3òûŽê¨Í*Nsí”öm` â_®Ÿ 9.áü±­q€Xé@·µ±Î©ÌŦCY^ôŸ(û\œL»ý ’,¹è'ê¨fèØÞî”Eª ÐD º:G‚6ÑlXc›Ñxµº{qãHÓœ_­~ü‚Få…xЭîiÆ€y:®S1ýo}Ã7ÓVÍ‘G8ý¦*ÛœœÎ0ªð—rG¢ƒI4­j rY”nø@~1ÒyÎ…Ï¡æÍd )x‘­é‰OÌâ±±P3³Ð3sžLÔE¦ëŸù™šjç©©Y0vH QÒOy\@¤øóaWPèvå*àP¡Ë»"¬¥;“ ”°D:˜?Ý—ùffî,ÁÍÏRæüÂwœ8æó,™Ÿ%Tû¡Qn¶PƒNQ–Œ‰e§(‹±Á8}s ßÑ­£Fµþu¶GµÕ8°½m9´‘Pk•'b<¨‘œ-êÑ’!ñaÓQ8Ýt Ü[¥vŒ¦ˆJÇê¢k‡/š5ó²½¢ëÊ=s>zƒUÜx0ãEä§ ÌN~§Ÿš[î\Ëà¦22`{¿€9îµ;Ã?»E­W´s79F¸Â€+…à1ú¶üôጠendstream endobj 25 0 obj << /F2 19 0 R /F3 23 0 R >> endobj 16 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 25 0 R >> endobj 28 0 obj << /Type/Encoding /Differences[0/Gamma/Delta/Theta/Lambda/Xi/Pi/Sigma/Upsilon/Phi/Psi/Omega/ff/fi/fl/ffi/ffl/dotlessi/dotlessj/grave/acute/caron/breve/macron/ring/cedilla/germandbls/ae/oe/oslash/AE/OE/Oslash/suppress/exclam/quotedblright/numbersign/sterling/percent/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/exclamdown/equal/questiondown/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/quotedblleft/bracketright/circumflex/dotaccent/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/endash/emdash/hungarumlaut/tilde/dieresis/suppress 160/space/Gamma/Delta/Theta/Lambda/Xi/Pi/Sigma/Upsilon/Phi/Psi 173/Omega/ff/fi/fl/ffi/ffl/dotlessi/dotlessj/grave/acute/caron/breve/macron/ring/cedilla/germandbls/ae/oe/oslash/AE/OE/Oslash/suppress/dieresis] >> endobj 31 0 obj << /Encoding 28 0 R /Type/Font /Subtype/Type1 /Name/F4 /FontDescriptor 30 0 R /BaseFont/VILYPW+CMBXTI10 /FirstChar 33 /LastChar 196 /Widths[386.1 620.6 944.4 868.5 944.4 885.5 355.6 473.3 473.3 591.1 885.5 355.6 414.4 355.6 591.1 591.1 591.1 591.1 591.1 591.1 591.1 591.1 591.1 591.1 591.1 355.6 355.6 386.1 885.5 591.1 591.1 885.5 865.5 816.7 826.7 875.5 756.7 727.2 895.3 896.1 471.7 610.5 895 697.8 1072.8 896.1 855 787.2 855 859.4 650 796.1 880.8 865.5 1160 865.5 865.5 708.9 356.1 620.6 356.1 591.1 355.6 355.6 591.1 532.2 532.2 591.1 532.2 400 532.2 591.1 355.6 355.6 532.2 296.7 944.4 650 591.1 591.1 532.2 501.7 486.9 385 620.6 532.2 767.8 560.6 561.7 490.6 591.1 1182.2 591.1 591.1 591.1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 697.8 944.4 885.5 806.7 767.8 896.1 826.7 885.5 826.7 885.5 0 0 826.7 755.6 674.4 703.9 1044.7 1059.4 355.6 385 591.1 591.1 591.1 591.1 591.1 948.9 532.2 665 826.7 826.7 591.1 1022.8 1140.5 885.5 296.7 591.1] >> endobj 32 0 obj << /Type/Encoding /Differences[0/Gamma/Delta/Theta/Lambda/Xi/Pi/Sigma/Upsilon/Phi/Psi/Omega/arrowup/arrowdown/quotesingle/exclamdown/questiondown/dotlessi/dotlessj/grave/acute/caron/breve/macron/ring/cedilla/germandbls/ae/oe/oslash/AE/OE/Oslash/visiblespace/exclam/quotedbl/numbersign/dollar/percent/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/backslash/bracketright/asciicircum/underscore/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright/asciitilde/dieresis/visiblespace 160/space/Gamma/Delta/Theta/Lambda/Xi/Pi/Sigma/Upsilon/Phi/Psi 173/Omega/arrowup/arrowdown/quotesingle/exclamdown/questiondown/dotlessi/dotlessj/grave/acute/caron/breve/macron/ring/cedilla/germandbls/ae/oe/oslash/AE/OE/Oslash/visiblespace/dieresis] >> endobj 35 0 obj << /Encoding 32 0 R /Type/Font /Subtype/Type1 /Name/F5 /FontDescriptor 34 0 R /BaseFont/VAXAVY+CMTT10 /FirstChar 33 /LastChar 196 /Widths[525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 525 525 525 525 525 525 525 525 525 525 0 0 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525] >> endobj 38 0 obj << /Encoding 28 0 R /Type/Font /Subtype/Type1 /Name/F6 /FontDescriptor 37 0 R /BaseFont/HAVYZE+CMTI10 /FirstChar 33 /LastChar 196 /Widths[306.7 514.4 817.8 769.1 817.8 766.7 306.7 408.9 408.9 511.1 766.7 306.7 357.8 306.7 511.1 511.1 511.1 511.1 511.1 511.1 511.1 511.1 511.1 511.1 511.1 306.7 306.7 306.7 766.7 511.1 511.1 766.7 743.3 703.9 715.6 755 678.3 652.8 773.6 743.3 385.6 525 768.9 627.2 896.7 743.3 766.7 678.3 766.7 729.4 562.2 715.6 743.3 743.3 998.9 743.3 743.3 613.3 306.7 514.4 306.7 511.1 306.7 306.7 511.1 460 460 511.1 460 306.7 460 511.1 306.7 306.7 460 255.6 817.8 562.2 511.1 511.1 460 421.7 408.9 332.2 536.7 460 664.4 463.9 485.6 408.9 511.1 1022.2 511.1 511.1 511.1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 627.2 817.8 766.7 692.2 664.4 743.3 715.6 766.7 715.6 766.7 0 0 715.6 613.3 562.2 587.8 881.7 894.4 306.7 332.2 511.1 511.1 511.1 511.1 511.1 831.3 460 536.7 715.6 715.6 511.1 882.8 985 766.7 255.6 511.1] >> endobj 39 0 obj << /Filter[/FlateDecode] /Length 1273 >> stream xÚíšIsÛ6†ïýDy{zTp¦ÄQ22wÚ– P{.ÃÔoùóòþ<)’k½p#?¿X^æYj BHüv‚E\׋ìrY÷váÃmÒ<%Ú‡Þœ52nºš6ïÜIÀ”!KêÉâ Ù?Ò…Ö…38@©ç ó|ül÷ÎÛÇvà ˆ~Þµëì@‚v8É3]ÔG;¼¤Ö’’IáPM±ê{›¿Oï¬Bêk«“~6B)·ªÁÉä&±)~™ÜIÚfOöUJ…“6i +6!„–¡qGÈ¡AùÛådÊxü¯Nk×=’ozþ…Ý›ì!ÈØ^¾´ÃöÙèl"I¬ëÔþ¸é+ê8Ƚ*ÿÿà·+ÓVM*¹ÝN´³ñÙé]VÝ&ÛÍ„`³t?ÒæAÇîë(Bžâ@Õª2pGÙ¯ÕŸÀ'óïbiD `}b@ýš²]<ÖF¢'hEMÓj ëY–×záˆ!ø üØ]Tb úPEt9Ù«’ϵ.*»oðÜöÇP<_æ¾õ%'Az;+9P#&¾…"vw´P”ƒæ2‚øÄ2YrÞ6{š…M¤w6wêf×¶5\-‚„‘…øA¹¼/"˜H ðR[nš:3*7r»Fóã}‘ÕY’g_G¡5r øzf{Y!YiQÇ:p}~{7a,N²¼ë‚›Ò°Æ’6AkÜn´cxÜÈgq5ÃaIMØösCÝ’ a“Ü:KOÛ‡@zN˼tÛ=6[öÕvÃ}Ï»,`û4íºVÚQéöyððà>štEi^•žæ€ÑL±¦èEšÁ¯ä¡XŽô`ÐCIéGý¥:2õ0•!¦ô?M—Õ1êÇQ¯B4Yæe¹˜{zæãÆâÓp2±ËBÝáˆL]ïßÂPòäAŒå²Î³B;ã[}ñCwíEë¶°FhèyúŽ(‚?•wz¡«ì« ‘›åß3œQAñ·ŽíGç?Eì£'=_Ò\Óß(ýaŠ[¤y—éûc1Y£Iƒ‚º8åðmÄ'],Ÿ¬w¶ÓTûðU檣‹Y& Ò¼M íwNñ ù(ÁN¯'}•yå6˜1â°S–s}ß|!à«,êÕYù ,7^ V€d("ò  ªNj¬>@AŒhÐý?i^¦]y§†®ÐSR›¾à|ݸƒhø¼‰´‚îtöÃk‹Ö+ endstream endobj 40 0 obj << /F2 19 0 R /F1 10 0 R /F4 31 0 R /F5 35 0 R /F6 38 0 R >> endobj 27 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 40 0 R >> endobj 43 0 obj << /Filter[/FlateDecode] /Length 552 >> stream xÚå—KoÛ0 Çïû:Ú³")ëq]Ãì4ï4 E·:…¼°¸È>þèGâÔÊcØ¡]Ó“bR”£É¿d¥Akõ Úáƒz_\ÜB U1QÆ0xyfFWß’ªJ¿ŸTƹD•‘xZ‡ÌçÅM>,ÀœSº2[ßþ¨&“nÎîKõs®×å|U-æiÆÌÉçEš‘Kî§ef‡0ïMi†ÖvƒÛ÷ô<ÆñÎP[°yÿYwèÈ9 ¯2l€ ìì)vwËYôvÂtþ :Ã{н³§Æˆ èÍc‚f 莬æËÇ:H@n puºøþýnŸ»… Ç#wjП ¸Z–?ë7Û¿ÜùQ¡6?cfíÛ)ABÊ“¯u5­êª\ufˆðR7îð¦ÝYÇØÈjØ4›ˆzÆÚ5?%L×ßPÓZ'ëÙ,ª¤\4Âþ—rv&Æ}ÊL›²ßÍ¢d®“äe@}æùhÕ ×u]È­Cºa-Õ‡Iâ=«™$õ´5LÕ¹™ˆÕ‰F5Vß$¼Y~9]Çõ@,½¤²¼¦,Gy% £îÜ«yÝÅu/zE8Y\-"ÍsfÑWëßN”'Ò'R¶©M¶}×4'¨“‘€]{¨—¿ÊûªÞsŽÈ¹ÞÌ9Q”¯°Õw"m(Ê}„uR߉òdìLòq>‰ëÉI¡FÄD:ómÝù^ßýõñÜ endstream endobj 44 0 obj << /F2 19 0 R /F5 35 0 R /F6 38 0 R /F1 10 0 R /F4 31 0 R >> endobj 42 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 44 0 R >> endobj 47 0 obj << /Type/Encoding /Differences[0/Gamma/Delta/Theta/Lambda/Xi/Pi/Sigma/Upsilon/Phi/Psi/Omega/arrowup/arrowdown/quotesingle/exclamdown/questiondown/dotlessi/dotlessj/grave/acute/caron/breve/macron/ring/cedilla/germandbls/ae/oe/oslash/AE/OE/Oslash/suppress/exclam/quotedblright/numbersign/dollar/percent/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/quotedblleft/bracketright/circumflex/dotaccent/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/endash/emdash/hungarumlaut/tilde/dieresis/suppress 160/space/Gamma/Delta/Theta/Lambda/Xi/Pi/Sigma/Upsilon/Phi/Psi 173/Omega/arrowup/arrowdown/quotesingle/exclamdown/questiondown/dotlessi/dotlessj/grave/acute/caron/breve/macron/ring/cedilla/germandbls/ae/oe/oslash/AE/OE/Oslash/suppress/dieresis] >> endobj 50 0 obj << /Encoding 47 0 R /Type/Font /Subtype/Type1 /Name/F7 /FontDescriptor 49 0 R /BaseFont/FWDQLA+CMCSC10 /FirstChar 33 /LastChar 196 /Widths[319.4 552.8 902.8 552.8 902.8 844.4 319.4 436.1 436.1 552.8 844.4 319.4 377.8 319.4 552.8 552.8 552.8 552.8 552.8 552.8 552.8 552.8 552.8 552.8 552.8 319.4 319.4 844.4 844.4 844.4 523.6 844.4 813.9 770.8 786.1 829.2 741.7 712.5 851.4 813.9 405.6 566.7 843 683.3 988.9 813.9 844.4 741.7 844.4 800 611.1 786.1 813.9 813.9 1105.5 813.9 813.9 669.4 319.4 552.8 319.4 552.8 319.4 319.4 613.3 580 591.1 624.4 557.8 535.6 641.1 613.3 302.2 424.4 635.6 513.3 746.7 613.3 635.6 557.8 635.6 602.2 457.8 591.1 613.3 613.3 835.6 613.3 613.3 502.2 552.8 1105.5 552.8 552.8 552.8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 683.3 902.8 844.4 755.5 727.8 813.9 786.1 844.4 786.1 844.4 0 0 786.1 552.8 552.8 319.4 319.4 523.6 302.2 424.4 552.8 552.8 552.8 552.8 552.8 813.9 494.4 915.6 735.6 824.4 635.6 975 1091.7 844.4 319.4 552.8] >> endobj 51 0 obj << /Filter[/FlateDecode] /Length 591 >> stream xÚ…SËnÛ0¼÷+t¤€ˆIÉ’Ž-b- ( ôPô@XŒÅ†U=âúïËåRˆâ:èiÉ}Ípv¥4M£SäÍçèÓa³çKiÅ¢ÃSÄʈ ZeÑáþùÒż$Ó`ㄤž“¶]üóð5Êø–f.œ­h¹´)¶rvÕj³/Vè@'¼¤)–õ&.Krn1sÅ“´*þ]œdYJrV‘ÇØ±µÉ5Žà.ÉwÝÕBg¼?ÈNžÔªõˆÅƒGkŒB’ÞmŸÐž¯š´Øƒý [=éˆ«Ñ 唯‰•åžíyÐÓ¤\[!ÑÞfäñ25¶£¾$ˆÀ¶” ÔÎaç¥ ÿ®.ŒÈA¡÷×Þ?ì¨ú#ÛÞ¨(UNùB^KÕ88\¥¤L©`×»$Xö:EÁ8îx¯7H°°¢.æfiÌ}óxs—¶ÍË•.ÕM]UHÒSÀŒ£m[åÆ]ßÇ?S‡yƒú=ë!ݸíâ–56€¡âV±üêßÁ›PøËðéü€Ã}b@™¥AÙv‡ÏT endstream endobj 52 0 obj << /F2 19 0 R /F1 10 0 R /F7 50 0 R /F5 35 0 R >> endobj 46 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 52 0 R >> endobj 55 0 obj << /Filter[/FlateDecode] /Length 1658 >> stream xÚWK“ÛD¾ó+ —ÈU±"iF¯Ü$ªRä`pK²5=\)^óëé—,­w¸xfzZ=ýüº½ ü Øœ6´ü´ù~ÿæC´ ?7ûã&Ì6¡òs½Ùÿø‡÷CmÎc5lwJE^øv»Ó:ö>×¶é]®·Qê]ùÎt%n´÷þ¡˜\å¶»Hé$÷¢í_û_Þ|7aêG$φ›ÎýŒž·»8‰VBU@BS-BÓø&”d¥+]5*º‹2?`açf›eÞ¥eΕU*ð³d“Eííuýˆ›Ø3óyhMä‹íÊí»ð]k:sª†×pJ3ÏvÌu4Å8“D–uÝ«‘¿1ÿ*JxfšÆïF‘÷sçÆÊ”hx)/í a̪»Š2AŤ9‹¾iªb´}ÇçþÈëçëX3-öŠÆ8 ^\j[ J5ß]qßOÂhD ¸œ)cÏ„Ãd›’·òÅÀ lž<´¶Ïb¯Oqðƒ~ßfŠ^ŽrÍ/G98±+𩬸0Ö²©¬mwâÓ±2ã4P¦å+ PNc¿à±âe}oœm®¼¿ v¬}:07hž)x¨ƪsàVykkä ÒbÍMà}ºFÚOsIBÌÔüÙL ý8¦æ–WÕ%~›T„ŠAæ¡-*‚$p¼.QŽ ÀŸQæ|me{Ì4zïYb‡¿ãxÝfÚ{\É0Û4ü1ª’ *Ù¬Šá‹3¨ ´áyÈwmQÆÁöÎ:öTœøj}ÊS¥0Ó†Áõ8‹à¼ žíÖØC™‚×Eßž'Á-5k V’Ɇ¡™i´Ç©aÚ'ˆz†Ö»Ow™WÛ®ú–¬Ð “9Þ¿v(QçRvè„d\Á[[V'¸¦âÒXcõó®â«¶ç¢š«û‰ª åÜô&¡uuU¢+âÙc@¯8œÔ<ÉKB(³·¤åøõÎÙCƒ6QÙÃZÊz±€&ª €ÕRp¾òK‰ZœjÐÊ1ãqè[‘W‹`.ž+gwo†2V©·¯É5ÀaåãÛ'¢žådm*çø¡e50€ïïËík^,¹(õó¹9øâ劃¬ˆþºE¥ƒŒ+©§0b¹“˜ãVbN[ÇŒèS<ß„¨†‰ëœGò­SÐåÙxgïŸ ™ò[gž™=ð¸sc;éñ( Ü»Y~”d"®Î¶gLd!øëÇç•JQš |£¨9]ñµÈ0ÐU%ó±˜pn'¸5ü˜D™]@ÉL%‰™ó¹±…Á÷|'Ù×6^6Z°ô»'aýTÉ%©”’eÏÈH³e侞3Ç T ÑÒua¿t]8°¿꺀¿s8>R&k…mêÊüT€°Ú²Ë⤡tÌM©”Ck¬<%@±ÿ ÈAN†$ðu¾vØŽî4 ¼€±€°>Âè[;‚ðïaTI©ê£”“xÏ |qêÇÉÿé Z˜ÀYCÕVÜ*SHÆîÏ(NOÓ`æ03gãaq¦,r lèCã„‘àŒ^ Aÿ½ånzE•¹a&xlÚIÚU!uÚʹ›¦K7Ís—ù·®oewK,Ø\‹¥\ÍßqÔæ©‰$­i¾?Tsls™#Ö³²¯‡yz_€ÜA>öhÇ…Œ‘ìÃ$ÅÙ—†+Ái ‚ Æsê&óXQ2é°¥)„öl H@¿nd g3˜Ó`Î5>“QÛíøFbñŠ*¾?ý~e!¦`À¾2«aZmɾÃý\'q[f“y ªz1S©ñBÇÙ³Ç2'?™p¢Çsò“!ãƒWê°yR:]êå?B¡ŸÎèªq:³ÜÖžê9Æpl¿àÌœJÛÊ h†Û˜ˆ`4ÏôYÝÀÞTrÇðŽxzê#H€§szYybÔ0ú›ÝŸk_«u‚ÝîÔ˜–}Šhwäµígü…ú\ÊŠ'`‚9†ÙGn'&·;Š!>ò€9‹3ÉbÖq~áõ†—¦×Ê„/4²çÚÖü7ÑyŽÐG¹£“„‚NRH2F ¤á¤¼;:ŽëÌ+» ”û<ÈfM @¹I¢Dã>§T–ø‰þ¯¤Â*&1„ç1ø ™œDk‹ «8Íßï¿ùéWà endstream endobj 56 0 obj << /F2 19 0 R /F1 10 0 R /F7 50 0 R /F5 35 0 R >> endobj 54 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 56 0 R >> endobj 59 0 obj << /Filter[/FlateDecode] /Length 1930 >> stream xÚ•XëÛ6ÿ~… ®2°¦õ~äC\“6)Ü¢] 9t ”¶i‹°, µöEÿöÎpHY¶¼ÉÝ'>4ç7OÍ|æû³ÝÌ ?Îþý°ü!œ>+‚ÙÃvä³ bE<{xû›÷}É-Úù"ŠB/|=_ÄqâýÜ×µ¬w¸y÷çAà{¿~š/Â4Ë2/šÿþðÓò‡`d,4pQ0[ÄË Óp¾HÒpÄ&K ›0F6æt6’'FaaÎ’Èoªyž{ÇQŽ$3Vd3ßÝÛ"ó¸†1O¼JðÎNeM£.qHβ‚©å NüÐTâx`Íóäª,d…¥Ûõ²ö:ÙïZÙËžæaæ‰öW±×©¦”–k®Å†Ž¥°òHMDku–Vxñl Ë2#$æR :ðzCT²8‹=Þîú0„KuÇ­Èû é›ª«g¢ßµjßÑT—­¯'z À‚‚Ed‹ìšŠOõd,É­"Ï xaÄ2÷ýEÜ¢€9¥ó—µA „ã Æ’Œ/öH|Ù3Y£.y=<ˆ}„–Ù«·~¹ÿøæ¿“ ã„9¡Ø„Å",r–æùanŸ/Výîæã¼«ùªâ0ôˆ¦‘Pv|'‹,ˆ¼‡Ò’Œ¢®T}µ¡S+ð¥Üs¤´g_t¢á-YnV²Ó4S[c*> ¡få@UZ9hæ·S­Ým(«cqh4i8Œš‚Ÿ]ØW[«ª ÞÝ U’†Æš|m'UýE]Þ·Òi†€“PƒKë[ÈŽ‘ŸÄ!K“±ÝÛxqM“A³Qì{º•ëy˜zûgZsKý&YkŒ‰Ôys×·‚Vª·´¥B)Ž—´¥±S’ϲvÖ¸¹….¶@½©ìÝŸ‰ ºoPó1X„ã&Ü­^(PH<‡NÙ¶|g¬*uº³ Œlj¼?&8åùÙN²–º]Ég¾µîÙYC¨­qPÀ´&µµ'!¡(Xý!¼äI¶ª6oË=ýú†ÓF)Ë#ÐIêLí›.W²^v¥sŒ0e”@¾ü¹æg±‘­Xk\%`H-ºNAÓ AÅ‘ÓP©M¶²àÖŸøà×ÒR»ñH¨áôþ#e5çœÉXÝòµXñõÞ“èV8Û©;štV€½¥¶2?Û+”ÝšMÀ8‰5]|÷êý>½[²Stš·zxLxëË‹zã:ÕŠNõíÚ ±áš¯xgWÛVΊ¢û¼[ÞWº›Jyj7+âÿ×rJw-Ã/B_ Ò©JZ¡Öªr@nÝÄ„#±RÚ)ºÞ¨ã@KpPAŸ®ñ(ù“8s{ ÏŽ,‚ ™¶¯­\èÜŽ©.¯5ò8wØZúÚ¾RíF´W¶ õµx¼io§zuv渉 ½A6±œÐa'ªrêùQ)+•¨U¿+Ù—<Î)7ö#<6¼ÝÃêIì Ô¨GÃb,à[Ñ5SFD#Hû|逯.œ–ÇXAEnÛ¾ˆÍçcbÂÐéSÖWZ¸FÑ®<ɪ&/Þe¯µÉ›FêX¿ »KÔZº ñÙ²-dÌ©º!ttþÏ?­Húh=ÎíÀ†3«½¨øPo =ÈrqÿehÛ‹Æ>²u¼Ñ”…ëx8XcuÞ'Óâöà–˪»νØ ¢bDö ÎŠ)õ«R;°÷ƒ§ Ô é¥ æ¾Ëð0Ñš=tV;ºN`ø˜<¥|Dß•BP¥aUW…Åu¢ÌS¸D‰·NÚH¤.IbpÁÌ›…×+,³(X@“߀ÃïXXä!8ýÎÒF1Ú b:Ø¡Î&jEiWõ‰lª# Ë’1€ÆaƒÂÓ˜>Ì(ál uPi yX¸ßÿØ ¹·\Së¦Ù‚‘Ó-zÔ }Û@ À[V²Ì I­¥åã‚ï5*Q³øé¹ãdß/>-Þukz|”'¬ˆÆ–&o4ÚÁðä‹?JÒœe»gŽp€èT.‡Inëœ5äÇ ­z;?iÆfçC;™})0æøãÉ~Ç8n¼ý/ ® 9¯+i‚ef*hw¡Ùy,î¯ ;RXI3¿[‰ù‡ã§d-0Ú #òmÖÁ½ Æ°RÍù@äÇã~!À©ó†SŠs›¾2?‡`ÇØ×°€Uã¯ÿœ&ÊIÏ>Kýù@BÖât~Á¤K,†Ðª›]b0þµ2{÷ð¿ßSQ endstream endobj 60 0 obj << /F2 19 0 R /F1 10 0 R /F7 50 0 R /F5 35 0 R >> endobj 58 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 60 0 R >> endobj 63 0 obj << /Filter[/FlateDecode] /Length 413 >> stream xÚu‘KoÜ …÷ýìŠ«ÂÆØ“e©TµRYj¥&Rƒ †ZÝô·?FqU^p}9çó=×€`B@–ãxW® )¨[@+@s|ä þð¾ïåcÐ>CyÎ »Êç¼Ö¶››9¼ù’QJà·¯b¢,Kȳûú3 s€øW ¨Î*]²>ư¡×sQÀ;V”vû•šÁÅ&cözܤîKãììꢗapömê §Œ•P¯ÒFÚM:7{i»íbtç Ò:;ß…í#Òª Ü8?.AМ„§#ÇtÝÉ4„~;“søs–®‹§-r…d1ü9àïJ·2š0®ÂݺE…Kº _¯Ø9—ÑW/˜¨¸(Ó(â2Ê™ÎØÅ`«q Ò*‡è 2±”D'gòé}”vDŒ¤j÷, É>ÜB<9¯´ÇË6âÉÈæa//^È[×Äñ™©óú· 82ÒÓ`•›ð)¡;ï¢U›OIÿàÌðK'€¶ÿrh×ÏS?½ø>Ö¯þžàÌ• endstream endobj 64 0 obj << /F2 19 0 R /F5 35 0 R >> endobj 62 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 64 0 R >> endobj 67 0 obj << /Filter[/FlateDecode] /Length 1075 >> stream xÚVËŽÛ6Ý÷+´ Œ9âC¢ÔeÓ¤Eb ÒåÈ[Y$j\oúí½ä¥¶ŒNV¤HÞ×9÷¡(¦q"¿üý²{üÈ#ÓœE»çˆe4—Ñî×oäýQ·¶ì6[!8?o¶R&ä½iþâ‰: ¶•i6[®òLdó}÷ÇãG1E¹Wµ ,ÚÊœf^›Øl“”¯ä½ØÒéÌoyFc”ûdzë\ȈyÆÕKÜ·¾„W^§ZèŠ&y{}m½É2r>­, FÓ$<ªzT¶7M°õ´áŠ\Ü^‘sWÙª9à…vKNZoè†"}ÑUmp|è§×“ﯛDÝUfè'Ñ–e’ü[ÀŸ%Þ‰sÕìS{v"‚œt£È|€ê²§ŽI>—%Þ-ˆ“$óoþ±eÓ˜À:ËÀ§-¨Ç[–RïU`>™ð>ýÑòÕmËÎ ó„ôæä„yJú“®kWöˆ»¯¸teo†®Xq²ó<ÀÅ’8 èÈ)WK:&‘îJDm_:ã7 çU80-I_v‚sØßV Ât ºõ™fREÞ0|ÃV(Dqq9jGÆ«çÅpâ3×mìÑ«t¯œon-Ìéäk×_›ápy>?°GmÇëÑ„'ÕõXÅ3Ä%‚¹FŸÊÉJëCWçžEn“™Ûí’Cjë;)À!bR@7{çk,¦¨u߇íÛ&Ëç¤ûó®5NÇfá²ØÚ¦Bp÷-Á¹«õ2øç!ŽImÚ—¡íñä\y(á.ì¸^ðëÉ+,C¦Í®úAȨ4xáv˜Nj¾ºîɲg¤¡'=„ep­žñò⤠ÁQnn_ih† Н®!¸[@´|‘Àž9Ÿh{Y*“õÔ;9=W–\¤Âѽˆ¼ÆR0/¾ã¾2IÉк5 hÊDMøù’èò!“Ø•¯O¦Û—-Lmºõ4ŠaMC¡ém©÷¨~lg å §*™ºÙ[ªs@MÎi–±ÓÌ)ÇŒF0”¤> endobj 66 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 68 0 R >> endobj 73 0 obj << /Encoding 32 0 R /Type/Font /Subtype/Type1 /Name/F8 /FontDescriptor 72 0 R /BaseFont/ZWVUJE+CMTT12 /FirstChar 33 /LastChar 196 /Widths[514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 0 0 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6 514.6] >> endobj 76 0 obj << /Encoding 7 0 R /Type/Font /Subtype/Type1 /Name/F9 /FontDescriptor 75 0 R /BaseFont/DEDMJU+CMBX10 /FirstChar 33 /LastChar 196 /Widths[350 602.8 958.3 575 958.3 894.4 319.4 447.2 447.2 575 894.4 319.4 383.3 319.4 575 575 575 575 575 575 575 575 575 575 575 319.4 319.4 350 894.4 543.1 543.1 894.4 869.4 818.1 830.6 881.9 755.6 723.6 904.2 900 436.1 594.4 901.4 691.7 1091.7 900 863.9 786.1 863.9 862.5 638.9 800 884.7 869.4 1188.9 869.4 869.4 702.8 319.4 602.8 319.4 575 319.4 319.4 559 638.9 511.1 638.9 527.1 351.4 575 638.9 319.4 351.4 606.9 319.4 958.3 638.9 575 638.9 606.9 473.6 453.6 447.2 638.9 606.9 830.6 606.9 606.9 511.1 575 1150 575 575 575 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 691.7 958.3 894.4 805.6 766.7 900 830.6 894.4 830.6 894.4 0 0 830.6 670.8 638.9 638.9 958.3 958.3 319.4 351.4 575 575 575 575 575 869.4 511.1 597.2 830.6 894.4 575 1041.7 1169.4 894.4 319.4 575] >> endobj 77 0 obj << /Filter[/FlateDecode] /Length 2385 >> stream xÚ½YKܸ¾çW訦¹-’¢¤½yíqâ›ð ^ÀöAÓ­žV¬–zÌìüûÔ‹z´Æ#Àî©)>ŠÅâW_U±ƒÚ킇€~þür÷Ó{D;•EÁÝ1ˆÒ 2*³ÁÝ»ÏáÛS~é‹v³5F‡öçÍÖÚ8|Û´ö˜ðm•w]Ñm¶:µ. ÝæëÝßzQ¢4 ÛÂQ°µ™JIžÝlc§½„$%йUØj«RC+?a—, Ÿ7: ›?òû®©†¾¨žùûŒƒC×ã×.¼ ^aSÖØÛ󌾑•üÓ•õCUÈâ†æß‘×lèpŸWw•=kšLšêÄ©D;ÒòRmÒ4|:¯Î£•%2éeea*j–?;Ó.ìNÍPx»CÙ{Ú2Øš(SÎ[¸œ(&9² o'3aÞ÷Ÿ´ljî‚Ã’ñ¤Gš¨ÈˆOç¼Îàv¯•µ±J#™¤6[§ãðCÏ"÷ [»°Ï˺à Çh ¼Ì̆{œùt*÷8õÄÓÊó¥*΢¡L ðÂãPr?˜W|àÈhÂ˜ÑøTÖ‡ç<1üH¸Aþ¢ëÂÆ¥¥Iåt1~#h¿ƒ‘:6|[Øõ µlÚoжeÀ€SM{~â=áR¬ĉŠÓù]ÜJØËú_Úû‚MÖÂÁ Ã,Žaý1Ðɬ‰Ù†`ôÄ&á›ÕõiÀ‘ñ`#Õæj-.1ÉTä—Õçõ¾÷Û ¦vNÙl~…6³‘Ø “º2BÖ³äCøã}ÈX €í.UŽ“Ÿ7©A¤c/C!!(À‚RäÌîé e$÷;´ÃẎ=;ò_¯]×\yg׷þoZµ2‹Töƒ–KÕÎ[.¯º†±rhËGT—1¥=¦lXpw éyî °XÀ¬˜Xš‹`g‚žd÷àCõˆ,¼‘XÙt†, ´BÐÀ&pGþÍëB#9ò)4GþÝç=›œ´àÕe12!’ÔüZ±£kdÍMâS³<ƨl~*΀P«³ðÓIÔ^ÜC¾2~†§ùAÛ[·B­q£~ÁTyO€ÁN4x²ƒÃ COÃÜ^ë9à?‚Éê LªLú¢ ǦåF‘ µÑ}êÌ(k縜Ⱥr>o"pÞ Ú:ü¡¦.¤_6ܸ­çéPðI<¢•W¡í ÿÑ´gLÐ¥ÉÑ¢˜ýüÆ?]Ñ26[V¢©aö춘UO$ùá‹ûÅ1Ì£F"Z£pÚ÷CϽ ×3¶Ã»ÄM ëž°^ðÇ­4T}y*rprþ¯ 8BY¿5{ l|æ¼€ö`ÑŽ”Y!ŒÃØ”¥ámîÙÅFÉhi¥ ¿ãFÙKƒ_óÔV³ç±É#nÐÂ)G¡ø¿œÒ±r<£´Åõ_)Kþ¾T1aôÂ(9ÿì‹Ã)8™40sÝô<áÜ,n ˆH,ÞsDÁëfˆ›/:v —÷{ìä:á"O7#½hâ:²»ÄÓ6™ä#ç°=™ ¯ÌA:!~å—´2qtnZE ¶„í®lL†éÁ«®¼•I³Ë˜…B—Î2 ,Â!ü{Ä´E¾ÙñÀ²çVÎ?‹Xî<â`@nÅK‘ƒÁñÓX‡oªj “EvƒÞÊùkÑæ˜Ñ!ûEÄ {ÉL'î.™#ÏónˆüÁ ÝCǾÍsÑŸ$ã•õ×#ó¿­Mé_·¶½æÏU¾Æ¶4SBä„J«•‹•fáìSÖH.ÒMW‡ǦªXG>È]9'ÚâgÌ6#H«h‰t•baéñƒ‰‚ÃÆu¤@-F‡Àrþñ~>f^I27Õ:u²äÜ|ß6š›¿èN©ÑʰÏ2à%»)™ù~ÀK¯cnÇ2g™~–5ÿ‚1ë™N~6ú°:ÑÇpFŠº'æ+DCHN®õÛê]ª²EYót¨ ²k-/Ú¨]¼d©¢£lEOE‰NÍU¬Ó)‚0ð¢Î¸œ*^†ûªÜS¡™âII „§¼¬òûJö‚¬-!NÝ WvUÙá`YóZ†jŠáy/ž_ⱕçINí}ýó(‘ê¾®€Já‰âmAðt윔’ïV¢ó ‡À ÌYôJ})›Høway\ 󢾕” —< ë9ßd¼‘ß“l.–™•\Sú8WØY‰ƒ5†Ž1'R Ç“XÂ$CWE,)ÓY¿ÐÍJU'Ê¡´¼./C%ùc|Uù@Ǭâa{¡OùÛé¾Z¥k~C°8|KÌ÷1[ÞV Ä“—MT‰°#j¤Ûo#k>•ýIŠ×9íª®𸉱¼‡€--*M¥‚gŽÝœÀa롨1äðgs–•‹:G–ÍøA[#å¬ÃN‚k;· \8ã\þ^¢¶Ä Ñi|o¹î;>ðyz€º}¤bEtúzÃ)Î…J‡Eã›”UÆ"‘ð=éYžÞ¤TÄsÒiŒºW –™´hMù¿ˆ$øeëÍÆ@™:"yõÀ9â,܉cÜç‹`ùoŒy[ŠcÎ0ñi„¥(õ•¥g Te1ÓªñžÔ%´|=[=LùÏ»~s¡ãkÐoUy¯XܳzÇ¿/-Ei¼¢(€ßÿß±é\ì -EpJšÅ[¥"g,bÚi¢ † ¹?Ølx0€ «ýî˱¿¿©_ˆ¾*õÁ÷ö¢Úû¢ßŸ^Ê$4ÂéE‹‰Ï°iJ1–O…SK¡7¢wÙéÙÖ„1Kù|{ýN¡Ã¿É;;Ž9N&<¢FéŸ`n<¾Z€ÍO?SÚä¡jîù½pawk¦ŽìþŽe¿dú4Vi6=vþv4Wv4k;híŸdÆýÐbÁ‡äC[ßÞãÅ&x œF«lÝŽŽ~ß­|WÁÇà—»À¥Dh-â\ŠŽÐÖžžÁÆKnþÃÎuD~€tn讎e3zMs@É0ùÄF%ã§œ*¶Ê9î4f~ªè…SmÉkgž}Ç””,ÞÐŒqÞÖØ™!Mi们…â|ºbÒ«$XÎ×ù©Ÿw½O²x@…Tdâ~|æ|¯ÉÛO?rb¦ºY—™mÑ]8?~äç#|oŽvᯠþub5g½V cG_ž}‹Skå²]ëiZ—Ÿ1)ʼn(_ľñ}—,Ptµ)Ï¢œI_ð_þ ªql endstream endobj 78 0 obj << /F2 19 0 R /F1 10 0 R /F7 50 0 R /F5 35 0 R /F8 73 0 R /F9 76 0 R >> endobj 70 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 78 0 R >> endobj 81 0 obj << /Filter[/FlateDecode] /Length 1653 >> stream xÚÅÙŽÛ6ð½_¡·Ê@̈%R}k7›¦X£-° Ù¢Wju:ÖÝ¿ï ‡²%ÛÍnŽM^lr8šû"½€wçÙ¿Ÿ½ŸV/_ ,áÞjëqíñ%Ò[½ºõ¯òt×›v± CáËK)#ÿªi BBÿªL»Ît‹¥Ð2V¾Z¼_ýê ÍTâ-e´¥QÔ»¡ü8ñÓ:£Å¾(KZõ¹iÍ–(ªÀ¯ÍýB(ß²Œµ¿FÒ¾!Ômcw›¡3[,£(ôß6#ÝìµMû¢©;vy3”QFRÊwlv´iqeBS[ño úsÒ3´­©@¸·‡×+°šTÞÞÓœi Z‚²Ê«<1.ÆméÝ€U¦5‚H•RR~²ËHù«Ü Í—¯££ñ£˜‰DÀþ(ê¬Ù¿MëôÌa1'nRæ®mð}‘¡/ÂP¡Uq¡ýÊô9™-;cÆÎTèht¦ÿ‹´…µç¿[œ±å\³‘mßX›,Ã$daBf‹HW´XžÖwY£8…  Á@ÿV@\€ì£3°GURüyXh飱€ØÐ0Æ3pñ½ým_~£D¹Ú•¦r&&¶]S9.ÿyk¶9í» ~˜›Ê‰ü€Ûf˜†é–§Èž¢”ò%Àl€¸Ä…t|dþíMÝA@ÒvƒùòÂ!‡sc6ýC³IB¸µ"*ñ½ûf—Þ¹DÀ(Ù¸b"ö–äãV¾Û_ê®OëCü}¡C?m‹t]:Zÿm|¡cŒ½'o'àd–Dèí¥1 â1D6­mÎb^O-ô#Ñ/ ››Ž.*¥_Y†AÙo¥Ï1¯£ò20´ÚH&9$v p~ػ̎9 Œ•¥U/ †ê‚qÁ¢™y¾‘BR±(š(Dû…¨…¥ÕbPs¿éÏU4ÏTz›îvE}בÜÛ¶©¦®¥µMÌ¡Â_,Å1dpGÑд„ÑRJ6.jöõÝ1Ï\A:5P¯:*üM.µ&l›5v ÿoH²¯a™Ù¦CÙŸx=€ÆÖ!<"Ùq;:$bqŒ@É}âÜg@ö©©3V£yFîW¼OÞ‰HÕ”Òßçk¹.Z;[NŒÝ®L± »ºüµéz1å™AK ù (Š`,0¨bAtØÕ,à å-*8âÜ¢Ñ$ž#y  Û”5Ú§O g?€‘ýß…iAŸ˜'˜ ÚØ‘¸›‰õ8õ¸J‹Uë0 º&7ÌLÊó¼c¥CßT0mÒ$0Ò™{Èh ÔÙÖðP0Úo$„¤H€'Î7ÛMèÀÐþúmX—Åg+ -! ü¾o‹õЛ EÂHC?gJÿO‰i FÒ r)vˆédÙWv­Kã˜XôŸ8Žjd–4L¤ySf®ÒÑtLGJ"ÇL=’DŸ¥½M(½!FVõ \2Ǽnëq„nO»zÑM뮫 „B>£R¶A ìOV¥¥ŠŒœ4˜9ÿË^zv…°À¦ý}•j´bðú&øWD1¿àn0>ânm¸+%˜ub€ …Ó¸Yâñ~hÁn¯i8v×&{ôn~%ÔD;Èzdá$q9¹_Ø´Xôéºmž)"Ò‡Bkü²­RšÑíaß›~h Þ@ ¸¥Á[@[£E”`Æ[¬Ýu –ÅúÜo ÊÌXX_Ù6ûp&ªL8ºÔ…v˜Îƒïqî¦ce»Ð$Z&ž7„ðƒ áÊÞ¿\CHìõBòqqu¸g_Œ ¸6'gãòœ7áL^ž÷hH7—ÅKF.øÞrF¦›ïi…³€+Œ'-Î>ÊŽ(ݼ)„³w›éø”Rýó}žµ)ÖÒ}ý¨¥o ”)K{ÃqäC‚‚[±úàê¨aäiG 7µaÙ@¼Q,›ÐB–:Ôx3FÌÙN©Ž/WH©nú‡ó ¯¢Æ–8Ö÷êíÐâãš{: §7[yq‘–­I³:v­îJYEOJö©%;½×õ—BS)öXhBU‹Á÷ùv|%‡yâh ÷&Õñy#æó»/ÇËÈ»/ÎpÚ}EÄÇ&!f±€®ýÆÔ~yLí1í7ý¶_¤0m¿"Û/b»ö{½úî?æ ³È endstream endobj 82 0 obj << /F2 19 0 R /F5 35 0 R /F9 76 0 R /F1 10 0 R /F8 73 0 R >> endobj 80 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 82 0 R >> endobj 85 0 obj << /Filter[/FlateDecode] /Length 308 >> stream xÚµQ=oà Üû+±Tƒq ë6U;[]¢ ؆‚dÙåï—¯J‘:w:ütwïÝ4¨iÀHðžÇÃ‰Ü ŽÁ¨f·ˆS0¾œá Å·—¶ªÛ–@úTÕ”vpØ­Œ“«pNºª&Œ{ÈªËøg(˜Ô”#–LÎï›ób›‹è³b kÄ´Êl¼«ˆ4عU¤‡þ'0F¼‹gÕÉ–ƒ&YÎWkµÅÐgæ]€B¬C Lÿëµ w8¹ü½€…äi—È£–.l"}…÷ÖLWŸJëðfÖ5¿¦Ø",4o¯2w›Q‰Õ•‘ß3šm1³ðezÓÒki‹^›²`Α‚*~Sø;ÜBÈUÆÐY»oò1ýÈæ>ƒØ–ÜžQ}F-\.O¥³b%(É_LJaÁ - endstream endobj 86 0 obj << /F2 19 0 R /F9 76 0 R >> endobj 84 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 86 0 R >> endobj 91 0 obj << /Encoding 7 0 R /Type/Font /Subtype/Type1 /Name/F10 /FontDescriptor 90 0 R /BaseFont/LQVAPM+CMSS10 /FirstChar 33 /LastChar 196 /Widths[319.4 500 833.3 500 833.3 758.3 277.8 388.9 388.9 500 777.8 277.8 333.3 277.8 500 500 500 500 500 500 500 500 500 500 500 277.8 277.8 319.4 777.8 472.2 472.2 666.7 666.7 666.7 638.9 722.2 597.2 569.4 666.7 708.3 277.8 472.2 694.4 541.7 875 708.3 736.1 638.9 736.1 645.8 555.6 680.6 687.5 666.7 944.4 666.7 666.7 611.1 288.9 500 288.9 500 277.8 277.8 480.6 516.7 444.4 516.7 444.4 305.6 500 516.7 238.9 266.7 488.9 238.9 794.4 516.7 500 516.7 516.7 341.7 383.3 361.1 516.7 461.1 683.3 461.1 461.1 434.7 500 1000 500 500 500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 541.7 833.3 777.8 611.1 666.7 708.3 722.2 777.8 722.2 777.8 0 0 722.2 583.3 536.1 536.1 813.9 813.9 238.9 266.7 500 500 500 500 500 666.7 444.4 480.6 722.2 777.8 500 861.1 972.2 777.8 238.9 500] >> endobj 92 0 obj << /Filter[/FlateDecode] /Length 2112 >> stream xÚÅXK“¤6¾ûWp3Ñ…Aææµ§íÙˆµ7ìïDLû@ª‚5mSî¿ù€‚ê™9yOè™Je~ùe / ÂÐ;yôùÁûÇÃ7÷‹ ‹¼‡£¥^$ƒLyßð¿+óçÁt»½”ÂßìöJÅþÛ;¡}ÓìDê8%ý󦨫æ´Û ÉÔÏv¿?üó›ûÈ‹t HìN‹¼½Ê‚”$Ç»}œ”%C”%#’¥Õ,‹D,5S¨Ö^ª ’$c¥H¬¤_NŠ`ïÐ6}Õ=÷Ú#Ofì囥 Z(|Á“C ´§1]õ4“l˜;ÛV;­æ+™x oouËF1éNŠš®vûTÄþ»†¯«çë&ïÒêçz—fþå¼±‰A–ÚECYÁTúîˇÜyB¥^¸Mªbã ºƒ–ÅX–/샥•n>šfØ/ƒ$¶+Èj*Îüj ã#ÿPç}oú`Â)ýåÞ›áPÌÖâ÷2ƒ¥K»á)kyBQ²”÷}Õ?ç·E¦qfv-Ø^‡`{À…Î(Ð@ÿ%3®T”ù5Û©}æUƒßˆ¶lÌ&Ã@ {Φh/ÿÊ›ütC¡[I>[jQš¼ø˜¢«>ðÜ”„à’ÙÒ8×ÀÛ«¼Nškßú™Ë&?ßmUªÒ3Z¡^j:Í¡QÍÉŽ=w-ÚòcUþ2Ëücgú’g×Á‡c³œgÿz9™Æ<îxó-÷ï#ÐÆB} Ò`©½ˆ5ŸŠ °ºé*CÃ`1SW¬eÇü¶\zÚàÆ¼®¯V§þÅ´Ý“œg8ÊžŽÜ„k&ÑJ¿°³Iã…«ÏRD¿O NvñË.mÜåX'u»~jïxÇPæDÝ ,š×íi4> endobj 88 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 93 0 R >> endobj 98 0 obj << /Encoding 7 0 R /Type/Font /Subtype/Type1 /Name/F11 /FontDescriptor 97 0 R /BaseFont/NITMZE+CMSL10 /FirstChar 33 /LastChar 196 /Widths[277.8 500 833.3 500 833.3 777.8 277.8 388.9 388.9 500 777.8 277.8 333.3 277.8 500 500 500 500 500 500 500 500 500 500 500 277.8 277.8 277.8 777.8 472.2 472.2 777.8 750 708.3 722.2 763.9 680.6 652.8 784.7 750 361.1 513.9 777.8 625 916.7 750 777.8 680.6 777.8 736.1 555.6 722.2 750 750 1027.8 750 750 611.1 277.8 500 277.8 500 277.8 277.8 500 555.6 444.4 555.6 444.4 305.6 500 555.6 277.8 305.6 527.8 277.8 833.3 555.6 500 555.6 527.8 391.7 394.4 388.9 555.6 527.8 722.2 527.8 527.8 444.4 500 1000 500 500 500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 625 833.3 777.8 694.4 666.7 750 722.2 777.8 722.2 777.8 0 0 722.2 583.3 555.6 555.6 833.3 833.3 277.8 305.6 500 500 500 500 500 808.6 444.4 500 722.2 777.8 500 902.8 1013.9 777.8 277.8 500] >> endobj 99 0 obj << /Filter[/FlateDecode] /Length 2908 >> stream xÚÍZKãÆ¾çWè°j³|ðÁÙì$ÀöÁs0à5 ŽÔ3bV"$µãù÷©W“MQ3±ƒÉ‰ìîbuuuÕWi“ª4Ý5»úxÖõÀÒãø~+j•ó€VÙ܇±¯wcÓ!a•óeT“l0sè¢`®iŸÎ£‡…};ð*mkþ·úôtô¨ü”õ¼²Pë”qbyÝî<¬lÓjU#>‰JÎGÏ;E+Îe¦r+Ÿ½?6hüÈþ‡ó¸Úak3­ ›¸ñZV£ªbÍñc»v'ðm#”—và*›<` oŽ/_ðdpççÇ;üêP·¨½ óÐð³hF¡«‰S–€)ÐD- jÙÁƒÍç~'äx“²zy´€zßwcóð²>e©ŠâÚ)Ab§ ûÜQÍ4D~̸ܼÒÊØØ Ô©ûâ½n@œk!ËËmv逌EÊ#ÅãÜ¡&DÃ)Ï3O½ßù½owž]ÁäÄ<ò…nþD €‚2ÂÜb7Aö@†µ‘æZٶѪ2Äþçïüx³®ë»É"U¿ ä9Õü«ÕFÃf]±3åìLOì ›w3»yÞØJeâZ©2³9mœUóð¸ùBäÆ9Œ)4 Æ"wA ãž™Ã]ùo_ó–ŸYב©²¼fEkN |H?ݬݸ0*Íâ«ûØLH* 8Eš H +W‡S£,Kþ ~ƒÐfÜ áå»e8¬áaŸúøŒ\k\|‘ÅÞç¾%Њ{rxÿÐMŸ´! -…-ºž4öȳ~š­náDª–Ÿ·oqõ‡íö¾W‚÷­€ŒP’Ú 0\G, Æ+ÈIèT¸°¿ç7ŒûøÜËCÃx Ì‡õÈ¡æá±<è´€äèZÊ=ƒ/ù³Y{¿)fï_Äpà7H”Îl]®\ë²ÁØt’“"då$.LJ³ÒPY©*„Ú€Ànr©Ò#WÅ[0¥æ[@Á&¸‡{+õ.Ð=ÂP›½@{ cœ3ò»Ü/ùŽ—Î‚±¶ÿ–×Fñ)}á¦g±™¦øÙvBÑûóPßSö„ž4tB÷ÀÏÙá$¨¦˜/y8tç#Æ“ª @ƒƒ üµ•76ß7bd’)–&¡øíÈÒ4iý3 Â׋áÔsÃá½ZÙŠŒŸ IÀ*8¡ñhß"+fFÂ\¶O eÓ|D¸¹/MwÉ|+I¼ÿí©é¯…[ý?·õ~n±ÜJÃÃm¦R;C¼Íy&g{âÂE-ñÓ7ƒl8Ð+ ’Ôæ­àê0C‹ôÿíX^Ç:ȪßáÇZ¹ríÇkñ­1?’?®RåÂŽdn"¼”ɱF~Ãü—–¨*D‡M9Ñ,ÙNÍuR#¯ðwål:¯PöI<[|'T²QÁU%ƒøÚ®A0᪯’*ßv½‡]©kPV¡À›_G\±ØËÅigöF\œ|Q² k‘kÇ<ƒŠçJè!TÅìuŒ¡Ùù#»3 9×Úõ(®‰ÙÚ" ùùý±†kn”¦³‰9½™rÅhÿhÊÉþ ™ÂóF£û¢Cæ™*rpPm F‹ƒâ$fS8[qF¼Èü%+«Æ ÍoXgiX—ÓëóhmÞvN]]öEÞ£I¡Ò]J˜ÓüH§kuúÒp¬­è‚xQÉèöùkßHÍã8É›k0.Hó‰¶ÓVxHáËOlÇÇ < i«j•÷<SSʆñ€£D÷Â^¨E+ì…bX¸Iº2é ÷ŠtZCæÐ¥^ãX¥òj®G3HpÐJfI™¾œ|=œ{r Q–^àÖ»®ÝÚ!^t×ÓÈ'‹Àp­© q•!¿%#e5â½ ¿ê ´ÿˆ¹ìœ}2yÚ rᘵÜ5³AYÈÕëã9„ä4î›I™§ÙªšÊSQ]žB¶»#—CÛ ?oÆu H•*&M½<]ѼS-ámH°Œ¼X´]"‰²+Žr’ùÐúts±mñîLè2j,ñì ²?§œø)2l@…±ÀO‘Së 'óì}òäÊ‘³Ùêþñ ¾G}åÿÍç64D’5:„à ÚÙ ¯!¢Uv‘.¾g~…B¼Šc\\ÅHó“í8Ìê¡ýóÇDÑÉ }+J„èŒw)ñ¶¡ "Óëv`–J'·ÃY©µ3©µáyÀ„OÛµÛûE%ǵ7|øñ«A†ðÂs\ÁÃTÔyÆ¡t"3.àWÝ`V]¨€,7¤¸Á¾Þ¿ðëÁÌ1À L>÷ *´²5Pc>mj[†ƒ/8·~äF7VÞýg¦ÃV³óƒ°öêÁ·¤ê2O„Ù5+ Æ¿¦ž¦´32"ç”¶k%ˆOåN!YÝ©-t@}½®-õ~HÂ`UQŒCþ¯¸Q¦²òzùK'Èʲp¯ÞŸjŽ'†ZøœÒXSi¬ ¢Ë–\ÙJ® )i½wx¼|ÿ’RAFÈàLîEy÷ /°€œåØìšñ( Sžˆ–:+Ó®»jøõEEiËð› ¤öò .ÁŸ ¦«ãèPÚäÝÕY¢ž B:îøýœ¼rxyµßÕ˜Åÿy™AÐ/³¹Æ”qTcfyŽ9fäïëѪßÙÈ}ƒ.°Áüzë J¥/ŠLãªëEf:ïø†·¸9è¼]dF‘%’ß,Ä7ETdÁ¶ –§èç2˜Y9Žq“ã(nß}…”ë5×knQ¯!Qܨv˜ÀÄX6!7жvªÎh['Ð:ÿúiå'6Û‹è ˜ˆ ‡~ÕÇëÎüŽ/ëÍ.‡¢ÊýJ8f9$»9> endobj 95 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 100 0 R >> endobj 103 0 obj << /Filter[/FlateDecode] /Length 2491 >> stream xÚÍYKÜ6¾ï¯è£pÓzP¯{ðÚãÄ Ä ’Áz;u7{š°ZHjwæßo½(Q-%žãžDQd±ž_UQ›P…áæqC7ÿzxý>ÞD¡*£ÍÃq›(Q¥Þ<¼û¼=UOƒéî¶Iéw[­ÓàþÛ]œ¦¹‹‹`ÀOIðSÕjÛ<Þmã,KŠ Šîþxø÷ë÷Q4Ž •—›­.UA´Ï-¬Îƒƒá¥ I¨¢xÒ*Ûà ZÃÉà …aÓóí5=ϵG~~xý‹,:UÃ;}U×$ Œ­ì³ M?˜o°Í+ˆÄ«úS{©åëÎ-!Yñcg‡sÕYq6[–a ªŒX•ÂH|‰³°zìYêt’Zk:©ßÛÚÜ3Í ~»ón¡ ˆg²ôÕ‚P¦Š|AçÓoî—„RU”#¡m’ÆAÛ-Æ¡Ò ‚÷ÿ}{ÿëÇ_>.ˆF!Yš7(ð™2 >—•J;)þÞ²ÉnyÛ&eªò”5Òªm³$ÇÊ| ²4U:YZá&ÎTšÈ‚j:»» †éµ+"¦…rä¾Äi^/ r¬Í«z~öOìl{‹{É?‘íŸz•ý2C0±öÉ4_‚/wK…æªtªÀ S¯¶®™òŽc‹_.½;Ø6$ÕA±Ç¥ x$èlHêóø^5{ÑÆî ¤³Õ®žôCOv!!~0‹å&ŠT™’Qã4VÚéÈ+–`8™ÌþéôÀ…ŠKxˆÆiln0 ¦àÙ™áÒ5$%¼í^88dS½ÜuKÆiŸ(l`úäèùn{E¯DÚa¼ôFØ {Ó¨×ЗHP%ÊYwðdŽ¢Bcàôü.Èç¯õ“V–u`ÉgÙÑò'ñ¶îØvg*ŽH‘ð¥¿ì˜]X½àäÌŠ@<󮨶Ê2¶BÒj\•#Üá*‡ ž#ƒ'è—b¡V‰Ÿ+Šu‹”q@º(¢ 6U?ðG€¥³à™Ì”— ›vV‚}¨&ž>ví™G,9|õ “W;@–86‘bß»š­´G}|…<‰¨§¬Hžæ*1z—žàq‘Šàq™’à8#‚ã‹L\| 7Í‚ó´„=¾\;; ¦áCË“$9NLXRê 9ö­.ºØê(Ñ‹™èX3z˜L áE‰Îômç± 4 Wßž ÎAøþ¹7Oƒm›ªæ Šœª^à,)2ßfb±ý¥3H£Èp˜å/ö=nw\#À§C5T ˜m#(··¿ò|?º@|E 1Ç”ªÌJ¹2úü³ rX v6KGü+n•µPQMùjguE S9Æ82tÿ¥!ìºn2­Š8 S|?o²“•{¯7¿Cñ¸ÉJȬ47ÕuI4¦ò(ôRO¡"—ƒe¶Ì§Â¥1×XXÿä&÷W¢Q3Ì>…2¿§ïæ_–¬DPC….ñ®dÉm UO’ø~ ÅpóˆÜd®¬Ì\É ƒÃû û`aß6MÊõZ¨€c’áWë!¨ŸçÅ1ÐnÚaáI4É÷’Š(OœP:ðU¢p ƒ‘GŽ™ÊY †:šl’•ˆ !u¨âìE2êÉk‰7È;À\wµ½ò'´T™Ã`ÆséxÎC¯œ·ÊÍÍ™ü럨+ÑéÅ6,.$šyÁÁËó©XœE“À]sË1Q„‚šÌ°ßcíêp`üüe´ÉÓéä‰Ñ<Foü ÀÅÆÂr•Ü2‘Ιè è ”øp‹Ù¡îI¸©$ñAZ À˜b¦í¦û?ù=V©õtì£ñ³ ˜tf2¾ezÒTX©ÞÇê áê3sõã+®…FRÛL¶À™”‰ H€´ÚöÃ¼Š¾"ÅjØ#É“«¯øÌ^‰l›j#MLƒaRpÞ$Q!ë>U ¸c7Ÿ„È`öðÎöOHÆnKÕ„ÿØ’qÑ_Q^?-©Bοï;cšeR*‡öèØ 1øûÛڮݣ€‰œÍjéAuÆÆš¼G¤\“°„ˆu|ïÛ¦1{p¤ŒU=OÆv<ì¤ øõj›Ùô*á§7)¤í¤ÚD¨ùat›vRl•ˆ¤®ûì·ú»)Ä+(5ß–‹œŽÙ±ÚÎB(\Cø ϸþ\5ÕãŠà9tª™¯LdsM™):ÒË”¹Mô¬¤ºž¬¸ û»DЧg¿Á­êz‰¯nîXIiP¬öâþ°´k/§›è9ˆWãѦ“Å.LE%ÙÒ…!ß©¬üŽC€ec!­1`£Š"¿ÃyCiêõ‘lyh¹kLƒ§ªçN;M¥%t0¿¾ï4Þ¸á‚ã¥Ù£O1_Ñ´%dðæAw#|uæÆtŽ#ùiêC»„þäÊOU7Øý¥v¥Ö ãXų«:©œ!ÛvbÎÊ ÀjƈZßZiI4 æÔœŒ¦²{nò¸éT!³xÅC6}™ÏLì'G "X¾ãº•ØMÇRŸD GUÍÉ,ýؘDq]@¤?å¡uöÈK»j·<%gMá4ÞÁi A›Ï+m7gñ2òjxa¸1–`hÃìk°Š -.®Zñó‘Ÿžöá´;8ý½¹°é‹rf—¢D‹ûŽ§Îž«ÎÖÏló©FGojÿözj¼¸ñcß:Û×`½„¨¤Ã˜#÷GRûxí»Á¬à“uéLz¼2•Ú¸ê:Kh‚4æ›m/}-·I£íøõQˆÅ7ĸÈÃßÅPÝwçªÞ.GÛ¥IÝ]‡“ýèçz?ÖíŽ ãy  ÁNîØdaBÞ­„!«ÉÝáà6C©»QËæM£—GXK¹!²×Z1_§ñ¨âÇ™ÊùÚs2¦ñ˜ññÅýJéݨíÿ¢ôÒøò ô4zì°†úÎ~ÈsÑêo”xºïÿ¢ž(T¹ø½Ð;IE…X˜èYâ™[Çlní ]ÇKì´Š•Å£Zq̃ÑtïIÕ™PÕ©%^ü³n¯Êá(+|GVüp6r³W*rm•ñ ¢ûY?‘¬¯§Ï Qw.­ª— ¼úÛ/Eœ¸|öÿéš{… endstream endobj 104 0 obj << /F2 19 0 R /F11 98 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F1 10 0 R >> endobj 102 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 104 0 R >> endobj 109 0 obj << /Encoding 7 0 R /Type/Font /Subtype/Type1 /Name/F12 /FontDescriptor 108 0 R /BaseFont/VINYJK+CMB10 /FirstChar 33 /LastChar 196 /Widths[305.6 544.4 833.3 500 833.3 777.8 277.8 388.9 388.9 500 777.8 277.8 333.3 277.8 500 500 500 500 500 500 500 500 500 500 500 277.8 277.8 305.6 777.8 472.2 472.2 777.8 755.6 711.1 722.2 766.7 655.6 627.8 786.1 783.3 397.2 516.7 783.3 600 950 783.3 750 683.3 750 759.7 555.6 694.4 769.4 755.6 1033.3 755.6 755.6 611.1 280 544.4 280 500 277.8 277.8 486.1 555.6 444.4 555.6 466.7 305.6 500 555.6 277.8 305.6 527.8 277.8 833.3 555.6 500 555.6 527.8 427.8 394.4 390.3 555.6 527.8 722.2 527.8 527.8 444.4 500 1000 500 500 500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 600 833.3 777.8 700 666.7 783.3 722.2 777.8 722.2 777.8 0 0 722.2 583.3 555.6 555.6 833.3 833.3 277.8 305.6 500 500 500 500 500 755.6 444.4 559.7 722.2 777.8 500 905.6 1016.7 777.8 277.8 500] >> endobj 110 0 obj << /Filter[/FlateDecode] /Length 2644 >> stream xÚÍZÝÛ¸ï_á·j,#~ˆ¤èCî.¹k¶@³$÷ µi[lm-9Ûýï;Ã!%J¶w÷®—fŸL©ápø›/Ò‹œåùb³ð??-¾¿yýN,xÎJ¾¸Y/¸]pÉJµ¸ùñcööºëÝáêZJ‘ß]]+Udo¿\ “¹ý•°YC2û¹Ú¯šz¿¹ºZK›qqõËÍ_\±R.®Uɬg÷Ó¡º…)¥Î¶ø;tØ+²UKÔ}ÛS£Þã°òÚ’Tv_÷[ì·Ž«º»«ú%NݢȪsÿ>ºýÒÆõÁ-ûæáʊ숚‹ìöÖ¨hÊf.“—üEWð#'Uxu™Ý6ذ™_ö3Ñh%û‚t arØØaW5'S`vê©MåõŒÍé¶:Z2{ÿÐõn7añVUsßTx.ß1§M~˪iÜêg¿m›-KOЬ,ýÌ7{œ¤3÷Ÿjw×8ôeö¦ëŽ;G#ý¶ò§_À6Ã܆€¡²u{ ±Š~vÕ¾Ú¸u–M=™¼­:¸õv.ð]»¨U\04êðAE_´p¶óÓ£÷phÒg‘:wU×y–ws@mÄN˜qz:çÙ °›0æ¡”¹ ¬JÎ9úÓš¬±n€Ì¿. çBYDÅôøƒù<Û4í­Ç´u´‚!ÿ0Œ,«cçè«vM#„h„c”*9Fd³ŸÌSaOœäωd2˜6|sp›ô3ˆC¡ [Ž©<]hçÀ!{ÁWÝw~=ðîLèŵàLSýø×0Éd«ÀÈ{Š×ïŠ1‚–³Èýo¿¸}ÿ#Y@°ÊI´·³e˜û – ÎYYà(ˬãÕŠÎêí W!÷ åÙ\óR1%»…*X.‡~³xÉÅBi¢hÒYe_¿ãy"vάK}ÊÂ8Ç ³qWÞg=Ü‘•¼"õ¥Ö &{4\ôåÇS¾%£Ñ]Õ}‡ù'úùžØ)GÁ¬œj-å(Y^„Ñ3âÀzæ²4 *FØBš—xÝ+ºç¹bbœÈ Ó {Ž¿<öƒîyn™4D´äêÕ36XÀÄlƒùY•}º:Á—Šñ‰óy™°"g&mè'°EJI–çýÅ”‘̦lŒñîaÊÆX”4åô¿€}! –fž7ý¨ègÉtþ¨e„¿”¨`À¿PˆÙHˆTc‘ ˜”¿Îøo6n˜™„³jÊà.G ~j@6I fŒ¬õ¾j`T œ>cTJ&Ô„×ïdö«š€„P1¡Aª"õgç1:çöëD…§ÍAYV"úAVîÍ¡؈„h@- ¢B„þÿÄP`±;±‡7+\ІŠ~Ƭ”¾f<Ñ@ÉѤÎx¿“å,&b5fc5Ö±3gâ¬ü” ?04ÇgÝQlô¸_öu»§Þý¶¦*ƒæ„DZ×Ó JÿJL7PG“\¯ÊJŠßIÍsU7¿pY Ék¡³¹eO¹ižæ¥^ŸnˆÏ÷)F¨dŸ¸Ì®îû˜Æ¶‡"á!H¸rëêØ„Ë ¥NÂË:r{l—Óƒ¹tÊ‚Iúß¹Pù+‰."±UZHS8w¹R3!…Ëúù#îB[,ÙWT»3*Üÿ«îkŸÓÃxÝÓo·mÍŠÚ£ÜÚÒÕ üÎ7$TµhÖ£~(.f„2­P¦F­ïš šXjœÜÎLÎÃ_`½ãú3v2A×yd%“C¤‚€‹ù¨§0tƒû±†É@ ¼êÕ™ô‘ñh“õ*¨¤þ$ 㢬Tý¦Ÿ °}®Ž“­ÉéÎD.™)Ÿv<ΩûÎ5A¥õšÖó÷–H¸;x4|¹W,R0É_L wp»öJòìË¿›Es-| µf N~¸ŽÝp‚Ú0ˆ žñê÷ŠåbÈ#£cÄ-BŒiГåD¦ˆªT.˜‘)ðàÑsüuà#»¡ Pï­T‘\þ`§õ¿é}R ¼&‚wEsn½O;DaFÿ{^!±$MÉ ‘\±¢A!LUD…å‚™•, Á{œRg7Þ¬PFr²¸‡ªéZj-W…mCPCÉ£'e@êQ¢cßNnI¶U@}½ïzµ ˜ØÉeÇìA)Ùã+IJÚS#ˆ­Á³b‡V]QgWù“8ÂzÄé~ëÂ÷{·t]Wü#ªÂØìß¿:¹ÏÉðÙº;¨.¼¾bø7 TÀ²=îñ²¶×ð]pOxÏî¸R¢erGH!÷jÝ»®­*¼xb…UL¼õèu cÝÖÅ|1žû’‚º±á>Ì! ª’Y “¹ò›|–ϹÀ¼@¡qFc✠þ=mh–lY¦øSÙ–Ž3Zƒ^Ækõ2I+üÅ{áo[z¸*£ár¼z&—ƒv0¶3A\È$“}Zª˜³à’ã…4­ç‰ÓœÅc’2s‡0M+úñY ¡ïR΢Í!G3‰9 ¤þSuóí¡~ÜŸ‚½Ž¢F €åC? ½€xf‰¨äSh/ŸwcúÑ^ú’ãüÁGÓ: Œöq`É1¸!Ü•P!– 5É¢OX[Aì¹¼#h‘íZ%è‰3DáË*A'ù8.Гû‚(¶h ˆõ¤up÷6Ÿ·“ªóÉDû…Ö?oƒÄKÌ?ÏÑ›ûp_¨cÀW¡±á[`¢å‰ê›£7Ÿ¼ßZÃÛý©‡,ÇðqÑA£wÛ:Di¡§8­æ!£¢¡¦¡ÖÝaAd1ºp-g¯ÊHù@?ø¡Žvañ–~‡Üר¸½;TX¸{ˆjNÏ4Éc&œqaC~Q Gg÷4p ‰kCÝŠfíâ33…žaÞí‰Z“¤U@$ÀdžcðÞ€Q„€¡ /ˆjÆ8wg9§xÊ!3s(ÿÅ4E1$ÎCL1VÞyÖb¢›û”zLêfQLžF1u¦òþ0sâÎG1û¢zr ü¼[€² ìðZçþ¡p,ñn?öãCHŽÇ扅ø¶~ç=äñO6·O¸¥˜|x ˰$Ê™X1šÔÆ ©+8DŸÓëÓ+cǹSKá40RVhéw, 3¸€3tR“K²§o;š?¼ÓŸ.¦jÓï+æ~ooþð_í»¹t endstream endobj 111 0 obj << /F2 19 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R /F12 109 0 R >> endobj 106 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 111 0 R >> endobj 114 0 obj << /Filter[/FlateDecode] /Length 1135 >> stream xÚ­VKÛ6¾÷Wè(k†‘¢rK6Ù Ò¢…Èæ µ¸¶ YÚJ²ÝüûÎp([²w×ð¢“&çù}3CEœq­"¿|‰>.ÞÝÉHp–‰hñ  Ų$Z|úß®ó§Þµ³¹R26ïgó$ÑñmÓ:Èä´@À&ÞnfÒÆ5¤ÐÒEóHkI)¹¶Î+L5‹Ýu0U 2@Â$ñצë«3+ãrÛ¯]7¸j§>[×m«žÄÀ dÍCZ€·Ð>­®Ù€–4Yü-î€0¸Å#7-]m;vù²/weB0IŒÑI¨*ºÇ¬p ÑMíæÀ°/ w/uZ»‚þƒ:m6­ØVŽ˜ÒG¦”NX"#îÓyªö¶ß¸«Ÿ!5É$H2Dq–jàY2!üù7,1;úld‘,Ó¾°TfQ–¬|(ŠÛª UtæªÇŽÁþJ£ RökçkFcPCÞú¥ÿe®‹Â¾²¡Ã!mŸ×ƒµp¾ðúá%i’?åC&Nh÷5•ªuü·[úÌ cŸ õiÞ÷mù°íÏùH ãi€ÇvŽ‹J˜2¤¨,ñ ßÀÃJ8€¦†\%òUJ’3Jx:P;¤„Û)´pì¡…óÂ1´ïã}Ù¯‹6Ç›}}?CΔœi™±–\ra.ˆ½t̘ì¡Ú"&aDž2 0x& 1ée^fOHÃŒ¹LŸžÒÇ-Soï¢ß·eÿgYžÂý×¼ÎW0i®è&Á• Üù­v‚f‘Žpµ Öéú,/QѰiž‚ òAÂÿ–Go4üðO ÿªÊzu†æ¤e|†CvìÈùüÕáŠñ“Î0†Iýöθݶm˜OǶ€W¶^¹«šÃæ•¡yµÛÓÿI—˜0€à|“ŽN–Š@Sh9 2s>zÌtôx™u,öÄ%ˆ6$z(åîýKðëW‹^x°òa¡—lHPÇÀ ‰4í™7D= –~mêgà5LÛÁÕcðн ‚*Ìt|Ä  *@áíÙyçâS6 ÞnÙ:P»*Ù ã÷Ã÷‚R‚™gÇÌSëve³íèßI˜r4Ôn :L$š]LMS°ä0‰5Cû¥p†héÛ‰:Kdæðæ¼ejþ»Yf|=bw¿m¯{vàû)t–Õ¾³`™dUCšÖÄ4žÀ4’á<4(6èM'?Úqò+ø6Ô—nË´×Xì÷u˜³T8Zá¼Uζ>FîóFžUB”(“1%þJ~®¯yM®# O\ÆŒ ÀÊáW,Þø´ì@ í Á›BPmBˆ7ÿ ! “¼DHjF„ ÅžÜ«aT „H–ŠqÆ—éø¼øé?ODƒ endstream endobj 115 0 obj << /F2 19 0 R /F1 10 0 R /F5 35 0 R /F9 76 0 R >> endobj 113 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 115 0 R >> endobj 118 0 obj << /Filter[/FlateDecode] /Length 1479 >> stream xÚµWKsÛ6¾÷WðHÍXñ æÖ¦q¦=¸3nq4IìP¤†¤b·¿¾ ì’&EÙqlåD ì{?ì‹¢`øÏ§à·Õ»kðˆ¥VI9}–R0ƒj­vnîkjÂõ±´x+~¼•D,ÑAä/Êû=Ë7žÿŒ½LH:¸¶·"Ö•mf(¬MÂã~¢îœ™¡w ê ~ó±KàžcâmaÎeÜ)ܒʉnf^¹ó–Ø~·,íÚi,¹ÒÎî%D…Ç^Éû¢Ûß$vú¹ÏX¼JT˜µD®èÛl{:q[©ÒiíŽMEºæhñ¯ØÐÎΞ—±ÏºÜ¹eg[Ü “j°ÐK2aÝ ƒMV¶>Jfƒ³ÃYÃÕæ¾h-sy‡:[µEí<£'An1QÐi@‚‹nšÚ)÷­X[$åǶ«÷ÅYGŒùÍј3o/ÐYp`Yzv÷EµÅ(<êŒ1ðÖJ)Ãc‹õ¨qg[|sw‰žÛ¦ËŠŠƒ[¬ö€aÏ‹¬$.ͺ>XÌ §ü‡rÒËòØ4t¢üwadx…Ü¥6µ7 AØ–ë2ø /N ¥“â7¥ç‚úH9ƒÉâIí­\ÊSd…G*_8}ò×¥qþcó)Eï™ÙÁ#Í’ûï÷Y•mmÃü»4×ä,½ÑUÛeUn©@µdR½Æwy].~öÏGüy«ó“WVco ô>;ÜóþBÇ>î§-ö…/M·p³Žÿîè"ŒÛ:½ƒ-Ng®èí•ÛH»c‡' 4¢ãEL§3§'x‡y’kÀlFï½3Ä$ã¢ëÁTéð.k‹Üýš ®Å‘îÇHw‚Æ{\BÚ½ç†qØJ@¬Ÿk\;¶8çêÆÔ†$æõþPÚ\LdýDFRû ç»!4¾éa|“aYo‹<+û^›¤Æ·-Mn4oü0òüZ­Ï$gü…O`o!¿šÎ#‚ÖŒ±ÊÍ\穳þé”å"õƒé÷zÌaÂÁîp€|¶Ç»çæÄ7¶Ž5s£gJ]Ö‰êmN„‰~;Ê:›µôë†RO;ñ«œùõq຀[oêîŒ_ºåùõ “d³g|#jœ>ëªy£3­Ï ¹Ã»Ô;ÊêŒ^•_þQÐor endstream endobj 119 0 obj << /F2 19 0 R /F1 10 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R >> endobj 117 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 119 0 R >> endobj 122 0 obj << /Filter[/FlateDecode] /Length 373 >> stream xÚ…QËnƒ0¼÷+,õScóÊ­M“V•Ú‡JqTY` `N_°A䡪§ïÎî쬃|Á}¼Üº;( Î&(¢ ~ØÁuÎ%ZË&Ä…Áʲ)õàº,Dm¹T&¿-ÊžÓY¶ø¡±gíãg€)аi„B=êI´bàÈ  °“՘߼jJÑ™—ÌL=‘U3L…òP§&Å\/Ðj«Ad¹õŽ'(ô}„‰V¼¶lÏ¥ð…«$ yýc@2XP«œ¨èê…š˜&n*žtÚl×Gn¤G¿JÅ`Í+ÁàBSÌbÖÄ#¢ÞŸ+ŒZ‰¬‹¬©y~÷žªÕ¥Ð]²™{kÆÂoqßs!LÿÖJ~j*šÐJ9úïrQ–2Ø´ò³H§Uç{©\³Ç%úÀ'îûöª™æ ÕH:‘Ê‹ÕT¡J1;;ÈÙƳ´âÝ´ÁvÁØn0ƒnÛÏ'ÚÄW¿±¨ÌÕ endstream endobj 123 0 obj << /F2 19 0 R /F5 35 0 R >> endobj 121 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 123 0 R >> endobj 126 0 obj << /Filter[/FlateDecode] /Length 2157 >> stream xÚ­XIã¶¾¿_ᣠ´‹¢¶Ü2™à=`€È!4h‹¶•Ñâh™ç׿ڨ¥åÎ\rè6U,‹Åª¯ª¸Ùûûý漡Ÿ_6ž~ø¤6ÁÞÏ‚ÍÓi¤› ô3½yúùwïñb®½m·»0T^úãv§uä}üÖÛº+šÉ¡÷¹ÙîTêåCi;EAyA¼ýãé?| 6Aâ+’»ƒí‚ÍNg~J¢Óí.ŠÕBX‘°0a$b®šF½vJ;õž¶©òpû(ó̱LYÞà+¼³íy`ðgï = %“^‹:o¶*ñ^y®2µ9ã!q²©-*亞¥[T3ç™þ",Ǧu£Òtä={úh?üÐx ÍNNF":Åï$'LCï‘åüñŸYì]A/žˆŸ·ó›˜Ð•¿):ÁàhgXìæGü?5eÉ'¡jŠ4˜ž"çºËfb”Ùr¯pº—ÔÏ"Æx:dzŠÕúî^{ØG3‚w´_x>„Ë,äLÊÀdgKðs¦&][‹˜ð #% BÉ·kq”èŽ\&ÀÕ¦™¦cÊ8UÉmÊ®d˺˜|a4®éyTÈïW¿öÆFj6¸-óB€¿²ÛŽ2ËŽß5ÝOäÔIrÇe“d¬£œË&‚‚*I.ËÊj´N˜`ŠlY;úŒéñªÙu!‹¤*º‹â•„‹g“™ê¦Âatlw‰ÿŽÐ“bèÑ^I°Ø‡€\Z T¡€9°[‡æ@¤z~gg¡oÒ•ÂÃd  dY©¨b_; †t°êÐW‚»ï­—xvE))Œ×…c6­Z™vŽ×!ˆ 2ÔÌA¶ºÙ#ªÀÇùôŸP:^¡´Žç(Ì[hÇÆŠ§Ð‰y!*üމ\8àŒ a,uªŽ'J·¼ »”ÎÕ.´¦»‰è‹È’h¦ß[fç½¶ ÓEŽeû>ÚF\ÝÛ#èˆ5)Ìå>ðœ†úÈɨîÁÁ,ªmjxb@Èw'y¡ݽž¦©KEà7·³cðI¡_!nÑ%Pbhz× L«˜RC…gÃñ¯7ˆ¦ÚÕ# àŠw¨¿Lþ1sô0Žü G~yÁµ/ko×”ÄÌÃv‘þ~ÑKüF…»N%‡HSÅ]R¥·Â£xXj®2¯ØãhZV﹋K1‘CSbÕÔFPOt‘NÅüPaN(Œ˜•G¥:1ÁWšÍ0rX9­C}Ç$ä©/ƒ ^T ò–îYLç¤IW©³»Jž^¤Üý¬f€¹Ê8‹™j¦(Δ¸ëxØ‹pM ÐZ5®ÌXÆ+O÷œî›©] ×WLå”Ôj|Õ®¢“° Gcá°rmÂ áÆ›Å_W¦PɼF©º:+Ô‹l²–}SeC.‚sBÖòþ%Sg qnò$]G °8là/êó$ ’úåÖ8VæÐ=›AL-NbânÏC%Ýsç”ÁÌ(»r.Z]¢t|‘×0%xráÍZÑÃ%žðè`¡Ÿ:×âq‡R3‘ÆÎH¯8?~¯=àt|ä;CécnLP&ÿˆñô¦¡0x»·¯$|a_–Èî}…Á­_½{„+Éýuæ~ûÆñ¾8¼~–·¸·È–¤þÞÁß,›¸xÀݨ i\'¸x¢\C­ž’Êúea—…l‡ÄOcAÜ×êZWkë÷°7Jühñrðè {Sïzy‡x-ó¹PP°A‘”¥dù¨ô0s_¦Lí€;RÐ=*,y~ïvNa8d2òû§ÓËÓaÑ!§Ó)Ö„LˆMgJõ2õw4»Ñc®Ge¸ßàWD&s¡,š ƒköŽÛ3½P‹ãU72Úÿ^þåÞpÖ.ÞÁÀÄî9‰ä~|úÏÿUL¡ endstream endobj 127 0 obj << /F2 19 0 R /F1 10 0 R /F5 35 0 R /F3 23 0 R >> endobj 125 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 127 0 R >> endobj 130 0 obj << /Filter[/FlateDecode] /Length 2166 >> stream xÚÍX[¤¶~ϯàmiiÛ_¸DÊC²ÙDióéDÊD}po“ÐÐzg'¿þT¹l0Ð3g•§€Že__lâÁ(_R"‚üÃŒý´=fq€ìC',?{ÍBKŒEa<Ù3[Ýsáb¿§©hGŒ>$^‚)I}ì9»âëlÞ®l^ÂèþX”š€æž6í–Ýf¡¥OÔ ®j‚\(Æ& åhh®˜JœYL«²yÔ¥„Û+»¦ë7|´5™kkžuôÇ’˜ñÔw´ÇÞ/Œ—=W‹5 ƒÖõçÚ5—]ïà·¡¡„:ÔV à¯AÖ@Ä ƒîzÏ#–Rüû¯8¼G^LyÇ,W”Q‰`¡›Yn¿Ý½=èmí“ñÔçdz l·K‘c$G–& _ ÁÞ4{.j*³ðµ{ Ë¢(1žL£¿„UÙ‚¡LN§ 4ƸÂo¯«kiý–.ÐEö4»4»,Ïå8ô4‰UîÕàyâ\\l‡B Ô—Þ¿ëwhurc:¾§Á•k“™˜Gôi¦Nö=Ô¶^Wú2ž¨TC©`éÂÃï—è'ð¸µû ÕNÓ­ƒùÂÁ‰ƒÞ!½%‹œöµéø‚·wvJA’˜è—XSç 0 Ü|€;`ÆL("¦› Œ¼( ]4Þ‡v>žçS–¥~¿vF:ׯZì[òÈ/Ø`nˆ£„%Ž…+|Ñ”(¶Š_õxí1 yfKö¥þ¬¢,cƒt53üfƒ„; 7êĹ¹·F/ ÔÅÉøì–A’üåý‰w\—ŒÈ¶¹$Á ¢ c?ÉŠ kd¾^MÓt¦ph¸“E[vþŠ} Ü8~BwÀMä{0¬Ø„Öp ÁnBB`¿;Ò”-mÑ„h8,ÚW# ' ݦ‰ŽgañÙXZ*–8k¹ Ø U‚Kk°#7áÊu#…øÿ®åR± ô9Ržýx80±yÆH œx8axYÚ©‰j‚DÃQš$|w\/=BG°àvÆ`ý‘È}QæXX]Ü€wÅÄ Þöý = þ¥,Z´ús©/„ä„OÓó_ P b#‹g€²ÿK€RÐ F+„òXÅQl.K¯8R(üŠ‚ 4³>·Þ5ˆ>qŽÃ¡ÝO²ÈeøK„=Z¢, Àdúw¢(…á‹ÒgmqsË•ƒ†yâqå !k¶HÍ󥽚bþ+컟Çc.”h È “ⱚðXRë„KìZ5%W†·¹¡PɰG¦ï´ô7Ú vê®}i‰„k5n¦½ ‹ûöbC01yñYô@·`³™šUÆšk¤Ùl¬íM΄ƒ©ÿà[Ðã{zb}õp°y±¼¡p¸%âéÐù›·¿Œúr Xðíþº^ìÄi–ÔØÝÓÐÙs3s ` ´XoÞ8à§ìήNzocœn£ø¶YãÀRºtàfc!CNݵ±ä¹p Ÿs=šŠ?vgæ®"@²7ï+ÃXôãºMûï¹$Þ<îðßæ í+ûÖ¢r–"‚*¬Ú8ñî¸}ÊŽgPx®„I9—9ô›Œ3*a‰+aÔ›"!ydí'ãܾM Í>hÐyÔGú¶ÍÀ•‘Þ†Mw€'ýFsR˜möi`Bù¦> endobj 129 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 131 0 R >> endobj 134 0 obj << /Filter[/FlateDecode] /Length 2627 >> stream xÚÍY[¤Æ~ϯàÍŒ”ÆÔ…*°”—lÖŽcmìQÉkmè†ÙÆÛ # w<ÿ>çRECÏN,å ¨Ë©Sçò Qš¤iô1¢ÇwÑ_ï¿þVF"M Ý?D"„J Ýÿí—øÍ±|ëþn§”ŒóoîvZgñÛßǺš®Åa¿ëîv2«Ë©à-Y‹üî×û|ý­@ZitwpœˆvºHr"'*‘¼*ŸW)›¥´â¡kÇ%’vóbž·IngÓ&µå¹þ3KºªÊËidiü……úÏ®­×çigx·ËN˜412TõõxéQg²€S5¾ä<Üíïv™‰«#Oº¾¯‡Gòî®eíâøØ­ï& “Ø|ãv+î´N¤—R€Wÿþaƒ`ŽÚ èå·èåˆ ¼°b“ª‡ö+w‡s9—#)§R›(Ë’ÉhKI¬>ƒ¼s;1*Ñ©ˆËq¬ÏNB*§‹Ó³'iâ{ÁÂÄÁY˜H Y­Ö·’i‘xÍy…__ Pû 5í0ÖeEüȸyXûˆDT}™ H\ù5ÍÀÜ‚§ïœH´Âé@$èþªFkT YbëÝoi<^Ju{2’Ú¯¹´Àò5«"Ç3?¿í{@kn³,6ä(öe3 KÈp®’Ü0–ˆÿ;,±6Àþ¾Â´j…%µ"KŠ< &àp!®É‰T$©]ÐwÝ€%‰!ïU°äö3+pÒS$4ðâɈßRûoÏI–.ؘPMmQ¤PÁžöŠ „ +µ y8•ÃàˆP©*õ-¨L³${ *¥J‰›Éøô¾-2çÛðø6|-€¶€’öSBbf:ÿæoXÜ]úƒocêÍ{™ÙºâÑ=ÃÒµ+â:Á‘y*¸ ΓÜÖN&ÐÒ<¯IújÿÁYÿ2i”Y’f×jÆ—/£µ AàóÄ©b÷éòÈKH28ãE‚Hgòxppž.'rËv8…4V?€´MAg/~'LäÁq÷€ „;V’¼è­l+žcÑØpì.'7¼çXÂݹ`P]íªÏ‹Èkª`ËÞêS3ùí?kxÒ3Œ'km¡ðäW ‚» ݦhqZ§ÛIúð6~5ñ•à™_1yÃgÅz©çE7 ÄMø4s”Ä0JÊŒ£ä£ÛGš¦ÓÙ˜§z-˜À¥ Ü—™#ÆÍpi“­Â%йÙH+ ™WPÌ‹«.IŠÄš+¬b¥»¢zE¬\¤?Â` –®õA“XÙ šë:LB‘#'Àe³b*ÅÔ‹¥Ø§úyø#J±ìŠº\dÓÆ†¥Xîë#Še]‚äÄ4+S®Jpô®z"<ÆáO¸ =§êÏôÅRåBeÁ¦Ôü™êç¿#©z-z¸.oFÇÉpau0ŠBaN´ÈGá¤ïúr}WöÕê¸<›3qÕ•&h 2¢7xÚ,&77îÆMdß7rŸìS4Ìâß6‚o‘¤ù ±×šD_ÅÞG@-¯° D|ØYu8vd¶ÑÍ…d¡ã°o\ßµ§g~›Ý –pHÂárpOžØ—CÍS¢¤[g$=/Bx)]¬.Çz‡¬Ž‡Å Zªøûå& *)ž*çȃ¯UÝ7|XÅKúîÌS¢è{ìøIìœNîct„&I!')bEÇ?ë6äYO Y²ÌAp– 82v6ò¥ÂåBßÚͤ ÷à•[©y&·²ÕV'Úc⺾03²ÿtèëº];BfÚO¼y, 2}ÏÏ›SS·›%ZQ\‰ôî¶;´åÀ |dX#<åô_i>ƒ âº4é†ù¼WIÐÎ~½¬÷KƤ M(+â}CíÀSUyWKF ŸO Ùlž”L,F4fCß@Ã./ýê4øH*5š@`“76Ÿ—lPÉ0ÊMs5xËV‚K b¦È ½ $¥D()\âÁE‹d $b‹…dý÷z§#_ºƒXNüÎ× rbH‰¡ãp2djçÑÁuÂyö–<­|¥<ÕBžâ†<‹/ºLn¶å)ò4,O”V O±!O9§èøqKž‚åÉ–¦]Þ< ÙZ jÊž8%>\¦Æ&¢XÄYÆöƒƒ¶ÝåÀEß”ûSíÐÅe„3‚oeGwÊtË'–-‘Z –:‡2ËfØ©±ûöu¸äG8h8'ý„­à­ôj§(Á $qO¥ƒÊrÌèûfÁ`¥À{.í»“[¢•õ<=º}Eà °æèCÎ8åÁ@ÀÔ;‡UÝLgk÷°WTTS…qhêIß0°L訵˜qNŽõª7ÍY½X“ôjQ¨ËŽkòJ¿‚l¶¸2Á­^$"5ƒÜ"aqW…AæaãGLO+žàê2s h¤žšóbdæ0ä=mW! 0ç•ð¿_ƒ4׿\ž(ÁQz¹})4_¨2/Ê{õqÊIHø[u¾ºÃc.ê·¾5çô¬-o5iÜýcñ<„9jw tvŽ f¯ÿ`¿›sÝ]6ÿ©ØEÏ@MröŠ”q[p>ò åoð,ùAÎu9£žQf›ߨ\)Ï‚âç ÷¹JdT>·ö>ܺ âÔØtáNœÃ9|œšð½yp\³ Ã*È…Ô¶XLÕQ vr‚d î‰>ûƒÍ©\[…)çH~“wø%Rä“H×pÈPŽUè'Œ:°³„«³U6ÿÉ™‹¡Úíœ#îý ‹XP15äÌ¢gu®ËÖý$S>ÊÓ[Ýûô¸wæí›þêáß6í­kƒ)Ñ'‡È2jÒÞþߦ¾Ô#¡àÍ ‡4a§<SR’,‚X£Q±/è¿9L-¼,“I®xRk]‡å$,•¯ü½¶@µŽx>ÿ«Ú*LAõz{ãqª°>­ ¯Ýˤ£ÂÅ"Ÿ³¦ß7‚úQ4ã›iT#® 3÷]ð"yìZš[-»ø±ã˜Z5ȼÆî×þíç­Ílñ²CyÐRÙU-ŸÀÇùê³yº¿E?ÔÏ[`Τ\ùwmÕ:¨Á,©ËÆ€ß7U„š÷xa9q¶÷§ÿwQQ] endstream endobj 135 0 obj << /F2 19 0 R /F1 10 0 R /F8 73 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R >> endobj 133 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 135 0 R >> endobj 138 0 obj << /Filter[/FlateDecode] /Length 2083 >> stream xÚ•XmœÈþž_Á·e¤>hh^¬\$߯w¹XkEç‰bélYìгCÌÀ¯GÊO½tCãœò‰î¦¨®×§ªðÞ³G_¼Ÿv?ü,½0yèí^˜ya$òØÛýõwÿáXœÝm¶Q$ýìÕfÇÊómÐM_µ Gþc»ÙÊÌ//µîa¥B¥ü0ß|ÚýÝ‹B)2émã\dÄï÷G=‘>õKþ¸å?«I•‰4õúà­¾þ­hÊd 2GP• ™²OtÝ›è¤"ïÅ‹S‘*o)¡rïä©Pd©ÝÖÞ{Ò9÷ÂPäŠX˜½Ïâ·¯uÑl¢Ð¿œùÞ0p.D–›‹?ú¬ÆÇÍJ>0Šco Ö Ùšÿlª¦Šº†o’ÜŽiàÙÈÄ×W>=²ºb³U*öwǪG¢Ð©è; ïô©Ý€¿nÀìš¿*ìËç®xêyyÔuÉ«'¼àº¾5…[Í}÷dÁ€ÅU$.¼@ªÕ…«†ž_ê¯$}ƒï~gX‚CמxEWK•Ð7éü›²êÏŰÇó#i‡™¿Ãíõ\íA½+Ó ddRþøR—kñù^tNa"ÕQ¢ëª²ÔëØËc‘„Æ·Ÿ‡ê¤Û˰òl qbhÝ+‰‘³•R¨”ý¡.úþêDÚ6$£ûð<ž@Bé·EW®ã- s‘&SÀ1A8¤"³ò´O7þ¿ïYcÔ`Í0ND>ò»¸!)蘒¢pi* ÙôÇYš„#Uy¢Ìߣi@¾,óûc{¡ Írÿ‰`Dó‹K¯ÍñËQ7|Æ‘ GÅù\CL ADSõGmx-b&ð &ºi‹ 0iõ…"áÊ–š…doÌ×´ƒE©ÚÐ|õ(Fûû¶ët&Ú¦¬šg>'€ôd°°±c|&"JQõÚ•2ÁÒ—+›Ê@„ÒÝóå„b§˜IQQ^DylÌ‹¾8™Ua^ÚnåÎ0ÊEý°+Ö äStOÁçÊžˆÐ&ØJÒf_YÛÂbŽäT™‚Ig!öHpã'!G® |ÀÊÀn‹X¸‘ä?¤7¹uyþ,#H  ˜ Ö¡n’¤`®ôDa©2Ά®zº æNÔž…67´ˆ醵 ½FãØÿu0k°¨{£SÒQl‘Úš ZJ’ e}üA<\ šawK·4q4CÊ­Œ¥ÈóyP¶[`é„ïhhðϳ6T,%¼)õ¡¸ÔƒùâfLçHm:»ö\<ƒþüE_û•¬Y,‚Ø:ªå« †¹ ‹áªQ`ßÞ¬"¿Ëº£Àø.ͰÚ8¾ë5˜¾´ðC :•®ª$#@¥(Öº†jÊßï—®\ÄVdŸøΛó·Û®ð¥c|DSîàflXH§0Í…Œ¸rÏ÷K›“o¸ó 7ä~xî; Þ)yóD¥Ÿ×³Â0»ösˆüãbL…ô& )×¥yªÊn¸¨© ¤4 ü×Ôƒ¼.ðO' K®ƒQ¥þ(UÚº >kâÐoô ŸL̻͡‰MXvú ¥È”ži§³dßÍHMg…̹±Y8|ÿ\4×õ ˜wøüPWOâÃÛ{ÞaÐ zÒ†ŒI4o?óòêP}û‹sqÄ€êÊé\ý=ùíùMe×j¹ä÷»÷w3w‹ýãr.äò ZÄË5 œ\è|¹{Ý\ïXPûÉ;Èú—ßîÖÃG–¡¾NŒü .’yî·àˆŽ—/hmÇØÁ^Ìâ8ÀpeG \Ol ~ÔU?˜‹†™áT!Ró8 §‡¶†ÈL}¡ñ8fc¹Âõ(Ít¹eY¬riE0¡!,Q£$2ó¼‚ IŸ‹Î”E5Ý©ês RÁ¢ÂÎËÜe¶h?êŽ)óæÀ9Kª9ÊÎU°øÇ¬g1!xušþï¬òy‚IÆ“Fë¶ÓE#¨øïÈ$in2Nvç6uÿy@4¶#“KÌ &Q7/, Нæ–o}h§ß«ÃíºöxËCË<»¶^ÖVþZD¹ÿWš"ѱ€¯k3G~$Á ¦é%ãê/Çêb¥…<•¯Dy ):r&„Ð oW+!°¦KÑê&'µà¤\N<¤QóÖŒ}˜ œ½pkýâ ïYûq¸w'igàÀ9ã„ ¡±–0åGnNw$[ĤÑ$é;j*dpðŽþ¬$$´€¦ÁÃ¥f’¹¦âÍ`˜?SÁ]ճĶ“ùzP©mÇïy2·wYÕ&Æ&Q³Ðôà uJ°å¼…óbWå®/¿nh`ªŒ Wm=ÂPo­o~rúœz]s÷`úT¥©cÕß0øeàÿTÙY„ÿnÄß:]ë¢×7þ©ðNº°ÿ~‹¦Êa%±Êr{{kê³ûÓx´Ó° endstream endobj 139 0 obj << /F2 19 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R >> endobj 137 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 139 0 R >> endobj 142 0 obj << /Filter[/FlateDecode] /Length 2271 >> stream xÚ½XmÛ¸þÞ_!ôKe`͈%Q÷ín“\RÜ6Áe{W Z›»N–¶’¼Núëo^H‹²œ´E¿Ø_†Ã™gf2JD’Dýýýpû쥊d"JÝÞGÒD2¥ŽnŸ¿¯wÕãhûÕ:MUl¾[­µÎâGÛu×bwßt«µ2ñöÐØZ™Ì²X%«·}öR¢¬¤D¹kØNFk] C¢H…æYfš•"O£„fÜw›Ã°“‘7#Ô¤ÈH“T’&¼,›ŽµÎ¥PE´N•Pæ\|p|—ډ컜ì©ÞâÉR•Ä›¦þPñˆÛ)}µY©"þçTí–û7 t}×pDZn·vyæ=[Õà8¶«Ú;0uZÆ·;Ë{mºÞºI“ (±ið`×B$&Zƒ™dFÊ?ØÖöÕ«T™Æö e[VÝTªø¸ƒo ÁïsEV‡º~‹x æ=ÿ<¦O[nÏ÷4§=ëaá™TQ8‹_7µmÇ—è™7‡qá“™»©WKke„ÉÙ¿‡¾,öšN´*Ø¿X!ð‰ºN,ü5ñ¥KO¿àܪ¯«;Ÿ°è¢ ÿa|bh_ˆgT÷ÄÖ靖ŠHÀ±M!J,I‰È Pl 5‰:};Š ¥áN÷íPp&K¦€Ä<&Óg⤆/=“w¬·[Ë:•á=av‘r†àËŽ¯˜I‚{6q/%õGÛÐý®æÏƳBî^íTRÐÿŽh»3›åüs|—h‹}”ç"5§og±¼©ÆN˜|ò§ºèO|6ÕY PÂ"$¢s‰lšÍDr9ǧÆe…‚XåÌß3Ý›q`†x£mFÛÌOÙÔU1ølÁp¾xàŽÙ³ÌtÁ¡]5=;xzË#A¥AWÚD§3”Î ép‰û—>wlîk<„¸¯šÁ~å6B(ücòÕùCÄÀ©€ŸdpOR8.ÊcÁ ˜.‚;ÿô%ÿ®õ endstream endobj 143 0 obj << /F2 19 0 R /F1 10 0 R /F8 73 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R >> endobj 141 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 143 0 R >> endobj 146 0 obj << /Filter[/FlateDecode] /Length 2517 >> stream xÚÕYKÛȾçW¹„Xm²Ù|-ƒãµ³ÀÙ  Ø{à”D,EjIj4“_Ÿz5Ùå`‘[.£~T7««¾zލ ðýüÕûËãÛÚ •‡ÞãÞ 3/ŒTn¼Çï¿øïÅyª‡‡]i?ûîagLìx™ênlú—#ÿSÿ°Ó™_]Úz„QƱ¯Ã‡_ÿööcˆw9Þ»ƒÏ…ÞÎä*£«3©„©²…*JUyQô—©mºzsS¬U ËKjˆàyácÎÃv©Q©ñv:WiB‡ qœø§å ¸ûç¡ЉÿÜTü(ãWÍW ¬‡ºÃ‰¯8.àèëÈû=ßX °œúצ;ðFÑ ¼Š¨,5U=Ñ „fþ»¶’…>ö˶GàÞæíBƒjÚ\BÖV1Ô¬’²mf6Qs§æ¥éFÞ+º Æ?ƒÏÈ|Ídӱ歱8ÉMM‡D}QÖßÑwu«$9‚>Rúî—OõtD ¦¾Ü ú ñÇ‹øãL¥©hí'~Ó{dsÚ( ôdŒPþ»¹†*IFg*K¶ñ><¤ãÈ»zI ¢ $„*N¼“—‡ó¼õ>è½$V&åÅ,¢ËFxˆá*ˆ –"µ–O~õe?tXVY&Û/,¸?óÏßû®~Ã2yýÖÆõÎ)ØÊd¬à˜î>:”f¹"ò»YaÎÞö¡Ö*Êí;¶&š L}™B€G„bè”8Ù÷&²' 9ùä‚pxbrÿÇ=“Þ½²h‡º¨^yÒÈÏÍØ<µødæ~3ñîµAKÁÑÙ0c!pÅVâ ŽEw¨+ÒÔóïPÕIP—“,[üÃár’€AÚ •„â8ÐÓpMxF… .n¥£âÍ7Û³‰ ­#|½w4þ]G¯›£`H©å }Áæ´#²æwÜÏU` áÐ<ãÃÉA‚,&Šöuª§á•gäâR½l‹¢ÑÏéˆ`€«ù«W¾Ê93Î’–ã]?‰û‹r¥sWÏä´3rÚÕ›•JÁ%öÃPg‚HßUä–qýùÜxÑ^Äî‡þt†ò2 ¢b¦Y Ì”Ë{qÆxÄ‘Å#Ýrëê’àùà@DLv}« `²ßˆS§—§ÎËQv…Àzœy®¹Ï]Ám<·^I¥ßÑt7²85UÕÊTÒjsV,E (ý%4\iâ%JÈ|%p1e•AÈw"D 2³D’Ì= ùO¬]Ðþ€×­±*ì „›=K«uZ‹;TÖ𹎸ß/°m_çø>ÌÎ~” z&šý0'”opàâ@É'8úò~vó|öæw”gŸòs?¸š}ˆÀul1Ús¥ôý@YÖ}Mf} –ì(ƒ´¯À—ãki«[Ñêȳ'¤‚Ø-éK?ì8aƒÉ¹y©[> N"&'AÙø©G{ç¾·ï×Y@F·)±„ý±ž¶ñèÚLǧø3þ`@ûËPÖ»€œ+²~÷Ü^OÊ00½ý®ˆPÕÇþ޵è@-çÅóJã¯÷Å¥xÒŒ›o§1BŸÏîA(Õ=mÍY»²¹K€0YMB®1Œ`($ð08õãÄ# Èaéd¯Æ8ˆDÒʼn }˜ÌºÈÙF~…Ü_ðϾ¾ò¼$̉”ÚÂгbÃÉ·J»tB‰  g0)ñ$e#r–d ÍSÁywÂ1Í9¬×‡Å/Ãhœd¨­… űîxDïL8/Â98¤ªî”˲qÒ”(]=ŒJªF`0G!¿ãÔa˜Í†%~­T—>ʾkö¯l%@EåÞTÕ´ERŠ»h]qê€uŒœH)ùâ“=:¤­¤Ÿ›úºªU XtɯÛúA\bgéø÷é(GÞ:ÃcFüæ-šÃ\/y×û]»MÒ%i0ë çRkó;ÎJžšÃQcAÛ3Ùj£0nýIl½„óÔtÅ•ë¸Îyªú<Qz™A\ŠV 4oûáTœÝôÊɾ.eû»~‡`@Ù|B5Y\°˜¤1Ã2LÐò žZ¾öü»ä8!šÄÿQ:[‡Ó ð-òÒ,’¾xNY‹…yè”Ç´/,PRÒ_¶/:RTà4”m>‰VLƒ©Xü†ì˜ŒEÂ$l“9бùQ0ûöyΆð11ö„Œ3«Å®£›oÉ@g—÷ûýÖRçÐ?°oë'© éD–9ce Ñ&Ù›2ðÄúÈÌ—”3Šn},ÉØÿºÿE‚€}]Gpý;"¸H˜úú•š# x£?ò ?_çÎ3`2Ë(J¨'ìüƒÁ˜ª°hÇ#Çã½↬îÒÎ÷d¾åc^ÀßñýÔ^26Cƒ4?èrw&ýw¶t‰Œ¶•øåÌ:·ÎM&Z^ãU³ }|¸àiYÛvŒ½ey'EÃdŠp»A%,™oK.ˆê’ü¶- Û*bsè—>Ѫ‚Ñn- |ûÛ‚çå‘Í9G‚5ðˆì/yrsÞ«!?ƒ‡”µÜÓŽöŠ$÷-}Ç™¬,¬›[xˆÂºì9âÙÉy[µÇ¢kþÍ2×ì•4¸‘ÜHT†ß%¨'ÿƒø1v,ÃWÝàûÙjk¹K]̯a7g¨cá#©…Ñ C6ÑÙ"¹mÆÉªû6·ÑÑÄØ »›¨oM<1®"e¥X–ÝöJ¸G8Zf¹hÆM™më•c1T¥8²uëÆ¬|¤—Lázl&†éºûÌîH§*‹çösú_Ûϧþ¹f½onK5Ö¶w:Ð1w Ãot sîØn:ÐY|ÓÎb·¤9Ø_º†¢¼H³—Éø :ÀŒ‘Œ!K½²ßöšuj«™›$òuû­Ý¦ÕÌâc&ȸE‚ 8wÖKU¼ùäD‘²x§¡(©‰®¾kQŹ¥Ž"¥mÒö óOG1n_,]ȰÏöþ£|²8^ž(Äri[ñâ–E8á–ÆœæÁMñîôãWüFF…n:¿]¶<(fÌÍ ¹xÝË·—¹ÿq³W¨»"‹M0vCøâX¼yÈÛš M–|(×é ®Ø`²Ó&WIêzÉU? Êü:¶4³LœXžd7Ñ.É9LÜè“=ÿJ^•ûUIîÞ/-=p‹€ÖgdºnÁ{ø1Ç‹Cñ>”Љÿ‘•à©ß.Í`=ý[jQ©ç endstream endobj 147 0 obj << /F2 19 0 R /F1 10 0 R /F8 73 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R >> endobj 145 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 147 0 R >> endobj 150 0 obj << /Filter[/FlateDecode] /Length 2050 >> stream xÚ¥Xíœ6ÿÞ¿‚¬tëbcƒ‰ÔMs©úrzªä*UêUÙõí¢ppö6÷üõñØ``Ó¤í'ì±=3?{^‰–$Ñ!²Ÿï£—·_¿OXÁ£Ûûˆëˆ§¬Ñí«ßãïŽåã`ºÍ6ME¬_l¶Rªøúã`š¾j$§ñM»Ù ïOµéa¤¸R±›?nŒ¸dEmeÁ´ewÓö*âÎôÕÿ«æ3™Ä窮‰þÞò24Ù·=U%íü°ylž‘ªãcÙìkÓõW¸Æã¾¥Ínë`ërp.¤}»ºì{ÚT¹ïc×nDR÷ l‰„OÊ8ÝÌp,J&ã¾9ÕP9âO³4>wÕ@Ø`ù·§Ž–Zœš<ØÙ“ۚƠ ‚ÛNõIiØqߺÁp4´kwê:ÓàÖ¡~ö›ìmîN½Ù[ðâœ@( bWWxDÇà ».“„imEÆtawüþÞθúõë"✠­dk·fQâÞu“òøiFbÞ îæ'óÜÓ!ž¶•dŒçîÐ]ì6ðiCδvË÷]û`•º¾½e#®$ŠßjÍ4`®™~^GoÁ–-±Ð@,XBO5¿¿+2fó„/ìЯ5Õ9^“» ­«iyË‹u îò¦}ZaÜJ+T‡îZ[VM.•Çf8:7rûÀRjz`Xw‚“Fj®QÙoɬCÇì«Ccöš†Ö}­N08˜îÁÆ9ÊÌáÒÙÈH3•ZF·GÓ¹c¥ù>/&+s¦Àby¦X «Ài„Ÿ:ƒUœ©‚hN2>,¾7^Ö‚_n=sä§Ƴ? bÆï7Уò Eµ°bº ²x …Ê÷}Õ™Ýàb½N—áåšIà£ù 3ʦ©Ëî°Ä¦3–ò€x°ÒKfEÂR9cè„(D|í˜Î_ü?+¬î0š/€Q§Îyºªù°Äe—'^àr|É h:›±BXy"â·ÄÒj¹D•ª”‚åWêp§²$'ãÞ¸aC.fûaj3`yªqëí=Lu8z@Þ<˜ST 7{³Ê1Ùćʼݺ Ð)$µa¯Ù*1cŠqCƒ4n<ÁfÍ«ÐÙ)X¯f?œÃÚ¨7Ö›™gøo÷û µ.k›)É\:ãÎV:‰‹¡Wʾ°„Y¿©ú#űÉîä,8S4þDhX´˜LÌÍç&†D¥VÁÁ…ùE–LR–¤c̺é’1Orkg—¹ãu…ˆ\š,wÃÉ_“œ,¸ê¯9(X>á|—±ç)“Y€æ ì@Ìÿ!tÏ×CŸñ¡ŒKÊTm7l´’ñ·8ÍpzEðjSÎ/ýï\ GUƒ3£¶vws0-XP÷Lž×-thŒ/áne–Å{s_žê&íi¨+tAœŒ¦äa[5d¦¬·â[àÁ(wŠ aY¶©˜cÑæ –ÿ9iËš…'‚Iá‹LãPå}έìMÓëÞ(øUIå´×êÅÅŠµð^ÿq]`±ÜýÚ'2–ù“Ïÿúä¹ÚCE½ 4‚ ÿP™3áìhlY²b!§Ò޹>æ«¿ƒ[* endstream endobj 151 0 obj << /F2 19 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R /F5 35 0 R >> endobj 149 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 151 0 R >> endobj 154 0 obj << /Filter[/FlateDecode] /Length 1832 >> stream xÚÍXMÛ6½÷Wè(1+~SzH·IivoÝ"mºbK[IÎfÿ}‡R¢,ïnÒK{Ù•(r8o8óæÑYAŠ"û+óÿ~̾¿ýöËhAJšÝî3j2ÊI)²ÛþÈoÕý`»Õšs–›×«µ2ûy°M_·æùûvµf&ß¶‡'I¥Ì_ýyûӷ勉U”Îî¶£ÙZ”ÄxÓ†pbp–™fqMÏ ?cû¸=Ú…nˆ4aFꉖÞN½'¸LN°ÖЦ3ð”(qi>Ï)X0ßµ+@ö©Þ9dœùöXõ}|Ù·]Kuó—{¡yujñ±ÈêftþàVe>´8§·G»pRÛØð°ÇÃÁžp§ùÆ8L«¶Cýi%E^ vGœÿTEJ•­!F±ÝêOÿ‹ülèÜvàôÉîplã|{Äí«6ýÐUÞ9X½©z‹Oùk¿( b`;VZúíþ¸q_ÿÄ`–¥¤”>èa*†ó&9Ï"‰¸™¦Üåá;¾kbây÷ÛÎÚæUô¨¶C0x¯ÞÞBB =d”iR˜l]B.e'Èœ‚p^Ùïð0F e~ÌpÜ™I}t©¾ð)g-ø·Z$̚£ }™õNÆl€G8W|˜§ @þ.PS /g¸¯åª¡aÊ©¶.Y.Ö„&2ÆøÉ¸±RÍ–9üßBà`ˆh…£¥¹¹Ä=ƈˆ0Q¹"LÌç½­OÍÝ<IéJC´^æPº“™Xã‰,,"üÆ~®$!upÆ$ô®\;lî<¿~Ô¾ZÓsö;ù¢šNãÀ pŸþqßÙOu{î—ÁjJ½ç‚uÆÔ—FcÜîÿÛì®”¿žÂ3Á Î‘„ßÕMÝ 4Aàn Ü]/h›‚ÝÍHBý¯Tm|ƒm»á*ü ’¦ô0åÌ›‡6¯7m¨š¯Ö˜NkÁˆ’i´BGÔ;"¨…út´§@…ÞNZ¢†½|ø©¶8П±ðzmZ RÎÂýÆaX]ARÅy‡zëv=à&^–c÷Ðâ¢iLžz´L+G_IT/c'D„…4¯ü?œ;×M…2>Gp„$F¦p~= t–x4'’.ñ¸]Qfùí7:¥w1ðîy×UéèyÕœ´ô­|*³68„ù¿ÇÄ“d{îºpÀÇÇ—òv&a鱂ðåygÿ>×í'ë—‘aFbûhŸˆ4tcd£p éÊyI¤ãIðünW†ù´iÖ»L­÷ÀCWÇr€Á±‚ JîGyD×EШœGj¬ P,X¹°ÂS+QDrÄÊ âjAn$)Ù¿Ô‚?ÛÇ+ÝG!Ñ/ÓƒÁãàë+Äo?ùÑÆîž2дž•sÜ×ð•®Vª± K’¬4• “üudš¡Ó4Ä çü3 7ÂͯŸÖMJÍ¿J I¿­Ý¸õå›iql\\M¡&DáaèêÍyX&±a„ÇjÿàËöþ¾ªì´ ‚Ên­døÕmP÷ Ë ?U´¼E›dèÎWy„Æf;2x¿³ûê|tB0>Ý·<$AæÊ ®cÍ4·«t®C¥S ŠS‘ŸBG5¾£Òøðak?:6 <<_µÂËy€}+Y¶èÚažKjª©*ß$(›‘?—Â]Ô!Zcí^;ö%´îü 3Â_Î=S‰ò¢”ý~;ëBÔØ>ahêÎáóÉýu=Tå‘ ³yRµ˜îs"ãéÑ„kètqxì¢(‰po‘@4l|7I‰„( W˜ù‰)-‰2‰)m}]˜Ò¥ Kj ¥>£EñŒãà™ËkÄ—§²L°àûÆ c/b‰¦"–™©ˆe2•uU.áˆë’~„¤ž„·?š¾_@‚A®_„MEH3SÒdÊ û5ŠYþ*Õ—ÚR\ð5dâ‹ÐDéšÊ ß/ Á |ù´¢©mf*B›LU£|^­©’õ´IôtçJË\DÊ–‰9?É~5Á]^ïÓÚ¾¯7G‹¸‡ùî?‘Á›`áä˜wÁ;ÅÔ`.@B:RÙ·Fňâså„ûë+¿ð)¢y¢¡Â"ÉDþþqÜïnÚún…Lâ "í›3n†õ2ÿÿM’¾B~ýV^išéÂØô~i‡»ñ¥Þ¶M½¯í.Dl1HYÔî\/wv>“°IücÞño>|õBtý÷—¦¿\.¾ùúå•aÎüfÝ=äùÕáj>ß÷m¿­îíó «Íx[MUÔ¤*ÓÄÁͽà–:½> endobj 153 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 155 0 R >> endobj 158 0 obj << /Filter[/FlateDecode] /Length 2615 >> stream xÚÕYK“Û¸¾çW¨rYªÊ‚ |mU^ÇNœ”OžÚ=¬·R !K‘ IFùõéH"í87ç2€Fw£ûënh‹8^=­èß_V?=¼~¯V2¥\=ìW²XÉD”zõðç_£·sêm»Þ$‰ŠŠ×­ÓèÝKoëÎ55.'ÑÇf½QE´;W¶ƒQ*Ó4RzýÛÃß^¿Oo|Ó\¤Åj£KQëݯ7©ÒÑÛ<˜ÇÏQg«ý+\I#ûÜ^ÿˆVJ‰rµ$ ôñúöº­ìßíµ›oá3%Ê—x榈…†ý*¥¢ý…ݘ®¸Ñ%¹È’ULÏÎ^º§¤@é™"Ô?OIÿD’þ¼-0æ&“Bå @)ò’6ÿLì7*I#Óú ùmƒISΩZet9θ¢:™'ú¡›Yº“ñÇ?ζ¦"½¬áÖšö÷îd¶vÎ%U¢È¾…¯2úhêµÊ£+ëvqõ®Áùç::šÚ<¡#©$‹¿<ãù–Éûƒl›zkO=Oš=Ó_|”ô9ÆFf©ˆsö”i€½Ž±kû³©p’Eݶµp[ d–¥ÑÃÁ^yýÉÁùž¯c>îuUuæ«Å €Ä^`W?y¦öl{w 8£g4UhÎOϤ®ü™MmG¾¨  )ŽàKEmM̓GŠ-Ë“ëN•a+ãñ;\V‘éñ¿Ž S‘fðÕsèÝÑo'‡:RN4Áµs[ÔY±`¬´Dc¹Ž ù¿Žv¤Ë…;y“ƒKsû+Z‡n榖Ôb¤ðNÚ1¢ÔMÏ_ž]ç+"ÓÑ,Æï oß/¬§é¦ɲ ™b±“›ÝPb´æN R#Ì#‰, cSkˆf5KSÇ8ÿ¡ç ^uVF¦Þñú±i-Ø-‡µ7¼„¿ÑmâÒx›¸ÚYë—çmxzjá ÑÚmÏ®‹äŠx>y*nb—4•ydàM¼B£CÝ¢O( 4-ÌPœ1/ðµã¸Œ\Ík†§[ÛöÆùopÑŸUš?[ƒ’¾¥ŽG³](DNK­=‚Wé¨<ÆŒårp[¼’Ãb(xÉ ï*1% dè*Ò» YåøŠ/ýñÜ󚩺füêZ>ÙæhûöêIðöpÐõf‹2þN!Žç5íζmZtGkên` ž÷e×À­3G8Ô 4{ÌÍéÄ>aZ¦lü‡)ÄÀ–goe qÃfþ·sp)™má®grpø­E'MedG@À/טÏÂ~pŽÅûèÜ¿‘[RÄló\Èd²Î$‰‡¸‘–ƒ?િ ^r\gpR¶CãXª9ü*Dçz7œëƒFA˜o-ɳñ‹G<»ûIÇ ? ßÄëzˆ`î®çÿ|€F5ƒ""DéóâÕôvç—ž d @ƒ7Uå™Öû¦=š[H†ƒæÜ/1½ö¬»ø QNG¤À.ÈûÚBB“¨ ¸(–Š‹DŠ!íÛ×w¯˜#Ç’&Óàœ3Tf¨d4ãŽiÍÞ׎#–ãwÓöÂãU €U†üf·£›6À¤8\VPºkÎ8ñhJƒÖ‚Éy죇»kmŽ˜ÒpoQ•*äGµE9‘`@8f‡7ÑWÏÈl @[£u Ž¨ššªœ²=ò'Kp}qåÑÅ¡?àèf8˜ìÀ0m3IQ &|ßÙëpD· ÜÁ:5žÐ,p=×7!²Èí=‹>dµó .:yÉç†\KÖËÈz8éFÆø‡yïýcˆ<ŸÖ†\›LnšÌ@|pà!]àªÁ“}QɤÞ͆|…ã£{qõ¬z•E'Åc^ÿ+1mgîžAƒ1öÜr”™(”ì£~ý÷ûÙ”+)±\ÇB?É‘”ßp ‚ºU¢ó”Øsÿ5DE=7?£ã›ÖŸå8÷Ó—Û  R‰ºÒæÝÜhš¬.«êåՉȋÕq•—Bæã¼Z}‚Np “LpQ‹˜µ7Õe©rÐÝñ” èeÀT*-”¾ç*U*Ô„ëÅïØe°; ÙeYéŒ]VŠDNøm+7„i¹›7ae‚ÝJ®U€à¼‰rÌU„ØZ1dÁW€jvpÎ.zð¿…“:‹)©,…Q@µãñ-6uXòj_šC¥a(*4GŽïtâPrׄrR´”wpvKV`ß4/×°Qû…‘ ¾ï,({t5)ˆ[o¸Ì”C~`¨ð]ž¦¤€ËÎ7lÍÈ|5>áÃ߬€#+8t‡¸^´vþÃÄ„*Ë=Pâ§Ñl8qO5&ÔE)ìç¾{s®zžp6®Î–9¹ys«0ȇv»wxëbÕʼ…ϵÐzluþ•y6ˆZ>Rt761³Žºyþ4 ÉBœë :û¯q '†Îç~>s\Ìøe¤fF¯ßË8´Ó­bøùï20Ž(¶Í‰<œnϰõ'¶D×'*¦ZBÅ$0 |rTC=K“ì¤|]ÿ]O%}}ØÏ•‘…Æg¢@«s/ Îõ˜²–,£¾=S…¬œy²i‚€Ü°+©D‰8™tãÃÚûcÚÛžÛ[‘öšSkÍùTß ' …£h#~˜˜/®³¶Úҧ믲»…‘ø`Pº©üÞc!M'9ÏÏïb!͘cᥟ{-TÆ£, d¨%ï…<œNªÃO‹µÒä^úûŠ)4lòݶTøw3,Ïï ‹ «pj¡Va: «¾dØ"ûŸ ‹ç¹æÜý_7UÅÁý|j\\Ì$qzj@ÿ%¯Õßá€VöŃEíÀ^¶<ÿ2˜j¦É×ÀÅοxm¥¿¶rÄȈÀÃ{Á^C7¡ÒPöy± q>P`ˉg.ݸ¬ƒã÷msäcc|&VérvˆÓÛÓ|Íž‚Ö™õÏJ(æ<áVYÞ„î<˜þŠÿ®m›vÁíeŽeîäulèž&ðÜwß7¹râó5{%1¢x > endobj 157 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 159 0 R >> endobj 162 0 obj << /Filter[/FlateDecode] /Length 2374 >> stream xÚÕMoܺñÞ_±@Ñ^F$E‰ ðM^Ò¾>(£-†¼Ëõ ÑJ®¤íßÎP«}N.=ô$q8Î÷W±ˆãÕÃÊþºzóö“ZÉXäru³_I»’ZäÉêæ×/чCñØ»v½ÑZEöÝz“$&úøÜ»º+›Á:ºnÖe£Ý©rüiL¤ÌúëÍßW:ÎDœ¯6I.¬§÷åÚõÄÏ¢žN" ˜o?™‘cE–­bàŸ¥{ú[Qï*`ÂãM8M•ÈsÆûJ»ùJJ‘ÜÝ$1˜òþw ãYúxr½zZeFÄfµÑF˜|u\e¹YXV«Ï —•…ÔfSOæV™¬Þ’q2’‘± gF:2νFB}ñ@ËxH%Bfø6â}9îgÂZÞ¾x^‚âÂíz¡¯M„Q« ˜W’y??•ýv Æ8…û†LÚY§vÏ=¼ýœ9ÐîÈÄ„I™Æ(ä›S3B)zT½wî±#êåži\ëÄ[uS;áu¿Q jš‚(¹Èɳ~ƒsi6œK3:‡ ‚>Gö;÷TÖ»f­Òèéj½ÉL>èe[Ôôsꘆׄ?]>—KWU™F3“0ÿ¾þ×Ýkþšk¡4ã–u×»bG—4ûá¶Rd)Ëü8R‘¨ s4¢ÔуCÓIwí›öXô±ÒÇœÿ’™c{j[W£#ôt–­ 85*êt\C„ß{ýùlÀ S)ºpÑŒ.«ÜŽ*Ï¢'ÁƒIBE%æ7àýIj¢“dŒ÷£«O JZ -‰µ¢åtÏiä‹:$ƒÃuWŒzâº@ʧ¿áääà„¦„Iƒ0„S0­‚Ye”:ÕBç”æ)4¿|ƒÌ…ÊÏÇ(ɬ×pFËèBØZhM&züFª"5ÉX›ð3 :X) ×){lx˦޲¦ëÑymê @mTQbl¾ÑÀãUvA¼d^¶†¾mªÊköîIÛÞï§’ ^°d"VÖ¤ñmUt\UÁ¥!™·%ºt÷n c0'û²³¶:ì¯7&7ûÕí‹S…iBj +±ôa°dbˆ¼¡eJ2ì½À@˜ñsa°õJ1óò’[¦,:!XFÍʾñ¤úºÀXQYGù³4œ]*ІLt}B~›¡˜°dub@ôWŠ\Ž' Íœ™öF¯s½g:eÖg¼ÐZæ½umsš©âÜD÷Å?:®?ù𑼑š*pÎ"$£Q粜Õ×™8?cè…íD¿<öbíIÝ#ë6úx•™†‹efÉ­‰Þ³l…IF{ü„±Ç—QZŸ‰c¡Ý'Û°µ;×>•;ìme¢òHcÈBçðÞïÌÌ0i!½ÆôO8”Ž61Ó¼ž3@MQ9ÔPm Ïºæù»{ ¢Ryý>/¾ÈçxpêVCz'æ•Ñ"±Óöú|pÓ?܆´»L¹&*ýɹ ï9\0˜a]ÐЕÅhjžlx¦7ã…@ 9î±ø†ÄÜrrÒ)6Û¯N^A0¨ýTÒªP ‹ª|`/ø…ÔùfËÝ´kß\Óð/{eN“) SÿÐ:j‘4X¹ Oè‹peæÀؼ]˜Íú™ ‚ý\·¬Š0·‡f¥y!À‡Ž¯'ééú±Ê«,0عÿœ\½e /qí[¨BüTè´AÖÌ}ÚŠ"á=•ØtâóŒ Ö<ÖAÜÁÞ¿>6éШ\Û-IJ¯¯9çJXüRSÙT0å`T“bÈî ÕiÌ“ƒêh&GÕQfÐ0iØYj:&“º<®¡£yS¹}ÿfq±N„N‡‹ÏO§ãìô¦-€säáù i€Ý4ø:øèÜSf“ÀÆFfã45WäøÔAYw!ŽÄ÷Ö4½¹ÌI(‰ ”D¢’¿ £15°Tè„&ôYÆF­ë ‘A—àœ-…š D³¸ÂV54Ñ­ëOmíø«è&,”‡ÓcåΊRðÏ5íl‹j{‚ч›²?,‡Ð1oƒúŸPÉÿA~ÎaÀœ¤g¿<ËγÔÛ·§ú›X*´ý¹7±gÎÇ/؆ܼ }霬I±_›§à)Ù)­œéóò%+ƽ×]T…hx¹t\‡L1øÕÐYlyDÀŒÅÉÿ¼ñ}¤;4§jwaŠóÎÃE/.°˜$Øh0mMCH‰,„P9÷tpb†)~¤I>Ÿ¢}8‡§%¥P‚ÂÿNêjZ>pQÖ4iÓ|Äc¢R ?<" =À ú‡ÛÑ="¨dÄ‹özñ¤üÓG˜¯gCÌoû³å—TÃ=[©ÿ#]qâØòû›NXpzÒìFó*åLáÝ"x6ÿ…í_9ãèÈ=ã¢áçÔb™YÔä%wWzöŠöez ÄS½ ï(Óe§³ý»åc³ !NÏ›lrýò‰‰bò¡:4|€K…Ê=úÎíÙDwwe]öww·hžý‡‡\ ç·ë)™ÙkÖ^ ¡þ2žXÚ<¡sDñͽt·œ" ÷1+ÜtJt`ÿ)ž×¨mW$¹/Rs^øuï ;u·Lñ©?•¾òÒqïlø^$Éd’ã‰:$Wà0€Ú¦éïxÚA¢"ã!p·_¼¥ò½TÿL$³§9±©pL@—Ém–ôÐZ,÷+þ€ ç̶‡¦Üº:ÁÂû:êãÍŸþ Õß‚ endstream endobj 163 0 obj << /F2 19 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R /F1 10 0 R /F8 73 0 R >> endobj 161 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 163 0 R >> endobj 166 0 obj << /Filter[/FlateDecode] /Length 2526 >> stream xÚÍY[Ûº~ï¯úr´ÀšI]ôá4ÍéI‹ÁÉ}È[®MÛjdÉ•ä8{~}çBZ´åÍ¥EŸL g†äð›%"I¢MD?þ|÷âÉDT2º[G²Œ¤UÝýåCüjkö£íoZ«¸|y³HÓ,~ýe´íPw-’uü¶»Y¨2^;À(“Y«üæãÝß"™ŠJG‹´%©{k>Õí´$Y<ØÆ.GÐ2àw›vÅæ‘ôuýèxÓxÜZžÛÙöFñÁ‰ô޼êZˤϵaÒ'ä³OLÝ‚òÆön¥÷Ö‰½ç ðG)´Ð´ën;…-$›áƒÓ6|¼å3ïÍÆòH–ŽDû×6‹¼Ç<Ûm´»}cFËÓ§áÔ´1œ¢óú¾C®ÏõÊ®˜qÝõÌ3YÉ“_ÒÖÓ$e-”šMþáUc†á#ξø¥Š¤U†×¼p¬ _ hÕ2>üÝ>ýÊ[blÂ…*áÉ&™™±0*ÁšNypHYð}ª²‚ÓŒ[Ÿa¶¢LR‘J§äá°Ÿ­! håùÛ™x.”Ÿ|XuÇv&'ÊÕwŠÏ…¡½0^ýÌ\‰(üüƒy4ÏTèTè̱¡ªâ»­¼iØhˆ#¢”d5§†ëMóð2–‡¾G`C%0*À2%Ùö8ð2ÅLÆ1v“m]Êàx©‰Ga©Ùù”È*ÇÒSC\ø,ÀZqCn^éÒ|e"¼v@üá¡?Ìõ§™(ex ê$]×¹¥æ°R`XqnºuÎz”ŽG¢pÜvN”Î'8úý̴Ǻ]õ¸e­¯wf90ÑÇ ·J½Ùz[ƒã6ŒúîÏ65s¿¼ævEÊDÅ7ÝÐMfp§oŸœ÷ÞxÃá€XãÕÃe³øOüs.&œ“Ml½zh¿*ã}ë\ê7;úo †b~ƒ›¯Ê<ãII)*õ߇=HsýĽøª)ª˜Õ ¯?U“šSoÞ?6ÝØ]ó ]œÜ€Ce¯Ð§‰Çm½DÔly‰0`«ÒÀûzNøK噯§š}}ˆ‘y.Ò³sß«¬è‚o7.IÛå3#†ÑôãÀcç4oYÐð÷·FÏâꢌ7½5üÁúZuŽbÿ}0~±.TŠ<øÓžŒC1h>Í–šbk®ã7㥠‚Ò¿$xèª 5yðåÍU•(RÇó´”–®D6(.ŸX~ÿšìÕ¬B8æùäªð7Vöhª¾C8z}5bZDǪ:QBRºÄß2!ó¡‰ÞCITòC] ©Iâ. O1¹Õ¶±ã¥­Þ=’šŽñ„3ÇÿˆÀybŒª„V ¢h«{ލ\uÌnÀMÅõúJ½9_N¡á[nžy7¯]Œ?pFƒQÝ£5/U’’ï*¤«¯8ÿ™ÈϪ¼»›Ra¹©ç[sV8äSÍ80:=’ŸØzþ–½µnæ0Øk,S¦D5Þ;nÑï dÍ•çŽ,׬øâ ²«aP+hü yh’™!òoÛÃúKÝú”Ÿæ&§>¥üÁ¹þrÙ¬‰ KŒí3P@öÎî²/*ÿ|“aÅ^›ÇæJÚ^HÈ3YN‰»¼’¹ßÓ9ïcþ½åd7þJÏæ;=K”§bÀkØÅ%í%'q¸tYÔ‚½Ž”ÌXNŒ*Ÿö¦µsUàí™^aXdÔB.Å6pfÂE¥6x*ÜtžzwåF”JÏ—ó¡ž"A€¢BvHÈcÓò„iÀá[3ÖŸ ¬L 2)JtPž0­Ù¸¦Z8Ȥö8`Ëóø7@êy–2°B¥á{³ß“•«ØŸ¤U.¥ü]·ük˜yÝ›åa݉qà_Øc½?`;¹b¾±ãßi‚¹ÇBE§%ÉS3OÈá`œE"ÏJfjC¥Š¡k]RDƒqÝÎ;¤tj¿þ8»«lê‚Ë ¯;Ÿ8®ŠëSm;È[Kíò>ì©k›'OâZ\úšGj—Qp´¤bæVcïø;¸âÓ)‰Á8˜Ö)«òî—ld.“ùyEÑw(õ X°—a§ˆÿá*26gðÊ YÎåÉèÈS‡"%mçEà™t¬›†y]¢dro‡úw¯OëvÃÆô;Œ<‹Œ0ð'¡šTÓoêìH)§9U¡„6ÜN`v‚µ“ó*5,“é,þµ;=˜XvÓo<¯nΗ>?†}SOK(>*î¿Á±#ì©*¥;›Á5@{}„Íáò˜pSè¾F.9JhhÌ`@Xè¨eéqN:ô‰½˜N7XÌ—XÚ 3:›8-ƒ“1nÑé¸ð±¤yÅ,T\_»·ÖšýBUVúÇzìMOŸ:ÞØ‚|Aµà^Þáya@Q•‚Öwd‘ ©è³Ç›IöEäó7öÄŸûú ¶8¤*£rÞŠ„ScŠf•æð߆?ÁËá”=éÑóî?çD‰ÔûÅìö^ì›ýq'öO³ÀRS:ÿ‰UºÇ5Mi.èÞ©º;õûþ¦°´po‡ºñ/ <†z¬8)»@KçSyÎõóv¬ï°_E´Í“è1l!!ÓºGË·až£“ÌcxVŠÂgÔw×ô‚q+_?³¹éhµšú†LCßC£-²$ÃZhZ”úô횆"¥$–Ü3°åÀdGW€$Á†Á>éÜÇó‡©bš^6µßš©JDûRçþf^°”ÅE\ÿ™ZyUy'˜ô»™³äŽœ3Ná“r¸0˜jjO‰ÇP9ß_vT„+ÝÞ3Ÿ¯X·ó'ç€zZLÆoÖHJý\z¹3ÍKàÌùÞeQ~8Úšk~ÝZŠrbníщ_Ñ?kÏ-4F®ÑûªWí ±êÌ‘þ縯—][¯Ÿ.°ÂGÚ <v¶‹*‰µ³ÿv௔ÈR&æY~ùø« üÜè^Ao^`Gò î²uÝËéõóÊø™“ó¬éfþL ¡zi/ ]äô6‘'¤p•’2¢ûv†bU"Qb˜üÁ(󬡡‰Tåù³'ÕS“»^‹ fcêÖ¹=?Šu9LÙ»rܹ¦2vdË_üš…|Üt|â¯Ãþv8…g çâLbÅ£­™Æ Ã:š¦^®†cÅ~h‡ö§‘Y 8iøì0½õ¥­Ja7õæBÝÙ9€eéä´4,Ø4³˜à[vÝéÖ£m}ÇÏ#Wrò_ihªœ½NÚÕ9‹ÏúÐÏö;žz¤E÷÷ÝÖ§yè(º¤ó•pHüÃPŒg endstream endobj 167 0 obj << /F2 19 0 R /F9 76 0 R /F5 35 0 R /F11 98 0 R /F1 10 0 R /F8 73 0 R /F10 91 0 R >> endobj 165 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 167 0 R >> endobj 170 0 obj << /Filter[/FlateDecode] /Length 1844 >> stream xÚÕYKoÜ6¾÷W訲 _ɽ´MŠ0P ‹æ…²–½B´ZC’ɯ/‡CJÔãĽØä’ß73œÑ&”Pš\'îß/É»—¯yÂ(1,Ù]%L'L#“ÝÏïÒŸÅM_¶›­<Õßo¶Rf髇¾lºêÔ€X¤§Í–ëôò¶.;ÛÊX–¥\m>ì~M„T„©d+ ÑNß»‹²?À|•^Âj™Z-væË×Ùx‚L¥êüV4%NˆŽÈ94pÂ5 cÄd0º•ÔÞ,÷ãMùл³¼ÚÙ g"¹O”$\'[‘‘Ì$ÇDi"YèÖÉïD"”“éÜ©¹¯šËÓÆ‚pû1—müvïSÄêýfqêm&In÷µè2D÷¢ø´±H”¸¤?”¨;²wµSîqJÑuö¾*úò§ßWý§÷‡ªCÙ ÀæZ^«<Ý„ùA)qÀpmàæ[ÎHþHºiË»%Iy²U”‰)º#G§•M(bO£HÙÞzLª«•´ë*›ûMBˆCºH¯Á6ó´l–Æé²â¨·CÊtQ4Åu¨Ô.ª‡ªYƒÏžUFw¹j2 l»C¹°y{ %ž¢Ì┤p2p& ©© llxjmßB/ÃyÇ¢©nnëc6ŒaÄd>ÆÚFœ~ú%p‰œdq Dd6xB¥©3ç–¾ïŸ8mOÇPhð|uÕõ+¯m‰‰µsqóÑiャq!¾Tü8ø²ˆŠÅ½ nœ2ÂÄ·Ä Rá†ýnV¨œ&Ì LE/‹Ð³o¦½|ùM†ç×Þ®`̇C+Ú‡œµj0ªéP³ÁàFëÏ86y`t\ÍA4¡F„ÕŸhüÊîï½[ßš-)mq.F¶|Ê×>¨ÄU•påÒ ŽÙÔƒŠ¹>+¼n (þïlfµ~^š€}<%[u XêÞ«²¯ðÖÐ|B3o»àJW§Ç›S{,ê¹'u>Ä7k_fž c*#TGŒaƘ ò’Χ%¾ä0ÑicB“\ÌÕ1yU¤ïëŒIBÙÒiÿ)c –§1\ÑÍ;øüüÿÀeF Ñ|äÒ÷§\fZj0¢´EÓUQ¸Œ•,Lf Í”2ió#>ÑêågUë¹Ù‡ÛŒÉÕŒdeY+ôIAÔ¹¤_|-é?›åvâ“?…žáXZOSÇØŸql…Z '‹ßj¥ÌžV)ãӸ̋ æ\^l†ZñlõŠ EáâZÕwtø{+ 3ô0ߌ#Tÿ\M¬òèÓð#ïll*‡ ÏÝRà÷ Y ög–b…~TÁëæ@ÄJd¯ÌùÓ,æñBê܇£à¡X3˜Å^`{ËÍød¯¡ØxBÚĬ™)ùÜùÌ™„yŸ¾?å„BL~ôZx¾yÊïT’Ï>>Fþ«Ã ††ß©Ì'†QÖÐq’/N E6üj²±çÏÒ7W“å,­‹n¶źù…‹Ôyø™÷O3ÀèCgå÷ãÎÿj÷Ý_-RÙ endstream endobj 171 0 obj << /F2 19 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R >> endobj 169 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 171 0 R >> endobj 174 0 obj << /Filter[/FlateDecode] /Length 2350 >> stream xÚÕYKã6¾ï¯Ð-20æˆ%Q¹e^‹Y 1ƒc³@&h›nk[–’Üîþ÷©b•ÞîéÎ-{±$>ŠÅª¯žöÞ­çÿôÞmÞ~ =ˆLz›ƒ'µ'#‘)oóá7ÿýÑÜ·¶^­£(ôõÏ«µR±ÿñ±µe“W%GþMµZ‡Úߟ ÛÀ[,ãØõê÷Í¿¼("J½µÊ„vô~»±í×§þžvXùöSnàæqä]<-… ½u‹8óNž†gÜ}Þ7Œ§K7¦¢SÛ:J'Q(">è»Oû¾Zð»V‘#o ò•$ßs·YXp{´´É«sCÃÈ:ç,ò~a‘7ílÈìÚüa&QÜ-PqÊÿ|`ò=Á€dÂðqL¸zC'dé¡ôb?vì|˜Ž9³ê³Â A0iæ­CPXö÷ <2=…>§@Ñ¡Iµå Dw>áïn ýޝ1nb°lÍËa>Zót‰*z`j['—kô¢l¸è5Ê Ãב ÿmM]›òÖ.¥Š, å§À…l¥ˆyU ‹´T´ÎX÷04A¥N;˜À’¼¡§Y@ÆJ$ò%ŒØ‰t"Ø'2ð7ÇîŒî¹¯è,0ì r"-òDÈ·;ü:‚|òòvb¤jç{ç«&oÁ)6 ×­zæZY(tü®¥‚^ʦÜLm®5•Špå?v纶åRq©Jõ’rÒ`°ûïn‡2ÔVzCäï©%â`. ZáþØ+6ˆH½ÆyƒlÒ1R¾TÜê ¡ÀÄ6ìoZÓº×П¦(hnWäÖ©¼mh×íâ‰ßÏy±wXs{hÇ>ß!²Lý4%y9æ;Ü{¤½dSŠ0úD[M}Åm›¦!•îr`“õzÉÛãÿ•7–à4¡€¾g(Hc„ŽTm›¶ª¯L&¯@œbZ4’ã×38€0KI·a¦§ºÅ™­q䎾0$â2‡ •ñн¹`lk;Ýr9Z~›‰D¥ ’H…ä×5ðL"•Èäº Žf(*­…L;Ûü>pf žŸ¸d?(°ã±yÉhOýêÎa~ÿ†¼F˜R–7¤5ù±¢{ÇØäÛÂ%!@ù³›ãЀ/,ƒݲ[o›ò§–Fì£s²øZÕôDî¾K:ÃðÞü{' ¶Ùoi®ÙÕÖòºC]f'_òŽ„ ]Þü ·épCI ñ6öú|9Êw!Ot†#Én¾1.OÎóò Þ£$’§\Þ5 84=–2ǵ(ñOH_Á¤Í¹±<ÌWH}MZS´êC/ÎW4²%±3ÚvN!ñ+¦j ÆGmú[¥]Äâð˜B²ˆF¿GeCð¹¥GÓ,%\V„¬‡U¬ÀQåfK 9eg™üNC#p»Ã¹ ÷¶â'™“›¬1i p¤-=9Äž‰î Áqî bclóÚœÀÃ|ñΚ¾z,Áº˜Å• åœ_÷Gb¾@¹59Î *ZÜ9BÂü%Ußû¾÷)´ýyTcÑNŒK?ÀR<ÂVwŒ%¥ Ò8,á;{6gZ¸Ì’Ë(Ñ´¼/†k) :¥í†L;™LG¾ò'Þ?Ø#}ç|6Ç+ÝÅ«‘àÉ(v{ÉâBH8΃A®çP¦—(ƒ$Ô¡L/P¦G(Ãl¶¢1B 0Ê„2í°’Ê€îÉ%t OÊ4£LgìIÛªßI([À‰¤}v8„:rCÒ’%Òú3¤E#¤AÄ:AØ!8„ Ë <ò)!CZ§‡uJ‰¬óT§joÂ. r)A]¶:n¤±k ±Yp­:Á2™I$£r™à†r¨‰æ<Œ“ŒQVw:ø‚õµƒÙC¾'(¸L+Ôj0rXĆ€¯¤+A¦¹/ ý¶¨.ÆN7¼«HÅ&/cı[[ÚÚ2³TÈdŠàúQ²ÅÆK‚•ô¡ªOs@ôn©ä¦ =:h$ ÂÕ=D,¶,rË f„.VL‰ÉÄà~6.LÀTiNü檗„º —˜! ybsqE`ímÍMF(…ÛIg¢Sƒ]ù¼IðñdvÍÎD]ZËÑ&"Kv]z4ù)/LMûçrGWqÞC©žxÏ¿BCàòW§ëåÅxLBùÀè›FŽ3å8†Gqúœè:‹(Èf]£$‹…X½Â"ÒLD’/÷éT4ìš“UTì³c]…‹ë%û?Hy°”Hõeïûd|ÕÎ6?÷ÛS‘’Ó¸/.'Ñ!Ã윽­«s¹û×Ütk~Ö|Zõë–.þYâ.ÃÖþÝK¼ãL|t$ôñ, Zç)“Ào,úЄk:|ÙUEU74ëk"G™ ‚—ÝwÃÜCÀ¥Ýþ9óà-ÐÃØÿ`æ\´ ­35ÓÛÆ•wwKM eÓè^ÀvZÎ*¶“ s…ß]ù5J£Hò’Ê8ÞÌä©zÿÍ€Z&@jK³¾æÈbF a.u²¢e9{Ìo-ßQBùš„ã,üs õx¹ãÃþ³BÇM‰ñS¦ìÑ%Éô¯øŒ0"Å jÌ-ºŒYÉ*C( ´ÅI*ì\È0s•p}&#ç[Ü(wàï7ªš+åh,¢I3ùý`è(—¸ó ‡O:T4ä. Ï~ÍØKÅãd Qdþ{ãv&ìÞšVu°ˆÒ:’Ñ!L‹Í—¯‹›h%‚®{^Õ RÕDœNÃOOðÝ—ÍæËÍ‚&Ø(š4·d¹6Á‹®©¡_hjè>202ž ™z•/56^™D*Ü6`†f˜Áшú]f¿ŸÓŠ£-lf,iÁug´N¶iÌí•® P|eçúYJboâÝjŠ@!þ Ýlz(6¹zÖ¸-¬û?h0Ëj’eÈ`”³ã×à×áÃ,P|%¹›gN/¤:eW@Œåv׺ӹô–A——I91?˜˜:>(¡¡e Dä‚›×2ƒŒ{jÌ Áy8•ÜåΰŸü¢¥ëv޹áêóUú hÄ\4Îw¹lý¸ùÇŸ\‡B endstream endobj 175 0 obj << /F2 19 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R /F1 10 0 R /F8 73 0 R >> endobj 173 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 175 0 R >> endobj 178 0 obj << /Filter[/FlateDecode] /Length 2381 >> stream xÚÍYßÛ6~¿¿Â¸—ÓkVü!R*Ї\.鵸‡Ä@’ PlîZ­-’'ýëo†CJ”¥u“ÞÃÝË.I‘Ã!ç›ofèUÊÒtõ¸rÿ¾_ý}óÍK±â)+øjó°âùŠKV¨Õæo“çûòÔÛön-¥HòoïÖJeÉ‹O½­»ª©qX&¯š»µÈ“Ýù`;he<ËQܽßü¸&gJ¯Öª`¹“÷ö•í÷~>­)0ó›—Ù¨A–3“¯R·àU³³?Wõ®¹¼Ù¶ÖúÉ‘ºgšûÉïék±âœ~]«N©ý÷#ì,y²³”xÍ‹SóÅî"“«ËŠ‹Œårµ–ËŠÕúJòýÃê Ü\Ûsí·öèÄ}¼ƒk²×2Šd€LS°Ô\ËÌAOË<Ú®+-‰§Ñõ€uüÞ%þ3?¯¹4LføŸqu[”‚{AÖÝìn×Z°B’¤ÌMz ´óÍA`š…ÛŽw¤*Á2åç<´Í‘ÀÕï«[ÊY ñá­»]¡)+˜ÎWkH·Ïe×=aö<˜ýÕ“7 ¸q“†å†'PÉ$MWõû{Bî¯ç®¯Þ‰ÌlËÞ;ƒJ¾£oKgxþâ§Í‹×÷tܺÚÚÉŠôÞï:ËcãõöSOS¾£?5õÂY´dé-Sæ ¿OLyjmzöíPÒ¿®ªÖŸÄ_žÛºohðƒ;Ÿ±«ºÓ¡Ä3~Æ?vG'¬êàà~qÓ†»iíÕ-Q×ß•N.›[#å5{f9ÂRä/º D•0ðe©±oÚêwXëûò€cúJœ›ö€êÒ b¸ ìTõD®^2ù=Ž˜¤¬w4·Û7çƒo‡+tF'…#ã€!„ÒÉ;¡Ó¦ì±ãwTaGh´eí´‰o=îéšÁß3 jž%›0sÔ×;@9‰ýßµÎ|—š„… ýÉ¡åï4븯YRn·M»¤øÉÍÜd2Ϙ ^Óúáè¸?Zà Þh{_mQã=uýb;‚&؃ÆC`?ðñ¢Ty8CȺ?Üwð!dxƒ^Ø¿^¼ÜÌ”ƒ£ $q?¥™Î悈æ.*S¸— qgÚIfÌ\æë¾ÿçæfp\p©‚ñðÕñÑõr!™ŠýHš4ÙžÛÖŸi¨n  OÎÝ¡…ò4ùpöc´ŽKò9Zã˜ÄÄæòë©>—>Ø øv ÍUPЭÐÍžÚCyêpTÀ6 ðgjw{œrß½¸ R—ŽZÕñäTjÚ~PÇý&~VÙ6çznfÀÍ^Vuy€ËÈe€ÀÄ 21ÆDç{ ×<¡+ÜJÎb2Vñq«h ™ 9sŒqRRÙ‡«ËÞ¶v'­Fï#K¬Æ4&„ô²¡Â<{<õÄýÎÁüž°9š¶ú“ùßSI^þ…i_gû«”  ¾Øt•5`¹E@(™Xöˆ¹Ž.‰›CÕÖ™$d(sþh›£íÛÏÓ½ËëeÔß[¼< Ÿ"µ«~·¤.Ý£B¦õD(!;—ù„}9Ktõ©ªXÌ-+†J& p«7Ë9Ëcží\,5î8˜|†[Ž3Yirk}^‰­ï®î‡ÌÖÝË ã’Î)ÿ· -Jã¡—mà‰šÒЩ 9ª@é`:i¥å ZÂÕºØêÚ%}1[Tð7ìz³i¬C hssCؾ§´ƒ‚ ›NÇÅFsZ›®§Þx5Ð JD ]"­É˜ýy¨€|½Š5—œÔ«!ü™?&zþxÂÞè¸úk>Fö‰( ® —@Wøè‘2iB?8Ðxè·¶?·5U€€×I.|Ö‡öå­D¤¡*].—+„ÊîÊW'ÅÑ´É9âñ0„ÝÍ]‘%‹ú\Þ =›žlqlÊñé ÐÚºŠÃ+Îø¿UuçêN0Uº”§ìªìJ̵PñÕqI¿‡bôq?·=>³ð™Ë²1 ý%¸¤Á9¸œÒ©{©Î® x]PLj(Ó@v¡KS¿‹©Ç0n&Ôƒ¿H~+ª/ažEäüëêÁY ÈÄ[ÇÝŠ4Ba12 4<÷àÅÚŒè†\Nãê;$ 5Ï#xª Ëãó/ ©ÉA°gøïÌãT †£÷ÁÇ `&¦ â»ÉkE ƒ'§öõNȤ´fºøß#çä]sët¡>ŽòPà^Ÿþ=e-t¸¡‹èÙ:ôS M° ߇¬˜Ñ3šs*Ën"$¶i$ò~žä{jíǪ9w³ßdDòQd˜V‡Îª«»Þ–ÞÄ 3>–F&ÞìC‚taôO‡®!Ñc­j;ºõJ:_aY5ˆÒ#êBŸPí—†“.™<œë->J—‡ªw¨üÖSæ_þÝÎhÌ endstream endobj 179 0 obj << /F2 19 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R /F1 10 0 R /F8 73 0 R >> endobj 177 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 179 0 R >> endobj 182 0 obj << /Filter[/FlateDecode] /Length 2290 >> stream xÚÍY[Û6~ß_ᇠ"1+Ф(õm›&iŠ¢h6@l5¶<ÖŽ- $9Nö×÷\H‰²=žì¢ûd’"Ïý;‡žÅ"Žgw3úy3ûnùÍëd&c‘ËÙr3“ÙL*‘ëÙòû÷ÑËmñЗí|¡TeßÎZ›èÕ羬»ª©qYE7Í|‘dÑú°+;iL¤âùÇå3•aÔl¡s‘½÷/wUYÏõLó¦ú\Õqó7¯ó™”"7ÈÄ‚NêYL§ðØ»._ÏsáDF«CW®—U¿+ùp A’Ám B²ßWÝî˜_˜ã~[ò`uh[ÇÎÎ}Û 4–É㊎VËÏ;fº§kécÑ\—›â°ëùë¾ìºâ®¬ ¥…Ég‹$iÂÚøuÕ–¥W㣚PBå§š€(áÓ&å/eWýçi5ü¾¥«¤ö Šó©ªïpžFE¢Ê,j‘,¿À[âhígIqp¥ÄÓœi6'ßü©NÎIpɱª× ®'—³Ð¤°˜¹7Äý]Ùì˾ý2ÏT$æ‹ a‰u¦Áfí¾èq¬ˆ\ëýGgžTÿ®¾§mv;dOÞ:Aq\Ô¼ïOA/Í¡]9"Ǫßò¨.öNïfÔû„í‡Ýq/öÍ'g$‚3»§ö’NIghÖN–ƒ÷]wv‡ÎܾŸ‘þÍHÿæ } aÝ9Pa*Uô¶ç[ºmsØ­Ië ÑšO<ç–B¥MTàOýü¥ßbÀ¥Aÿ0îú–H' ©mÙž+H¦™0ŽgØ—>Ì»3†Áà}Çj·ã n)Û8fÚ\mE„™3 pÇÖ‡¥UopN—½`A“D mBk¡ÖO¹…p´Üz?¼Ä°Õ"–Ãì$ž„þM '>ÇÊût:朌ž¢ Ÿ±h´H³Ë{)LŠù~*ÇÇ3A²XÈtðú*âœR’Œ3‘ÅÄbfÂM™@gáÙ¸Sk‘ûÔµ?þkµkV÷gäL&¬ßB‹Õœî$AËyz‹sÌ£‹$G  ±~çc£ãMQCÌ·O` °p1L.=¾Ü9¤ÁÙý“éu¹Åäb`ïïÃaæB÷‘¼?M¼¾T{ú¤"º@ŸÇÁÞA¬;zäìÀmd¬c düÄ<$ÑáO=®4õªŠ—t×c(‘ÆBÉ0ö1г”ëÉö¶îú¢^¹’ç·y¾ÚVÅ-WE ×AiXŽ‘IQ…Îî/De!½+í±À9¯–P°B1yœ|XHÛI–•Îö3KÚÏw³_¡¤Y%2Å‹àdÖÇøÏM"Niž¨„ôg§T%è)Ö²D´éªÓÖ©:Á½„ž$ߥ¯Hþ Õ‹^oC5É9!(%y¹ª'ût˜ p}L/Ø “ªTŠ˜ +m*TöÿgY(…T£eÝ|jY\tèóU–ˆzËN©zËdÿ}èúêCbìªøom{z”O¬«ë*oÝzˆKx¯ba}>’È/oßü°¦Råº-ØIžy+|Ã…N÷(º&ÕQ¥„å| ÑmA¥Á= T¯]cC:’”J‡>ûL»ò)åªD$>¦Ixe¢rÿÐûöá Ú<£^”Ÿ«®wþ‚w× ÿPa©tÒË“=СÀ€îU©’ëàyvm[}"»^rE<Éx¬¶°ÍÆÉgòC“ùÄð_¤"¯è‰´6q;Ÿ39’~Ïd@ÁsM¾éÇAÇåoYÜãnj25–Í&·ÑÛÍ…*-Ãòé KeIh)¸°X­@¥ãݪ9ô®ÀT¹°S?b>8äS6sÅÒX/DÚ1uQ ‹„®Cq }ÿç:OâTäžÑ›Kºà÷ý9ª‰‹Þ ©_>©“d<«_)+ x5æzú)ãN‡qGàkãÁ;žQÕoc5>Ðj¨FüÊ™?½ã_ÛS‹eú'xq<±ø1Á\ì üÐF¢/?_î†Ò‰Ý`ç‘·!+’éëÐPùÿäîX>y=eô@æ€íǦ²ô4ˆB™C›ÌemlPÐèŒQ‚6ð[ˆÉÂ5ØíK`ý¢òˆ $}¤sº¢4M^ 4¼ë±¶)£ú'íSå]ÑœNÇz,xZôÏÅC•({w“ÚO¸Ú‘‚ü:÷oðõåŠ$u¯I8`b Æ™#F«>¸hoÿ<çß=òÑpxÒà 4›ÃŽÇÕ†›züØñ=Åôâtò¸†¸ Á/»²c·;«Š|„ë¸Ú=O>©‰ú꺄՘$Š|4èlþÚÇ¡ï¨Ä¼ò(dNÿzpÖ.¦«“2ïJ³G¯AW›½Î /)¯¶u*lëÔµ¶ÎIº°6ºÔÓIýu=] ýE"ÇžÎͧ=.BI9(8 •k‘Ø€VN½Ú ­<ÊLh=Ñ¢ƒ¯Xû?·è£ïþ…-zì[ôWË¿ý È) endstream endobj 183 0 obj << /F2 19 0 R /F9 76 0 R /F5 35 0 R /F1 10 0 R /F8 73 0 R >> endobj 181 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 183 0 R >> endobj 188 0 obj << /Encoding 32 0 R /Type/Font /Subtype/Type1 /Name/F13 /FontDescriptor 187 0 R /BaseFont/HAXCHY+CMSLTT10 /FirstChar 33 /LastChar 196 /Widths[525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 525 525 525 525 525 525 525 525 525 525 0 0 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525] >> endobj 189 0 obj << /Filter[/FlateDecode] /Length 2742 >> stream xÚÍYI“ÛÆ¾çWð*ƒUC½`óMžHöÄš”+šJ’*Éæ`aÑhòëó–n AP²Ë¹äÄF/¯_¿å{ WQE«Çýü¸úááÕ[¹Q˜‹ÕÃa%²•Pa®WùÜ‹soÚõF)d߯7ZÇÁ›/½©»²©qZ÷Íz#³`?T¦ƒQ,â8Pbýéá¯+©e˜'«ÎÃŒè}¸«»¾¨w†þc© hËb[Ù™æ0'jþYÖûf-Óàù‡ò£Œ“õ&MƒOHýÕÛ|%D˜ÇÈõF ŠtÑ5§5°ûL,¼y€wÆjõ¼J²0W‡q¾:­Rf©û¬VïA«T…™¢¹,!B[¾sN(סL'Byªü‚Pž‡*ö ý:t=ÒJwE’#þ=©o¤ S ¢,ú‡£ÉåÑ™œzÞ¥ƒSQV,¸Ìà=noHR'‚Xð/CEówÀ™†»ˆ3­bâ¬;“:Q©iÐT¼ÜÐÎS3Ô¨áÞìy‚ôýy Ðôpæooßßð¨ìí¡òñ¸–I@Ÿ*Ø}ÃkC]ì?—[ÞÔ7¼Pìv {»%th›ÓÄ1½ L ìŸtÓ›žG#„QOªAÂm¸Þ$iÌ/‘}1 €[t¦ýŒÇèp®;6CµçµCkÌPKZæÓQÃ’([Ã'ìÍÉÅͼù÷Zаæd€oßþ PÂY0t“§Y™³ÕHgHÊòœÎë[³óõÑ…ͳ5‡¦µ§ 6°þØšbzÄIë=l•å—²­¦Î-.áÀ÷ĵ†Ñ—Fcú0¹ÿ½{92uÔ® ÐÖ@,/%pÄA‰#”\š·Ì¦f.H¬ÆiÐ ,Ü]UtYzQšLŽqߨ«àšÃaéCào‰ÝŠ>šhPrÄ{2«ìPÇ¢~4Ì@,z^q6LÛ›'"NÃØ’ÿóýë»w ” c'#÷¾}SÛ‹IIq e÷Öie¾%uæ\´EÛudŽ6‘ÄÁk+˜tº ÀQK{Ù¹Zg`ѧ%G"Lœôv¦í‹²®^˜~ÙÕßõþU›®8¶i¯È4áG[ìDæNª°ÌR­ézžjÖÕþüçñÞTÌ|¿GÏS©È"$³1gJûk%™ –dê< Ó”•Z ’¾mN'¼g¨Ëáá¸õæžYÀ¡©Ùµ}%â2É®£¬LòÑ^pÌ÷ÚùzÏ'-‰Üò7ß—ÑÃpކ#l3ôüQñmd¯O´®ny­3§â|ŒèBŸóxŠo2×îÚ\O×ÂØ>¥ª:þFó—y *|ª¢åÉQïŸ×t¼WÏGjù@ïî1»)/³“cë²;Ë Á`³äd§%˜ç»Lr`¾÷à /1´ÃàäRþœì ´E\:>,¦Ã€•LÜßü‚ §4÷ýò*©Ý…ø¶²~\òmQ$’AÃF#4ÏH2ÁI·­°KåÉðÄ©@–騘 ¸½Ù¢e¿š]OáBqŒÄm=Cj„éâà…´Ì(qq‡7×lÞE‚c_0HƒžÄÞò2ÒLh£œÓ,‚xI †´ w8æ}Ù´OC»¤èù_Åù´ ¥ãP9dó3âTSò aÑac$ Uö?Ǹ+±íõ/÷ßl±Øt>†e ~°/»sUqÙE¶>ð„mу“½ð$ðýÐñ>«‰qày®=ECÈAã”7ð"ó ÑÈÖ£©M[î–ú.)w@`ö`z•hï0“X¸…%æC…Dl{²;2Êüð×Ý ú†-è´vƒ54¦Ç?€2 xRÍÉèeüS™œÊ’ÑŒüÄßîð#ìK×›yŽTÁíж6+—…zéY±aEB `W–Vºá|fìo)—ÆU&Æë婘ŽÚ€aveQñ b`eWÿ½H.T”„ZØ×¼LÚ½ºö¦X`¡Ä»¾³Lø÷CöðÅUéEª=>žêÇ(&ÙLùà¢~”~ý(¿U?ÞN¡* ž®•|Ûï)!d׫!éû²ˆL0õñôî R’ˆt˜ä—¤p”Ä3Zgrø¦+¯—‘ÎD×ËHkö %âE¤šüWŸãBY_–›“Ÿãüäç7¬‡YÙ…’õ) OVÙÿŸ>sH@=uÒç…6aΖòßЦ¥3*Ó'4êr¢ô›MO›ñi xúTþ®¶€FÑ,Ú¿ûñ§e_ß)ü¾À<èªL¡N.ÃnúͰ[Öç¡_ÐRYg—Q—Rïxv½÷l@Õ‰òÃîÝD^i]^}Š7è”PŸ²RúÎ9 è,¨LÏ[iäóêNNÕ,Ú< ö~¤aæ áè?Úw¶arêD 墶å 2J…DB…„]…ŠãÜsxÀï+ @ì€xoÿMÛ¼ìjΟ’a_—WI/¶PY$G)F K¦;6Ï E&¦¼M:TÎ&*sè—À š»C‹ãùtº…‚çÊñ<ÌÜù+†Ÿu~t°ƒÔúu1·Iñ×z±vm©øÂqbLZ0ŠÎú! ³>¹Lîn7´Œ]qt½ÃÁ¸z4§+šZ ˆ{º(ðØ×‚>ŒzÝ7×ÔáI»7_®ˆÓûSáª8ç4| “'#=(Žœ£¹s«*×›jW– ¼PJßxG·›%j ]Ø*( ùc*¶P5|¼öJ Ø={&VÞî™ÈÌÁ5]k¹sØTl]EÕ]¤& 磆:\º±È"tËHB©¾Ž¡ó3úñElK\]˜ ¢U‡f–+¬L´½Ž7ÙNI’Rǃ 7Ž3àŠ}Û@QohxÞâ*¸FÃCëÌŸ§Ûª|Z[rÜE˜÷r %eÉ!ˆÒ_Z+ˆ›VŒS .€Âp@   °$2[\AÔÔ~ »Ÿ¤`uT/Ô‚ÿö¤ßŠi¸ê¼ëÓEÊ é fÞ‰Ða„)3”Œ˜zÛoN™gá4–”‡ã¥l‚Øa3Œ} šG/.|$Å›xÙ6¸¿°¤$á±Ù×½+ôÿE±%ÄZ•ÀÞäbÏ+d$Br·Ûè‹´JÁ‹‘AS_MR#å;$\ÂÒà[¤kŒ6¼Æ¦ÓÛ»Ì Y¸píq 87db* ^W•m‘Cê,õ²GŽ(Àm:l»›¶ó+6 *ððZ\9àþoê­ý»ÿ’f~ååcSßo;zÊXÉ„ÖÀþô_Á$øt endstream endobj 190 0 obj << /F2 19 0 R /F9 76 0 R /F5 35 0 R /F7 50 0 R /F1 10 0 R /F8 73 0 R /F12 109 0 R /F11 98 0 R /F13 188 0 R /F10 91 0 R >> endobj 185 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 190 0 R >> endobj 193 0 obj << /Filter[/FlateDecode] /Length 1837 >> stream xÚÕYKsÛ6¾÷WðHÍDñ$Ù™Ú¤iÓNNñ´‡$Z‚,NhJCÒQòﻋDH¤'“:ÎÅÀöñí p’±,K®÷óGòÛÅÓ"á+yr±Ix‘pÉJ•\<“>ÛVûÁv‹¥”"-~^,•ÒéïÛöõ®Åe™¾Ú-–¢H×7ía¤¹Ö©‹w%¢PŒ—ÉR•¬pç½ye‡-Òçéw«Nʧ/ô(.Xž'™Ûð²Ýß ÛOVíºAm$m¡YQzÚwŽçï ˜–É!Q9Ëu²”šé2¹N4gE¦MòÚ)^&œ³R;¶‚Ò}/Œ;o³ëÄg#G)žãÛÔçãwÅ„òŸí‡(jÛ˜g˜ž$4ãôíb¢ÚRN~@ƒ`i8ïg ’ak „ÕM×ï:2©“«nMT»ÖÒ—.o«®ZA­[út<É®ëÁ®i<Ø#$…a†'KÁtß Êø„ª·OhH¡k€ÈowÐÛ "Ö¡OÂþæ.éØ}ˆeM@.I®H‘5…®í)ÒAhÏ®S°K­™ŽŸ)pz×3…Fö¤[;ï’•î ‡ŠÃýEÝÖý6˜ú˜Ç!×»ö‰Ÿ„&j ¿ŸØ–+ÁTè+Öu¿oªOà…€â9û\0¤ƒÌ×û’N¹ä%âL„½˜Å^~ ösÐgc ß} saOó;À‚‚nJ5g»n˜íÍ>×\ëoSÒ!™<ö_ébõg·FsyÃî)MuUäèÇN3v |í)&.Q]·sB•9“ò˜›ÐðpYæ$Ο5à2ïhkdfðé _i³“Ø%@lÂkC q>ØÙ©±‡e?> endobj 192 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 194 0 R >> endobj 197 0 obj << /Filter[/FlateDecode] /Length 2609 >> stream xÚÕÉŽÛÈõž¯Ðm(Àâ°’Å9$c;q‚¸‘Àc ØRu‹±H $åîÎ×çmÅE¢íÉ%À\D֫Ǫ·oÚ$q’l7ôøëæ/w߿ՕąÚÜ=l”Û(vs÷úCôñ<¾ÛîŒÑ‘ûãvgm½y|ÓWmƒ`ýØnwÚE‡ËÉ÷ð–ª4ŒÙ~¼ûûÆ$yœ›-bGç}øÑGÄÏ£~m#80¿›N¤.ÎóMBTÍù2üT5‡ö‰ñf”f:. ÁûÈ»ÅF©¸Hqwg`0“ýΗFQÉt‚Qqnã—HöÕ´ŸÇ.Rîàø³|,›ÃÉwÃøx9;^¶ž™­?ñ*èËzK‰ÊLlt e{ÃëNå6Vž&V¬›»£‡ã¬fÞàÍDµHבta«}àqÍLžHûcU=?÷åéä娡åg8\Gƒöе5¿]z2؉0EºO˜Ö”he[I£jˆo¥l’Ø8a}M²·Š‡— ØþØ^NbK÷Ä:rj²¨äû C÷§²ïB¹69¤šTŽ|‡2ú‡ùÓrs½+âÔ îS5ùÔò|îÚsW•¼ÙéÂÆÚÌ¥pr¯šG$C©è¡EÑ)ùC5Z4¥XÜ+ÂRVÅvi”7Ûb$ðtâïEš´¨š­Î¢Ï[Lû Iï@•04§éãmŒ e/' ßõÌ°Ê êef¢}{òlòe÷x©}ƒj8š<}sË^áâ4½±7ÚBk àÃßõ·š…— ®_mãפ¥³¥@‰dò |ÕŸñ'ˆŠôª ³Ó:(¼ý€Vö…XäÂ%5º–Žž Md0#(WqšOQ‰ˆÛªÏƒD“~ßy/T®Å «1€Î ïÏ·AVå×u» evTjã4`µ`@ÚÿíÁÁA~ž“©\_Æ Ìì‚§/ñ$ÖÁùÛfÍÞ'g¾×(CˤqÔBÒ(à1ÃV 7°ÐÅÕÀÞûà·©B“™‰ï½fáTäb€k~ÿPK´eî(¤~D%::—hûNGÚÁ©sÑ»?;’ï8 š7bÓ`TZÝd­»:ŸÒZ½H§¶”K†òÓ–=†—äÍðÒ—u`4X0:z©‹±þRžk*v餻uR³85W¤rn‚WÂpJžôÛ2¸X|Â?ïýy´’ #< Ía‘‡ËNÂRõØ´ø3ØV>P¯ é4s”Qì º‡ôîÑMˆÖNEHÕôg¿n¶’ÔæÅSžRñdO7ÒØ9æ”2'¦Ùù3qX ,Ð)¼Eý=¡k:‹iý™sã¾*O ÙŸ*OI½ÃqÂ×.EÖípz¤¶iØßa0Ø{Ð]š†BŸfEžW— x{—£b…ŒŠ;µHŠBå]`ÒðË€ ÞP·~<ƒÿùv$ýÙ6¤d¸P®i©Ä[f“2b`Q´ÀXëé ›»cÕO¤Qi'jžæ¦ÌZ¤RüF%T‘‘YˆôÙ9 r%¤u ªÅ–YâM*Þàû{N'â à2÷w g>†³Ù :?·ð”Ǽ@^(©]X3W¬˜ºz<ŽùÚ€kqšË™ŸX*„Lˆ ÃNeËÚTÄ×éF\WÙ¡ôŠÏú|b¢ ¨Ìp Q¬ÿ¡äÇ‚y@æ1•&@ësÕ ¾l½Ý‚õµrߣãþn(%’à ü,Bhú…Q±sÛ”,üØ•uHÃ^2åˆ-à­ ý°Ð;^ðæLŠ·§N,–@š³lSÀÅZ€Óæ=ôe…èBP CçÓSM)ÆŒ).5V¡¨wš—$Rì;’¶k£Üpr[©WLRL=Ø;âî>Þ£>Á)VÊbHsnn?d6WWöos½° Ä ŸÇÑ“pá›òžú\´?UOPP¶Ë’è' ¸QÉÙhW³¯ATi">„àÚ÷=Ší:Ø.Ú•i?®DvÌP2ƒ-Û¥”Ñ£(”Y‰1ð²R)Ph , ÎRP™ Pîš2âË“k|ŸbG["wfÑ“0#žzy©'“âXE·^jäI”Òi›„/™uF©˜¢S˜üÉõ(Õ¡löâ5ÿÚb Ó=7 zìï/ŸÃŠÐvìÈ?ó&x>•Ü‚š)¨¤ß¾9¨8±6íŠÐ~ŠÄl>(ù44öç• «Òø:SßÚvxk–QiŽÏSÕ㼇d]YNÍ®K~àÿL³üïàtÄfó¼Ž[ˆVŽ^4©Õ+®>£øòLeºüdh;ßòÖ†Øe‹-0ŽPŒÚÝWRBî…¼O•C¬ð[.ÇÎQ9RAÛÞ šÒÛAÓ·ª];¡^µj|¿ô²zðÃG™yáhguø±&0•qû¤òÂBNî©Âx€s€eÀYãлµïž>“ßò€Þ\þÏôu,ÒUÿ¡a¹@.Ðyðs¥µðFkÐ:Zª$Œ“ÿzð­ÜƒCôüŽ×âóEýÌÀ…ûY¾ZêrËæÓÕçÏpÏÀ¯Ô»#é–O¸åà(žùÇc¤»¶¾o—Éoîþð_ΠãÔ endstream endobj 198 0 obj << /F2 19 0 R /F5 35 0 R /F9 76 0 R /F10 91 0 R /F11 98 0 R /F12 109 0 R /F1 10 0 R /F8 73 0 R /F7 50 0 R >> endobj 196 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 198 0 R >> endobj 201 0 obj << /Filter[/FlateDecode] /Length 2241 >> stream xÚ¥X[ã¶~ï¯0Ð •µV"%KZƒ¦I¶Ø", ¦ÅÎb@Ëô˜]\Qï¼ô·ç\H]lo’‹<$Ïå;z…Q´xZÐ矋ܽ}/qñân·ˆóE,Ã"YÜýø)øa¯½î–+)EP¼[®’$ ~íMez£-£(Ê™,?ßýëíûxg¡ .+`/VIæÄ¨X®Òµ˜¥Ók¼s%rõÝ^!ƒ][UíRdÁÉ4OL²µª*þ›?‡®}êTmyVª†›%p #«5‘“@¹}Š?"Í:Û»]½>ð¨‡[×Ái T·¥#"¨Õ3ŠAâg£ør‡‘XD$ú¡Zæypª/””q˜º=ŠÙ펠lSEY˜¬+0~œ2¾5 *ßWÚZöÃVÛç¾E!e‚BÂWyY¦i ª Ñb!Ñ)|íSÓÖúR®Tf÷ʳ^Ûž°‰“WÜÞ;>°ë´ÝWnß^•¸øŒ?zËnw Œ^?ͤÁt$ ÚÐð¡GªZ‡n,Ï\`0þ9Ÿ²e)Ó8Yã m4on;>_·æÑæØ÷mcݵ÷{ ÉE¼L*p¤£¸ÆÁ´¬L‰ôŒ¤·o˜ªØùe[׈4Â0Ÿ¼|Ñå±×[‚¹Vãu˜ˆ1#™ÍUƒy=8ÔOXC˜í´ê¤“ÌÙ»@ì÷ªç‘Ù9Ž­Ùæ2˜ÀODØxÐy'Hwlšáª‰T×=;Š 4Α'Ãc®"G¨ƒóðÖí<àp-¾†^Ô_LoçÎ-;e÷š]šM\ÊÊç˜Xz]ööÌ«,¼³Æ}3@3ò/Ö^pbÓ¹è@àk¯*í”±Ú/Z]íxLÑUxûáÀƒvÇ+Œn\ñ[Zu¼8Úƒt”1 Æ8!P¬Ú걬›ŒcUÆ‘)c²ËÈÖšM¥yò/|íÜP ˆ,&S„Ñ`ço°ÌËØœ‹íUçDPS éSWãb=d-§>e_ñ†ÞÑE×$ùBd¤ ‚ý‹«){·¶9>ñà´çXÞsWÇÎ'1š›†޹©Nè5 -ŘɮF7£pÍÙ@¬SLX(ÓømË+'QH' NC‘¸êóÿ·!ðéÊ‹:‘¤a>©P"Þ/1"tÖy83ïùž¦mV›J5Ï<­L£±>d9Ö,ŽÅ¡ï£‘â忺ý®džYù<ÏÄ·eIJóÓ#†$]Ë4øi—PÞk÷í±Ú:%C-Œ‹$öŒô  P©ÍÊêƒêà>wr\µµ pKçzGï$ÚOw°y,N‹" s¨¡pO¸.5˜R„ë| T‹_àùTæÈ[¦x·àWÚïU…y>í¹@ ø³Vrš_°z;oµÁð Š:KŸƒmX´ßbu÷BÙÒåE O)}+44dìi¢ŸæLêªï\ü]ö`}ÍýZ²j­›MžA‘Oý0"ÙE4QhBd…`°5öP)o™¸ðårºg^ŸŠ±ÁE²$|GEaBðñ×MD¼–^œÄqævç‰aâ‰, Î\”ÑÔîu +gÚe,-,ìtÏi—Ü »®­Ç‹Îs8kŒòo~üðËÇŸ¿ÿÏeÐgn¿×:3jÕˆ¼+ó8Læ Š¾Ò6»JäöЛöJ^þU¶b5_¯ÖÅx(ŒŒ­¢)›–fne™óX–ðÅW+š++¼'¸¿/¸¿ò “˜®J|m!j8`6dã|-Ÿ©³bèµq̪çgC†b×ãÁU„åIÈp9|í÷~ é¾>ô<æê‘`y ®ëo Å|º_9@i wnÈß_A¯k BèNiÑÿ‘aG1H.êx¦;Ì ó‘úêš0îää©€íþGWÔ‡~Lx܈¯¥ÿ&k¶³ý“´=V½{ȵ.oõ*õFq}¢µxRNÎ éDFü{Æý‹1}Ü%Œ19>î|6rܺ)+ÍüÀ0™(Í:ã2êŒ_Õ¸tá‹éÚ¦Rc"£‹‡0.9šª7ŽÅîØ”=»:ñfÂMý•4œ\Æôy8&Q˜úX»Rbzÿï:‹õÞ }i‘Ðó#ã?„c¢îÑäÿî€]ãßã@nÁ~ Ü)'âN#”i¸•-’‘Íìu óY÷8Æ¿»&9âûÆ¿áU}¨Ü+Ãr¨¸48{O`ê²¼gOñÿ²=yOì»ñAù°|w™å ]­§PútÐ}eþn ¸+`6œ~þ')Õ0Á£oÀàÿÝû”…çвxäãÏ÷·<º‡ßwÑô(GîÍÍ ›óåÅïkÀÛãcè™%Ôn©¦ÔsÖ¹ˆ“\å7—²M.·z§ @-¼tRõïmøY@Û‡àay)§Q†Û×èÀïË!t)ò›7Ä)¥ÿä®é3å•~]§´ÔžWô§eÐÀÏ%ˆ‘ØìÊèæ3£ù\½ÁÔ¦>´;c_­ëNÿòŽÒ! endstream endobj 202 0 obj << /F2 19 0 R /F1 10 0 R /F7 50 0 R /F5 35 0 R >> endobj 200 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 202 0 R >> endobj 205 0 obj << /Filter[/FlateDecode] /Length 848 >> stream xÚUKoÛ8¾ï¯Ð­³¢(ÚRA±»mÒK]ì¡* Fblb%J©8ù÷!)lj›lOœç7£y)Éh–%»Ä?Ÿ’?·ï>æ ËhÅ’ímÂÊ„qZÉöïo䯽šÒç9©.ÓUQòÕéN;­,ˆ³,+ é÷í?ï>ŠG”¼¤›*Y-=ÐÕÕUºyAz9Ö¤“ýM+Q Hs^û`©uí0;z˜´S5ièN¹N»˜: foÃó¦®Í›:½ÀÀ‰`Tðdi³ö¡§­º•sç~ØfRÊЦÓÊ8Kïd7+‹pˆ¾«gáïáƒ{¯Ë^‡Pÿ^_K#wP£3ÕËÆ¾·º¥2­¨UçFß>F]„ÚIñœü~+ê«~Ò;T—kxÖ‹ÕçÁ)hS)ˆÛK¨~HWù†´sç[’Fš@Ü€¦$ÑC÷£g‡É©’áÕ†HÓ>‡{l\à&Q¡¦)ðÝuÛ1˜D›ÐVàÐTÆí†Ï ^Ù!ÇÅ´C ùgàoCšªqéÃ&»Ø$‡„‰œÂ2±¦Oú ¶9 ºä ¿—úÈbƒZ =v‡>ÄÖ6¼ÓlŒ6»(4P¼âDÝa"ÊÄ<÷üÛ/tL^Ý“²VƆüå´hîRQ˜G å5à%­Äi—áët‹ýà<`r^<).f&z¥ŽÇ¸†5ЫNð.DN¬ƒ€=$ ΢Œ…@BvvfÇQ@7Ò©hsÐnÄ2"ö;‹äÝÜî¬ôZ³ÝGw nÙcØÙ:ä2Ìer/ô?„á°ê^6®{̀߄eƒ;Âóe,ŸhõFÆ&«§ý¬]$u´ªs±1ÚîUĺIó5ñQ Ë¥&?(ñ+ƒÉõ£C]4ôuöËJ3vÚÉÚPØè¸Gt`ü§„&ûD9ÎEW§nA>ÜË~ì ª/\ÙÇ^û3˲¢"·Ãïkx`Íüû;§ñòüLQ ¹äYU‘Jà"ôÓ}ñ(z妵ÏB½p]ý¬‰`ëˆÅ³Hˆ,Ô†/Šþ  ì¦ü?˜çg=B±lÁXÞ5_ Øk ýê/°`VK¬<‚¿³(^ƒç®Ý?Ç Ð endstream endobj 206 0 obj << /F2 19 0 R /F5 35 0 R >> endobj 204 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 206 0 R >> endobj 209 0 obj << /Filter[/FlateDecode] /Length 2051 >> stream xÚ­XKÛȾçW¹,X²ùö%€íu^»ìÙiQ-©!’Ͱ›–ç’ßžª®"ÅŽ ™ûY]ϯª¸‰Â(Úœ6þóçÍ›û»÷bGaoÜÄIX¥›ûwÿ ÞžeïÔ°Ý%‰âèõv—¦Yp¿-“À¼3°e0Lòí¿îÿz÷>ÞÄE(<•7»´ KO(޶»,pµþª¿°|6Å7w¢ #ºñ·ÎlE\;z|?žìkº•Üní²°ª6»8cbøw‘‹5mæJü©ŸL§^]‘ƒ>ñ‡Ó8àGC4 ÞŒ¼ìÎjP4”4A?˜½Ü7´lnÚhUk^n”¼XÿTØqÄ?mE¨ÁêO8óÄ¢`¿ÝáêYúÚŒ¿ÖPœMì'aœyöm«ÿn’µñ7ЦÌ'Œð& Râ=ôTvy˜ç@© cá)½ß–"z j"°ºí&ÃÁ0ÃÀg«:dÜýŸVˆü߀é, eAÒZYt¬(NÊáFV9ÐÀé B´h”¸ >´$ù,¨Øòp´Šoì‚Yï~©¥º»_uw0(ÀõƒìäI ÄtnÐûÑ)»T3I¡;R¼MƒßEV øNúFÖÞ‰@ÅGÙ4º;ÑÆ^Öøò…f†‰Ì¢Òôhº:¾"or8jÓöÚɽn´ÛŠ«¥Â» |êFZTJš׳®1μ#; ª aæ> a‰à$(ŰyÓ»H:a<¦<rZ:áÝ6ì8ãÈÄQÂ@€¦Ç¤£-(1Ö€åùe ¨d5ºŽ¥½ÐÀ'Q"tZl4yi,[h,ÉB1©ì+…fa‘ð¦iÝ„ÃyämKïñF|â%VìêòŠ•" Óôk¬Þ|µº›cÅø=BÌYxÍÔ= È”ªÃ‰ãì:Õ[ྀ:Ù„Þ†–[v¼)—“ôþòBu8{šPÖ¶+±„ˆÃüË*©Âd‚-0úÚ´·í¯ªs.M>i«÷SåÖËÁ16>#Hþœ³(Œ‹o1Æ3ažÆe˜'ßç?+ %@ U¸‡a‹ãtNx¸îåÃÅz.èp.R`©QÄPÅx ñÒ þ3êa"ˆ §ÄÛuA÷I¸ã“‚›Ó1emKNÔÝݤeO}lѸ‚8_éÀTuitÞZ"º²¦”~m©, o¥ë‹`éßQ< [99ÿ2a1Ü›¹è§T86\pÌàÃÑá lObì{/’ÜŸ¾Êç7&¤˜n§ÚØsUì±­rgîr|+•<c´'¡bzçÌîH**«œ,¼Ï[it;sÜaŒìœnÕ˧Û_ê‚F8ŽëqP²ö`¸‡ÀEK§UMÃ'É3 ±¼YTAÔ˜–¶¼?ã€+òBÈÑQA•ùãg(ö‹ª 0=Úùh1h€Y¿r³~RÎÖ²Ÿ+ã[ôB—ºô˜¶f®OÕÜ?sÓLŒi’Ã[\PÂÝùXõ/|ä=ðëhèBë>-/¯&¯içð¤ þÑïÖ2¤S¯ >^¶ØR/“=,ŠžJ-ª$sÓ@Ön¤"XTÀ¡\J+ªµº÷*ÆÃÜÙ»9]@7JQj”b@/߸ã²ø½ÛœÑ†4ADzï§–RÌXJ:(ùêQ9),‰ “ÊG˜Ä'Ì:›EIM™á¢ìºÏÃlÊJš ‰0¡Á­_‚wXM®ÈÊÄ ö Œõ©í4©Eú)gê1‹ŠUuÐõlšy’?¬Rƒ‡Kƒ’_›—¯M3×Ç0÷§ä‰ýÎcx7l ÚzªáºÇÉâ°Ì=Nù\“lZXµÌ{ÕŽ«ž2 «béàP‹w·*†sÌQ)F¼9…aÜ\¼ã©OѶfÐu#6u/‡?+ÿû„]st»¦ãrÅÖƒîÝT°¸3±úN vyÈÿªKý_!ŸioùŽ=г4þ*k1R!4Î>H¤PãÎ/’ußžewRŠëäIõ-f!qŒ‘J§¨üyÅAoh‘Ú <ȧl+ñ÷‰.ÉXZƒNÎÁôYOs]Ÿ&Y~+çúæ¹ÖÚ[•P»… —ýM` ºüsVÜ/ú_ˆÜ?úåõïu½Ea:=ñß»Ùû‡uáYÜÈM¼ÿÃÿú¥ÛI endstream endobj 210 0 obj << /F2 19 0 R /F1 10 0 R /F3 23 0 R /F7 50 0 R /F5 35 0 R >> endobj 208 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 210 0 R >> endobj 213 0 obj << /Filter[/FlateDecode] /Length 1408 >> stream xÚuWIsÛ6¾÷Wpz 5ÑwæÖ¤IšNÜfZÏäP÷€H°ˆ˜$X4£ß·€’ÇñámÀÛ>@Qš¤itˆèó>z}sõ.‹Dš´"º¹‹D‰Ì–À…8GÀ­Ž=¦¡¨ yÆÃ ±bÊ‹6tðG ‰¢Ž,/zš· ùó“~-“: “j ý‡yè•òÃX}æ|W”oX_ŸóýxC«¢~vÇötš‰úÑL§xÏ»Ïv@“Å<3Îc›:Ã̃áy¢ß`é;é9yYÝ&¢½lrBƒ”Ç_gç™êäž oÂÐ S¯ϰ¢}BNˆj­Ègž8è<…”ܨ)ûN'wèêÚ„æ#{4 pN4ñ`l°×ã®×ã:>«·^ùÀ R±y rÏö·@²B¯ïÔí†uÜ ½S){9{À–ŽsµÆuê4É)±ÊÍ}À¢NžÇ”³²Xí=#N˾g†¥|ŬZ› ¤žÉáoj´ú‘:‹ÿ‚þê¦;é¡ús{ cѾcJ[3òøµ°óì™ûe>Soõús5D<Å/cQïu#ÊBcÅ«ø]Rõ¡ñ]òL~^¡ÒèE.8hà¨Zl1ä†9G¾Üyý@(kB–ÌÛ“+§I³Þ$xÝ´?¼n²¤ª‚Òº=å½Ê§YwfƲ#ó¿Yû (™CÍ>ôzC°«‰ Ô–ö%Íú(pç'@¡ ò—8há9·FÅ#Ú”8Ûݲ3‚âÛÄ&ð[^¬ß$Þú¼X.9pÏ Ï=rÚ*©OaöÓ°þ-œþ p­ÞÞüô?\\k endstream endobj 214 0 obj << /F2 19 0 R /F1 10 0 R /F7 50 0 R /F5 35 0 R >> endobj 212 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 214 0 R >> endobj 217 0 obj << /Filter[/FlateDecode] /Length 674 >> stream xÚ•SMoÔ0½ó+|t$2å£BµP„ÔÊ!›u²)ÙdIÂÏgl'[J(ˆ“íûͼç7$‚("qËkr¹Ý\sÂ"ÈÙ–„¥„ È$Ù¾üL¯ùÉè>…à”ñ‹ ”RÑ«® xBM^ŸyÓ–]òDªŒŠ4ø²}»¹f„%Àdˆ• e©Ce<UÌŽˆœDz÷ú׆¤í&ä)DþùåXa±LÒ^Ÿp“Ò®7ÃSDˆ-unÆ^/ùo£æ§­ž|üعgû±ÑKn·`Þr•ü8‡µ)fàáÐÍÞß©:¿šyýhÕÐN'ž ú®nî4*ÓbªûÊ“R÷¤Â™JÍ”cõlE\A"Hä’–§-ÑÔ+$Î@±ùÚ‹¿a¦ošz„A¯`–Í7Ÿÿ l’„‰‚Laÿ10á✺œÓïN~¨»vð‘®ô€É=`"!NrM¦t:®ªŠÄÒÓT7GÛ¹ÏÓþ·m7¶…ÞÏ•Z¿šƒ^•dèÇTýWMÇd7ÔÆUcô–®t“(<ß?sºØlNÍt„¡ûB—]_ihµÙ¬mc%>¿¼ œª¯¶m‘’‰ÈLÚfÃÌÉ|<ŸS‘$ ¹Á±µ£²(‘8¼Ýûq´*!éu ‚ápÄ©°!ñ'™¥2˜¦ Êåá#íKt®<·+¹­`ÄÞ"ò¢sà‹¿j4H3šã’EôÆéˆLjés§'O)z§ A3EßçuS·USûy—‘ô3m1LŸö+¿â…ÜI…©Ó¸kêÂï¯>ÝøM^zü¾È[ox™KüÀz6gZ]KtáÞo½ÔÒyÐ|·±ëvŽä•~ô?˜” äÃùÝMiÑœÕÖÃÑŒ?üôÖ“Ÿfèƒ endstream endobj 218 0 obj << /F2 19 0 R /F1 10 0 R /F5 35 0 R /F7 50 0 R >> endobj 216 0 obj << /ProcSet[/PDF/Text/ImageC] /Font 218 0 R >> endobj 9 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[-53 -251 1139 750] /FontName/HCXOLA+CMBX12 /ItalicAngle 0 /StemV 109 /FontFile 8 0 R /Flags 4 >> endobj 8 0 obj << /Filter[/FlateDecode] /Length1 716 /Length2 8675 /Length3 533 /Length 9226 >> stream xÚí’eTýÖåñàîwi‚»Cp‡¦iœÆ Á‚»{°àÁÝ]ƒ{ 8Á ‚û<÷¹ï}gͽóeÖ|›5U_êì³ÿûüêT1ÐhjsHY9[Bä¡0 'P £&mä9¹ÑdÜ ˜3Tƒˆ€ÂÂ@€”‡ €‡áåáçCGgÈ8»ø¸ÙÙØÂÌ2,ÿp ¤œ nv` ‚ÙBœþ ƒÚÎ`;̇rthýãˆ;@ âqó„Xq¢£+;0 ` ±±ƒ¢sýJ jí ü§låáò¯–'ÄÍý/.ó_œ,€¿(­œ¡Ž>+ˆ5:—ºó_Ó ±ücýo¨þ=\ÞÃÑQäôø¿õ}“£Ï9œ\<`7€š³Ä úïV}È?á¤ÿcŒ äh–‚Ú8BÜÿ”ìÜåí¼!Všv0°-Àäèù[‡@­þ᯵ý À¥(c ¡*Åö_ŸôŸ]M¦ããòß¹ÿ°ÿ]ÿgý×zÜì¼ÆÜœÜÜÀ¿ŒÝÿz2ý·irP°³•Ô  A­@nVÿ-ü'•´´³·/?/€ƒ‡y…‚üÜ~ÿ«QjçêQ’ðsss ÿ­‚=ÜÜ PØß¿Á_oü¯ÚÚî¯ý@ Þ0zög";xsv*¶k‹«ÞEZýöÉ%Mô®âÓ þ›|âéÑs GdÎïöᡚWËX'š1éwÌÔuþjïzoÌ||È+׈Y­Ç;w®Eªb8f šŒ¸zÎ 1mæÆ lùü¾fÈ8ë¬òa.ÝC4dÍ‹#þÝ+V•FlÝß–v£ö:àDÀ‹r^*Il9æ7µ(Ð)+#RŸZ?§O¼¡/º—’ºóŠ x³×=Õ&Ô Ðýs³º¹±ÆLƒº-¼ôü§©…°Ñø.DŒïD˯?¨&åTHÔ?§7å¸ì{!¼ÏÉɦ["ï ¡_Ô¥gÉQª¾„K1ÍlË,ý| ˆTíð¼Ow%Ïë*Ë¡+¾™".Eíe à¤N¨ÚÕ Ü#sœ¹ À2O5ãviHšÖF·Ð«E4Ýì8rúOÞ¯u~q+ÙgyüpÌn\Ú¾‡é«Zœºé ÀägIÿ%¾gµI›×¡ã~}8Q7¯}Tº“Yvü%Ûn¶™¶î|úÉ€_Ñ#íÍ žæ8Ê{|4—µ<Ø»“ß²7ÕISCž™Ÿ£Ï©-@DH–½vf­~hØ8?* 9}@”5ÆQõï¨=s×ÖNOa5›]Še„wЀ±ÌðÅ=Uí¶žPã+÷FEÞ.ïp±»Ý‘}Û×W†¯±E„ñ0•dú¥ñOAô¾ÜZ¦Ö—wÅöòǾËÝÜ žZS[ü–Q§›ËÌïŒæ(¢ «è—3ÒÑG ~ŠëˆîþŒÏݧš:â¾K€§£Z‹°°°]9¢K;¹¦hXÈ!?‰¡-¸ ¨%u¼0uÁðÀeq¼eGeFÔ4bCl¼ïc1ð]†Ý‹h9È­·Èñ»Ø#R£â]ú¡{hT瀊t³8;xZè©&¾©@?Mw<À &-«BpÎc™_`FHˆ—-w¸PrvÚZ—%- }ëC¡TÌl=’#Ké~  •Âë,zb{tóy`zÈÞÙðä,¸‰ªo!ʸq>æ…ÃË1·ägïžSS‡l$q8Z¿yM+Jâ­vúLÖjLÀu ‰™/ Zص{¢úµøÖX¢êù­¸f¢¢ðÆÞâ\³Dðaiªïï%Vn ÏNW–Gò•LùÍç«‚,ákÎæÖÍáÒ&S-2Ul ê´· h–ÄPŸçdÚd½ð;[ò_Èœ3[÷‹àãm—˜Fâ[6¤›"Vkå¢oà)¿ÇIùá•tc]~”¬À"haàÙ2y3»­ ËyDÀÅ^Š@8ˆßí¡[-p‚|\Äi…šs–üÈ£p¬Â¼žÌa컣JåÃzU©œ0çiÿõ ƒUòOXŸµG-'è`ÞdúÃ#í³!. ¢ùÁ6èN1¡ähÉÅzÍ»ÂhH!Éðêt­ýî<“€'£ÐA]ú!pçs«¶¹2„Ñ™žru[ú;_ïæµI#€bL™+ççűûhÞhtËk õxy5~¸s‹:ܼ÷•gq¨pË¢&1Ê¿¡úËCZzœ2G•aoPàÏ“U†jó2r42ƒíê¹% i¤þ•ˆu­~:%¤ÃóÎ@I>ÎÓ|ˆU¡ WêZ1a4S¾ "0Y;Ÿ$œ×áw…>º?ôg‹4.Í<ï´\ &1[0]"\Sú?Ö4Gò¸Ï`îœ0ÍVÏJŠNZ|-æ”8ßé bymÆ>vNnÃpL‰Fzõ›™ØÇÒÍx3v™dV‚Hé§êK.µq€üκZ«ÁGq–ÜÙ¹D4ž¥}ÍŽ„F ÍÎçZxlÔ­|æÚõzìœ"à ÆÁ€lÙºCÁ¬Ñ|÷s‡D‡Øöa€Î­Íì3€È;i ;•9 › 9ËÝ"_ó;væMˆK˜ˆU¢%+Xbùèº[à·ßŽ"üÓe"À[­Á ˜!¼bw’1rî“qâwÞÝ‚Á¾¡L”ùãL—“ËgÖk-,KÄJÂäq²½|LûÀMð!"I·¼¡dµ³7þ3`5í¢Á~ òý^`gæë›ú‰õúòÓZ*‰ÐËŽ?öÁC|mIÿã°ý«?þÿ:¦Æj8ÛËzæËc±„ “/Ð̺ߚdl÷{­n¶›Ä¤Ä…©ÆSÏ)×€óZÊRŠ!‚6SgžícÇ©ÛFá`’bå`rŠò^iwð®c^W2UŠö}v»È}6JúƒÔõ§åO­„òJB§já?0û-¿ÔrÛmÑz¤y•¢#ò’9OnžŠø9ÆÑn|Vw×(µÔà“ lŒZê8OÀ¢ª7]äô¬\ [ Vw _{#½¿ÒdΖýÈ“örÐ[vø)>Cý $ø¢©h²°þq#<º§¸Ë_ ä.ùÉèÊvéF3ZÎÑ&0YRÑí”;þvŸõB†ªX>ö€Ò&zÆ!õÏ2z¾ú o‡nöoTUbD?Ó£U¦‘JE’÷ØÈÍçr£)™Rúï4¢zRµBˆá¾8šl ò"š^8Q?®zÀÁ:oi?Òk4ÎPŸ5ïU3FÙIG%ÆosnP‡Œlðó§·eá}T¢{ñÂF•úP{EX’‘´Šî€£òAÚùòÖþ–)ßÊç¹£ªÍ`ò4¤ÿ[áH“ª2uÛ¯%ŽõOjè¶·†§yê¢û-å™b‹;aSìvc}ð o·=ö»)m¿ëã1¾ ’í×1®¿Ê;‹†Ï.L4‘ftˆÛÛ‰5òêÏ„êÄpîmEe ‡”Àê/z/²hpŸ @§® -v³©‘ÿLš}4ëÝ·«´]#œþ…âðœTæÏ,Z°Ð­|_#¡Xg#¤ø²DB뻲mÖª‹»ÄÅÖN ¼ô{!(è-K]ëhôÑáÖ(´w^뜎W˜Ùñ‡N µ¼8 N¡-Jú²m€ ¯¤9(9@Ù‘ÓÕ® / Òu&ÑÂêǸÊÂÜyú6x8@PbqÏÉS8õÉ"õ[û57*^ñƒ±÷Ës…•ë1­¿jîi3•ʆ\ï˜4ó æ»øÏÌ=$oÊ&×tAžå_à'¡•î ÆÎ0ìöø{ w¤«¼ØÇ Ó©LBe/ÃÌãžvH„E–ÛÐe±žÓw’ø¬tgiKê-Ú(fFÄsÈ,2òÄkm¡™RAщ8Qß/Tàõ£ÂO`í—„OOL¤}!Ki£x¡È»ÿâ^,Äì~F’u^ÖÏ=¥7½äWÍ“Z1õxùÎ)ÏÑ„§çA[G9f¤ƒ7¥E¢Ò xt®´—­µßG åö3)<ô[€[C©¨O*‹Jáj‚ÛÙîj'ÅhI ¥x¤Æ•Е#Ÿ!$åCVìì•Ü4(Õ¡%…Õëä~nøŠhÍ¿{§6‹å¨qaOtÌ·HìU’øa7ú6†ê®ñ&W¢'ˆpöÒ6ãwºÖÉí&(9ºÜàÐWíoú‘ÆûQcDwÕYÕ+AfúU'íÒVô¸¨VË*hD’uPrC°”læG ʦ÷þŽšpm|ù‚á^Y¦æŸFN•sP®øJÔ¨CCnœÍ¾\»L5äÊ®îíŸc/s½nÐ+è5uMøÞANO}u” :CF.6dÕZù½*üÙR‘û~œàW‡`ïM~¸WÚ>»,Ö?vAÝ­,O!G»ϽlÙt™c!öö\²M>ZÒ÷Ø-Gá°Í9ö].D=£ŒEAÌ™éʪ÷Å(‘N.rt¾²’‘VËK*ftÆ™mÈ«JsÞø™´S[h)„Ë3O îÏ”`ÍR¿ôƨpÙL©—<•…QÚa`m_ÞÒÚ4Ü SI{rBïêaL G&sŠà?‰"{è=£¦Z=4SfÐhÛ"ÖXñŠ˜õd×~uhL6u6%¥ñˤSq\(ê«ü[ªçV!åòÂEƒ¤F#/Üéï•çèCàƒ*Ëãúi¶í™Ö×}: >YŸ?ø/:¥0Ó +ð¾öwÃèŠân sEA¼ó/¢*üê3vËÆóqôÒØÙ¿{Hwj"—¼ƒƒñsùI$´íXÏQMÁ«ˆ±åƪ:z½è>\6~)r# ±U2ç„á¿®? íNs+GÛ#t4ظîŸÁÅò tM «†pÕÛA–08âý™”ÆB`=RDSÙÀ+ú{ÝnôÍb.Šú»¡e"ýa“ãã¯4tžÆïkí襯tÇ;wöÐ3H)‰·ªs!õnÙ+Í?GÀé%PX,¤¡yý9o$Oì^>]“/iM(i„šÏù×i°Áà'ù,­&¥e”dsfÉ Ôš µQFœGJ–ê&wÆÎJ-Oûæ_f•—VDµjk[~.CU©‘, f\¢ÏGZ·%/}q¹+ÐKš°¨ù«Ò'/ƒ=Äš ‘wݦFõ²­7_õîu¢LÞ„ÍžFÏ•Ôëx4p.³§»I †] -¤Z´T'»²6gîb®j?Kñ~jñ¤inPš‰EI’¥ÌUqÞ $=_‹½œÒåÎÄú7òoì+„EG±¥aõ[j¹ê®oø/Móµ²’f8Sl8q_ÕíH˵:WnR¤•éx¶˜ªkÄ+WíåˆùX²Y£;Hn´YBy皟BÅœfq,Ã2ÖGâ-‘“Ôòp· ÇBLžzBÖ¯ŠÙœÛpJp%ýFö‡$êµ4ܘÙ<ÝÒb&¡‹Ð'\UÔUžt„÷(Àíž)«Ç£kÓÒms'S‹ÉíS³m/Þkø$ø`ºl ,á9?bÈ¥3%S[5A‰ÖŒ•íƒøûc·H‰”Lp9³ÿ %°®¾î¹ …xÜBd^žØK¯åõ望I´„K¬Ax a*{*ÐÉ—w.rÛ°;ûð vš&ZÇwçjŸbÛý¾^ƒ|{¼àà ŽÔçpóÀö[Òx̆½Ÿ¼Æ§¬zÞ]Ìϯ&»¶ý¸|8c†ØÖ¬B|×Ðñ„?ÜÔáÖN×…½ÇA¹©#q>\w®&î‡\‡h?N×0·nW¾:´FTsìòˆW´¦§T^D”ÁsYo Ø|ϺÍÄxÔëlõèICNŸv,ñ cÑ:¢3„ëo©LxLÔ³ˆ~øc«KÕ–vùhü¿¡å’Ù¹¦H¼Êô P<þ©¯L/© #§·pNÔ&ay‡óS»¶âî|]0ö£7;ZôÎpŒVˆ¿Fà>¸K/´ïûb~úƒIU3/ÚæÐet%p‰ÙŽËø³åW@èñB ¤©cŽñ¹KjR¡òFðZ8ùc…­'¥ú3/xò:1!ÿà½qƒ.Ï×y…ïò¸²k^q'&¾-øaQ\yf~”O7ºshÝ –è‰kÚf™0ÛŸ¾"9âî…“ÀiLï`cß@ì­Í@9ý¢pdÜJ@J4M\L‹=<_«²É[‚•z¥²»m3–äÞèËl_äy 8ª4ý\0Éÿ&¿@‰Ï&'ûäKëer¨_3Ïobœ(¡z•û“ì¥eRù} ™djJµ† ÿ½kç@»ÖšxYq¢qÆ®‘Dÿ{èc3©âE-z•&oò™ZÜÖT\0v*㜖&hG"òŽcè㟠¡,-„QÇ´”Q”íýN·JÖvGŠî¯©šÊnxñ™ð‡DÉóˆoÃɧ¿un|±GŽÆ\ŒýMrâ"¿6A0Šm‘xÄjãÓBé(Qä78^½“í¦±ª‹VL(iU¥K¸Ú#’¯jûÏÑÆJÂ,ÙÝà xÛ‘ú ÉúiŸ—3ÅŠz¨w‘Ã6üË[ È¿Á«4ˆy¿×/“óI]/OÖX*CûYŽûðŸ3‰pÔÂÙæ Ì k‚ÙLh‚¡Ÿºð4=Š}å{ršpuGE {ÞÜðjz;*,êÏ]í7[=B#™£6Ê[9V‚Iµ¦’t{£"ÑiòÞOÉiàŸì‡5(šÃÄç{¾Ï ,ó p%8X߆i×áƒín'"æäÚ=„u(³Éß©\“¾e¤ó1\­ÿ¶ÏÚ[Ádä” Y« Á q§Ü·ˆo¸*Ó!›ÇWîýþÑÒ˜ÁA:Ú;%®ñCPûGµ¾2ÐUøˆ±ð9i*}ÂgziÙ¯#d|\äj³|Ÿ²£ºêeKsü.ßZíпW(ŽÏ$~OJþ.c¬µ Õ/»~¾cÄlU3Ý4Ê—-ø*;èdA0÷êQ_’ŽX6øþ7ú}¦¸½a^.Ìá:fs¹œÉö®—Äv‘m"JÏÄö•÷é}]#ñoã†tºEšÒÅJ:bôNŠ,ô#ÇyÅ4‚`…SQŽ"ŸFÏZm”•x×ö™u½?ŠÕH¬Õº°2;¯Ùã–ÛæÜ­Ê$qÝ‹…P W^s{×̦ñ±C æøÎÃãmqZFè‡é"Í#µG—Ôí¦‡†Qí“iѸMSDܵz‚ò±Ô¤Dë/°Ÿòé—çbB·–ÙM€¦„ìPŒZQuëù‚•ž¾’OÿÙA¢æZ“(ÁÞ‘ü8%_õÂYCNý[hÐñ7Ø‚@¢ªZÆùèF´^i‘º§jÄéù;m-¤ƒ§õ…ŽYŠGÜ5¢q„0Ö7oæ%X•?ãOXG÷.cÚÖes`<¾¬â-Ô?ÅgÖø0ܧe="ß¾çj;û~GÔÎT×ûöNµ$þÖÔfILOs¢f–é9ÝŒ?çOßóÄg¯‰ Ê«½å¦~½xÆíŽ eü^Ƕ 7¬§¥º¶¢½ã»Ã½>þŸÜ¡Ž¥ÍÜSk«“<•ñS£Ž[5ö‚0©e»ÅT–—)ï 1Å:žœaXú×9­Åä}»¸).ánÁ÷†`&ØDÿF6Ûâ©Â¢P®Ñ½DÅ£9³¦Fò÷ð\¹75#Ñ5EzDp°pâk©˜ô¹H@\k£gŸ1G)ìñÂN·þÜþ²§‰dýúFýJtÂóý¦{‹¬«¿×€å]n-ë9(VîîÜ^g­¦vü.—F×T`ùÂVœîƒ’„"Rˆšvja=¡U€q\ZgLé«ä¦sCÃxùÚ5Ýï=ïø5.…'îÿË ýÿü?v„€Ü`ÎN 7tôÿ³„× endstream endobj 18 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[-251 -250 1009 969] /FontName/DCHSRE+CMR10 /ItalicAngle 0 /StemV 69 /FontFile 17 0 R /Flags 4 >> endobj 17 0 obj << /Filter[/FlateDecode] /Length1 720 /Length2 15577 /Length3 533 /Length 16134 >> stream xÚí²c”nÍÖ¦™ØiÛ¶í̶mç“¶mÛ¶mÛ6vš;mõžsú«ýUÿ©QÿzôŠ5Öˆ9ç½î¸bF)(Ó šØÄìlé˜è™¸ …e•˜ ™è… ÉÈ„†Îv¶"†ÎnB&..fB1€Ñ?“^n6Vn6fhh2Ba;{G 3sgBJaª©8mŽƆ¶„²†Îæ›LŒ ­ •íŒ-Îô„„‚ÖÖ„JÿúʼnP àpt˜ÐCC31šX;Ì,l¡þE%ikjGÈñŸ´‰‹ý•\ŽNÿpRþ›”ŠðN;[kB€)4ƒœÝ?ëþ¡ùßûáúïæb.ÖÖr†6ÿ²ÿW¯þ—²¡…µÇÿ%°³±wq8ÊÚ™mÿ»Tð6Y€‰…‹Í¯J:Z[ ÚšYÿ“²p³p˜(X8›šZ;þØšüwˆ:÷oa e%QšÿëŠ †¶Î*öÿÓö_êÇLÿwüO{-Ü µÿé/Ó?ÂÆÍtÿÛb¢¶Æv&¶f„ÊΆ¶&†Ž&ÿ3ñ¿B Ù¹{Ñ1³1þóùçž12rr±sùü?•ª¶.IB6FFF.–g]¶Îÿ¾ ÿìø¿bS‹ú¸Œ¡ý•e)/)7ÐÑõ«È)—>ËÂyU9¾­ù6bÚÓö½ë)žÆø7[¬ ½É>Ù[ôOª?~¦ý¤8é3¦ã–|xÏ%7z&ƒ=oÕ`ä^XëÖ3X?s`ä ‘:æÚÕùSâxµzá_5|Ôò™å?)ÃÛäIÑVð;â—ÊêŒÓ…ˆƒ&ZgDl6§ÖÑ6³~ƒåÜe¬^8ÓA×Ea`‰\,ÀE½}-À{XALw>yô|"c´o¿m¶ævåk¤ 0·2iâ,h¨îA¸½áMwb×tPï^%‰ÌØüËNeFÖé®eæüºcŽèè½Z„@¹’Þú£™´:wbº¥œèƒ÷…–zÐÔà‘g"‹¼E3õ¬ü”,žâÝ«ŠzÅ °‡c`Œt‚Èšbûcerõü®Ò,ÕÖs‘äE²² dä&Ôí‰SŠ2“n’ž13ÚÏÁ뇎@Ù÷*Þ‰Õ­#«øÆt[³·_d«ß³UzWŠ*—4P}§öS w´v'´¢i`§ë¶XáO\[L&Îé@^Óo,탯±±† À˜Öâ¨lç㢀Õ1!€²œªËçP'­ny©%F€Þþ›Ö¡I»üÀeUóƒÍ²¢Ò™›•¥Œ°`X5> 9±›áÓ‹G¦¡ûÒW˜È1£ÖPÅŸ³Œï£ÙÚœVYÊ ”¡xº<¤D|lªÕ €þy›õç(ö[¶y‹ÔãE둚XŽÂýñÁ_«ª¯Voi)„VLñJV¾jÛ8ªûJ(¶IŠÛ+`1 –½&¹»¶a¦>Ǫ'-QbRSÖ Ù"ï\‡ ûS¹.¦ŸÁ‰ ÷q7«\'íͪ"Æææ¸ŒÃX H¯ïp_Ãó'b„½A¨Ó©” ¡ëûFÑÿeñ'b~1hÆŽ‡µˆmZtŠ:‡®ó¬M-Ѹ2¬û£MÑå•áûs­]90‹$쵿ýyÛ]b£Â“*R†F>?¤ÕÓag û2YÉ”ì]ކ…†K óëßÁ<¡•Ûõ¬Q™j–œ¬ iÊë-‡ä’¦å2ò¦} ¨™¸ÆÐ‹¯x95äKdš¨à[M»i_S ø¯oº?ž›˜¸°ÛlÒ+ê¯uè•Ío7õP°Ï ãt_ÛO:þŒwµËZbõt%5¿øéøÃ;‡…@{B·"àHr^uþ4ZÅ€…PáÙ©_ñ9Ás®ÎMýã—}øy»ÝcWi—fkîç²ã“%Ä_£It/DíØ¯Uj-âˉ‚Lp¦‰ qs‘b˜™îŒ#k¼›Åèœ/‚!)20R,3¬÷ë voòÉx_97´'ñ+SDšÊ,ÑNÁSæÈÐæ}…0|Ñ (é¾–ô•´HskœÇ•å¹6;ïûõî½ia¨ öª–©ŽZÑw5bJI@NÀ*µ£*2|’w®œ†OÒbEm hͶ½ŠÃ Uça%,¹³4&’˜1Nù¾ðœ¦7™µ©ï‰U"¼‘ÁpWäæg2ƒ¼Ï©Q¼¾qòý#“ó/EÌ'oª”žs¤!ÊÙ^OªõÃÚAèXî««å1Ã6Cò¼ÊÑ/-ÁÏAÌ=a†l ¢V⊇Ì}¥’‘ðLIpaKÚs:)è:£Ó¸•ZVõ›n¾´H)XfLÏÒUdÅiEøC½{9¶ª×˜ð5>Ð×Í(,(}À“PxÿÕ~wŽš‰@÷¥•ÎùÌŠôü¾UÎÚû0(Ï´†ÛþquG9F R/ÎÕsL´‡•ëRï³ù¹º•_Dvä[”†‚¶òQ«†¶]ßIîá¤Ó¶Š(úq¹™ªù¯‘Eô5‡d?¡—~̲-¬«(zŒ÷—86I«› ©Š{üŒå÷»ÅêKwnŠÊÔ«ðÆÔ¿Jy J >-!˜È{)Ö,œ›É#~ŒJx±B¿~E®š¨,Óì[ "s5pv‡†mëÁ_^·™H8z!—9‘?ozœÛ/¡¢„–ADùH<4wkÊïQà4×…:U£Pés²œÍ™üÒSDé©6÷SG™š!‘È ¸™«C§gÇ;¢[“J܆” ^¿G Æ›êæ0;Ìb»úqs††¿˜XGŠv­ìd\.ÓUL‡ŸÀ‚ýP]&ðuƒJû\$86@ÞNÍ/àVm‡—p£ð×Ùª'ö§²» Cø“)‡Àåâ¹X®'Fhl©ÌsVä‹Y:ìÉÙ?Ö¼u†ïÝÑÂrô…ÔÆ{Ï(*I3éßX³¬kNòã²’ tvÚ³î ½h©<ŸŒ’5Hôq¢SžºEè.šdKaÎL fsu2ƒ+UiíÆ½½ÕOu“f(,• )ÿIXõÊgä&†ýtÎþ+C‘°mäïìÂe¼x”–,i£Z;a÷%qþÕ˜Ù“D·RO¸Ø•¾áuÄÚK3' 5éz¯àÚéËÈÍ3?>–é‚p®(=d5 Õ¿©¯Sqs3Þ7IM7vk »™ÔÉÝ%¾DY¨´“à•‚ÞþæÒR–õ:¢Ô³$ôQ.¯0¸Š8롨›ê)ä¡Kw-_››¦Êˆ_!Ús [AU‡tà°&RÈJ9G›þtt!@Ý ‚l£GÂLºe レ¿=¿u"ïKrtÖ¼ŒxEÆM-Öãkå¶)y:’ä¼Á-ÆI@ +g pþ;yvŸÓKt ô›¢]F¸)ï@¼}?…Ó$ŽÔ†YY½PŒ];‹eË|fõP6cÍÚ„ ÐðÉaC¯ $Ñèé-Ò€&Ë=‘Ýõdªub‡tàŽý ÁITz,¹A˜Þš[£«}ê†U—É}|ÏŠ¼ÏOBK‡gÉfž€«žõˆ½jË”ùB€ósrOôwHSïÚв4uáFì:cüYû™ÉpÂPí¡¶&½\!£É)Ð_L*Üg¤Ç'§_¹t¯Õõ̰Î"½DmZH†÷@Åã|O¶ .AAJŸ-ùP§§á{ó(kq«zÛ˜5SŽ Þo¬dänf5Qk×½{TÌ÷’ìïÎÅyµféÅ>1ª-³üLŒµ>kÎ-ǧ×Ò÷*l¡@Å,7¸íJWç³›ÅñSC¡£#nÇ‹»4þ«»<†•l®‰‚¢Kî?²&ЊÄVMÓ˜<ÛXN嘨3 ´-$ß·žs÷ÈɸZh Ç×™%ƒôqåÙ˜ JJæš°ʼ˜QÏ 5ÃQE\‚ Bþ®Fcå„¶já6ï¢õ½³Õ§f{"á ,þ!ìŸè‰Ö,LñôÅ• ° i)'`vyþ£1t{z>ÌÇ2M¿‰b9wòó²ø ·Z…4Vî¼Ðä9`X7¢š™fg¦N,²^æ'ÖE1² ™“:w'Nž¼¶Må7jÄ´±é×­Vk\vFÀoØ×ós‘JáÇó¹_– ¿(œDbp,©ëù¹ÍIŸØå™ívšÐdŸy¯G ¹Ÿ1|^üC¹#Æåس*×1r†CL¢FFÙ'” m”$| ™sq<˜0Ñõ£3/r„MËZx®l*ž É+Ó ùWV¿¸¡è¨D³ y—çë—£(¡`R/.cývPÛZD1CÔ© ²üŽ6üˆÝ ¾+Ý%eÁ+÷§¬ðºæît»•6>lÜž¢ï=dL]<\HHÊ>sb]ðØ–r4bLXn޲,|` ŠÁ/S*xÈ~kr+7˜œ±¶¯Þ›¬Ëµ4‘,Š'Eõ<OIœ´sÏÌ#î2´ö X*Ÿ€5|ìÌ(AïyYtûåWžáˆEÙ’v¿üHéfòÚŽjŸ³¶k¤Õ4ú•¨¸àFÓ±Dþƒýëîkà µíÚqWµJu²9jæ;ZÏÝzž}¯úáˆ,kp#iZòÑ߉rn’¦L,9ð8t~” 'mh»V-ÆNJ&—ÓÌjCÍÔwQë9îuùº*{dQHÅË™ÊuwH.LU~šŽ2݇² höGHZq±Ú{Õ©o¢J0Þh:Ë~Õý&\ÍT¯áÞ] .bóa$áV´)æÞÛÛ˜JHWWÐΩ¹¶áA§.~ {Ú s2Ô¹Kþf…9MÅ«õ7_ «V5=ØnÆ$½ºbáÄ!Úí&¶žgMöÑ„‡àËã`ñºÙC5Hšu`œn÷všž±Њúäl–týÅÉ…TQéNËO‡º¾Œ!jëã¸2ÈÂA nÂ}aî'†Éj•¡ƒO^­àIÅZÍ©Zî™|\qڢà “X¬Ç3Ïs—õÇàãèÚuG±@Åo(9 ¹cD$¨ã‰!bè÷ÆÏ"&áÿT³è&ÌpÛmž+¬L˜‡˜ïqK·ËEÅèeêûáöa½óÓãêºZM§}€‹Ç¯î¡‹ÔjtÐ+®Z™¤ý^µÝïT¦M†3×˜Ž¢–ð¥!+9-õ—³AuýYÿVvûD¹ïù-!õÀ\céõ•6¾Z ?r7”¥‹¤ÀŠˆõI&Ç„;s¥­ÜgB4j(õh`\ýUѦ6g‚ZhJ(²oÉK'üXØ [§PÐ¥`éì Äà`ÔÙ` @2Ÿ[f`o°gëk‹ZÌp|±? Î’©"‚h \ü‘k¬•ä€ÍLWæÚ€PÊæH[ÊÈó´ƒ…ÿôšù!¡‰<_¼ Q5«g¶ùÙoÚmð ÝÆ_YÚœp¤wRE1®µU¤ëØ´æYMÕÁÏSè6á]†ÝÇ*æõ§}¼,J«¾ m8⿊”‰’ÊrE9üGº}¯<ß¶KʸР2ðÃÒH˜žìh7m„âQ˜îuáì_²ëz¦) ôSûV«t²V»ëÉîïÈ;»™ÂàfË8¾'„áçæ¤“% ªoªPk\ŦîªDã¸×ìÿì!$ Õöð÷Ç´%“ÌÉ6Œú‹´Ð2£Ô—£Š(4søcr·–‰%O™ú;WXL…“¢¨0œ4ðÁéÕÍg:ÉŠ¨ŒÉÍõ^RVH¯‘̯ÿœò4_ö\|mÍ FÒû›×¤ÖÜËÁ%œ7 p(Ô*ÎÈ.Ðë¤JÍÐ ò -Í‘Ýë“­XeÅzúK«¾AéÉ}y¡³„"o¦*¹Pä‹—P_ü”ó]ÅvóªÒûš -Y ƒÀ +¶aŒJÆËe áVW½Ûÿ}ðÌ®Ý,ï€'ï©K{¶ÍbgøI[ iDˆkPq \ÑZªþrx‹¼Aâó¬qãÝ|,ê&WýÄZÄeûý`—Y.›:¬ÔéªÊŸÄy•ÛÒÁr|ð™ìúš¼þ‘îõ÷¼×Ƀ󥑜ö@š2gŸï.é%šèµš¥øè]Ùl-¾>³ÎáO•õ§;CŠ7Õ{¹7ÿkk¦Õòå²ñUdD:&¾"ƒ0Æ›E ÷”ž&ÃÜ·7"ÃÂÙE}¯“òÕ¥Êc//P%qhFÿï{{½(òmþS±¿‹‹ì™ài×ÕñÂE¯hè>Â6΄Á 'µáÁ¥±lN)5Åe¾·‘oX¸i]x›½áéòoYÍ%;0èv³y‰Œ †ŽÃðâý+,^š$Ë­Ÿ·, ÓÍ„üý‹û7òäîFì†O%ÿ(„+¨y뜿דú3ƒÈ@¤àˆ3¶R-/Ì»k³Ã™ìv`ÁD)®+\–ºƒq…Â3ÀD…Û6xrìÛ Ž½tG‰&ñ­¯¾xupA§é¿G›ßÁÜòo|ÅM ›{¯ôöÁ’v0W&&䢾HðÔ´*'3U0!Ę aÁ•ñh%¦Z1”x³‡šm&Hõk(ðKÝUP€wô‘›_…ïÄC§üˆ¨<þº £ÐlÚv›6Äw|„§*cÉO™ô¬÷Ž Ð!ð†ÜUà©5è;3cÿPÈŽÏ»-Ý• Ö /'QsnB1ñë9/”ô9¢ny£´Ûº —Ÿ’±ùT´ŽôŽÖMa­Œ]É@w5ëæ®åÈ¢(µè/F£^ã!V@F΋÷êؾýPÃjðâd ÿÀ×õjäˆÆ}v”Ëg7æ¼Î~/™ˆ©õÍ´2g—@ø¸Ÿ?rHIt Ù/!œ—%%ú“õY5´%ÑÍÚ§ÀŽpÔJÚÑw:ܱNöŽy 5~G÷%àðçÎ;¥8¢k•§^`¦ÀÄy½šÛl«^iÿZÊðæÝw'†‘Õ¼œÁà„ûÍPÜ7Ûá›jÓè“í_ŒÖ0B²™h³¶¨ýIH|Þ¿µš4éä]¬`,¹Ã@¥YèÄÇF’gåJѾ/i u±<”¦{‡—{.*%;)]#Ò@Ü}X"x0uÚXíVÜS+V¶®¤Ÿ”Dž3Ák›ÉD¦Ü˜]¬ëÏ( žŽÍ³fûó½¾¢©|:ÇÊC†DÆÉŸŽ›$§ wê0qßæÐ5ë(9È=\J.æ¬^ŒÚ­Tï±ßé%l¬Ü?\ûkøKŠ|/Ì€Þâ÷xn{ŽÂß^&d˜ {bz#•ã€I×Ü]¬ à(—<ÔU±ß°Å™6pÔ¸ ¡'ž8¡Góàø¶”óÆ øF¼w?VþRþë€ÐÕE¤/” zpj¥kWö§ ¢TU0²°Þý7†ýŽÌÌˆŽ 4S[˜ƒ0E$ž.Æ÷LUN/ GPÕ»ÎÛôÓð¸ÃnYA Ù»[R½2úgø°B¶À“~äVü¡n."_ sP ªSì}þ<jê‹ ÿZp¢å]¿œ2Ìl\¡¹m{HÒWéð6ËŒfçB­fâÜA8ÔŠ÷¢ò©K';ˆU)ƒé RH«ÍjÞË6óšmXÚXe¦…äNÍ&Ù­2«Áå ZêB”Mþ Žòs½Da¼Jk扗fòœxã`„½Ä—8¯¨P©i^¯H9Ú#rÿ †Ç†yö ¢ßf}q5–гôÐN¹ »äR„¨Ï^BXˆääL8m¹>Ê.€§b¼e¦Ÿ 2ªÍzlËÓýøC\ÚŠ†áa$ ªšSkkÅ#«=nÉ4ž ú2»×~PЪ3³ èðëº×¾´\É™p³ÎI˜¹®)m-©¶ÍöÆ–NHV÷cÐi"cBsˆ£ävF ¢ªú]Èœ8ˆÖ§öˆbËo.ó!$èè„OèÊ6/´}ÅlµT‡Íƒ6«1rr¡ÌAŸUÖΣúÜ%ñ†‡ Ó<¨þ®îÕ‚—ý sTsÛRÙø—A6@Ï‚pê„fÞܹa4R'QÖCFæ_6ÿ̰†fc"¶jÓ®IÍI'ä3PLô—Öì,Õ#Û.u¢ Ï"‘ ±mqx5Œe-ÕÑ®wùN¢ÿN…¹´ŒõG¡2²Fðö¤H¡«â„ìhÿùCUÀݯ-'S7Ö‡øœ|,9E*[]ÝÕÛ0gÍ»s`]5m™ázQ¥çi„²Â°ŒZ® OzŒ0Dn6O£”ì†[sΒЬª—ý"(%”ª¬ˆ,OÌ ]¹å/ÓüHÁxäûäÜ–}R™ƒÐ+„›n©Q`>îðÛÕ[)’ðRvï!çW?5(z@ u'JötP9rg“ù0VÊÙ²LØóÁB‡¤«¤Á=ñWÞr½ ÜÒlvþ-ûCgÛ-X’É]^_#Ð:8þÒï×(”^{5b<’ܯ­*8|ª%ë{ ›ÃîQÊLW-E½L}=Û*$'ð»Åä4 `Ü×ʬ,mX|ÆT¾\Î*­\)sÀAâš×–*Ùàfʼî/[õ»÷Iîèä^’Ë­9&ÚE”ôcÀX 2£¼˜ ëÏØû¶v+ÁFË)¡‹\©²FNë Ôm°\ Z+”SùSƒ }ýMìã·³Ë{pÍ„ 猊¬é3‘€¯kmà7Ð 0ÎÅ/)J!&ëã¯Î+g·‰±ç¥S™`h*»Ë©K ÇåN³+¨R%»îgùÃtšæ`3D´SÖŸlÚÐ6ú®ë¼ƒv²vÇÆb_·ê¼;ÃNeЏáç¦Läê:¼€/kyT·<©za–¥EnØ„4É-†JÒ>½Ì›ò†ë 1Fõ}\’aæâ(ñå$)1êPv†ª'kR {S„æâÃ4ü¯ø¡Šœºã%µdÙ¢Zäf +Öo­è¥ßñ>Lpûá»_îš%³$î?»iØ‘²x®›®?ȅϦ>i+m$tê pÜÃà¦Å4Ôz6F+,r«¹`ãûíC–&Ä@p5“(ï *§-AšÔ¯4Gƒ;`™Ù„. -É™\LhRT˜–?° 6î—Ï«­ºÁŸÉ÷ÍÞÚl)ámاž-¹)Ë]gúp¾iX Äî'çܲx蜻(—K<Bc‚zä'ZèèáCÝeÛ …¥â¾{¸î0QýLc-á„[1Óà+$*=ˆ^¯,ig»Ú3Z´úÌšèiœ sÖ]NÍ»ÜcYo¤ Á»Ä¹_žh^ÜK$K1Fõÿмö›“æ'”±¯%¶v6פsði\ÐŒ¨Î‹•YyZbr® VúÛ/H"å13°ƒºÚNVœ_Ø,ú°1¾Ì7±à…{›máG"£æ$Í<Þà?Á H HûèÃ+:æ¹B¨Z‡g@5õ#™0VÂÇ‹NŸôª'öiQÝÎjUÓ—'Y4ÐkPË]s¶ë´pûÍX›%D3$¾øÂA1›½Y–@4Ö&ç`öj Û´ß^÷"Ç×(|VöÞÄ‚ïá `Mñ†ÄÛŠØ8TÔpF=ŽbL¯ŸÆ|[šÙÌÄ9¾+ aFxu:¼ß-UŠŒ^pèã„æy²—"»\šÇÁø "Cô~V’G)v þûÕS‡(içÿ— LvA²Bq] }™G÷>¹²H¨³Ãœ•þZdË'M(1sáø&.MHŸ?­M\œûïÁ?enK®(ñðKbRú{R¢ØOEÛÛlõ'hEîÅòÃÙßUÅ‘½œƒt‰Â~“Î ƒè»¡Õ.„"çEÙ¦JQ£p€u’Â߸  k°/ˆd¤œbÔ×¹YÄVf5aMX7W•sc¤m~B”½!ø+xz†ÛœU(CóŽÍ?‚“|)ýÏÜ{º µ:ÿ·7ÀyX¿ìHŠbÞ³ˆè!Ù8|ymÿjÛý¥ýo~w÷ݰt&®YàÊí:V”Nôª * Ü3 Úg×I3PÌ´«Ä´0Õí½@.\EO®¤MYWµVns¯„_1eГ'±3¢—Û°ÓNó/#/È(.…¾hœf“œ÷ß§™J·Yçc˜­}LIÓã&SßIû+lhÐRûPxÝÓMuOƆÅ`½§|Æ=ùÃz|oÏõ³± R«É0Ù³]ÿf€y7ç¼£ÏÇȦ‚ÝF¿¨?qŒnãGlmÙ¥ (˸"ÙþÊ?U€ˆÃ;Ï®- gܹC¦óeˤ(4ÈÈŽ+Ù׆žŒx9’hXNQ3O¶Ý´Àoï'—«ó³Â"xˆkoçk¾Rä£ucB|IE„fÃWpE§)£«§Q¤7Ò×3Î䯀ª}ŽÝ”6nuU C –“Ax´#¬‘™#§%6ˆm ©øâ@2é>é—÷L7»tää‹á± åf´C [jÆq<îq$PìA±Ë#"Ù Ô¯þfáÙZä… ÉÜ”Ð@ÙÿÞë°ì¶+‡­¦3?-Ù5•2n+ߎtaƒÌ‚<ÁBêv:[Ê¢­®½éd¡Ÿtž,J`PÝ~KÒ5MG^ËXæ=r?³+’ÍPärÚýhÅ ÐªÆ w‹ hºL?òßñ=Ä„Øì[ Eäb›vE¥Ú˜7e¥ÛÂvéêù½rQƒ¥~Áay¬:EÎ%Ž3°Hü¯VðÓÞ¡fFÍ0éÞø±Îg˜9›DÙ~”s°h ¯¬†´Ý÷9kO¥†K0ÅàM•z¥œ¬Â+I´+°(œ/Ú¶6LEŠØzý…2×l.—uιLõùaUÒ"\ÉÈ:ç·Hq"ò þ{4¬¬ãÚc%ÃÅqߌcZ‰‡tdã…wõFÁišã8–´ñ$¤rA~¥ÌÎ$ËC™cÝ>R^ãˆiI·¼æ¥«ºÎÂÑÑØíô!þ“2É×,Y’I%¥×Rãåüdˆ‚k[C·XKî5ÖÕ†­¡Àã—ÉŽþ‰¼È@«½Nš¬ê Lì#µÞœûªeê¾áGH|ø½¤I©¸ëTêµfÕìï‰ËxÙ÷ÒÃúVϘ¥ÔEàÔí`íHŠ‹ ë`ÇE„¼óªÓÆ×þÌ…Su!F'û5ydõ߸½ œH;#žÞÃùoÚÛ˜P“uÆ%g¿L‡:™oñ¥8ŒÙ\’)§ÌFÎßëËU¡vTeñ×éð*⇱+ ×w*Ñ51/k̪ÁºÉe±ýä.º÷+ßžù®mip$Þ½{îšô´= ¿çÿP<õï¿FO0FÖç}Þ¹ˆÞ–Íä5ùXC^ŽPVmF£^ZÅ.ëÆ\+Æ>µ|ò´ ëz1ãÕçc°Ú±^$|•‚U˜>£ˆ~O#a*%éU]”{HgÔè^ÔD§§¬" œÒà'H–¨H~µca^¥¬Ú  xp;Œw­`éÌ¿x¥PGÁdäÿ H/ÛÓaànê ¿§Bô qÍŒÙún–ÈÍyN1Líxú©¬³Ä©ÈÖ™ÚO®?ÉÀÝѯ:äiŸùív\帀=”•·Ò4ÐöûÀœtȬÓv8— “¤(ƒGæ”’eàjNRÍ–gå…ÄÊÃK/¢­½?Æ;‘4c Ò¹Ýò´éìÄ ” sʯ®‚pÄ c=åï$uÂO15 ˆSW\lâQ-¶hPÒùÒ¯È~*¬:-ÄÆÕ°¢p zâÎF]§ü¦ÞñìÈê¬ÚàìCÁŸä+Åãw)È®jF†©rõ¥??é˜c—>éHææÑ0•Ó ‘J[¹~;ú<õ­_îKªåÊm’¶*•ÝÒ=§‘÷}5»ž Hê¼ Þ%ßî8þÎuÍÔ鹺™§÷ÝViABî/¢Rò#Ú4’Gw³æegRT_yBãÆ–»i\Ä]•¦‚Â\ÔeûX³¼vW´0Ú½JÔ»| Üý¸ ùà¨UˆÍ&pl9®·×u˜>‘USé¶2q{ñ…›£A|ÕÊ*ëFþ]ðR·ð¶™“‰CäR°‹ Í×2c­ö`—&Ie¿'"èÆÚi­"ñ까°W‹s¡TVÅþÌ‚ÌÎo3c,;™ltP™Nçñ¡èXfÆK)³(v þy€Ûù£ó:Óãõp‘ɯ­ûk Æ*·¥ËL¼â}eMjG-ÐqRžw›õê·Z‰€Ê#}¬Ï¨æZø°]ëLÑM”èô*Úþ2-H¢û ƒ«jKuJŒþ-Øk³Ý°·Ì³e€Âö 'u’ëÎàÇ?jÊOãžú‘¥ro@£§~ *µH ´8 9òõ2~±¦˜n }^†÷¹HAAË–ÑoòéÈÍX¿z¥ª§A¶»”Ã,…1S fckW\Ý=ýž‰ç%jYÙŠ‚³”\(wjš@BÁDJNUC)ÿÒézÔ$Ê+|ƒûxJu2íÍú8ÓõCúñOÏ£ïþ0­¹Hñ — Фâ1ÐÆ³.×|üÛ*&ÞX¦kÖŸ“å 8‹OòÝ|ighì+ÆÃ”öÕH¡/Ñãìýi7œ¨6‚jÞj“Èâø(û~섎”CTÈ´B1-qXnsй‚Ê;bãsÙ:;°v[yÍ[ÇW(€ÃU^”‹%.ð¯0ˆ<óûºúœ ©²–6^c´F&› ؈ô‘3ìõçêF§_#;`ÔÔøIóa{ûêšÁ°¡Þ‰X‹v±…‡^ðy¾m¸§Ûû5"šÞÇ{Ûö7»XYÞËA~ô`µÝ*æ³B´(Ä9„¥n_Á«ç>HKiv{Q~(£léÞÉ_NËò ®€A¸Hü“àÚ|5=¡Š  ÞÝÖ ?:Œxj0a§|Yi¸32!âUžÞò8T´¹aš0X“àtG%æãM”)?t¶Â¢ZZ¶óm‡k´°rä•ñÙD¡*æg«˜L21Ã\) ¦¬ dÏ5¬3-¯ooŒ¶¬ÝÊ8çÊ ¹óÆ®õ3WŨÅvˆ¥«ÛõÛÝ[í£H¸Ÿq_!5ßxm¹_¿š/ˈ>w¢Q·Ü5¤È6a`2¡fuñaèpx®ýz7¸M£—ͳ@¿É熺’b3hêtmW‹£loü¨3RDëÏÁñ£‰ö0Gæï6ØîÓïKléÞk|5×yæPðQÖw¸¤r êW·‹›"‰à¨dúk«ÚÒ´€µYÏòŒ1fr•vý "eê[ ½žªÂýÎ¥ê¸P¤buý\ü°úû?<àÍøæ·^8êÈp¯xg6Ù&„{úf”Ñ›fÎtGÉTŽvYŸžã<„Kfg…¬Ø5˜ñÊ`Õ𪭽õŸ ÁÀ‘Œ%#`ö€ çWR½I 9HB]~ä-¥ªû ‡f2#‹´«&IM»= Ž}þÞŽ_™Ññâl€±õQeQ3Úsli8—ŸH²hV#¢I2 [)ʧy¹ hn±Ù„ rÀEƒQ· dƒ‘†–fùp:_+¾$ú[ÇÁ«&’Oo† Ù(i÷¥Kª-iÖý¬4+ÑíÁnÉVk%æ¯åfÇÅá{µ{ǯm m`èÒüM´“Ä''Ôço³¥æ«Þím§KYŒ›ïqù4‰ÒÃÆ%ÒØlhBù©ínrŽ¿"/P²Óp8*+ç>KE,Êù™ÐŠÆ?7Þ8ïГÕâšó¼.ñR;‰ˆ’JΚ B5u”BÈÃbKYjn* t·?ÄÏÙ¸O÷+oŽMƒ¡èÆY7ý{ù?%mx³Æ?~®[³Ã-nüûì³ö3Zš²VïòR?»ª„¥5X‡7Id+¼`^ö)ì&Gß i¨Cq‚Ü%<¹­h­|Ú_‰Fò‰r0À¿:KñpYëâ3ý£´í§»½¯ës ÉøäŽ?é\”ð3Ñ£1@Rqî4ˆû ·ëx¬Å±®Ü€U y HE\øÌ õkÅY«N\Í!–k6Ú Q7S,q]xy¶`]‰i}‰çòè…Leå Ž ï1R' ÎO{&Ù½g˜ðÝt2媙ÄгºÖ–÷¨ðYCFk^Žñ;'("lqᤢÚÅÉØQTÍRR¶¼(ûý¢·X(¸ÄúiªȜݠ“ÅÎOÁ8Å-³ÄÿűY+ƬQÒè?KèGÏŒˆås§¹í;E”–-܉`Mq§Jù¢D•?ËÛ϶ž–/ Al̲O²Á©_òð·„}âÚNâv&Æ ÐÜÞ€NqûÕœB?kï©ÛüžãJ+¹´Ò¢Âe…<-"¢3ië|pÛŽ™´Ò!}™êb|;XýzÀ%¬kŸ/~W8ý.Ø&H'Ê^hzv`)ªàü-¹§£=¥/ÌuÇë.”mÛLaN¢‚;?úYå€Ù+ó¼·h((7#äõ£jýò·õÇ'ÉUøVËo,â„ ×)ÁæÍ”VvšU=;A4¾¸îz½Â”' ü¡Þ8|ÖªtºrgAiøà—²ˆõCÃåÐi¼ž‰¼yMQó^³…Œh¶Ü°–k¦’ÆÙiéÁ2¨†—:ñ¨‘bÑà½@š^’NHX¯)eq?Ó†Ãæâ4ìˆ8¬Î›p7ÜPônÝNBà ;Ðp Ù³éØß:ú„»dÏ­Š@n$暉®]á°cØ[a[âWžÍ?ã Fa©|Ë?Q2»”ŸhЦRGâ^BP/¯K á‚B4]¨û…ª~X¦fððïtizHÚ@·‘‚NkÒã6m}Îm[PJÕÜN’³^ÑxÛÚ¥á6”F…—ÅcÀQ’„úNâ¤Qc&‹™˜`®;Ѝ.é… qÏU«"5Žœª/HË ™Jl²LŽ#XgX^œÀ(Mâ\S%|aN‚¾ ×mOíÙvÔ‚§z©pE’Îl™GÈ£=™[‘¼ŠÇmÁøŒâ»4Êl’&dõ޲Ãi9„Q©d <~5  s_ÚŸ|UÜ4A›éÐ!A‹,6öZ&ÕïöÖñÊêFÇuRØ+¶ß¬ Å(eÄÛŸ~Œo(N0Do…¹,{)üþ,Èÿ=¿4Û‰ƒ Ø"ïi êOõC“ÚÅè*f-¿bÓC‡ŸGßR›ñR¸»–`¢=âí;šL /º…Š%MË`26à¡—P<ƒP³Ü5#“æá˜Uñ¡ä/º¡ÖëÅ·ÞQûRykŸ•h”Þ”EƒŸô@ü™…Hbk²u4a*…_u>ù\Öæfá &…§i.Ÿm„Ø8ÁrxjÏ_YÒbì¶…~m"•m~@&U„Ũm‹ÿÎx6µv„GÊX¼íKgù¼Ë©•,Á%œ·ãaÿÌ‹ï¿_Ú·NºÏËCÕ¶ÃÐIhÙöÈ#‹ígµÎ«„åA6…ÍÕ_±T ÿMC$í6³Âo ^Σ!(y~1üŒà­¡ùpËï)ÚÕ(³,|‘c”‡Fc^*)ê7´}Ó)R9£†Žš\À0í+Rˆå¥è-xÕŒ×ðYƒö‘—Ôl‘È|¥£¡]|ÞÎu‚¢È[% ³!¤î° 8ý!šfÇÞdmU†)PÜAÇàŸ5rˆÍ&"FNõÄQ×,}¹Ø¸yÔÒ{ôêcyl|„ H,<89¶5±³°D€%÷ª\;›bÛÏXÊñt醉·ªm#{d"Ù·Þ¯h’,µJ/là-]0ˆ%芕o_”ÉýÓÞM­³Ùê$Éq%½LU×,–¯ijt*Ýv¦Æ.Hû%„‘¿†O9¨Àõ{º¤‡«sÙ©â$]âMpîÂ$úƇŒ]tu£¡µNç™Ë„¯‘—õb6HÚl–• ¢?¸y@W1ùâ^¾I¶›Ä  ¥~e¬Œ¥i¯-Y‘ ™@ÉW¿FâÚV”aÞÜ%N¿¥óB&ë>5˜§zõ%ù쵋#Fì=œ>½ÏêqDzC9óøÖYå#×I#­ˆ Ì›FœÞ 1•¼±ÛÉÕÎÖˆd7Ú‡õ\càÊP žK/%"-ªH&¡(ÇWž¦Öqrmë—k5[I |CRJ“q,MÊ{«nÔYy  Íç¾7ûs—ùM%··Còó!ÿ®BôaoäIXyp§)>J³Ýû¤Áü´™ˆ€/ô=°õáÃRzló<–£ß304+ÿ¨š ä‚›Ç]H»íåê€öbܧi,ÛÞhœéÑpi²šÏi ÛÈ«zrÓL?Ãô'ö¬ã¾BŽô0 N „f,Á"÷ÁØÅá·û›µÃü(ûÏ­«ËÈØDgázÙ¸­ú« ÏPQmÿ‚2nyƒÜYúQ%ñM¾;I}èç&§%ø†hˆĸ8*— •L'ÍWöB]Ù/(£ °!.}’-Cí¢Vh+´³g¹MäxÁê\Õ¿áÈ8ž-niI! '¾ž(_εv'9ëå'c¦öæWÊ>ã×Ã<Å7)unnÇHIOóÓ§C8²Y>IL¾ñ²™R'2åI t ”ÕµÂ}ð:•ìr|Ï‹)­ùãy+l*†gu¼_—´ÝSŸLÍûð3[7crð}?¸UÃ}Ñ8ðÙ7°\Òµºë|,ÏÖ4jLÇ(èlx15þòOLqÎ(ˆ1‚ÓFs|ûmä—£æY—s&ñ¯}TJ…¯Z™w‰·áœ1(SŒ2.[é]TÐãÖ“D8½Dx~>e[Tc0~¤^`D‹Ù%%èjl+CW´œ«‰Àù”8ýO°÷Ub!nÓB©Å6Ïvziæf"½[‡gž4«ìIÎhô…±DŸ„<ï± ˆãš‚¿ËÈa,8“³qø?pz”6ÏOã@ÄCU9¼ìo,¸ÕÅcšÇ€*»ƒƒ]ÈŠí’ùƒ³vy±±°2è["Ì:£g³⃀ã?Vk.<0\$3·¤ÑÙ”c>~k¢C4#'m:„>ËÁ8×¢S¯7Ih·“[µ.>F¸_›ûûAœóiVeÕÙ|%qÅy´[ôDÐŒ.¨³‚4L˜+Æl+ù~&yg)Ÿ1¸ì™`P¡¦þñ¢‚J—¯^ÿëj¼dö[]{!YÇLO¸ Ú'è›ÊUA’°øzFÙÓáãàÓ€Î_ž²"àÁ*}(Cæ"²½Óz8[û¸²~™~Þ€€“çü"6̲؀£ýaö»D0ÙѧþÏԼ؟4™vhÖ_Q®lµÇÂ#¬ ùbäWÝ·!¢X»?ǃ¿,‹ÄÖ¦Ç!t4ÅÇ ©ÀC|ä½x{Ôÿ>6Üæ`‡Þ•Tà….(æö{MëÀHõàP˜—[=9, Y=ôà+Çéq‡Íä”.—®¾‹È-Õ¸rؤºDb¸mt¬î±ÜAîŒ(LbâGR‡öldÜ$=\ªÈðýˆã8÷ULÄŽ œ„S;OÐiÏž}¨”>»XYê?Ïžˆÿ¶Ô[hõ>hñ,dcQ´?ì÷•Ú5ÑBY®°M,A¾ä†¤Û#58£éô#HL4ë¬,FÔ3hnKôÍ¢Y¼JWšã“ÌIå]‚åXlž»Eð©NØÝ2Ûç‹YÑš)qÕêä½é¶½ÌGÀ(‚q+gâmø“$ÙËBC*æß„CbÄsÐ}å¤[è2Õobw!Ûø°¾rO!tµ2p}%MÇiY¾w8U»¼¿ ríÈÍÏhOšï·¤ëªƒ²_ÿ‡ÌWÜŸ@è|]ŠÜv%dîÙþ8„Qøˆ>Z_—œI€R qÞ·qèùäî^ÏfgvM^Ž5)-uCiñ{˰$ÔÅóƒ›/¸ý¬Õð.Ïjš’ÞñbŒk  æ…-• ÁiЩŒû×y0P¤J;I»ÏbËM?ú\Ž×* UåƒÐ´ïOK´˜yÿå}œpœvíêI.æsí3³€ðq£¼ Ú p™\}ñ¡HÙʃºFum‹Ô•–Sz?«ó¹†öÌ]Óµ÷‘ؼ..Š'£•`¬jü˜8ׯ[z$Ãjf[G*N™‡˜ïjäÁPñ×9áîöx®õEáÙ‰«àó¹m¬âAÉ`ª{R$;*˜Ä4{µÚÊáÉÆ8=>¿N‘ï7æ²çlî’³/ÕÃÂÜ+T 6öš¬¢bzš%z,‹q”²w žrÖÔ éöºx€lúK—Õ¥ÍÀ”cäZùדPªhP~(äçbŒnËãØ󷬤!lKA·c"0Fü+¼5ÑMðtä±tÄ‹ÿ¨´x–ŸZÖpØ©6›'Í–PȓیM4ÇßÜjXã×­Ëš0[ÆãèÎÁ<}©ŒË“>ˆT¬‰ebŠNnW¤Ï[…8„ã÷®c®;axWV5ƃ¡“S

¯"}«!x¡ä÷{¥Ö²=o1s1{[c¢­Ôgu"H2D&úã2’ÉQ ¢ùzÚ¤ ´h‹ÄY&]ÕÍàß ¬—ïð_u¤0a+ ½ÅЧÀ+ÓÓ]mîF´Z8N"ᆘ'N) ¨Q<‰;°µŒÁ»‰\íy’üÁÓ»ÇF¥N…쯙“pØ0°òüŸñ¡¿cI ³Öž\¤Uá*X轊ïåÙÿ+›fJªF27Ö£âMä”xÇ£óvf´:ߥsrù݃sE‡ Eª¶ŸŒ'/!v úˆ+)Dªç:Äê°y'Æ,lè]Ú–Ë©«(¥¤º¤f²Ì€©·k=[#YÑ,gÍ4bé×O–òT°MOß{’¯ëè_êb[©Å1hx:ÉÛožØ>úiäåÑ"z}äjß©zãu@W'-üv@ŒNéRé²Á"p‡wY†{\? 5‚sƒ¨ÞäÔ1”Ç=ÓÍ ¯ÞcáI®”7Ð ÊH>ô‚<¸x/‰¬A˰Kj U>¿d˜xॺÈ)î†ÉÓûæªa­JÅx @Ž2 çNñ†TD‰ŸÁ…‡ÕÙ;Œíi&·Že%ÍÒçfö-þf<$I85í¶Ôyž·bléC®(¾áƒôë8¡knP²ß=V«çÌ´àØ»ùÔ¥žË(wëÍýF©²[Òó[XbÍ!ÒÀµ§6j&Ó÷†C‘é%SJ9¦¦/¡£Ô älÄ‘N¹¸ºÒ]‹üUzÇë’º EöìUq}YÎÆ¤€|éœyòëŒ ª¬¢Çÿ,ßÍYl.'êúÎê ü³"º%2%YÈùH™'t½Bi°Ö#m2º4öàÀó¦îG¢ˆJZk·W9¾È¤Vî'aèÐÀáºòoÏnø{Ï~)_2N9… „Å® ß…±.65ŒçXwG(R±UŒuôNlÍf+VâO!¸²8%GR£+O•=ÏTÃÆÏÃ׎5qzÆ™¤.ˆÀQžv§Õ~E“ mW–S¹^nqf>r‚È ewŠ“‰Šˆ!Ãñ^9ò:<.›Ö|ó,?bI©”ôÀÒÎܾþú#w˜‰Ø«Ú¤GQ~óý-FíxÌ,—5ÏÜ/~Þv=a㿃;+ÉXÖ÷ó+ÍrOJ YLH™"ó'ŸªŒØP%E®÷81æÎpa>yóº_4'3ï˜ÝÔ¼ÚMCáj}ó{ŒµÚAswñ)p¹QÌǹÖzhºE½Â³IÍî.Џ«gäf3»_¡Xßvׇð#»µÖ1£QŸN•– 1»x>Bتw0V ?Éå½:ý±¢3×}=Råv,Ðb©ê‰ sÚDS“]N…粨çÈë«ðÖF=ОM!˜^Ih /7ˆ¼æÊCÝÕ==áÔÓü/Ãå%Ƙˆ²Ph_$B›F; OÆqˆIœQi™,xn˜?Qge¨±ö%vçœ,æ]õ‘ø_õ¶—à…ãm-Iú8 =Ò03Ú.•ó¬!»˜Q~i8Yq<ê€?…k_'I«>ÕÜ¥Ì8L}^Ýíp¢•™$üh(aü3‘ðüvÌ-ÝT>ë ˆ:DZ¼&Ê+ÅvÆÀDÇ%à­· ë\Õ×y¡qà) è5yJàĽO-çÍ2Í€åÁÅvk^<±8—ùÒ­–Éæ=>jÛV—øbß‚è<5Oµ<( C}üÞˆ)¿@^–°¶hmÑëOò+]19ø¸£ýŠ£‡ÿÓôÖp±`}gbܤ#àkBx¿ŒójEi8³Ô*±ÉŒK÷º»¹[x©ge÷lÔŠƒ2<ôUê‘#³Hä¶ëvW.1þ>Ðÿ¿Áÿ' Œ­†ŽÎv6†ŽVÐÐÿ§\¢{ endstream endobj 22 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[-29 -960 1116 775] /FontName/BSZITK+CMSY10 /ItalicAngle -14.035 /StemV 85 /FontFile 21 0 R /Flags 68 >> endobj 21 0 obj << /Filter[/FlateDecode] /Length1 724 /Length2 727 /Length3 533 /Length 1243 >> stream xÚSU ÖuLÉOJuËÏ+Ñ5Ô3´Rpö Ž44P0Ô3àRUu.JM,ÉÌÏsI,IµR0´´4Tp,MW04U00·22°25çâRUpÎ/¨,ÊLÏ(QÐpÖ©2WpÌM-ÊLNÌSðM,ÉHÍ’œ˜£œŸœ™ZR©§ à˜“£ÒR¬”ZœZT–š¢ÇÅeh¨’™\¢”šž™Ç¥r”g^Z¾‚9D8¥´&U–ZT t—‚К @W¦äçåT*¤¤¦qéûåmKº…dgaqºán¥99~‰¹ ãÁ…!Ÿ˜›™S U‘Ÿ[PZ’Z¤à›Ÿ’Z”‡®4<â8ßÔ”ÌÒ\tYÏ’ÄœÌdǼôœT]C=cSˆDf±[fEjJ@fIr†BZbNq*X<5/Ý)Àà;Dß)8Ê3Ä[µÙ€Ä̼’Ê‚T„r0ßÁSQf…B´ž!P!ÂX±h¶¹æ%ç§dæ¥+—$æ¥$¥À0]åä”_Q­kd© kiLj††f ææ¦µ¨ Có2 KS=]L ,Œ ®L.-*JÍ+' aü´L`(¥¦V¤&sÍž#šÉ¯#«ý5áË«Já»Nœ»¹a–zÔ&CI¿óM“ƒX›òý¦Çž;w\[çóÂŽ™gîÉß4½0·Ñš3çØ›ûþÊ;—ñn‘ŸY¶ÆØ(ù$Ç÷g÷ú´0½eXœWÒžìË1ÿJÔ—Å]aKu¬¹×ÿz]XýáÞüßU- ûÝ£Ò%6· t½ 6µ¨,˜Õ`Ñï÷ÇN|+ªèKgÞ÷dªq;÷)yA«æW§ïpºq3ÇÉz¡ºí]£ÏK½&j¹ØŸuù~xÙ?˰ «ä*¼Öûó wOÙ­¹\v»Âwv%ß|ùáðþ3Ù:Y³Y}î|vÿ¿ß¿Ã÷X¯©çd?/°yÊa^¯:ïËÛæ-³w^fyŽás˜Òì”ò“íß[ïàÓžûù÷Ðô¿|‹g—ë÷¦û…•š;ºûãùÄà¢} [о¯ŠØ×£˜»÷~ã®¶m7 {–õ?Òéúhr=<ÿÉ©¨ªŸ qKÏøÝ®<°:­Ïÿw}Ô¼­žè8ÛØwÆÿµ·Ã›îK3¤?'”lóqf0ýlãÃÅÄžóåÈ7ù“ §®z¨yþËËhëhö\2J%'ˆ'vN_Ù¥KWÿÌ> ¿Å4£‚÷˜È¦ßS¿ÿ9búÊ­ïXÃù=ù×n0îz$ðD…ùdίÙ›×y¤\}À§o-W`ÜÈ+/vÞ5iÚJÞ¾ŠüîF‰ƒ¿ =«Uöc°zYU³ª1Ów¦¨{ã¬_¿“M./6â³°“[ßÝyNͰ٬ÕÁ1e­ë•Î+Ê2n˜šL26ðø ¾(0™é±RòŽðF³:Ë—[V®<6AíÏŸ'o”¨lœtª%×Yò@¨Ä+ƬÜÀwÛÛ…Ô Ø™¸ï‹ýöâ­Ë®ÔìË+åß³[®ñ{ì¼ü»ÙÛËyWlöÉÙ~(58¹¥ëÛó‹«…–•ßTsH×°>z­Qá°{IÂsWÎ­Ç ·Eæ>ùÎdrÍŒ3—5×b«s™þŸ”ÊŸzú‡&I~¹n@!à5`Xœ“šXT’Ÿ›X”ÍÅM¸} endstream endobj 30 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[-29 -250 1274 754] /FontName/VILYPW+CMBXTI10 /ItalicAngle -14.04 /StemV 107 /FontFile 29 0 R /Flags 68 >> endobj 29 0 obj << /Filter[/FlateDecode] /Length1 727 /Length2 1052 /Length3 533 /Length 1583 >> stream xÚí’iTSGÇQ4âB\08x@LÌÃ@Ø1¬†¢²ä™÷†÷ÂË"ˆ(¨7@«YT—*XH@ˆ-Æ*»Š@ÝÀ‚¢U9¢Ø .§Ú/=ýÖÓ™/sïüçsgLýüil„X‰z¸„Ñ!;àêã´Œ1DgPLM]I–`îKP;ÙÚB€-È@,;¦µÝ…b \ Q‰ "%ÀÌuîŠØÑ(‰ñ`øÀ’H4Zmƒ…ÀŸàa¨$Ž[(K‡ŽˆÁRTŒ’2¡S(Œ'+Q†Sæaqp>XÃiD*ú¸%CI±š ˜©9ç5%BàÂ8€ |Ê|_B] U³üc¬¿¡úÒÜC*úÂÑCöZõ•ŽÆ„q4D´H*AIàC (‰)]Žã¹¯ q$°ã±q4ˆIg0‡ó˜Ø‹E?L‹|X(FßçQù’Cݽ÷ó9ÞÁ~Ë->½íð¾Œá’eq"0>xCŸcuŸH,„2è ¤ªçÇUØõÜq`¸øK`IäSâk."6žfi h–Vê¿fÉb–3á¯Â‹‘¢7`Å`0X¬a*ž”$Q\òþ?¨ïü1æcê6¡h,Ê£dÐÇ4"æQ-z¹/•³–+®·o̯¶µuÒ˜£}fÎ̼3/£–,¼}7Éò#W\mvF+kï_Þã¹ÐsÂØÉ}áÚ¬âï - õ޿޳®I((—Ï¥òìÁ ÕLÍ5á(ÿX½å Q~½–«¹ÁÜ>©ij¬t¶ãªó¹ß–÷=Lžøô÷¹V™CˆŒ?©U÷dתIñã´\smIÙ~)Ͳlã+,D‘ôÇlF“”läÀ£©ÞçšnŠ›âÞ\òpÊ쬌ß\¶,Ñ¢·ôå¬í))Qé½*ÜûIwO··¡{ƒuaÖèQÇCó†/¦ù5§É»µB–$úuŒÕÜÚ»Ìí%yÕHC^Ô~®‹ƒ±Ùïƒë§Ð/™ìª¶©¼z8yì±g>ëñ/ð[”·F`±™À9 ©‘“ Õ4Ô< -r«(}¾º–èý®°rëôó"§ÄÐÆ“2œ9Ÿ(©+>îfj5ÇùÇfeÌÞD"íÑÖ¬Hêü~›¥úÚUñ’n,f·, î¯;X¦Wô ±sDß®LVÐaDPQ?ÕãJÜ/Ë(ù¶•òˆˆÈÑAÙÆï¦U…FØ|“CŒËzÔ¿/™Û¥4EX–­™äîÄÜñëª '–»­CíÙS,®1djX{t¶-<›ã¬MY´ùù~y\þtA×ã—f‚÷3àéò)òÖEç òÀ‘a5§±Ô”f“"J[Üo#Bw4-ЦìîÝ××oÐ[ù–+ªXQÓì[~G²LqÚ‹µMßGœÞ3oüX³–Áª·*Þñé!/´Ìç‰úZZ*©×]D³zŒ:¤M|æX1ýHpG[À&:µ®ºÐp]Õ­ÌŒ[áJÕ3…£ãh%ªHSùê{6éËm­™}§°@ŸïÍÞ¾dõU³³¥ý\ ³¿ðÞ=/QÒ§s.\dRíÎI¾}äÝÝõNjӦ6êd_9Íš²Æÿç{Çë¸L0?fWB¦èÞH¸l.ã\u<î¨*wM®5J_µ+˜¶==ÝŸª»¸£YÕY¿˜ÙX;ÆãÎÅ„ ö'µìXz4w ÖüTÜÛçi5»Û]»ä}ÆÌÛŽgÏ7gÈñþ:îH€Tæô½è”ž²wŠ0f„=n©Uá®G.Üjçºqº V¾ž¼fó¹MãÚçÂÚ‹4KÖ1…ì4ê¢ÚÃA÷*ÃÞhž>ÙúË.ÏiÊ:aþ5£–ú"Õ£(jâàà™¼øÐ+é·!Z—B{‹Sru$¿ÃÈ|K»¾–[ÐÛV¹±‹.mòâÞwì¼Æþk]ªµo¼’m`½øîò- γ*n<]¿Î±ÔFeïçõ¸läŠÈBƒ¬’Ww¹¿™=ЛSߨZçõS{`Ãæm;¥—„;;uójòEÎ'ןáÍ,8ü këyÆ¿”ÿ þ> endobj 33 0 obj << /Filter[/FlateDecode] /Length1 719 /Length2 14186 /Length3 533 /Length 14744 >> stream xÚí·St.ïÖíÛ¶mÎØ¶m¼ÁŒßض“;™±mÛ¶mgfÆ9ÿµ¾ýíÓöÚûæ´swÚ©ª‹côêϯzÕÍCIª¬Æ(bî` t°wadebå%SPWge!aeba…£¤L\¬ìÅM\¼$¬<Ž4n/Åz^<*î*z:oÝ9ýœó´ÇÉkEKAà £ôÞûÙŸ“œÄÊ–Îæ*Sö›¤p¦È^–]P¢"P×ümb IR8 >:,¼¶ç³[OV¨¢xº¸çJ÷xþ3bÜ€õ kQ®ðŽy$¢¢fBà,»9EÂÕëB¼SzÖµ…Žà8Èüë¾ÖµýdBØž:õ±ZK_gp;( ¿Ñˆ–ûðèZÛ´·ËÀ¯#ç“ü—àwmÞ¾.Û×}Ös–צõJœ®ã…ž€Þš*k½uò³á;W~ù:s ¹p¾óþQ‡‹U‰˜þu4b}@nÔ«…voaÉ•’…·Žæ1\åj¯5+Töã`Ôj—ƒ =É}S­ƒ8C¬ yöêÔò’eÃS~—'”½:ùBê΂ó$!˜Æ“ɤHµ`ճĕéÒîÖÉR:rµ_Ì=¿óç[ü¦#Kt_N‹ßäÂ-é–´VƒGìöç‰à°º)Y¬ÉFYæi8ܽF\‘CÑ Ýëþð™ZpN,vÆåA¨¿ÁèwM‚ó€+5Pž0ì3Åx2ëù‘KF,TÄ›zI:²çš›e¼ý5;îgÎ!þ-.cBTî¡Kò^ÊþMç‘4oNl»Ä =ÕF$²rP\Ãò†³“.YŽKn,ï™…Užæù1Y€"îß5ˆu¦ÕÔ˜°ƒd‘„î†BhJDœ~‡w(þ•OjDé~>ÎðsΟYÒ%ùˆn˜ø¸Ç–W³™ó›pE®CMaÛ¹o„@ý}åm°é3åCc—Ž«µléy­Ñ˜pñü‹ÒLÁ1›&WUÖbwÑhÝ;™ƒt&ëë\î;LV`c h]^õ„cã š HWs*ÆÊâFçCÿÐ'¼Õß#}ý•OU#êEá¸%9 ñcÇMêSÍEgE+½Î~xž­ ÿÕT1íÖŽì¤ Æ#!¤ç^L?&|\P¸ÙF Ó•Q7ª7Ø b3ï/láÅ%l…K\­»ã'÷¨üK0›ù7+SL[)¤oÅÈ^ѵÝc¡T4Ÿ¨&p¿¯¿× ¸43WMe¿›èèÒÕ&“Å!)hº¦š¬T%{ó>Bôó >eÀNŃMm™/›Nm=ª¦r1"ÕH=5&¨Þò'nç'RCô,(hïVj<ÆcGªåÌÈ’ºYçgÖ Å±:u±³ #†@6¯[æ ´jáÌðÌÒ%Ú6Û´úË=©ÚΘޤ†±Þù,ýe’v½ôWÐ?`j*)CÀ¬Y 4NH¡„¤sgÕD—gß {þÞòp¾×g‡µ“®R‘±b±Éõ›1X—¥'Q>añ¸ˆâ@ÑåTy»µ \×X ”³÷â°Á¸ÁjiÏ•ðä %áË? R·'H¬ƒÞpŸ±ŸÞ¦w‡5àœgþúõâØï¡A ÷¶*Ì ¤(;eiÍ·´P¢³(¹R– Nd¥RÖuNïï<›Þ9ŒAòSèu“ɰ!Ïrëbgë )üÑ<0¶™#Š·¿*ìKg[‡|ôá ðÕ7ûkPò¹‚Q¡íéxÖîNÜs©ü%ڱ믦dhÊ̦À]^ã³Sa­ëëœ^‰¯©Žb“«oÙâ nÂqج§mРtK‘.‡˜e£Nµ.pª*õâÙ×mCr‰-¢dзXôÇ4[2Ö¬åÞPu«ÅbŒbKÅŸb+Ž%€j(Hª Ìά›çÒJß‹\rï|7yi#Sgøš1£qŸ”Ï~¤á]¹–?9ÆðC]âv¤©ÝõÔ§@¸öæ¾,9ûI$-dÙUM;;ùÛ•ûêô¾éz ¹ýý:¹*ú~'VÀø¾³x*WåÂ:±áÁ4wÖQÒÝŒþ±;1VjÝ`l”†Ú¿±;r§ˆÈ`Þ?9ãÞ ƒk«üÏÚÔ’X^rŠß„ðjìl0:+üQ&RIŸ= v1 Õ -ªøipî}¶†•"Ï¥e^äìn²¯¨þ’x³!#ž0ïí#:F .?rµ?_’õá<˜×8±÷’d¾®êç2€´ã+‚Þcm³ žyÉÿB-\ø‚slqpd;LÖ<Ó(Š·± 4D÷AµSùçÝ©ô`UÎ=£wYû+uŽýE¡³ŸÆß!Bìžó{i„Öü3õÙ$åÉGéC(ƒkA²‡c®MÅ2 Z·WG-0K•0÷Õh™þj¾ •8¥×_<[ÇA?`²ÐšUê°qGj/ž•mzØ´Ö ‚ìÍ‹àB/Û5ß“WBÉcQµÌÝYó~ª=g¯—þÞ¥´º“Ìʘ]ŠB0fÙÐ…'j`™Jàò°þ…Š`ÚZ=‘`²†uÿQÁ‹7q¤€I,ˆ-•MkŽ9£ü*Žf*ùVÇB75|©bšï¢¨ä¡3Ö׌ÔÉñÝCª=¥n6I*´á”]X£$Á„ŠרuÄ‹ ä&yÒqÙ gÀüqÍÕ&€Ìœ{Ÿ½à¸éf¹Jûï´ÜÙ*à-`Ac¿R„ £üõ„cc/yÓk•Ÿæ©æ‹£Á` îÉ= ©±êÕ¥~š²é yRN¡J–!çdî‹Ö¶)Æo`ÝÀU@1ç×õ&g¸-XR6‚Y}‚xÊïn`)܆±ïÄ™'^æý k©rqeù¦3wTÔ`JF¾®P9 ŸxLÙ'·0"*ÎW5 ÃBóµâ3ó«ôiócAbkgT&±n ±ruÄ:$úi§ Ñ ƒU²f†t,Dú×3úJ^ý„_H&s Lópì( ]E ßg`§ðqî@¨¶h#Ñ'´ær÷ó¥ƒ +Úý” 3 R,báõW>y~m­5êS‰ý¤÷².5ùEÂ;¿5MäFiä6úŠÆ|F‡ö¹ˆ°Jc¸«'hs SºH%dhím2¤^+­š”ÂL sqDj;ƒÀY!;é—EÍŸOg± *à³1ÀãqjøÚ!{rZ»÷¬y­}_Lبïfœ“[HBÛk®´,§ ~õŸ×Ë*ĦÂîOŠ0£jíH–1Û‘Ç5<žÔS°$›ŸvzO<¼1Ý‚4&‚Üã¹ÐL§–C%A¶Ïíkë5ë“í ɹEÕ€Gðö„ì2øBqwø>„B”ö~AÌnE ,Éݪþþqá=Ô‘*GQ›Ð±ÁQ¬œJ&}ÞJÎù£Š¿\ÇRÿil „B" ìõôà3éÃ(¼išj=ôÊ’GÀ2oÿÌ‚uà˜.¥'é»a#gš!ʵ§éÚ®QB^1<ÎPßdŽŸCîÑ1‰÷Û~tCì4(,V3DÈ!£"£±Ì‘ÕwÔß·ÈMȲã¨À[ ²7'¸]:ç¾¾´rFŽâÊ«ƒehoÁe‘hêúÆÖŸJ¶o^[è0f’€þ„"ìnƒf6†ßnùI vuv¬IÏ—xÇiÜVŸ¿(<- “ ˆ¤sØÅ] &kÉJu®îæÊÉHV¦é¿8àOðD‰jj#ÈÖ9«d›¸¼úŽëñù¿4‚Õ¢ ^üŠåK’VРƒˆßÜYKÜÕ#sÜýÈæ-x¥ØŽ3ÔósHä¥Ášå:ƒÜ¦õ·7"™mc¸¶Ð;,×Þµ\úãDr´É¡*K>|Ï;ù73’ÿô(Exœgõqb¸°5©¢)õ¼kî$ziÁY?ŠêRàÝŠþjvó¡}KHÖl{ͧ…«é=ïªÓ2jUŒìëNErAÿ¸ á~¿°Ýz×Àð1gëí588ûv¨žai6С*­ØWÙ€%çÝpònE2ËbÝö&šïLñ¢ÝJLl®jÁ.–ú£Àxd.s'2SäÏ)“͇:rûù3}ÔáÔ¼Ùl¬7v»C™÷ô»s)C¡—sï@”©Ê”©RºQЬïfjüXÃf5™ŽhV^µ¸žu}†´½!å6ˆKma|s4Ê—öb'j_J³mµ¨kaþ¸[©mÇ4OÃ/á¶"¼3o/Ùü’Kc¡4³ßawóý§£™Ú0O"¦ŸD Ìž( /íàôóÉJyçJÊ‘¸]‰ñb4Åv©M·;8£²0½‰ã·QbVËŒïh‘œ+ûÖNžp0’ ´»¿åUä°ŸRÎg={:°¾Q¥¦'À”×?7ÿg^a‰i]núCñmû²£†+úÎ/ú/†}¾¥‡EsÇîk¬Ad$-ŠØuþ5ª~4ŒïºaÃÇ¡t·“³hÈ+5¼n.yZk?îMd,› ¾Œ=ýEˆ8[×¥ÑаC¡bR/É“¥“vg9°¤gMêft¸Y=B®O9N„ Gïî#bH»Ñ|¨â”¤ú.Í‹ BZ«‰‰GÇ{ªãæ}ï¬s…šá>Ý™¡£w«Ç‘qxÜR'Û´¦æn·º¤ZΤ yÈÑa¶ëûݡӈályû`«/å¡«rK k‘'… ß™¤Ýð„>cˆ;èM­È^ÖFŽÂÙKØ›9ôaSm3ÔÏ£9A>Hv=Þ#7u<ª“'Ê’“ì.<>üU» ¸d¦ ¿'#G ›ŸRhë¬÷Úo^äB°ÚàÚ!'¨GQ‡’§zZö¥Ô„9™ÿŒ.tq‡Êa·ÁèwT•žÐö{³Ê±ÌbT§üb¯Wü{·Ò¯0™vœÚ_,»¾ásbwV3¿o¹û›Os2×)êû óGUöÒV_Ù-—KÊÁ5°5›LY!nÑš µãÏ`’À,ólÖ^§ Õñ¡ŽÞ¬€Ðëš»p–Ȩ›¬bÏÕCrxE:i3¶YmgÈùÎ÷¡˜‘wâ$Mÿ¨]’­Î™iþgó°Û/¼4ÔñrŽû}²ÅaN½zûìúRÑAý™?É3VeÓ`¹×ùigo-ö §Æ!v×¹ô:h\(Â0SÓíuzã·”¥nœÞž¢•Ü¹ŽŒ$Å$ÁÏ%jb ö‰mv˜ÈIv-,‰xò­*´¨‡ðÄ,›\)º °vˆùî.µë7~× CaFijŸQ\ô&¬J±í•?Ìùã¾R´@¸JHª®»kVüþz…·K¼î•¨?rBupbȬìƒã!vføqÖR<ºÀþUE8:sÏD¢Wš êh(ر®—Æá²îƒÏ´8^/zëîô–š´ŠHü¹£gÈùühDúY^ΠHûÒn}­^0©Xd†D [¡ <¶Œ¿ °ŒöœÏʼn¥· m kÙáˆ0ê?½MñkE'ÇÉNªØ(¿ÛmÞ(µ0ÈŽt‹‚Õ qD—rd r˜æƒÀPŠÓ–àìÚY ˆ?I©x,>˜²5¤ß—„dEGŸ8|؇ޚÙäÈð›{f¤'[LÖ5¸cŸÌ Ó F¯#²:U…Ýëv¥œ¡7ÿ“[D^‹¨Æ®ÆÚº+? „¸S5Ô‰Œ§øIïUv« Ä’Æ.7EëjFf(Y0®C6""ÌÉ}¯¾@‹gN\”GJîB¦ q:Þ´ŒN”`ì¤$ãRhý0ˆ¾¢¾¹¹-T¯ÔÎ" QâCÙvËo†A%Ę5Ì;¢9‹vFn|z*ϳw˜‹c2’ü4±þDÞ-úÃøËú9ñi>ùmåú/êº[÷oŽÙòW»ûÆñ4„aÖ6ƒ5y#¹”H.vÊÙ.¬S?À{D~0O;ÿBËÔb“’hw·ã÷/„y$÷¹ÅL·9LU±¾³.d/Á,¦ñ?.ªãäRv ñ^Ãçß]ÒéÊ“ü…0éA¨ Öœñ'ÙÉ?—¦øD1œfŠx“Ã;'®e‘mT4Øþ€:¾½#õ=Žª ØI·æc+™õÑ Š¤ /Û÷¶Ø Bªð¤8¨Ó_növ>²ƒ9f»+âtŸGõÙ‚/çžW]_®/£FÃ]å6'ß°'PisÚ}ò¢°­£D •d¦¯o¡,ßÛÈK†¨$ÁAý©*¶š´r1Ó(Ï=%€ïc_ûëŒLÁ¦JêàÉÁ©°ègH^¨ Ä­+Oÿ-ꢑyfV “i¥I•Ø?“bÚ×øC_RBÍØÂãÅOM*Ÿ«ËóK1Z+A9Ð ïHË+4‹NO•öðû»G6YZ8"Ova9ëk•òßÒ€½ ¨âlêÁâ˜Â{2ts ¿Í À_ÝM;áV&U¯(õʟ˽ÕÖOFmÛ«®¿¤…Ÿ[]A…˜I~xüj#”4@ˆ m\²7Ø|àͽèÛuàKd¤N ÛÚå0‚—ýŒÄó•K>¼àm«y?Œ0á å*çîÀ'~ˆ3mi[Qv¢ V Œó@JBHíÊ…»w¿‹%&õX×¹æ÷Vðf{ÕR?ç¤Ïñð5œ¿¡ÄqM•nzXH O6Þ¼Ä+}˜tBJÆoµ ë É<ú=3ä¿©¸¶Û“U` Ê«º®;›C °=  –IŒ•]îßtoV ˜WId0f/^Ý!o°²*dFœ_׿¢S@;¢Õ}º@ ¬ž/v2É/MÍÐUfq{Í@ë¿îVTÛ/o'(‹Ý›ž>Íå9¦²T5KûÓ…¨bnÐiÅ©ÜTYe,`²©LSqøµóyÑvw6Ô¥L%ŸzYvãF¹º?÷ (¼¤Â@œÄ¨DQ?0ßÏ·¦1aÖqÅR£^~d s;X¶PËOu+ÈésN#±{I5£ÊI72xq«²xå4YÑD—‰ÒÅPp_ÕÅVzÊE® Øið°â¯qò;6 ‰q¢ŒÜ4-BÁʇhš„ˆ|.ð}F_fFîEìdÌ µ~L× ¶K¯—ü¹™¡w n+g÷áp4k—gle¸»eºjÃ_z4ñÇUÞçü‡mÎpS óRÞ"B½wáµ.¡ŸX¼_u\¢1Ç£9Xëù`†ÔætFÎmæÇ墘uEFð)úÍs‘ÚMjŠ€ü,5ûÕB§ôºÄð»þà$Ò}Wä þV Õ\½($4—_à {¡wo{É6ûäZ"ºY#m]fÝØÌr~`jøUM*"x¹…¡¦µÌ§›[ Þ ò“Ê2*_$¡tóyØêÁcë¼ó ÚùGˆ~&L«“Á‹mZF<ó"DN?tƒ×/M;}«’ÒúC4s"mòeEýÕ$;…Ykëë€\§iš £¹(ö{r!ῆ×~›U|¾ß©òº4øW&dåÌÓ¨†V©[À¸KÝ‹3WT%R# ­åZ§”Œü ‡)Ak|L{-åt·ù”¥‹q;<£8E).Ž­"Æå .öÞUO£4,"­x q([–ýέ¬Eμ'ºSõ‹õÒ|öX§óxÉ„šI­%r#jtÎ]ü£¯3üYá+qX^8I»©Y(!ZðN¹÷gß&¹ÁÊ'ºÄïW×1ȽÿJ[rÃ"óÙ~6ñ‡4G-)”0ݤþ¾šwg‹/·Éj§M%«cëV_僡;õI,/ɧNMiLŘìZI•ÎÜ™ïCãÖo»í§©f¥ô½ž(ƒdÕØžv>…åÖµØsÓÇê© °;!òN'¶Á»­¢ ãä2t¢M8µ@§óî#C„xy3švXmxsHל?­GW„Áï)Ê~ì`fwLxƧZ$4a'˜6ŠyÙŽ:# ‘¡+˜‡ü6S囿í[6/žJ' <,æå Þ¯ á–]J¨ûåSxþCÉžf” µØ¸R™€¬¦B˜d–”Uq’© ÝTYÈ +^c·cŽ?Ÿ˜v:AЖV¤ÃÐeªUyšÀï¹]o¾bµÅ ×T.§s .°Þbq+ö1™U¤ j7ðwßN¡ƒ©z~ýQtØüíXB‹›ôÈ–•kÝU¸_; ƪö˜Ê!Ø ÝÔ?÷û¯Ü[\GDçNñ²¼ÒoU=L ’çÉeŽy4 VÒ:CNìuýØÂY~?Ðnçq‚CCÔa@à£ê@N}±ÆŠ3E˜Uo¹˜Û;w >ÿÔýõ =0NHºÐ Ñcûþ‡²eËp ¸p@ðp›“óÂì«c)rÚ¢Fa‰:iE:ÐF×Vè{eÖQ4©ÙC’“™Ìã©ÎʆªQÉ·´¥»‡>Ûd²TpúÂÛ=’í夕p;;4LuŸh[®”.‹SÊUHâ‹à*d5†î‡¤â¾ï­\üT¢S‰øU°bÇ€ºå‡%Mü×ú±ŠõŒz€¹'q|(‚‰ç õx,RÌ Ó®üƒ«Ö­Zî¾—¯ƒgÜê4Ó ¥+Övgé†Öž¸'ràäëÙEC~ù©j/Lñøã§P%Þ1u?×i•cšôè<¢uÌ.“Ã10ÕÓܘå pÜ^~»©eEHP SõZð̺aùFU÷bƒ÷Žà?¢Hd5W…¢¥+êuš¬æ~ FqÙnÁ1½lÂ\ÊO ÅnÏ–³Êdq„}Æ š®˜Î±D ~Í“7˜0í¡îû•ß*sÜì!Z!‰í´v—íy1~Ù,ÇӛȽgvÕHÉzò½+ã¹â?¾;$HN˜B,·ã@aÙ¤Ñs[ˆ@Aœ‡‰'Ôx ö1N­[7z>ãuw÷|W£Æ^þ_ÓFò:ÜØ¾Àîýî¨UNÍÆ¸@ø>\ݵ¼õ`âÄ ._6®e?5{ž¥h–Ô!Åít‰Ò ®RQê®éhúü=©‹wSæÚu@’§5¯%Ù·Ê3 ÆñÆÿ½Ôh…ÓVâ/jÒåëzœÝsÐ=%P5éç‡Ýæú†eû…‰êtm\ž®ørñ¡Û”€9M•é«4fs†’Ž(:ÿ îµ€I Iš&Øc6N^¢JìÞÜîé'°äõ:âX¡+ ñ ¿9®“Së/ŸèYÐËj&™5kž¹{<±×øàpŒ¡™¤ñ ÉË1xìášcØV«­F,l·R(ß2@ÔL¹Lûɦyîj‘5{½zæ=‚šŸï«{¨Ï[ _¤['x\”Â×[Ë·¸¥êiFV!pCfž §tÀ“œ%iCƒ!Ǩ_¢®Ë×Äò#F%§%õ1kŸß¥Fi°@“J°¯ýú¹ôv‰/0¿5ÿšâí¸À$>ÆÏª¿g ÅßIòí2ƒÉ ¤‡ª^šûösló¤ÏßAe­V`}˜¡Ÿd ü³ùq^Š/eÀή~?KÍû5DJ‚ìÔ“âHJºº™p¨CÎäD\¶0¾í ô¯0Ó•·f3¼Ü#°»]R;nà>¨á®³_Þó(ýå°ÆŠ- ¾a4{‹ÖÖY>3mn÷¨Oš­-©„˦D´¢EZ§GǯÂÕIöŠq8ü‰&ë]¯º?ÏÌ>º÷ù˜<3_?\ßbYúö¾4‹Ï³½è Ç*ÃØÙ ¾ Ÿ£`—B{ê_»+Ql^cЦøüœúIi zb†?Û§5‰EFG­VjxÉL'¹Ÿ¥}Š ½dÒC«1³ žøohºªø‘%µvT eË›ýýù\¾ÈEÈo^….ŠB¼9>iD6è @ 7ÿ£;`yÛ¿û9xmÚ¦¼Òá&–h©\ì­ÛÊYƒïnÞ×rn#«àißZ²”˜þÀ¹ŒA…}½Iî„3»$ÏIÌD‡v1'DUa§æÊ4(I‚0Ï0õÐì]©Ûo‘nFƒ÷3=»A#m/R¼â(u£Ë*Áײñáôl0ÞøÝgQªŸ¾&ÄÔ6íd`LZ~¯Gáä§ÄÐ,rÝKU¥LóJ_ï||RN• EÚY­ºP\zßËS}­f[…ÐÂéäi©^¸a‚ ’î?6g È…w«ú=íøÝ1çà þþû±ïºÓO9í3-t­Pöýö“s!£DI /,×IŽT§Êi#w­.`‘‡‰>0”¢òmkwbšª;AþZáš@ïÉ)¾û¯yÏ9ÍB£f¯W ø/ÉÅeÕ}Š‚aç_ Lêë0/,—ËyzY)OѹåK]HU¹–Š䛉·ÕΈ/´&`Ú&_î0cÍžrØ·clÂÅG“çj ?Ù„¤q>ÿF,Ç@Hk|~ÓË•mðªTi†9^äiBÊÞ,v¾c¼¯Þ #pzýt­5ò4£®»üTž§ÚŠÜB)JH_ï +6nl_ušêï¶´`¢l³œjŸvÕ¼ YjŠ* ¸qL›ž¥sëXâöóåÍD¬‡¾q«ªÝŠíŠ÷¿¶¯¡2ô±hpýh³¾¥áý½†¼rd|Ðk,yÆÃ÷&™œë¢ H5MHÛ.ëoM‡*Xâš‹äzÙ<ßCë2û?zìvqú$W_µ;÷åP©ÿBÎ+æZSýצedlWPÁô§õße×áu>Pö¦Ž‡À€ÝŒ¾8¢ÀŒìs9ÀhQÆE+ƒi<à•m-g0ÌØ“’z)vR£àg³Wy€å Ëžðù¬Ÿ¬ý-\øAû.Þƒç¸ö0®½€Ûüx’œŠêb™UK.&|0ºÑ½”~›{­ŽóéÛöfÈ»µ¤‘™Si¡¼Ì1-Þ˜ga@'Õªnâ…Aõm"Ñ}1ßRARuX> ÚP¦ô N1â%«}2 :‚ªÍþôÊBW…ú5G6ÑC"N‘þøyV¡ßL©®¯ÚŽEkö¬‡{E£†û!Äç,+3»_:Îx0JÓÿXGÞ£ù˜P½²›0=Ë0Úhí1[صœÓ­Qâwvèº_ÀˆœõkÑÚ)ÃÐ"æØ—k±C·‚Þ®Ãþ°1ñÅKìºoâx;¥mŸèÂì3Tñð=3ÓªÓu«e’ ©×x^¯7z ŽQ†ÝöÈ€1£SfC5¯ˆxìmt÷OÆmY¨U°ÃÖºÚñà†ŠZíÚorõ6ØÒ¹öë›÷´Þ_UýÊ{̇áqêÔ²g‹‹%*yÑ/ë3e€·ÎñC Ôkï6WVûНè”Ô‰{$8Ç&SBÙÐä ÓûVFg:Ó¾mÙFbãÐn]-ÂÓ•]òš~²X‡ÈÅ>ÃEbVgA†M8z.4óªPÀžàÆ‹vàžßvD§cFÈ41ÕZ~5VÖÈ=o‹i€•¡Ùe)Uã¹Ê‹ZcÊÒWf‘ÇãX$&q†c<ÂÛÓþ1l½öð° v²\¼‹Q[‚<"ËïÂÎÅLコ¿"RášîYêÌ—N«àñ,¨kV!—eºUØ.07™|ÕJ‹€R•°ƒ„­7qÙ=K…±Š?09,ŽsWý»b^€Ì*‘h¼[>qî•7ÚÁ°¼É,â)Ïþ¾Y~sîö‚ìð˜ÚѪ¦w˜›Þihæ¸T\0Ò¬iL" «þÏçó§Îk;RDV¢¼4牟ZKçt=,YùÍ•‘SÌ5FèÎ5sa|fsv¹hRÀå” µ0ɶ¯´Ÿ›iÄ.‚<î ‘So”ž9»9º‡ø8 l ª?×á´²bBCd(ñ7^ŒÅ`R@fÒ7Í>’tJæyæµxƒE¶àú­½*äÓn]]ÞÊGÃŽ,¤‰?›õY¬jX†¡ÔÉñLé-*ƒ>— D/(Þ"ãŒÕÍû†é¥;‹žm™+°Ò‘ÓÛ¡]¼JxS ´çØ"`e)ÌèG‰ø$r¬Á<×.ƒÿóçj^[QTê"¼{hZ"å’Ž_Ágô d»@ŒØ` ‘H1¿›ÊžÉ¤D’SW%"ÿ¶à I¿­È;®®/˜ÃÒb‰ÖºAû[}(hI[BÅU‡JîÙ©CÛQ%E›W+óËÿËîÿ7øÿ„™-Àèâ`g´ƒû¿œˆ4 endstream endobj 37 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[-163 -250 1146 969] /FontName/HAVYZE+CMTI10 /ItalicAngle -14.04 /StemV 68 /FontFile 36 0 R /Flags 68 >> endobj 36 0 obj << /Filter[/FlateDecode] /Length1 728 /Length2 1666 /Length3 533 /Length 2205 >> stream xÚí’k8TûÇ%;Œ";lagIî͘!÷\‡‰¹cfai.Ì,·F¹ä’\r‹äФP¢ÜK 'ÊP’2ÅæHn[ngÒîìç´Ï›óœwç9k­ÿß÷÷]¿ÿg}×_NÆÆiL¢y‚8FbP]‹w°À  6AÈÉaé †hTS ê5zrœGWCS£‰@ÈXš_òöE¬ÒW—`Lé‘@ðؤp† dÀžF„@8Æd2`÷õ`2@z HB! @‚ˆ0à zCT„êW, ª Ðú&“ü¾·A:ƒÃ(n’*NJH BÕšÆÙäÐüÇ`ÿ†ëÇá¸2Ùš@ù:~3¬¿ô ˆò‡ƒFñ €A:€§‘@:õGëQð$A”»0 ©Þd@b Ð¾éƒ$&ú^2ÜÔA*éGN~›ªæÆNÎ.f*üÝo]D…Bü@ý§}³ÆüYsR¢CÁ€+š3†cäÜßWn?ìfF%ÒHÕ°‡ TNú§ðW*Z0‰ÑTjœã†9  èhêœþW§#ò-L 4­­¦½©èt oÎ'¯½ NJ  áö¶xÅ)ÅA1´˜{¹¼b$k£w«|:s'Hʶ7¨™-=»~)DüjìEùÅC/Øñ³øKÏûJ;÷ K_E/›¢î‰-ÜÞ–y#Týݨ¯Þ¼Ëê½»&vgfÏl’vœÕü Qíßv›ïî=2ÚÄœ,.bÆ›cÖ_¼êFá„Ozæ0¯,wt]™×kõ,)ê”TŒ,V^ë~8_ëêFd7$¹uØ ¤${WäÜŸgŽ …„ÿmÚ'êöÕ ~ÒG†Ã׸¦St>̇éÞ¢WdccÃ7×­šÇÚ§;oíKåYóC §¡\*4áâO—Cƒ…‰õwÕ]êÞ¬=æÍ å>¦@ü²-ë¤WÄÃeÖc²Z•³”`;,Ø6`ËGÁ¶–kg4cÜ7tù*¥*ýî›§-™#Û#.?ÓW?c'x:a@¯îÆ© üœ·íü¬òõI Ý£å²üDl—¯ì醶Ò÷³äòX ÇáýõF;ªx¬€âÙ½W ™¿¦»~¼dâºñ|é¤ÞÉW‹Ó5•Æ"ý}'2›J5D­³£HŸ †Å£OÔH^ ±~²@EyJs»ð>§Mß;W¬$=Tðy³ód¾ƒÄÇñó-õŒ¬w4¼e\-ŠDXg»®Ê4ôcF"À1ñ‡7ÖÏ^Zwòæ2üŒóSÙz]ëSsŽx/ÑŒÅÏy?ÿÂÇóÜEnGJØú©xê…ô¡=¯˜%¤ˆc—ÁÎ]„¥ÙßñªÛµ„FϬPó²=j$\rî˜yÙ¾¬ªš®%š[î,¤qWbšr‘“ ±gWÌ«”"¤"§oÿ"ÕhûÓ:˜ Á¯ÏŸ.S=‚ ;© EˆPbz5wÜNÌ%¤¼ÎŸÜBŸˆì’z$¨­ Jj…JN…»rPCÓ‰ò.8:ç&Þ"õ0ˆ½M|Üž'Üô”îqÙ§ SOŸ0RËÖõîq×’rVígÒ‚â°U]N}ŸÖYÁ…ªþx®»Ì†yJëøÌ1=ì›ðÃOKJ7¤·»«>ȇc×d\Ç’—{ö Š” ‰½ÕUÊEžqsR{`.Ý 9°×:Îîm}¨YÆ­1—V¬8¿S§´‡Á”ßÓ;ERV…°5_úöÁ#¸´(%v^ÔÂ+!Ž (蔊õ#Ôc¼÷‹þò]¡;]pÌÐ#Õ5YuöÞòi.É"î&Und…}–kÖžLØŠ;¬À$ô¹ð©œ­‘ÚÜÊõÅõl Š¯Ê“æZ?£^êu,Ž=®ucU?<¢ÎK#².-E%4Ôi,@_vHuæ¸îLA–ÒÒ!DãPÖÔj›Uº“Ý–Âìó«äKOÎø†p-‡Orkí ™YGãóÔ^3³}ƒÕö*:”Ä0—5ÂXë¢u©âFCAQʉ¯354Ë#¾¹þLöÚ§"~¡Û ý®Úsõ°ã5àÇ2]ùI¯·¬\îl1ñáŽJ÷¦†u ÍÓw <®,wI&ˆp7ÿ^nкƒ/žÒQs†'´öÝ]/³K”Ç šß©Ñ«±ìÃñ}qÅi:f+AvªøÏ%¤5ëB8П­f–|¿78¤V1–2u´ÐØr—Z“ÁÓ‹Q‡žôÖ—ieMlj™Þ›ìn|ð cYI²àYÃÔÅ“B¢«J_˜6¶7nËÁæ÷šáìZdÄõÕÁßꊈHõóѽ*ÍY}#¾¿/L ÷‘:zö­8_Yx–0Qgm·Yˆ)–GªŸÐßêžð)!0ïæåPv„}¥¼¡e‘æ³ÿïµ¾•`j©s_r#¿l­|×ÈV¡rî}ü^o¬Nä"Ù[tdrm;ƒÂ™ËmXËu— ³ý‚«žê%+ññ›G‘.8Q~/©0—æ…šIÁn+ÓÛÐä¬äÁrŸ Ë&a“‹OÅß-÷P¯ÅU \Ùoh–ÔeÍ»ESKE‘¶”pJJŒ*ºDlwô‰Y.b+ªGnüZ7«,[µ¤œwðÆÙs\±iHuÅç•H½É³n—áN1Õ°º·ÀĦjú`gLf}Ρý_^ˆÿøŸ@$ƒ:L£è'ˆkf’¯ endstream endobj 49 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[14 -250 1077 750] /FontName/FWDQLA+CMCSC10 /ItalicAngle 0 /StemV 72 /FontFile 48 0 R /Flags 4 >> endobj 48 0 obj << /Filter[/FlateDecode] /Length1 720 /Length2 3056 /Length3 533 /Length 3603 >> stream xÚí’i<”o߯³•­ ²dÙ1cû2öu0c7þf0ŒÆ6ö¡,ÙÊÉV–%‘„±G’¥ى­zþ÷óyºŸ7÷ç~÷|žëzsýŽßqç÷ü§°€â MpÂèð¾WÀ’`% –‰B ‚%AôÂÂZD ÊKÀk£|1J@°¢"õs‚€`y%Yˆ’¬"=½0P‹àHĺºù/i‰ýtÉ¡ž"Ö…š |Ý0ž'!Î(ApÆb|%@(„ÿüÅÇø`ˆþ´$== Dc}NW,ž^ê'•Þ…”ÿ-£ý¼þnùcˆ>'\ÀK'œbÀJ4 ¢1.ôR0ÂÉj˜–ë¡ú3\ׇƒ¡<ÆÿžÔ¿PžX\àYž^~¾"Є€ÆñZ­1¿éL0h¬ŸçŸ]_ë Å»â0@Ðo 룋%aÐfX_g7  çƒù¥cðè?!N&÷ AJ×ZÛÜ*þ÷±þn›¡°x_‹@¯ÿôÿªÁÿ]ŸŒˆˆ%íA’ øÄxòþýåðÇr:xg‹w"|Qx4Šˆþ§ð¯XššR0XxEZîä–äåòr Ðÿé³Äc½ý0Ú@9èÄ!ýKuö#1xß_7ádÇ×.Ø“ù`0$Œ3}v–⪯øŽãvÛèEëgýŸ¢ŠºÕ(DÏ< cäzÉo2—âÁ·ÄæÒN-…D϶¡Ù—izzçèÎïþõýŒüãhnñÖ²|È`hqs°±.ïÈ&@–2Hå/ŒËÃé#Bsÿ”DyY9bŒ“ä'¢êÑ’oÔ¼;ƒék…ãu“Š¿ `й²ØÐÌ@­uï~ìÍ[)ÒMQ{X»gë"Œ£B7óÐ9ã§šk…uo—7öä XNc­IqcáÇÖÉèÆè±°i©ÇÒDZyA €ù—%ÈÚ:—amßu+‡ÃM‡Ü)Ñ”÷;RqFËúÚ×ÖqQ‡cUçŽÖmŒ=Hu³M%𠚈Cý^ƒøÈËß9û¦› uÓŸpRP¹~Þ³eRZ—qlà pîPɉ`eàã^Ôpëâ?¤ã%·Í›…/¦«vD¹¯OÐßÛZ ƾûr³ê¸ÜÂéÝÊD†`¿{Ú´å„¶IÓ6ãì½ôÃÉšÞ½+‘E=fë¶Ñ”OdWÞ速›GÔÕVõb訫ͭ}‚(¸ÂYà=›-ȧçʧ‘=z¼pÆXXÓÇš{鬰KËRm^ØÑ–h÷MœjÂ9±E:ÍêQ^SÍã}1RÈEkf3:ÞNûU)_]oUÔ­j„Êlî+dg6dŒx…òØ×ˆ*·l[‹¯{£!A¯DêwÕQ 4[(û‡åýÖäó-fÑÛ6ý~'Á½g³{ç¿tm_§ûî,wGVÚ”_V¾rŸÛ:L6ô xˆôFŠy¼dA4êׇA*b>Ë3‚Xö7ßÇng>˜×`¤¾pë&¢ÿ<&ïZë/¯¥Ë–—>ÖÙ|{éMnëþ†0ââÊTŸecýe!­ñáà¸Ðo36jÆ\¼u-F;ªõ‰%Þ­Å·Ë’îÕ× ÷ )¢m˜ûgrÅ5:ò, EçOíp=®¦µy:ùlÔô1È¿\µ:ÑŒ‚D™ /×Ýs‚<<›¬ØÐqÇ:HzÕñ³HÿÕ ‹<•½àå­EGÉw8bÌñ”òAô"Aî‹¡ÂÉ¡Ï#Û²dÐÉ'6UAdøD®ÌĬ‘Ð*mšÖà²]ÇsÞûS7ùWòü©jZé<ÿzmòjó’ÿÚmŸ>‰ô2Å Lþlúª¡`Ô}5–gŸÅ­DHͪ¼ÚÚ$¼¶î ­ãá¦Ï >)mIkþ¸ôZ·- ôs_ídè¢þNc»‹F•­[Uj>*|<¬þº”ìBgàA°jŸbsROÊ‚§Ý®|¸2±‹¯F1+Böh^",1u\I@Y¯§cb:Éî•Må-amf°kòtzñ HiuFi3Ÿ{) kÂØ™ÝÊõªŠ»AKra^DVsí:cCT~ôõÒÞ£d ¿ûŒµqéjé’ˆXbapTU–4ÙSÀ„<B\tåÕPVÖ™¹çîJk¨ÌαB­I^3£õf£k† ªî*?ôþjÞž}vã€,,Ðä;÷YãUԦΕ$å¥ngêÝ2…åÑeºwÛÔ¶Yw&µSXÏYÞzã¿zÕK²YfÅ‘{E­XÞʉ×eò°Ù‰×p<¾êözàüöyzr°!Á0&ɶαÏ:¢9'D}±­Ñ¿’#>l›U%)Ï@Zj‰8ˆ^J‹%PMÒL0xlG;ä'¥åA¹ª%·¶(áš/GjŒOÉ\þ7ª­gwë÷ Ô‚ÃýüÝWöµí߇ˆŠ$}¥I™b¤å*Ç–ís¨Â—ßÏÕ{]+ WˆÒß(·=Hp0q¥‹x”Ãre?v"0XÌõÙó´‡cš›Z³ílîÑM{™ sŒj¨h6(³ñ³ñ4•ö3ùi¡KÚÈö¦ëMãQ2×cç=)œƒ®´ Êë7ÄÐñºd˜Á|”&¾z|û”—aY·èÓ [y^V]r ÿWÆÁÏæŠæn‡Ë!±ÚXzljÖE«‘a¢2”®–‰x¿Ç½›ð$fE¬:l-À–Tモ$¤·zgÒãkÌÙ¾áGñÇþ·õH»Ñ¢Võ¦F›¶f_6Ä_•ò5¡_¿-¸eô¿|ÓœzK@úŠÞ[Ú cQŽ´î úywþ‚—jïñ°éÇáêÔ€rß Pø  4OEuÎ÷îå%jý8ˆÎó‘¶D“y¥l›éÐÒÎ*"<‹xÿÔ^Z1çæ‚kp¦&ãQ\—·À>»Vvs[+— €,åƒ ÊhŒ¨GeÙÕ¿ÚÓ£ˆÓI-Èt h˜GPÚ§DÕó\»%ýRÒrùÍÌËÉáw †æ"’¦¼:TÅÕçfåÑÃ/[©„_ó“Ò¾ªWo†Öaăk£1nOÞ[–ó\1_aB˜î„ȉ¥ÍÜPÏ^~¤·È80§’O5tç\¤½YQ˜2ýаi:®½·ª;”8œý=ý¼}?˲" Õ'`IPboš0h²3¿«Ýµ/2V›ÉZBs\¼e%f8t©µEÓa»â9Ëä)wFË™~%±Å©î&•_9¯X{6F&¸=?™[Y{€ Þ j³~·þö —XÓV°7ÒØ†w«sþ±¯ð†÷ƒ÷÷8›‚Æ:z‚\3”dâŽއ Àjv“}žFf¢´Ÿ¹ÖëdZ¸x…nå Î%l.­^}íŠO™ù0®T‘:µœ¶µ ¿/n²Ûø&ÿ>9鲨;šoOX#ÎáÛçÕ‚'œ¹¢Hjüº^ð.”ºYÅ!ýl¤¸Z:‹Y5C¦_SÞ+ ‡ªŠâ’ô‡Èvæ„9²ú¤§ÈxÐ4 UŸºÈ͸'Wß+Ðy<ÅÙ „s=-¦Rß{ȪM;­_ ô %v¾@¹èe‹±IN53 [§ŸKu2Åa{tKÉ+å”$t8ŒNýéÄø:Qõ‰w=›<×§G1Es%fæðvª}׋^Ï…>íÄ\«æ¹#Eº˜y•å-爜bù,;}éÍv°­³ZÛ6­ºEiÛ!Û¤r['¢yŒ]Þz(TgJ¸Õv0“\æÎª¼žÇÁ™²ê$DÒÙ1Ä5p¿†›) ‹D"Z Äf—ÓL4d#¥ ç4}C¾6̰e;õ“?q_©~–Ýò±y‡nDÇQû©Ö‹0êjŸÅãõP+0ÑÍo +áv·ÂÙã”ù^Â;ñƒ}/¯}{åÿ< ß|úF 6üÔ»™G–ød¡¼XqêNêò¾7ÈRÉàÅy/vzÉI`4-Ðûù6¢.éJÕ™Bp\ß`4‰3‘f@Ä #Æ$«Ž"ëEÑå©"Qœ~V2º7y€ ÒZè]Ù¼èeè 0HÜ­ä­n9Ç¥©Ï¸zÕ‡í/Ž÷®ð˜ê\#ËÅŒÜö€Ûáës=)©Öuâ+¯^¢2wÛ®8T…îr`¿„|ŠíÚrÇŒÎFrå”Spä\WÒH_êâ·nSwÚdw ÞÓ¸%3¡'á•N·áñ±ì¢ÊwÂ`àèe¯´b\H r5¿\ŽpÙuNõÐâΚªªÊt€Qyd[›EÍ“u‚í‹ÖêUqKÐJxP¡J,×´”×B¿´_ïâ©ï7’”Z 6Y~ѯ6‚ìÖØÇpÀP™æVôÿð"À‡A} ž(¢=ý?Çr0t endstream endobj 72 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[-1 -234 524 695] /FontName/ZWVUJE+CMTT12 /ItalicAngle 0 /StemV 65 /FontFile 71 0 R /Flags 4 >> endobj 71 0 obj << /Filter[/FlateDecode] /Length1 715 /Length2 5186 /Length3 533 /Length 5736 >> stream xÚí—g8\m»†õ!jôLtÁ0:Ñ£&Ñ¢ŒÎ0£ŽÆ`F ¢D Q5¢D—QCÄ(Ñ{¢-¢ÄÎû¾ß÷íc¿ßþ³ýo{­?ë¾îk]Ϲîgýyøn‹ªA‘0-$- ‚÷ôLL@PœŠï A»"4L’—Ô|œ⌂”´‚” àÒ‹ruvAï ýá’¨yÀP®Ž@‚vyüq„ÀÆHGW ÔàpÀ£?^ñ<‚yÃP¾0(Š @]ј³+‚Jì(]„ û— õñügˆòþÍüÍ)øM E"àXæD%¦ü½ì7Ëÿë¿¡ú{¸–®ñø#þÏAý[âá ÇþÃôðôAÃP=$†BüÝ †ý§ƒºúxü½«‹†À]ÕÎp@ü/ÉÕ[˃º¢]h”ìO†€þá÷Üþ$³›™Þ×þÇžþÕ5„¸"Ð&XÏÅþaÿ³ýgý{>(W ÀJ(.úmü}ÿóÉæo«i"‘PW„3À A@!(è¿„§RWGbDAQ I)€´„@F^:è¿ÚL®^>0] €´¸¸¸œ¤äŸª£ C ÿü ~ï?k'×ßÃÁ00GªÌ,&WB;Nácû£¶1npcÿ¸!ÕÇ é“<æÁžýkpRà„[T„»áÑäõ³-øôSÁ[*OßëÝMl;±íÄbÙÊg™ï8áš­–*â×E‡Þ[‰µîÂâ>ØYÕçIØ’ÒÎ@óО-ø®Û¬uçcŸƒ[¦ å–Â-_&kVc¾5(8Žiú=x.œmwRM†¸µd`ÁVK0y„IFü2=T5}ÉmôJb<­4"¡zH\‹TZ8A!%ÐJÞ©Œ݇ól;³g ;A…ãñÍó’¹?ŠR|uW%l¿@Þ-0W Whá£BÏÇ•·:œµ\nNn”OÃf·J8Ÿý˜ƒÎõäãíVT}ó Ÿ[CG³²ä•óÔß«˜Ùq ÷§¢”&½Vë‚e±‡b¡Ròƒ XŒË‘IA«Ò‘‘[ùóþ÷HÐK}±!ÌqÚá»ëÕ›‚íà¢×³áæ§O™Í‡á×õ÷!ÍäÔ Íƒ×Á-m*FóÓRýéÁ»#«iÓÍk ÒŽ8_¢r»%D»t5:tús WiÔ8> "ܾ+ôìÐʉ¹_°˜vÒ]*¼ÿð~2¤=WÑ9?>y À¯º†¼ËoÕªU­¸Áco —˜ïÖK„DfÕèmðw T}Ìüf•Em¹BiÍ4Œ©hÒ¥_` ßõ¦O°ÉºÅv÷e{ŽÈ7xÔÀJIyÓ<«rÆ9Õp´ ±D“‰–kúL ¬–K4 :n «Ê›¶}ž­évhoÿ|-{â’vŒô•”"£Rܤñ§ˆNRÙO:•E Ó(³´×@‚ÓHûª·%¼ž£vÛ™>Oô^!7ê†ÒƒàFf ÝKï$nQ1?[ä(«ž‰°TLžq btîÌ•l‘z­ùFªíØsïÓZ(mE楴 /ûiÚ‡XUþú@ói2FíÙã÷j¤a7ó¼1ÏdW˜Þ› Û£í«8,‰¬ü¸>/ðsƒcE$a¶´Õ…Ë=’þnÊ<l¼‚ïY0„÷.-òEí\1-R2óÍ×¥òÅ¿^ïÛôü‘~ð`@-ÍÓ§~`^LËÑ|L6½r0žL×7;ÆÈá±÷ÅçY²ËsÖÏŽU†Â4 ÷[øâe¢éLêõÀùc²¸:öù…Aù )Î[9?ÖC¿<ûàÎŽ êü°šìµ…÷æÕØëޱçË8á†ÌöƒúWŸ\•XFOZ6Æ2k¶^95FÚ·z…Ü)™TUUÝŸpDS|˜úäNV¤T"ß‹ÍúTJ«ØÆ}ß j•ŧÐù½èa>UÈÏè×ÒŠØÑž¯]¥¦ !À°ÎÜ#Un‘ÍÒôVö'Þ\sskoo?験€Oåeƒt´GÁn?ù Ôy+ëÜe°ÕnÔž{Œt4HU°ò ¡ƒ×Õ[q<¡W“ W ³|Æ]gË©¯ëd¨,Tjc|é>^¬*3T~Ç‚ã¥Ùm)ÊÙe{’€Ué©_4\~¦8¢†0Œ’Çêð“Ì ìÈþœ'È4±Â1¥µ>|xZÿsBŒ %,¡Ø–”Eô^8*dpÖëuMúöÅ%0QÒÌ ·¤=àsöÉÑŠÜAÝœÅJcMTKlüÏ+mŒ0kî âåÙ0eó˜a:`{Þ“¶Ë]”°ÑP ón–Ö²˜{Þ5ÁÜo G¨²G{i„Tï–ºt 'z–ðW÷lb?#]ß=mŠb«rë/x%&Ê6VaÙú%8µë9Ô9TÍUY½á“¥É3ïß µªÙzô»ä·Ú)•2¿¦8÷ñÞ;X}õ±õ9|m(Šz?Ž©]ØP»)—ÑÛWk ãEìŒx!ÃêHºáh$l'³lޝ%p^ô­©võ–D¸U±¯¿|tZ3OñöÎÚ1Ù=ÞyýÛùí´†¦3ã«bøê3\»áN‡|ØaIÊbnhyÛ@Ï‡ÈÆøz…™' \ò[ˆE5™KÒlx™r¹8»ñ1w†vj#{þêf–éí.+F³ÀM·œËÝm2#Á¤îawúÅSßÜ›„]| u>ÒlÖ|9¥s׃~å3+ÇU< èá¯H+o×[](?òa0S>É®ó §ðÈê2~¦Žt&ÅEzǧº$_rØTeX2Ùâ" WûxÀŒtÀµø¤/Q‡Û©,¯•s([‹Ž'¨ª×wDFƒ‚¸à>»«7Y©VDÆé%¤¦Gíh‰‰Ö+ÙZ˜z0•ž,}*ê3ΨSWé ‚_·HXêðË×ï— A‘Áß›‘ð5Ï÷¢FNôÉ=ê„ÝÜ«ûñøö%ç^Õ¢oîƒ-ا;0¬rƒïËRº ã‘öF•€Ê…_Ã_š''íËÇÆ"Þej8Æp¶2§?XÍ÷Þ…},\·ŠIHèéÐy­>—+¼s«“,’kzEÝø )¸ã¢o1@íTÅR»§Äè¸jX†vú¹ÅÚHŽØ«óÌD³z<Øi¿±¢>àövžAš2'Τ‰ÁoPX¹P×5}ª@C~d™8|’; ¥°ÈÒȳxP! {O»ûð‚G˜™U78O$ìsyþpl½TD*–BÕ¬Nê6²÷S€6Å(r,~¾î]D0¬Õ¹Q¹—*µ *—úÒf;YQØ×>öæ¡XŽí‹rE_ d¼f&x¡\T¦î™ˆ±íò®Ëuzq…]ŠexsâÆ‘{`´ûTpöÁ©á/ú~MĮ̂˜Jñ—2ž;±ê«íÙÒgÇ–>çøM½àpª¸7é𮦛V.w˜ôÏ_ÍøÓG0‚äR6qˆ¸¹º2£‰0•CâW4ƒzOŽ‚¢tsc×Ak°³ Rä¥ÿL¹úþ ÛÍÝGkß2æ›3cceó:“YzÅÑaÑ{fû^òdãA+蜸 ¦±¾ØòŒzU úq&ëÕ(;Þ(V)n_Á)ì¥ÉšðÆÌ„8¥ލ¯ñ3vE$vÛ{†J ÀSÛ8¨¼(Z§ØèÁјfçÑöDíÌG¤ÐŠâêÆNŸYUn.j< ê:Í›9ÝFd¡gáÙ·¹èGÙ[½Õ˜ óŸûB²Ü_Iföí<¬“æ©Ú^ëT2]}p'çþIýv:)hé~%ÿ½r»;΢ u³ÿ>Áãô{šG=X½•?rõ‘•›†IMa”J›vÒ‹é>:L±{u11is ·~ÿÎa‘ϯG˜%%>ázý öºaÙÏWüs¬ˆY¦cþ³™Ú+[ØÇ*q¡Ÿ«Ýœ íkøLèr#Œ8ŒcAOÕjÉsÑJ´Ö³#þNÜ#±ÕŸQXDØ« ¸K²R‚°¥Ø8ÚLÙpöckvÿ)Ú ƒÚÛN”$ëF4Ù#-ú§§`7Ï5ÀÁÔ!8‡‹eá¶È'·“©°ìÕõ_c”+óz“‘eÍŸù ^ÓâJž#|7¼YÅqÏÓCùŽ‘»¹ÚÞ%}y©¦ê»§.Pq|ô,8ïÙQø›ß:Õ‹"ÀΦ}-s×M¦‚fçþ½º8½†ó ‘¥9× »½õ€.³2~æ!zhÆ”Çsn â†‘ÒÅ™sOö2ÿOÈ!·Ì±w?nPœºŠ' Ëê žp£Ï3ˆÛ´ #šTã˜áÏËPX1tx›Æ>«w ,ᯒLŠîëf. 9ô;À‹ã76³©¦kd}‰Q— ³Õ~I:Mt¹>‹…Š%Ñ®:˜jØRÃàx„0T<úä£3£ã|m¤ÝWܧ¿bÚ39æ!Ââ8@]ðîôñ‘ì¾ ¸â&·yô…|–Ãs´û²Õ¶ïpBû1ÃŽ q@<òô²Ëh”§_P–O‚s¨¹@¨}õ‰†Û9-’¤œOÀ’ÕENÊDko¦qûFaJéùáZ˃1ƒ€¸OÎø˜Fáu²åXKõÏAžÙæ`”zËæpˆà6¨ãð™_©7ôV¥(³£w-IoY¢-0;ŸåÖÁ&8a]<=m ö²ª—ê+ÖÏ"ß™—ÜSÐI¥ÞZ=)¡Y`F[Õ?·P´Í[Ö_&Äõøä¡ xB:¹¹ˆDfÿ˜Ïe‘à[VROYÕeòn™%׿lÏÒkw˜JR,2a³õÃâ›h)³ßîë4M:»pFÆC£Œ¢PY±c ßËyÉášp"+æßôÄ—´–EïG>ÿôžô¡Ó»ÓuóìÍËÂUj—µ,õÛ.Ëðñȯì†áâ¶>.¯ÀõåØžß$",)×ê'kØ3#‚œˆŽ_ÝC5ûí™7x†qøó±V‹ó@¸æYIo· jNwßú¹CM ¯4-öm/mÄä*¤é}”%´¨ý°Á©…Öåj©æïVSÙH‹Î>„ æLŒÎO´2£Q3?Ïëûäéµlž1ì}ë`éè´®®é,¶LPc«ë?ÃË0N¶)7çæš…Øöö“{iS ~¾ô÷æ³ài:ý!нzâ<ÎÚr_Œ´®Ý‰)Ð69K¸=õ‹¿¼PŒ­UÆxÊüîAÄ&5OŸ{­¼[Xž'&&²fKÌF‰}”Ú‘eØÆ~{àœv#g4W•#TºÇŸ "‘vÆšBþ“rÍø©|ÚZ½wüˆ@ö䮉%²»áB×DS6–¡W?ç¾éî‚ðM—»ªA8P=®QI§ç"øO*DˆÂkQö½¾YÑIN–+ kþð2þÄç^Zv³F¬ù¹Ù†¸Ÿ\ÜûC'ꢰÉ|_í·ð%½)¿L¨œŸ@´]ðû’zß22Þ³ÙÐóâs–ý:7¿r÷p; "ġ妦Æ3±Ynsí ®† µ¶WÔ6MògøòBó „]+Ù.oEÅ?ö{"óq„Uj×s¼Ö#pNæn‹¬Ž°%¶9ùR²mWçp~֭׸ûsC·9€‚‚€èÇån¦þ§in©7ÊßÜÛ]Á/Ý-¹¯”l¹wº¾zLÌ‚ZŸ¡+ø’ɦ%¤ ©³ôŒ> è@*cS´Ò¶B(ߣî¦Å¾EÓ,ýz )º2ˆ`$×¢TïIPÞÔ¸4‡»@%ZyH:·Ôïºú~µ,ðµ0òJBÊ‚Y­xù#>˜Ì[cÝw£¯ÍkXPš2é’Zñ츑3•‰šx?Î+Ͱ†øe®éÀ숓c§²%´wvÛ5b¤šÜëÍŸ*ªÕ¯{œ}-ò`xx’•a“¬ºmñ¼¯óƒ-‹èºIòNßXBZÇÑ‹vɶ¸™}K”j€³Šþv;Æ€½[깟•SK4æµüSS¯Ë‹:¾‡öe ¡ G¤^ö#ÁQSï ¿öD/ TcˆÄ—ZÔ‰(# rj‹©d|Ë"+š¶*Iåœ1Êëk `Â#à"51 0 ÍJ¶Ý³”¯ ’2Åì Dr)Ô¹Åx%¢’;qÆi5¯ŸÙ¼âBÝž`I¡íx}Ç—W8zF":ÿ@¼£yZ½çká³€ž¾oó)ñÚ‰e£œ}Í Ã_/?hÑ´m„*£ðøT·´‡áiŸÝê+n®‚ÆÒc%vè>r+õ *68e˜ ï %éCÕ®Z¾¾¨-$ ùáãtÎ(ÐuÐ{ìLAô½š¤œ]|=úŽHŠ0eä U7‘8fè'°’±w:Š­mlέY, b¸ ­¦#ô"„Zõ”êlîZ$žOW¸ü¸ ë(3g¤C]—ÏÅd$³ir¨³>¤²eqÿ¢0½(K×*ÅT$%<és\ oMjÅè†,æ‘h´ ^©©~6i]bz„( ¡[ì˜ÜNI4‚ô‰EÄŠ ñüÚ¦Ÿðæ·o¶ü’K&æWÙªší ãí3Ö•R;1ÈÌd½BÁ§+/Ï nð„…ì{cÇ€üßž¤Ò!ª–pð¾Ñ¨Ãûš/‹Î\²L `»Ö ž6î1UMíòžb±ØÏ¼²É,ÂÖþÖá໹ ^\[q…‘ÂŽaÄx:Åt©íg¯õç;Óœ | sœ.WÐM禤äÒùýþ³I=›I}ÍFõCMòhyÁú¦¥Êo¨iÌCùߪb’ÇMÉ3ÒÎÔ.¤pÝ8ýâ 7Dˆé~¿! ‡f¾W ï<±§ÿ}`ÍÏp°s)ºö °Í˜ÈY¹õ|1Á4Õ¶m%çØcÆL+T7+ê®–s0Æ×ÉeM&¶¶LðÃþšíQ‡?SŒžþ›ürfÒà;ez5…7xü7hŽKà}Er±Îwèé` ·ï ÚŒ•Ž2¬ŒšÇ5‹~®¡$á¢miœ-¾F°p8Âé`ªVùdÈÿ¸ 1ª¤ëî{¬Rì;=‡LMÓ5ºÒ!PDzö4ƒ6VI5xÏ“:ÏmïûS”DØ/yÓAÏû*–± ƒUÁFoësÞ/¤Pi…˜sø¨²žJª‹†HÝþÒÛ~7;5ç¹ôÒneÚøÿò¢úÿ€ÿŽp…Fz@PîTTÿß°go endstream endobj 75 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[-301 -250 1164 946] /FontName/DEDMJU+CMBX10 /ItalicAngle 0 /StemV 114 /FontFile 74 0 R /Flags 4 >> endobj 74 0 obj << /Filter[/FlateDecode] /Length1 721 /Length2 10316 /Length3 533 /Length 10870 >> stream xÚívUTѶ%îîàw—`Á îîv€ƒî®ÁÝà4¸»[pww§sï}ïõèûú§GÿõèªúØk®YsÍšµ?6Õg%U&3{ ¤½˜‰™ &/ªÅÆ `cfeE¢¢sƒAövâÆ` €—— 4ù»øûðqqò±r#!QÄìÜA–`­Ý?X<[ #ÈÔØ o ¶Úþ15¶¨Ú›‚€`wf@ÄÆ òWœ*@' £ ÐŒ ‰ `2L€ ;$–Ø’¶3·ðü 6svøÏ– ÐÑé¯/í?Òþú4³·³q˜Í‘XìÿÎþuólìãëßÅ%mlŒmÿ!ÿϰþ[ߨdãþ {[g0Ð oot´ûwª&ð_æDímþÛi°± ÈTÄÎÂ`ýr’¹Í”@`SK€¹±ðŸ8ÐÎìß-ü îŸXÄ%ÄåeÔþã·þ««d ²«¹;ü—î?èÿ¬Ùþgý7G@—õo¾l‰ïÿ\éÿÛ4 ;S{3@llgfìhö_Àw%*jïæÉÄñWˆ‰ëï>cãæðrr{ÿ¯Lu;Ðwg ´8€‹•••‡ûË?QSgGG øŸ;áï'ÿgmúè4EòSU–§=¥]ÆcÅ3,§¦ ˜}- óPçy·\ŽÞkNÙòª¡¹Zi°Fò¢zåÂj0<¼©xùó•ùÂ63ë-p,½Ü>äq©,¬ð€ÉYsó'ó5 YÁ€Ù2Ù¬Ñ#”# ÃÒö¨åM¦ ÍdWP~­£ kR¤ÄUZ&m‰»)©6UN%én·]ýƒ»’ñ¤à&gýð%ú2Êgg+H'8IF AåÓ}û±Ì@±Ó*²‹î|ömGEϪ]Ú*貕¤^hU‰nƒ4òwé­ëÉa? 'vƒ@#pkïv صõ¸@‡¬Éï.AùPö5lÈ“O†„j˲ešS]ÑJ Ûmpfáôg"Œ^oÑæYÈÕ7l!Daº)þ“¡Ooûi¢“fù¨H fÊÂc=O£2‚É9²Hîãt¿„7&ïhÕ…í§~øÉ…l’jR°Ø“n˜±^ãl ‰0b/ õÓƒ"‡ üµ ‹ELª=K%cª’àFÕý6¢@¶/½t6æ¼P‹>áyþJÔ4´˜þ!”÷ºGë†õ,°6ƒk^mNæ¥øø áôœÖâßÒ ~½f< ßÑÎÏD“^þ4Zà½Ð ZQFŽÄD¬¸íñ9\pioÇ+ß ¿‘É0Venr•pIØÔ ør\ÎÄËÛÈ÷R‡ÛÚÇ1å”a¢‚ûý9ô†'øó‹Ê A˜Qž¡jÞcH%êzm^Àý¶Oxm“©ÔÓÛÜÏØ‘O&DÇK”Ý߈ô<îF…¼¯„Œ“‡÷ êD GçÛîso»¹¦L¬¶’qô(ñ*ݺ]Çô»á~3ÎÏP´ Ùš°½àAâ8i¬  Bžo|ýGU”½Z—¶Ú¨.[yóò¡&d;wb @ƒ“¹ã’Ôu¤aw›Ã±é$–ŒÅ‚¬«­+5ËظHâ48úÊ1ÌßNÇůŽªtmNá«¡ïfÃퟸòRËL 8|¸}H±~àî½ ½þ¤4)ØrÀäD“žgÉÛ!ÿÅõëáú6}“ÌŠ-Çrv1¤GöƒôSŠN̉þân<3ÄNnTE•N¢iLàê=‰—¾LgPô›6ø+BulKÛø y±óñ›(?q?rå…ÄP,w> änѵoÎÎ&c9+ÇÀ’3¸µ8[M"ÕX™7cÂ`ÒÌdfó~^„ññ.YŸF¡ÑR€·zVàL*dîð§*Ÿ Œ ;œý‹õb³^QqµJiqÀž+÷ 8½µn|Žp§¿îs9’+çÛuÊJãJ}_SËÕóÒ•pwÛ±JôNgŒƒ¦û¡«\ø ‡{:ÙžÊG€1³ß­Õu¡K˘ä¥çÎY5ž“ Œ§ Dê ×C_4jGˆ>y=³Gܽ¡ÞtN‹ô¯ëYKS…3†‰)E~£u›· É”U†·VKHzz}è´&( 6†ÊÌf´ìÙ…iâøá±&ß°6Žaš„€ßã+ m_/Óúøí#'¦õëp²)Úyÿ /6ÈCþ¯§5’Û­-‚É%.ke’TŠVìOÚžaŽL_&ŽZÞ$™k œù /Kùã´Þ‡ë¦åóÆ%þ±Àå7 ¯eå°ãwM…T‡”[Ž~þôh!“TåÒ¨hÜ•Y”1¥*!„Ú„»ÅÛEbÁ¼¿Q¯z¤›g‘¤±Òœ±_3H]bÌ™º|T Ìd’þ¾¢l’"¾2P’hÃW™Õ1OùC±¤¾lLÆuá'<›víoÛÕ0üŸzŒ.?⢠2Äñtx\ÆíHÓN S^êØeøUß[Äø®¿3Éâ6_>ÿâ÷UåÓàtÁ°í#O+LL˜Ì™pì2–£ÂZ›‹U&ÛwÆo%Qð`~Íë}„9æ—ÖJ‘ßvo„zàï#–ä1:×ÜNdzžäu×¹?‰HE:Üç]â7*®-FtÁ>‚ÑÛlûºrRž- 2°$U£Ú­ÿÕ¿™GÇÁÕÓ’óëÂb¶  RÛæ/ü°X?rØV•‚Ò¸§§åçÏ»ú·v‹±w¤9îû÷åÁòsÇ Â—¯ÜŨZ¥—¾ìÆK¸š Uel:;aY‡J æMW–оÖjåÛ·ªÌk&RšÐ#`-è|Ûää%þðsyã§óW©;kCõÏŸ_Í £@ îfa(V™:rÞ¨`\ÀÖ7]!°>l0¿%ERóqb0r{»§"mœª“Š ©qùu}ýËrÍ·F]®.Ìvõ9JKBê²2?ðk>~нÀA…¾ÕÌf…gÃøÞ?ÀÒÝ*Ðze1éØÅ=|Ãæ´3^øF2XÞÝêÎd0WÒ ‚€TÓS¢Æª­@›äîµâÓ—t8F“ÃíÌ”áØ ð²œuBý\åÃõ]ð˜?8þ“¸4÷v¬Ðf¢ÙôZÿ|i¦õKÄË,Áá08=D Bø`/Ï‘›è^4V¹·ªB,P$úÕ±'R†p‡Tü"!QBŠöúU'ÈÐ{Úp4íäŽÔ˜¨¡ Ó©²Ûõdøv5®NæëŠ5Jr³=ÿ¼Ñ &<7,8ëð¿Ï]0-¦úöLE†ƒË©„†î÷P¹fƒDÃç/¼rvöºÅHâgBÕž„—”~ýpzoÐ]¶B¡ÒVŽ ÎOÂS”´…{•Hæ§È•Ý…`58¬(˜„ˆao®ç5¢ã>‹h·IÒ/I7#†v( Yg0„ù a޹þ¼†² ¶që¥Å˜Ú…§Áô’Ü­ÃñЉQ *âT§{ ¨hQÏCùNΙv°1»«’BNÖùE9W m…y›mgdWƳ©¹ 2üúvì¸ÂE5EöóQ9‘¢Y{}üˆM÷'>áM S—ü'Ô##n èêáõ÷AhgáÞï÷æ%ø]L³ñ@Æ/ Ä4ïØºX%>óÈóæžÿ&%] caZý'”§BéƒW‚÷yÁJ1âeëp¿ZEßßÇk1YÊíˆÆeQw6ø0”DS„ 域aÔË;%=?¢u5Ó#pÚKÎzóѼÏyÄÒlW5DY¶‚­K£ Xâ.öŒñJñ¤dJy[.ÅTÞ‚ÆßvPˆkq©Šˆq9á_?Ù~{$Sá貨º[€GØ•½©ÐñVb é ÚÂêÓ4¢9C-…v¿üCjª+6’ʦÀ=³ó£„î×ÔÕî„´y7;o”ÙR8ú´œðÖ°ÔF€pîYQ~¡%ž*°‘ð– ¶¾bâSô¯wkΆ'œí1¾ƒdöºÃò´SïÏTP¶™l‹h T %¾ ^ÜŒŠ»z˜û1Ë\«“t<Œ\cÿŽìþGC¿e—þKi0‚wz;±ê¡!gˆ8-#âš¶98±Ã*›­QÏÝ‹µ68A_6™o²bp=H/'„¢éÔ.Nvþ>¨:Á•FÆÐz‘oI:ƒ"rÿ3²±ãï6ÔQ¡ƒŸSƒ‚ŸìD3íøvúÜW¹zÝ·¼­Ì.Õð„7§ #¾Ì\ØRÄì­F i ó|)®YÊL¡üöÙjm=)QÁ¾·Ç?­^ËmFp!&|wYê-0_ÿzÀý.îŽñÎXV}?¯k1b}z?žF[IqƒYE’µ’/µ›EùÝóQF:!~×g_ ç·ÄÕôàÇ>u9UØE7[¬¸Ç sÜF\R“‰)‰nx.^#KÞ±üí° Y ØßšI»¿—W×ìó¡Ç\V·Nœt„ÐÍoJ¸hHÒ N)G bQ ÝЬÿο® “ç÷‚æèa³•“Zÿ}¾UÃÂZëöJ°~Ý2Ù°›PPbõV½ÕT²/2¤áíAäٚ˦Dÿ ß§òFó?=ÞÈ–^DZ"¡&°»¼5_ŽÌ suo!XücR˜\9áÓ 1ýÖßîTLåÑŽÍ_àÆ.qˆa^¡à &ãјEº–ÀÚJª…N{]¢ž#¢ft”ÂæüÝO(_ óRäåÃËAyÁqMnµÚ25’[¿ ÄÜmfW\Yðw¶‘põ%èWÛ†6ÛV¡¢)T•¯ãùnD)h4Ìá—Â<—Å”»øÖ´Š«ËIdAÃl ±OëUSQÐfýÚkþFs㻃!‚‹;ªêØßÀ“Û¯PñÂÓLÄñF—hë¼Å<úð hó‘ý#é[aß“Éë[Y«—{Í«Á’/γA6ø(ê Æx'oö¤/CÐ8“ÁczÐÉlþVeŠsÅØ_ÑÁùÊ}s‡¯Ú[L±/Úß4 Gc¿uïPÔ&+ô} oGDë …]t‡,Ù‡¤¹>CWñú¨eH…¸D&§ï 7Ýø¤ž¶ÙÜGö%êÛujŒzrw ÂAUÒÄÝJñi®©ëõ3ðÊMz»Îº ¼—Gád§ùg$ûç­k†ˆáëÞÖÐÖ¼`̉É]q'>Q€¼ûÆÏH%…3M*+j²ç×AÆ„6’S:{¶àÃ÷P2vƒŸià˜¡8©FiˆGß!@+gc>Tþ–¤¤T l.éíXlIïlX-)cÃhÉ‹]Oyë$:G]ùyUž_}b zÊ>}Iáœ]ôOíCVÜzù*93óãÈÒË_"ÊÈP¼ˆn¾ÇB¶ \ïØÀo³ëµ‚Áííìï‹q[a—‚«o”¢j‘SpèæS&]Ê—: lcù¾ì •‡Zš+ZZ`’JÓÌì4NšcT|E©ýjÝvZq˜VP¨g²Náço ³Þ[RÂÏͲêšU±»!Í,¯Vmæümý|y£. |%—ˆx®j.Zøñ“ŸÌ#nÏ£•[‹†¥2SÉ´üʹI¥‡LXZ‡gG¢^k>©<{T}zé÷E˜–Õ?LªZ®]¡XHëZQ´lN†ç" »›­Š8D t˜Æm®ð_Bòa’ŠÝq‰Èy)’I˜&òKX,ؾà@µ Иë¸å浬KŒ_YŽà`NMã¿N0£"%†n(¨ÏþÂé"¾ø¢à‘5[+yôS+ëøÔ@‹€g¢-j4XR1ÉêPÖœ[”rò‚˜N,nP‹Î_ÎÔ4{–¼Ø¥§9⪥KÇ‚JvƒÖO†13ÑÄ·F­ÜºbÁ ^Toqi†(×ËeX·6-ˆáÛ–ÁrZdȃüŠ®ëj{×JÅqäKà ÓgEøÂ¯Ø,­ó¯ãöä «ÜÚwÌ ãGDJQHòIU°G^&ãÙ7åsóaߨ}.xä8úîÅØ·u«š…#n„ó5²E’žR6z¸›?¯ÂiG"YtwªB-Íמ€Ðk­Iú#¸¿çÞu2*§èCm\cä ôn.³’w|¡ö.cÓ%O¾TP^B%?eÅß{瘦̩ñ2Ö\…ƒYÀþ^~ÆÉÌ¡W‚`]hµ9ÜÆ©ºŽ\ÙbúƨÏS>J…™—š2ÓH®_ƒ@É"໣hÃb Á?• K‘`UNn?Íšè¹mgŸãÞ8 “Ü]ßè‘OÉ’Ð ˆ1}Ûþ¨Ö¾ÍxÆæ°v¤ž„9EL°â,'Ä>ueºuÍÍŸÔôŽ TSžTé³/>ˆZ6ØëË’‡8çh[“¾Â—·*¦ºRí\ ªó°Ü„ÄñÜ}³%$fi R0'2ôCÒÓœü^o8lˆÁ§_ö^R(sÃ24¥ÐdÏð¤‡/"݈KIÓ|ù-_ÖÚFÚöxÁ±¾nœ*ëݹ,è¼¢Â28l§«˜÷ëO“›¸Wc5}:uÛ^ÜäTLòg¯2tº¦-ÚUÄCðyBwžC»¡¬?’ÛåÇo0‚4¸<Ô©n3{Bøˆ{BžƒV6» Á9JˆM7VOÂZD#¶‚Ò´kgÏÃ9fÛuvŸÐáƾ¸&¶§IÖz"¨¤tN¢úD`fäsw«bå“ãÙ½ÛU_Ú9`ødV,9®A# Z“2ë–a¢,1 ˜7€JÉm—r®äö~¦Ûf7RÙ£“1r°aQirn¦$—#õˆ-bÔùtÏx•ª˜õ¦…eöìõÐÚö¬tð2÷áP5öâ“[È6Ú?¶U˜HëÞ&䌟d @»“;MñK•e’œl§sík€KLr®™½³t+¡2™ôæ>êó¦àö§u[9¡€1'˜~²B^YÜN†Dªxlyw¶óùŸîO¬aœp;l|ý{P:&D¡}zø„aÞ ¥VØ0²pe{B¥½¼!’÷ XÒT{yÞ¿IÊF*WëQ§˜0ÖnÏg#«Ï¾CÞ€±uH¿Æ©0m_Ê21ÙLÒM ͪÕÅD¾Iø,Pí¢§J­š$Ÿnr®Cne´¾¤d¿Hâ¢WÜÆ¬ú.nÁÁ»f¨­ápš6Gp[!ÿ¡¢ðé›1GômeÁÈ”=*7KY{Úâu]c““ŒØN+Û1¿É=¦5h=þ©»<¶œeÑN,Ûqn¶öûxgxVÈÈá.P"`·øln<ëH €E©Ü¦c9 ¦·)Äí€vÎɱrÛIè{ݨªmŸ7å/æ°˜*ûƒ¼”N2D¯€ƒóž rùC?—rƒHˆ¶@9Èø)óùâQ‹H—3ðqΜn¼/¯›(¿¸)’xK‡ÚÝCõé‹.ñÊ6¹.À=Z~r6aýŸ¯$Çô y衇ü¯æoñ}“nš+'FähmÌ冲ƒ½7Õ_{«5f¼5“d˜ë"û€Øir‘1‰€·2ï$žVÖ’Ÿl4ǯFcH ŒöÇI ÎÚ|ÌõäMjç÷¶$ÞhA¡ñÀaal½Å~x¨¹µ”“íðel-¨q6`Ú™pC†ßCÛózd \$g…§Ú¼ ª“è²ét74v¶Cu’6š!Dp£c”aÊ NI7ʰZë;<?XŽž‰ÇS]@K¥ÓO‚Ḳ“¶9]˜*;„YÎÏã]7Ô˜,ÉÉ¢ïx‰TpØb{–Ħ¯gËkŒÙ$TÚgÉh½OjPTÖ*ÛØÀ†Õäũ۸͕]ô÷]3¡â'ˆŽ‰¿ï•¡N ŒøqŸ¡µçÁfß›í¡vNÖÉš¬TdèL†dÜRÃyüneBSq  ÜKa¤iãTÌ3D¦–ûЧhU®Œœ‰g^=ÜšV×ü&í +rxôlðeîW!»8ûųÛƒ<Úä7aâüAæñ” “ƒú^ –ÀOç9RU7€{ƒÚ--=”çdlôïA-tc¿SÜU^3¦Tû¹Öl/²ª B“·†×n ÷+ŸÈ&xèåÑ_ –ÚÏ„ž_JÞ……ŵÖÎqÔ*‹C»1/MäP¢ÚÒa¦N‚×¼ù©–óY„Uñ.ûW×›&¯1ˆÑÒ{žù™–-Ã?ñ¾]÷–£”ç¿¢Î×rÑä/›rTÔ¥ "òcK£b¤v›…¿i/>áàö8ž<ý.\Ð4!ð(wàdü0 ¨i?Ä„zW—åÿÑA‡ǹd_¿‚Ìz°g›þãü—ýˆ§Yø»-˜X²†ê<ï‚FPHêáù1–â‡6 CÚ—+EÿEöDÿ¸ÎâØŽŠÂ}ÁÑÿªGJwªEØ:RÙ »´,зt³Ÿ·]_¸íYGßÍå}¬£ÖH—Z›¯“¿Úš–#:vF [›¢¯ŠP“ëÞ€Ý?[+ppâ”ÂSÝZÊdhåž k"º—l„"‚Øu0¡µÓ¦3)é~J­¤eÔ-×ßnLCH}Ùï5_¦7í%¶j:œ”(J›Jü bR4¢à=¬_‚:5W§BWlËiTØc9g“¨_í'qö}”V×áq÷ê¥ û¡g¥ƒNgý:5ãóGGƒ$‘ú9 P§NrƒÏÁÈqÆík ú¤p Ó“uÆ´,cÛ.Rtš´÷ïÚC¿U¯Ó ´îľ¾X–·ÔçFY fôË÷Ö•GÚ‹ÏC®(ÆDÝ™làJÜÍFàjî‰Æ¦œYô.XðŽ4/ ó t®§†•‚~:©ƒVwËצjµª‚÷&ÜC—ôV ¯÷‘Kúò&pWÁí:ƒ&  ¤èÎÃeÃÑñL;zIÉŽEqyÍ£·÷M?‰¤ãsŠår ßw^£–.vœ(Ép,L'@ï½UP¹™ÖS%i=>9‘ÌñUÞ°CÈÂã^?}-éNW‚ñÀðÑ×/ÊkÖFßÓ34‚l• »–¾uU ¢Í]eˆË# .-§ëø\àDZ”®ÈÖKØ·Ÿß‘áêd#>R“ €×ù7‹‚‡G¼­ÿôãeYã’Þ B¢‚¼Á¢²‰<ÃEÙ˜ÜotùËX^áÇ CZt Pzcó0£ã{…Ѫ”ŸIã—hîwhº¨<Ï›p‰bÃ0~•ƒhƒ?Xðu;@Ëp“WîDãd{Z#–ÃK‘7Bÿ1tâ¶+"v"­:ZëCòyõ½ZQ!õK'"!á {-øéNÂÚn˜¨½±ƒñÝnߺæÏŠ©ÁÑï÷roÊhÖÁ½ð5es¼¼em‘¶èD½L‡ýn tÇêÞÁüï~Xá™Zê“¢Ê×Мy¤š ›NÈq¦ûKp•—3Æ2ø’ì.¹*èeOkF®¦ys=+¢pG°$ìa\Ÿ·U?fÆß6,àGfÖæ„îyÇâèÚÒE:VÙva|ÇöYOTS*ɱöfª‡i‘Â=ÐJRïð—ûxåœÃVFÞ˜˜vZB'ŽÈÁ§dY~Lå°³¥†²CÉýg¨傃]¿/žÚ5¡âãU¼Á’úæ…ñŸã_cÝ=6W#Nñš “!®ùÒ‰¿“¼ÂM.fb1õ³xY=ÕÐ6ó4Ù`ÐXiñ%+ùÊ8¥ïøFU‚wÑ)T¡A¼ÞØÃ¹œÑCzÚ\rØ2U•o´°Ñq¡ª«Ø®!mwb?ó•à¥ëd Äôp \BÒ)fÉm.%ñž ͺâÛ7Øg°I¨ÝPãî%ÖÚœp_÷¯á´“±zßëÑUÌ¥ÂÔKN3~ÃÍOÍ>p釦ihq̘ʤØ|®^KÄ ¾Ã͇¾F5”¨.ˆÛ%ø‰âFàTe@4’²5=÷â`*@jغٿ¼¤eºUб‡Ài‘è„.ŽI"OÓ_ZZ¿,ð/F*ÞKL£_òÚJS'‘“eb_ØTÕQ‡òy-ëöjÈEk–Þ2ZØÁIìaÇ'ì‡4æ ¥q“–B5‰òg~J¼°ÆJŸÏ¯ÿuHö¡ŽŠÃ„+zÔËúÛ^[Å\÷ÄýœfRô„)H kê•fÓ)‘¾Ãg§ëuéjû\zóK9á“n±4#eÕ0êüÛ!º¤ân܆ãòiÔðGÇÊf²Ð…EwåÃ…4•£R6†LÚ“ø‚ÙWë™ò¯ójlðøè ;¬áW_d8½ÄÜ]FRJèÜíG*ºå8u‹BÙ»z vuE`Žp…aΡn Îu+)³àœ$ùé—µn$4–cÙkÉ´Ñ\™è´òŸÛ(Ѳ|rî.$6PÃH³†|;³˜tDÇì’*U¤?µxCè¡å¬”ü(!d+ç×.7ø½.cæ X¸­îLèu•ŽÎiìCжˆ¿óïÞ¶Åîëw¨J·óØw!ÀZêÕT¯£Õèþô„*}(¸áR³åe&¿Á+˜êÈ>ƒÅ³ä—¸ ‚6 Ò½¡ÖM+Æçš/в@À78H§P³¿¤8Äê›8ƒ{.ÑÃ}¶ñáSßZjSÅ™1CýÂŽîwRX…Èö\Xä™&I&1a…½ËœXîh ÉBf6oëõBÜL¤÷¯…ˆûœl¦€Õ¤}/{¶\@÷È eÓ}Ë~j¸K1ÝB³#!ºå s.²r  vßée™¾|Í;B\¶í3òºàLÉ;!­¦¡ þm€ADì^¾L˜mïZ„ÓãZçZD’< œ¾ðV¬ãý\ð %Ëûj\¦Øl£?Ôà›WéÝ Ó^t..ˆÐe–Oí>f^~X׿õµâÔç=èÊ×Viqå°þ,Ζï`«ëHklÛ¸PãFU½ IøÏa9õ÷ƒ½®YìÌ/aýj X tI>§ŠÇª@RÚ Î…ö¬ˆT}»üÁäßõµ¡q¬«þ£‘Íûcõ0µ‡³|Û¼ÓüÅéîVÜX s%‚YÓ<%¨?´IÚW“2c ›7g-×|i-Í¢vwܪ.ÇâHÅD/ÀC¥‹%ìÙäJù«=+OÎsuy{ø,!€Ñ“U~:úëÝ´'ÒtÚúvÄã y…xX·Á…lZˆ =²Ü‹÷:üå(ÿ¬®K¤úÂúk‹‚ê44MÑ–J9Bº¶ÌTZ¨¦L/?þèük~¾1q®—վ櫬8äÐy“öXq“ “%¿2.¤!9âs8¹ù8•ü©ì>žqºl™ÿ×=áq6¾˜¦Ò=ÙÕmòL‡¾èO—P9È#ȉ{<}ja ÊݦKü?ñ]hÈù»™%éj ó·u¿]ø`”b·ñ%Fêbjè'øèºeä÷Ñ‹b©}ÚÉò °[q»ÿœµû %ŠŸ:>†6xó×^Ô`j˜¦³€µr[~dN'6Û‰œÑð@$Óý }&sç8&«9F;¾p:|CƔژƒ’¡ÝT»ú=±›TR=oc’†òD¤€+s±gPkMtϺÃÍÜN%ž% Íð[>e«—G¹í£i•]ñfbZ –§¶@=¶€Ïk°KÅÁаdkV±ÌXÕ” l÷FH4`:w0 t|ÒEÉ[VåÄÒÝZš¦ó )¼¤=¡R ¾½ÄµõQ*[èHÁâ¦&6ðQ//)åéT×1ÖûD“öÈX¥öt5ŽRqñàÚðV{bK<@<“ßL˜©r–~U–?£Ôæ+ã\D6Û9‘|ÂX~º k­çPm0ë,W²¤¬•Ju_ž[ñÙÛŠJ޵êÑå5_À+¯çìþ b‡ªðHÚ9h-1“Ê,àè¾âXï&Õ^÷ì~ÏІ² ïQÑÐ 7Ç<³[ÝQbÎ"ßf£?aƒOo‚ /׬Mt­‰?ËÚ·õ#§êÍüÒä«psÒï»jŸ"w2ÄT²ñ›Ûcnú}ždÑuÎܘt^P¥$°ôZð¢6]gî›4[_EÆ[@F¹^«ˆQ‚eÝ“V²PÁu »ß¢‘*NšŽ¾«ÀëFub±pû“#R¡ÙûÌÿ€<ù2dk-¾ž” ’ ).Ðm¶ÖD`;­ê9<3&â×7l‰`º¸–5WnW(Šºdöfú {ÔnˆÁ\oEß°ü]ŸYâ<ð‡kÍhôP'ù†§Tùîo›2jÄvÝmÎ]Y§ /©[ÀÁ«Ü㓤ùá€Ó Q˜nòÛ"E*¢!‹]ïOZÈ„súP ƒ°öÅD¥iPÝÌ{•k*whŽ„ ÈúDìÜE,)ïd| ÔϿѷ¸ÈIaž<Qãñúj!W\oÚyÜv»Fè 1án ½Âà“$yÚ,ÓMp1föQsá>í2ñg¾qе:8ãku¬{™è)Z³):]F=ÎÈ1¤?®f&/‡mR|þ3kô éjÔd^[ª&g S¡Â‡Ð¢ÂÐ â#õWBdý¿¼þ¿Àÿ¦6@cG°½­±£5ÒÿtFá endstream endobj 90 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[-61 -250 999 759] /FontName/LQVAPM+CMSS10 /ItalicAngle 0 /StemV 78 /FontFile 89 0 R /Flags 4 >> endobj 89 0 obj << /Filter[/FlateDecode] /Length1 717 /Length2 1218 /Length3 533 /Length 1751 >> stream xÚí’},T…pAÀƸŒËÈà(Þó[Bo8‡Á™@ Ráâ@ˆ ‹y(w”‡ô÷2$ ùÐ ƒÅ¡*.ÀDŹ PQB*Ì#XìÇT§Á*–ÿ먇»J‚ý p>~aP¿ëƒBD ûÅ C$8,<0£‹­á÷p0„H„‹»î8(@¸Žh (ï%$Ô‘Â Á¹|€ BáF¡ÅªÁ- Xìóòudy~ù¨ï»,Añ²_sçí 5õßµj@bD p(d …ª2ªÖ‡·Ïæ‚r1A6¢(†~~Oåä„I#Ì­©€¹%0 €NcDýÖçƒ"" ìî Ð(Š ÅjAåJÄbÅþÕ…?ÔZÃ_«>pÉá˜íq?ßîÐkà賨MßÒŒ:ìÆå7>òÿ˜Ùhÿ:.ƒ–@sèæ+t”;¬%xÖýRiþÐȬ–ë—»RœRš“ÈÄæNß`tÜîÄýè8.Ð÷-©Ê½ùÂf®»FëØ²¼65¢-­ý%ß{Èñ‰þÃ`ÄS*ìúf—Ýc·øá¿¾ªÝúYë“ÔÜá?ÖÖžÝÇàçÆ™;EÃÅf is„§YRüÞJ‹F(‰ò´+rÊoÖmU¶Þ$Z4ck¬c©¿íÞñ0Ñ?#c«J?j4…ÃFLâÇ{÷ ÞO»¢N’7éÞ`8oBÙ%FŠäL†Ž&g[ÈeæXkû[ÊÖH#iˆïâÉ]¢Õ×îm6z)ÛÀ§dWfHÏŽÏf»–ܰ¬ê¶þָ𚛖2‹ÑƧnD³NðŽ^û†ú·^ÃyÛ«›¤îí]:¬ýym·Ó F¦Â4‹^¶«PóêF#+b"«ÚÈ« -®êÚyÔ |÷g£äiZýݺêŸ$õîÔh0>iW€Å<·«ìM>^dÛ ’X܊̬3*_Úòðvõ9§´I0ÿ•ß8îš^mÍ¿Rq=sèv!Ê‹³[§Ã°ƒ> endobj 96 0 obj << /Filter[/FlateDecode] /Length1 722 /Length2 8919 /Length3 533 /Length 9480 >> stream xÚí”UTѶ¦q‚;ÁnwwwwÙ6²qwww‚“@àîîîBîsν}GŸÛ/=ú­GW½Ôüç_ÿújÖ‹†RM“EÜÊÑ$ãucageH*k*±ì¬@TI¹Ä*eî°óó³ÄÝ­û<\@ ** @ÒÑÉÛbmã —dø‡‹ îrXšCÊæn6 ‡¿!–æöMGKÈÍ›··hüãW€Èäâ²bEEegXA,Ý k•íPòP°#€÷_²•»Ó¶<@.®¹ô9)­¡öÞ+•MÅñïj ¿,ÿÇXÿª—q··W1wøGü?õßúæ{ïÿp8:8¹»\ÊŽV è¿[uAÿ‚SYAÜþ½+ïfn±‡ZÛƒ,ü¬\<ÿ’!®2/•ÄÍÒ6·wýSA­þäïðþ‰Á¦"¯¥l Íô?ö_]5sÔMËÛ þ—ýŸ5ûÕ‡äñY@ö¿Æ¿÷>ÿÛjÒPKG+Ô éfµ2w±úŸÂ§’pôòeáá°ppÿÝHìœ^n ÿÿjÔ†BœÝAòRn ÈËÏ÷OÕÒÝÅuûçføûÅÿYƒ!gy,Q?å@`M™?0Ý™Ýö.Qé¶Oï…~ãç¥Cn @§øìO¯@¾™û9sÅ¥óë$_‚CïÜÎ`š¬˜,& þ½É32om8)S^y!Ϭig ­)ÿUÏ“ ÎGÈ®Xàxu윞÷‚ a ^&òr§¶ë*Tì¼ßÄ:ÿfv…Ð!dàÆþŽS]ª`‡í‹† YP7ÂÑú1h¾¤E_ú›o•» ÓÙ@#çýVÕ6ä¹>óm6¾båüî ݌뮚•ñ®÷‘N%»@Q¦FÑȸœÚF(«j&2Ù µBIB´ÝÏ`}­MkÑžÉòœb½Ï­*~ ,ÕT¼†8¶ŒÅp®ÄúÁÁ`ÕwÚ¬“Eô´DëÀ ^‘kÄ•‚𱡱 }£Â{³]cF¸ŸŽmO‹ÆjTù6H‚.qöù5“j±ÎGÔ{ÆÝ•,i¶^°ÝcPÏgz‡•ûìÙ©|‚è{Š›™Ó|’þ—Ð4,©Iâ·3Ž4¬CGÊOøùÊ©"×áÙçY=  ã%ÓV´Þ^½‚"ô;âìa®mã ²Ïæ’‚]íïs|S,õÌGéüm3> Ð¾–•–Fó¿½({vëÿ ž=Xéî æ$ö¶rŠÀ¶mha$ÇÑW$Y¤ÐÝ’µ#¨ÿ˜Ð…_ H(%¯|‘+[.~ʸÌ6ì}žI®+:qƦ-ÏûˆP-Ø`ñâÓ¢Â5W„ ßð†?THïXðn-¨÷-I¹ŠžKöÉH|ݾáÇÊÔ2jhó)ÔŸ¹ë6§ 8ÔŽáPb™wñÎÜ¢ÕבL·˜âÛý FY?ñ NÔÃJŠè”Á4Êx°ê£Qáym*PêÒüX‹Ø|†7zÜÒ¸×ÂxâTï%Q3ßG‰x`;¯ÀäçSσ÷|f˜W—â³ÐÓ'G½Ã&xúµ< ¶²ëhLèV]íl°·eGùq¶CŠ8%úR~š|Í~ë·˜ÄÐÝà ßú©ØEXV& ¨ˆÄ¢ìåûXœG¬¢¸$G§9££­ñ]¦éºíäÝÕŠö„ðÒAàÍ’ ¬ ÛJê03jÔV™ž,ÐSÇs­ä€;O{‡4é·™îpí¬„þe^\Åþ ~½ ýVŸÎñC(j[ë…C³Ef/†uTžEô<ª‡âþ€4`×n \½n]wQ>:Êù]‰¤FZЗ…ÏÔ ~Ô{˜6€ ðŽM—j=ûþÞ=1óœ,ûÎx†‰Zß=½ô~/èK÷Z¬™Ëˆ ÷ÀMÀ£mAŸ¯](S2ºto„³ö±¹:°°ÿsÇÕÓª b7ïcµÄakK ÿ»W¢I5çSªôYoÛƒÝ+Óü|aÆÚ¥º’X¤¡?z”éÜ>ÛTÏ8¶[ öF°6n(Dª(l¡i¬W÷ÚÐ$°Ñ'{ãžÑ^W·1 ßssŒmŽ¢ŒAë‹ÜÎ)"ûèݲ¼J‘ü3±¹Å`æˆîÑJãšÍÞ7ôÂ&m¡ f¼Núªá¨GÒ–i3qeç9òƒñfóúN¯«&Â計âÁëPwMûiP š¨-²O„±(èe“nÑ]“,'ý„ÊÚÆm?±#“;2‚+ƒósG‡ÈÉËO–Ó]2AÖ5æµ’!Ä=šJ´…ݨ¨÷0“­;“µ˜{}á¥v' K;ô‰8;Æž¢{Ü·6„Ý.×ô¸±-Ñö‹1¿ î€v=ÉÛ âj¸þ•~g…MzA<°ëÔß…`ý¨{²æƒ½«]%ë2ww¾É¢†+MwcBöHËŒ=–cÙ ñú²Móo ÈzîM¬è°­$EQNnǘ%ÛfcÜŸKÙ¦zÑ5ã-þ¢gEIã>¬oÿ*Þâ¤,. £qô?Ô Ç¹!ý¶Ûp²Ëóÿ‰ÏØÅ4­©Ä|YÜëâCoywÑ&ÿˆU>Œ,ØjÃŒóU½¤zÍ¢ÕÈ_‘¬Q+Gæùe f,Œ¡á[)/.ð°l6µê®ˆ›ý+5ljKÇÍ,˜ëqø£+Iº†ÐöÏ+V¬¸{)s!g9M¥Iÿ¦„yvC_®ƒh‘T«™EØvŸŠÝÑÆ]¹}K–ÅFw¹QJH¼|aîœÀÌ•2VqýŒ.¹ŸìSíoÈ4ËÔ'•é ˆ¤|‘í˜*âú=MÕÉ0j8èYÃø• šS¶UÜ„°Áóº c¹l ÔRæp‰ÀÃñ¼I6µ}cª“º¶j^2l½{¥I×~Íj"A¬ï«Â†]K­6fÁÔuÏúíK.K}ܾ"½b ½_ÿwaû§ѾÇg?¢ðx!Sbºq® ~’|ýðÖlÎÛ˜7£ÀF™q§w´ÊlËq\ùˆZ‘óÛÆ1èSe/Aao½öóÀÛ4¬úÏ”ø_ËkÌxKsŸÍ/Ckú¨ºW«)«²÷„ña<•±a[››&òœ0©§L^u—NßzR¬|>iÃ…ÁiÞ;Ò¹¡£-K\;Wžz§0FlìH5wm{­Í+êÖhÆ'›áZýÐï°7bP-Öüvp ×l•/óYs,v:ĺâ“1zïºöŸ¼®•é+;ZM/õlè%éK)7àbûØÃ©M+Oø½gCW9¸Ê0Ñïù„Î:MbéØ}öòMOBÛbÛw7-3FÍ|eG{b¼Õ¸%Isa8k\.W 4'ÞÞe¶’,¦Å6H´™q p ¨]Aã«Ri‚€<2Zó e9êü å—ŠØÙÀQ°£~ƒºRZºÅwµ9ã¶õ3,j“j­³$çx[ü}·8+A3tlû•×µRqrŒêh%¿…MVe·È¢—ìûð2fŸÝ¢j]+‰íIZ4Ê‹ûwáê„_BBu}=YѤ{XèÆ*…hzصöo…׫û_³/SèlßûGüü‘¿€¬X¦4\,»NVeÁ²ág¡Xž„Š ÁÝæ×§Šÿ†ëL¢´ ‰G Q;žÚ„,ŽIRVyýp¨Rú¿…›‡#ŸfêÈÅåÑþgf³C•Sl€?¬4 ½¾»õ—Âsòhû˜þÞrðÀùãcŽÚÝNR1å~`5l¦ÿÆç‚Ëʤö€Ã]KÖÖ°ˆ¦‰p±ŽùJ¡sF W¤Z¼õÇ(-ýV¾ïMbö)ðiŸAþ7M—ªªÜ3?#ZlÕVpæƒ^ÖÕØhK®ˆ«Ñq —JØ¡& ¡•;•ó3_—.b÷ þIXåɕ뵑Ldípdl3)Tîi%ǧ€¾j”I:—Ê®°ÍjaݼÓ=”ð.ô†cne˵/âШáL͵ðjí2,™òƒpôÛy}&z3(!c?¯õnMb¢¥®ÜèœìŽäÉvOÿ¼•EoØ<6¨wáÞÑ9r‘gßöÊNUÝÙzþ–Š*W_{$”Ôìù{í/óð³I5µ³æ†x«ß2'bmÍÃÔ/±S'ªÍ’>¿v[ž[°må^Hö¡·PøQ¿«˜;7(ÄòGR-8qUÈ ;´šŠ‚¾ÿ¹¬?’ò”¼B@Jf¤™i^áX}+Ó‹b›zŠöÅó¢õ?B’ô^¬=2×c^¥W(`SÚÚ‡ã„Í£h®M ³½+4¢ÕÏâY˜ áÅ£n$içO;Ï·'÷–ädÅaAlÒRƒYjw‰É<>‰2:6蔎ˆi´9µäöÔ ž›ÒoÉNÃT{ޓܥ ~˜óOÙžö"ϣŌæV$  Fó£¨ÄýšVY¶ÆT]™R¼bjþ8㾟ߨn„¾WMç~‚1jñ: i¤ísÑÈ\LÌ:¡Ÿ¥æä_î ¬"ß/E†«WM/Q•Q×ßÓ·é+Åzû’;Mêf”Í¤Þ ¾¦xZ,ÛKë´½X7xú0(r𒤡§^‚(»4£³7&N_ùÍŒ’¾à’$ãyÚÇž}=èhEK꾓E×ÎÀ¬f'#ËÓÁNT=¶{‘½,Q<ÇGñ ëiä]ÛÚcÈ¿äax…}„ü"®‹ïg‹?“ËåѲ}˜¿ `âïè®Z‹nh°uõ]ÞýnmVh dÅÞ¨&×5Þk³!ÃBlî'¢m¶WBn?q keÐÉ9s­+wmŸ¡:¯è¨ÔöÚæÎôPôWZý&Ò0®´h1“]¿éV ‘ô^±Y5hþþIú#CÏ[~§Fû ?¡ /ÊúèÃÜ·åkRîN«U³bjʺÎÇ ø­¸¨s3ZæÇO³V³¨àã²,±Ÿî;¥ò5×ÁqÙýЉ•ð¦k²qa;Icñ'|ªžaÎ*m˜r&Þù¯{Ñ—r¨T$-K}óT‰0R¾ GÐ0rŽ0d$pœàj5‘[¿vÞ(siã[G)@--]Ü7“DêŽe¤ªö7Ê…Pa•²­ÚYÃYƒdóâÒÒN&,š'¥m¹olÌ^¼7m¶Voq”¹ ÍGT«½öæÈ£Ù•ʨt°—Õ—W ªÈƒ¾$ ÷–êrБ÷lÚ’÷/”üìÝ—g«d«BsÖ‚g ª5ÄšNp–£Ã&ˆOÇÒòº¹ví¨k‘ðOè(†y¤TðcÿžÅåÔ†ø.¶VŸs$eV¸M ªXL¾ V°JçˆØ‘TµçÃHÚàZþÖ‘xÚ‘ÐUŒ_k…Þ”ržîÁ~ŒÎ€DÐÖóâÃ4ãåq·üaž ÎSÍíçhâäaX]í!OûÜa.0ÐXèïæ¨`}ŸèÅ+¿Ç§ñß¿—ßÅÁg­ÍL»}~˜ÌR¼"w{›x» &îŠEPóÊ=.9MT(òÊVмˆž†Ñ$½G†ŸU4-‚Cû<€§UÊY6í,.?J©t¦%Ä–žæý⥚—É’ ÄQ…S;#FÍ!Çí¥gW¼Gä«aí«ì'N䆡ð„3931Ëðñ‹=1²‰¡>uq¦:LM"]eÌ_yÞJ²³Uá±ÐÚ¶‹–O|QwÊf8ì"ñ¨ÄMÄ\;° §hvßü¤óØhò‚ý©+S™5•>Üäg;ùè†ÔVW®åö-¥j|÷=® ‘’åw3]ƹÒTÂ^c%ò‰–‚²9@¯;á¹I©PQµ|ks‚ò–Un·ëWM&WŸÀÑŽ?>ÜåÏLx"72Ò÷r”Œ˜÷K8C&O« ¯–}Ä^(µHJ¡¢ý'²j±Ÿ>‹¦¼7›Õ’ÙH3_´U_•o¿Õ›`«ÀO˜ñ̾ÕOéfãˆDµî/—ÃäÜP ÄT$+>¾jôJ¶9nµÆý‹³5îHé|«ˆœáÑ·Õ›àðe\…ánT-‡á5ïÍ©Ÿ´"IÀv¦—0E8lkÍ)=»ÉŽwŒá²r˲¼%¡Ûkfd"òÌ<2¯„Kœ ŽZAèùzõ\f˜+ƒÁcý1ž@©4(å´dÝ#qØô&Ÿûu¢2„ß HB:Í÷-fÚSfZò±:z;SÒƒFÿùö ¾—pËIÌ>Œx<ébÉrªè­£¿=’þÆ'ñÂB@u:-?¬<Ù‹âe¾tî)›¦aòõ=³¼«gÔ™aÔÜŸ”¸„¦{}JT¶Õ5u=ˆ*ÉçI©Ð •a¹ž\j VÃÊ‹N^ëu#—$÷¹¢ãOX"¹-û04[ŸŒ¼V¢.´R%êq6¾Œçß=йw†µ|3ý'a>Míi冖/›üŠ/²àE¬Øf¤«*Ÿ‰¡ºjr ¥¸.§3&/ûÇ€”X±Ù¹ºÛwyíoÝ,ñt `†£XŒ_Ì¿|GàÔòÃÉcMÀúø?\øi[R¢ %!Ìj8ã},ÿìfÆÛ…„°±>½b”Ê*?äᱚ‡2X¿¸ä„2qþÐ#ÜïqRïÌ ¾å^Ö˜üæ’{~þ‡Hlõ•À2½¶ -fš_mwž(ÉðýG(áob{‡¥WY¾ÙÀ?Ãóa±[z›^Dt®ªŸÛê_1ÎŽs/ù"°¦&íZú°GÉ1úrm¾vå]O˜•©´äm¸óÒè‰ Áý0RÒWiŒèCøp±+rðüüV”b«@ì·›”w¡BçTjqLžÁd¶Î•ÿm ŸÕÞVn–ü"Í% ¹£)%’­ÂA‡ù&ìûã‡ÆÖEªžA¨‘ÿÜ XU!ÓªX‡ã¦3_”B>Š2d,ê?ëšfÄÀ44kïd^É”µ&Ési…ºÔoÒêhyÆAnÍ᯦C•¾8|8¶ôl©ƒDVk9¤¬¦¯g/¦ÖÇÖ¹®/ÃôBEµú¨ýÒ3åõœ‚Q:•[ØÙºO@[—m!9åC¹Z‹ ô3Ìø2åh2ÑX»ü¢-!æ>%r7‚™XÂÌôî×¶’\ÄÇËTÆ£\™HíOyº±¡È0;Ÿl2}Én‰>ŸÌâcõGt|~[#ê»÷8,KëôøØ ³b@·ø: saMÁp¤({ÐlT­Õ3¹Ã8Z:üp¨™}!—´¹'W­°ÜÎ3®2ÿtöha›wæ× äÙg\"‘,z~]ÄI•þéÈ¶à ‹~VHˆœtÕß6‰4;™.' %ÛƒKpŒ¸TȤAh6¡L&$tÊòââB&éDýˆÜ¨¬_(ÀýZkŸç x¢—"H9ÅÇQ[8^ IßWbÙK±«BÊù\Àg•µ @³ ‰ý2™ å‘ÅE8ìz ÈÓº):Õ—ãš;cF,¤â¹°sÓ¢Æq¥¡š×m¤Ä_ONûÁ¤a ´Î\';uòþ]ÿíÖöÛ›â+…]c§‰:Nñ³œ"åÌ¡• œFµ ŽY«G«ÓÔâ«yó b<¼ÏFq¤ ëŽø-Àõ{ëvµb¥|4îBõúW7Øšå@Ʃ؀j’\©ò2oÒØ "&'ö—s›÷VQ ¥Æ ã>x¾”ÔG¤k}îåbà®öDBC¢5ãs×J¯/Ä#à3uëS§eö„Ò4ÖsŸ–É]#ÅCšËõŽ–5r –Å C]ÅFÞ'¦“©`³ˆOätÙå{ÛSïóiÞ»îœÞ¯ûF‰mZÄ>ìêA›tîL€È’RéZñ½Ý/PPëÓ£\¯T´±.‘^/Dñ‰é±À¬‚;¯·»3ï?–‘­ðíõÃÝñF!ýŸð£KïžãQÇg±ÐÍWÙ0‡ï{Áï°ä÷/±7?Ô¾T“ƼNE;ñA™s y¥>Sà@9rOlÐYVŸ¹Fý!u]¾Uf%Ñ:ëxsø±1±[-‡Ö³»ù*îEÔW„º‡y¼‘¿ø²¯Ù±7{6”«¶÷˜iù®ƒy틯bØ #Sm­0ÅÓ§ËÌ?òÄהا–_iÃ’ÂTüðÕifñ¯ñ$‹¬T³à•åºOA×¹Ì(pðÞ³=åü®K]¤Ú°Dm#Û D"ð›Ønè|Ïû°#8¥s¿º»=ÞÖxÔAÌçÚ}…¯E†µÅi‰Žrâ"?Y\tº³Ò¤ÑÎÚ,haoŽQ)R{öÇè#Ÿm“L·ª* <ô\±!ƒü矦¦Î¯Gå´$oŠDÍþ¾n¥ÏˆYø} ýù£‡Žñ]ZÊ?Ñ7Û¦äfê¹Õ+Ë3ÓÆ3¼<É?¾_‹N¡Ÿ©!Î\‘º}’üU%©Ïô€Ù‹]«7°«À9¢×P°BhzÕm"-ä3hòÇgM9¼ûQÚj8C=Q°dfîa£vù¶ïJ­GÊmôËìÄÔ’‰æöZ2.UU1# Åãhx·»Þ÷)RÛî‡Å1ù¯cOŒºÕ…¡ÒkîjÒ›Þ6A…PgÔï<ðïU …ð^Êàçj׋™Ë–á6oÀNs4˜côOãVÌÁ'óûµ÷eLŽŸÐ²ïÀÖ;ÓsïÚOÛ<(“íÂð¤š"nÃQ ,Y¿PŠp *®tÿnœ±aè=Kåd££•Ù^Š™r7™V­kÊX€_£| ª²ËˆÛi8ïÖ© ¥’^ñ¨]ÉE3äÀg•j÷‹Ž?ÿôܳC‘Øïï`êiä–ÙÝìñð| ­(òÉÅfÉb«òH'4¤âð_GÜ¿­i:¤Š—Ò«µõ¦äBÓsgEM¶¦/î——öha íz†¸&&|Mì|¶’µ^.´Ž­[Ø‹Y¬TÌÃY]éªP²»ÐZ•†¦—ïå5§ýOpŒà~Œ»oúº¬¦<ʦ³Ïʺ[?i)nÝØ¥O*̼ó—òrs|°×Oœ‘3 ÅîN³S&øô ?p¡""À~756MC‘qé¾…‡E<›:õÊÛaÎEým“ã!RŸë^ ø üpÖ‘ð2KaÅ"K¬Å9YŒ¿ ™vÒµ3¬9|P%ÅTö¨j/»¹{tr‚°¹Víì<Kß>7Ó3]6Œ®…¥XÍ&ƒ•­k.¾é›yÒÌt¸ÈˆÝØ4 ¢‚•e÷äwW—xÛÎ ‹– ¦k^Ã=Âd(s…*]h(ÌÓj1E­í˜ô1ç1¾×üüË—¨.'”ÈÜKu·\j+úünª¹Rep{(7Vq²N:XŒøp+x~6BñËKøb‡r¼NÊ]Z[ˆ&;Ü<ŸÒ‹¦ºPºÂQ4¤Ë>¼Î§;žl&4¨Ñµš¤Èîo`ÅDLäºq{²Jõ£±ì¸Åʆ½øŠX+^–œŒLÚd)¾ká³qÛj!¾ GïÒ_([„!¸ŽŠî%C“1ë3Uc`Jõ¾“ºÃ,Ìç(þ‚Ñi§ú“z™)îËfõ:õ& Õ§¡—£ö¨Û±×¤àý•X¡<<Õck;ÌÉYÙõ'ã wpZ DN@(²à·ÕðbÒ<ŸÞÝ!Û#¥ÊÔpý±†lQèöÜjWðWƒW/³Ø?©B{Öòñfƒ/ÃÃë÷#câ—„ç~†&‘ØtÍŸÄ¥)¹; ©=Üôº¦ Y™áeÈ2‡&Ý^Ÿ´*¹DP¡uK}âø™Êyô ’uÁ’ ˆÌ¥ˆ:¨ ç2œ„;±š”âY#,%‰ÜÇ¥ˆ(Ì äN@Ù¡Z¹½-ܸ?ŒMÓ—eB{øUQ=7á&(ŒIPÉ­LÎsÂÏ.$[“y½¡ N£Ï^ Á´fì<Ú±<2'¾• öþqÝ n³8ç¼%0$ñö™ÁL%PO‚ÙJÂ,CÚñȵÁ½œáJZC¥4S%YôN,J®'iX˜±6õµÖ¤ §Š¡j ÎèiR¡_KÉàÉëä†>V¾ävòšÐÝèT­ÜÚz5¾½N™ê“è6~²¿E ó ›cUàË-šÁd| ûÃ:‡®è ¶T~ô6¡»%s^|òŒ¿aŒ =6R`6‹yŸÅ7ï êœÒ‚h¢ i,ÔLk+f!äùiš-ä¢Õ×Mç]!ë‘% ZÞÎ!ºA½1³áÝLËêñœ¥8[d¹f \~ŠEw”À`cá…H÷ì,X ›Amô:‰lЄy§”n†"k²0HÃUȫ֔wGs¯çÕî<-[Nü)r[cËísŒKjê`ïJÙÑ ›îÎk‡°Y¥gmdÂäøbam`U<¥`9±Œg3a§"®b÷Ç69‰»ãÃLTó¨s»«Âä&ùÖÏþÖh})Ÿ(P;ïñ—„úµÃ%±µs4¥épl&¯$šïP !Ú§•V–ôR×_Oo8T¶_š$&"¢}­z‹â|œÏéÝxÿ—êÿø"ÀÒdîâæè`îb‡Šú?ñ©> endstream endobj 108 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[-62 -250 1011 750] /FontName/VINYJK+CMB10 /ItalicAngle 0 /StemV 108 /FontFile 107 0 R /Flags 4 >> endobj 107 0 obj << /Filter[/FlateDecode] /Length1 712 /Length2 4393 /Length3 533 /Length 4935 >> stream xÚí•g8œk·ÇÑ¢ £%‚(ÁèÆè]¢×è†c”Á-ºè½]ô!ƒ z‰ND !„D‰hAd´cï}ö{®³ßç|:×yî/ÏZëÿü×ï^÷}]'›®¿ÌÕ®êŠÂð I•´…@@!'§Å ]QÊP \ ( <@á뺸”ˆ¨”ˆ8'PÉÕÍD8`€ÜJ<¨$€ .p4ÒŠjA1p—k;¨3ÐÀÕ Çø œú|áÔ‡{ÀÑ^p˜™†´Ãmá$ŠLð$ ”½+Pâ¯4ÌÓíï’íq侯ä^CÂ\Qξ@ÜžLPÛõºüåƒêŸæªžÎÎÚP—?ìÿÓ¿•¡.Hgßÿ¸º¸ybàh –+ ŽFýSjÿ‹MÑÕùߺh` ÎH;Âý•Bz¨"}à0]$ÆÎhuö€ÿ™‡£`ÿD¸žÚŸ‚ÆÚ¦óýužu¡HÆÐ×í_¶¨ÿŒ…þ+¾é4 €@B×Âëõ÷›å?š© ì\aHh€¢`P4ì_‰‡RTtõñãò ‹]ßÐõiKˆþ»Ð…t÷„k(Å@ „ø_Tvžh4…ùó\oøïØy=8ÜnG–“KÄ·~x‡ïØæ¨k†Ý¤ulV—ìmé,±“B†‰wû·œo Ì9F†;éÍSà¾ëÆežrß“‹z¨Iê:±êõõe®ùÄÀk?ÒnþåXª6~ƒŠkF ع {cmÞÈW(ægu“jéVˆqë8ègcj:› ìø«1ãëšo\ùÚ†'e7£âý8…/Ïú¤ž5®3£cô­¦ p  ¿4ú)Ïo4ýZÝižžC0JûÙv®ïØþ4½$Q[å3Oy¸ÉÔ¨êÅ‹®IŽñÅMc<ß·ôªEÏ5|¸Ä­ •œÛW.õ#9äňÖB:-åÞOW´ëÔoM ûDYº“ÍF‰5% «Ö6º¾ÆSµø…¹±™éGb—8e ¿¿IÒ‰äË»²Š¥Rä £i[įœ¿»’*gÓà\.e“ÄÌQH l³HÅ1м½¬„¸‹ãǨ„‘ök“ˆI牫 k 殓p7Џ·)O:oªxVì=Ëv´$Áõ*êù«µÆÛLýâ¹s5IÅÄ˪lÒä²'î«ÇÎ>aéjûlŸaæ–ÒÌGc!l¨&—ß3˘`.j~Öêä£:D¿ró<×jPÁfX••X¹¶SB)½\úÝô+.ƒÙò˜Be:Júé$pa&¯£1f׺a™ìcÐ÷­ØWœÛß羲т\”;Pmu‰C¢†X#hµpõäD1‰,FÌ_ºfmìmB°2D©d´[;¬=ññË¿²É"*W @†.ÇnÅžnì]O ð3õ‡ÀU•–b•,ê%"ÜÖ“ŒOw—ˆtøó†­Ö^=—¬Š>›®3¶¨Ž—¤U£¬º\Ç×ê3'SÂkËÒ¨Ÿñsež'Ú=ÑoŠfèÆmlÜó|ÛÅ¢G;]E`N»œ¹×#j† =‘­ÈzšÖFjĘJ©…3b´•!újLQßÑLµêôep‡HfÚ—t^¶Š;Û³ü1Êâíë2ýl X…zAâ’€Àj´ݘˆSÉ+%™åÒb´Ã&æÇã¦ã‰ûÊwfÈÈ¥”^›:ÓãfùùSs ¦{ÞÒK>ôM7îñHéè錽G3P°ìØËGÛ ¿e~ƒ©éÄÅFå9›Î]ȶÆ%ɳɨ…È5†;Þ4¿'ƒÐ žÙò#,dmáÈ ºÔà!ÍÕÎU1v*°fõ!w´4Ú$·"XówK3Í•O‰ÈŠ2¹Pã¹!¿BÁí÷Âtë¤OçŽØ/Ó¼ê¸î»ÍF« z´%9ßN¼ÕœóÚ‰¬˜³ýþà÷^©(ºVvÃbC˜sñ`X{\_ÐFWêõðäÝzOƒµ—Û3Mä_V\GÜ aÜ=k3Â4õS'¯AÅÝ„¹ Ú”íG<’à{$pK—æ‹Ñ3ä>•í¾t§w7ñ¯Ûncœ,’ËÇïÙƒà)ƺ[7':ü¹ 3e9 šIôeµz ©§Î§'Í“‡±Jë+ ²ÂáŸ6Óï»SuH݉£V«µ‘r|hîyœ<„©ƒA:s ¾tÈ)ç侨œI[«ðŒ]½|Yò† S ®x°cyÆ4«¥G}e2èÖ°ÄšãÒ®a¡]иo6N9Üë=Tÿ.­U¹JÁ©€ð°C üÞã)‡êíð‘þSÀÂ¥š³ˆ‡+‚ý].ª¢ŒtþDYQB€¼^ÐH|ùJëb”þqÝBÇb _¸Äm3d:iÊé8Òà´ðþï€,wAkµ¶B±FfÞQcûÆ­Óy"uMvÍǤ¨ñ61.•ƒÞf¾W¼çEá0‚²L êáÌuL½i\e¡Á–…Ö«|þ§Ý““ÉÓVc6ù±£IîTKAb¯Î °šbÞkÇîçó¨*á Ô¶6âë®’sý­Q³Eü¢6ÒLò?Ê¿ÍPšÍ¼>µ£¸”$Jö ñðůf´”Pû9çFÛ›õƒŠ­œVjX!Ò¶í,ÜP†c’[§¶ »&XJ|¤-X)y#¸›)åSClgƒ[°Ví"`G¤Z±eìâ¾;ôØWý[‚ÒzÖ0õד¶¦eE)çtöËÆ:↔M‚3,,„'ù÷ëðí‚ýËÚC Ÿt¿’‡w®¼øéØQ•¥üHiÇ V3Áqª`@-¤Aƒè šAFiÖŒ—´q€Iô¿mh÷—Ò1]ì5Ö+*ÿ¢zž&oóJwNrÙõ<°IC*°ˆO~™^!lðPW+|殳 7õu²”Žä¹EWˆ§´F~ÙÃó«#z˜} ¾÷Ö‹O¾Ø‰‰ÄÏ:ˆô8Ÿ–Tˆ²ÿÂ-Rì±h,?Lã«ÃE>ëÉ; 3»iå[F/$1Q¸ pDÐÓŠÖ˜JÃÊz>v.µUÓVøzdî½zm ›ž$>ë]ô›§ìc)ˆJŽ“‚ÆøÆñÈ ›]2?¼çÀ(^ ¬mŽ¥4 zz +åóÞ“µ¦Ô ­é¿ñûýìM©9N›âJÙÕià7J¬¤‰ºÈ7ª”Ò P7’TæNȲšímîú‘Ì>¾&‚¦;RD—/%{x¹²Žz_PeÚ›–Ê©®<…¾3TÀꎴ³ì·)cèÍö‹‘ˆo'„æB³ý4V=}]ÐõEš”ñÃÝl—ÅK©Ôrs‘ÏÙñÏêƒÌGÕ?X·©^j_¶NEcz6ærzo†æ Ü›2¸µªJÝigÑ‘¦49šü®R|ñû6mà®ñqüªtºXã¬Uª€:#}—:á”®i®²ˆ›Û ÈL3úž)à›¯VGc)TKKûV¿ueÜŽiReòj¿Ï0ϲ0• ÚHž×#âm3äÙ,/5 _É›¹wD¿z³{ǶýÊÂ%ïR9(.xe™\Ô¿•”E¸`¹ûñFú%LGAï­ M-…x'»\›7ÞùÁïF5Õ𺹹¬­ WHÍ—lMWÌ…¾º¸ïôaK}mÉZü•šB>&=—£ðÝg˜jÛöGÆ®ӳ悬—RàꞨ$xÞI}üùˆ|ͶË:tø$Q—wŽ~2Úˆó6¬½ã_ùSØ{"-ì‰thy¿¬"¤¥óY2¾Žå˜–?¡ˆ#ïç—ãtn#_B ÛIEodpø+vÖ·MÅ?mçà˜qȪ- \¡òZã“}S·é!{ÎzàÛf±PØÝ E—Zðæxì‚A‘…L‹XêDkÚ:æ>¼;‘Ãàt¥ ”“²0ѱrdîïõXíTslúu_%þ 2ëN¦£ÉWëÞW=˜Vc;ôºi7am‹UÜ&õ ŠIÊœ•’ó¾ˆø­›–Ö/“¶®Ëgût£¢Yð4¶1óòã0¨òœ²«ï`œ6Wî† Xfzi–¡V;;¬1kx]ø.‘ô¢[~f»ÁHÔFQ@“?.€‰CÅÅ5…ØŠMº ºLÜ?²Z^º.¨à%eJš¡• jôDѪ§½‡ö4/v^j™'ìØ| Z‡¨P|M6YÇwªñ JÐÕÜ?ÊõRÀû„ ¡gÙ•¦¹#ÂSìk3J"_i|?ô¥ÅI!9z‘ÚsÖþ{Ù¨ZqÏF˜ iãù Üw¸œ½Ú-Ýw•¡ÜÇ“Ï<ŽçÇÓÝ4UóÁàC dSøýâ/@šþ•ò U_ñÁ­–ع҆ÞSŸe_Ûˆ3³‚XBÀeÑ)ÉËGÎq{²7–>l9/ÜZj«{º¬yIrào•Îh¶ëžßMP$|eº+Îñ¬Œ°Þ+šVNõ]\¤£…Þ”¯59¬¦6–\Mlêl–¤óAÜ•¼«µ~R89eÿ£²’¸I4ÁdÑp°Û}1eZ½ÆM6’Š$¹áå5ªÈr¾&×{& zÔ¤.¿h<:Àiö N©ƒ]bË+À#nnPcÙPü¥°DVæAuˆÙÞ¼ŽÜ@¶N št-+yìÉU¬Ù–ÍÛ© ¡ÙŒvxJx:shO6N-»‡BÓ65lÿ «EMl{7¸ &ögÓ»ŸJàýz¬>CDÜÇb-KâY§UAr ébk&aÛ&bùÒv^úч­‰NÜ~Ê~=³Ä—p‹ÞÜ” ©9ï†å$€Pˆd»ßüÝ“_3®>ª†S:á.+­4`©ãÝøj]åÄ—lŽßFŸë;ÀÒ ©ôDÊÑ¡ÉA…)æêëü¸osuŽÞ|çOî1(Rf‡uù«aõÝFbcK¿Yµß×i=dëP”E•Ý©[|<£¦‹¥ï7’kö†º­Ì; ㈷fDI—íŽNÓFà/oºoeçÈ»ùͯ†îèíH<*\SŸÛÏ=‘ƒ6œ§DŽfá±$Ù¦Žô­6ú£¶¨òÎ"íTB ª±r”Ôß@xûÅ;¹=nÙ£DW{$pÍû. >^ÎÔ›wp–ÚNš$(&”?,ÀïC>"•¦VÃh.I&7¥”ß\ÿ‰Í´ž4¤O²ÇŒºð2áñ>ûQp'ß%¼Ø*H0‹zÅ-ʵ‹ü>ÝAŸmWX }÷ê×÷éjÞÁãzþ—#€ÒgU£9—Éq†2‚$!†ä0G…Baèh%3À4&mˆÓ=…¨„1M³@æÖ…è^K„pÿÅ¡§VùÌá“~áÓ',g ±Ù-#|ã’ìˆÇ£’¥ßͱ€ªè‘[ŠÚàÅãÅGs¯ØýÜ Ö wî-ɶ;yMKøŽÀ_’jAü…ïÁ‰Ñ3Zþ"¾ÜAÂTÐÿð!ûƒÿvÎp(ãêE;‘‘ý»?݉ endstream endobj 187 0 obj << /Type/FontDescriptor /CapHeight 850 /Ascent 850 /Descent -200 /FontBBox[-20 -233 617 696] /FontName/HAXCHY+CMSLTT10 /ItalicAngle -9.46 /StemV 69 /FontFile 186 0 R /Flags 68 >> endobj 186 0 obj << /Filter[/FlateDecode] /Length1 726 /Length2 2368 /Length3 533 /Length 2908 >> stream xÚí’y<ÔÝǹ²![âgËò˜i¦±K²Ù—uÌüFØcFƒÈñ â!RB*ÅØEÉ–‘)Ĉ’íªžåu{î?÷uÿ»¯{Î?çûý~Îç¼Ï÷EY;G˜!Žì š‘I4ŽÔŒ­­œœ G@© †F “L04P@êè Cºp`»®©«ŽÔUGA Š€1™J%ø§ÊÆ*ßTZ€a H%`1$ÀC;n›`1DÀ‘Œ%€´P8‰€Ã·-Á€ RC@A"K|A? ²ÿš„'Z?Ò8:åRH Þæ”·9U€mJ™D p ²ß†¼}¸ÍòcýªŸÍÍèD¢ &ð›ýï­ú›H †þ®!Rè4 X“q •ô³Ôüg âôÀŸ«h†HÀ’üˆ Ó«kþH‚Í gG a4*üžI¸Ÿ9¶»÷b¿…¡«±…Û/¾íº†@¢9…R@ñ׆ï1ò¯x»OTpGÀä¶p{þ±òüéϸ«Y’= “›2¡CWÐð ¥»MlÙˆæ¬">ÅêV@äIâjv΋É{/XÑ|Þ åL7W9eð!qÍ4ùš’ˆ^_¯Â _ØŠVôqÿ¬´6vÅZ]½¡±‚Xé]…ÎÓÂTÝ®²¡ÈÄNKa[Öqõ«é¼( 51—=»êuj\ÁúØN1“ àÀW×ïëÆ%Æ” }>œs#ï¨ã:³‘"‘|D2†äDj—œM¢Ö.NŒˆ~­~…VàEEFw>à +9c4Q_,2½¥!å†5-|+ѽÞ}ëÃz©¨ßBñ‹ÃçkV&cŽŸ{i2q)ºž`•pC8}ølTé"þkÃr€¼@íÇ—GÆIeÍÍ”ægóYmȺÒ|v5j¸ná}쉥ҪS~ÏÑrc¡4‰œñHŤƒÖשönPE•&÷«|¸£lô¨…¿Œíf%—Ç/²$©³áY±™ÞRÏ眬 «“ÃiÑ#˜—_'E. ï*(ô ’Gìmã(w½þÀÜ–yàñšNsÈÈ|M kBÑtNߤýüM†©¥ë(™²°ÙP, Î8Eaö¤É|^—”¸>ñ6¥èF9/i5‚ì…ü³Kš‹ Wnî 3ð¾¤_fuý–‰ Êå¤Qaަ–‹ÏíŸM5ã©-ruÕ· 0ŸõÞŒ#=Ä.ë·|ÉÛbÕ…/(²ÅŒ¿äáAïÉ:ÇÐødÓXçøn í0+0ZªIåÙŸ´®ð[èYËp±CªÖ—² (Ò§ï9sá½}Õ‘‘G¢÷i…1§K#ÛWêÄ+µ×»ÈáB9щ‘U¸(]óžcˆ[¦ËõœªÄæþ /¯˜E­‚嶺¦ 1¼¹–)' á.ŒN~Áio“a(­\"ëÛá.ŽÔ“):Ã'–d¥¹ÉŸt‡¸-îC-—ŒÏÎ`. ÖD=ͪhwµ§$—ØáM ÔØ c8Y³ï/?¦ÂU+Õ/b¸ÍµŠ1‚ÊoP—P(èà.½Ò™b…'Á yÉU3ªùA9M–÷3EØ­{ xð½Ûšá{¤é}ÒŠ.:¡Xj—W>i}^R1§Îiâ‘…pœ¯§\FoºGèªLÝ÷ édë‚Þk‘Oi&I ãx]ÀLãSNr?Ý.¾"VÅ'OuNãWw}·ÉÐ…¡­Ðh!ô©Víg—FŒ9ÚËç¤à•Ê÷Ð_Òw[LðíH–MËöÍÂeÙr`p·*3~Ô?Ý Ic =ã·ÚOꦥ¥©cÝŽUm´¬ aù@héÞM]?Ó{âÑkmÒþ,xk¸1~dÊΰi½kÊòü¬ò(NÎÓ ìùAxa掬˽+m ÃK·m$P"ò}F}¸8Éy•¥Ùa†,W"br¢ÏO=¡L ‡<šâÏæ»sJ€ö¨ìùT/‘À£±?Ò ¿;u£¹(ýeÇ{’væƒ€êÆ¢/šðâ±sá’dU~‡A=yiÍÎòMïSQîËïJƒLû3¦•&T'Åœ3»q©ÆuVlœ›bgð…_Žý¦ín›ìWþ׺XÏ>xÏIEÔ´ê™òyÕ“x¥[­²˜ËË£d¸Îy\½—¹;ŠºyÊmÔ¦t´ºïdµªý5 /iµ¢­ñ¹seó×W?šTrþ¥Í› ̺ùi¢B§mÒ§güZqN \'K¬ÝdK –*œŠáÎxÆÞßР­b˜|ž>ÍAÿ$lvh8®çÙÖXé÷9UäéÈ Oî­½þ¢°dÅ Œ5p«¹=ÃÏ“<ì=2w·ô È×K–£™ñC;—ä5ó¼&9ù¬Ù†Wp„®Þ3òŠçÔXÒoùú$æ9¦!%U©ótü‰tæLŽE²Æ+ö†>Øn>Ñ·gݱ…oºIÄñ•ÍäÏù3ôž¦Í,_¡ÖMÛî@Æœ:/:ç4ìb\SíUÜ f<6V‡TWšßªm¨c ‹Ì9.õjƒÆçTlÒ¸Yí¥aÌŒ ­ÝU–í 0àZÊ®–7kaج…yðê$~š ê>I×]?úÞ/©Â;yÅÄG`]¿#Áû@ÙI)ùÜP)òhä·¯éT1Ѧ!í“_i8  -w–Èmê”ZÐ$Š: ƒ Wšwå›:½ñOŸ»eÕƒÏ{7(e’üüùŽd„„.=Yc-ÂÕ¨g™¶VbY›JRK’c>Y5²”* Tî쿪©ç-{`Ê’ª™Ý—Ï4Ç7x¿ |Tú±9`kÔÑ_M ç1u>gƒ²g(‚ͱËìór\Û÷‰Õø–œÞ¾Æ%`Þ Ø¨´¦^……gžZõÈ=Z[íS=ég¹!’û”tT&‘Öád.â/ÖWÑt6BuóUÂӉޫé;¹zû ä×µK…ò÷o´<â9RË=ºò¾ "M&ãƒwàœ[}Ý]!» /Õò±ÿ#%V™oZg‘Ou'GÕôy^ŸzAÆ>‹ôƒo•]K}›è“UÒ“¯ §Ãô²«Ê-3n·»:Íì)5•¢}Q¾5—¹B¥+MK¬@Á¸€?55êãQkòÆéMT+h šeEtù =õ›ßìÕçi¿ìºàü V%1ý~¶ŒÇÆj–€vof¤úK±'Üê{¦ŽÕI›I}T gtj‰UË êð]ã®;}"Ó2hêG„ÐÝØ»š ­¢ü7”û.†×QVÊ—m‡õK,’Øn\çïGä–²Û›[·…=Þ“¹h/1RN”k\ã¾³ñy¨ÃV‡ù¸ ]> endobj 5 0 obj << /Type/Page /Resources 6 0 R /Contents[11 0 R 4 0 R 12 0 R 13 0 R] /Parent 220 0 R >> endobj 15 0 obj << /Type/Page /Resources 16 0 R /Contents[11 0 R 4 0 R 24 0 R 13 0 R] /Parent 220 0 R >> endobj 220 0 obj << /Type/Pages /Count 2 /Kids[5 0 R 15 0 R] /Parent 219 0 R >> endobj 26 0 obj << /Type/Page /Resources 27 0 R /Contents[11 0 R 4 0 R 39 0 R 13 0 R] /Parent 221 0 R >> endobj 41 0 obj << /Type/Page /Resources 42 0 R /Contents[11 0 R 4 0 R 43 0 R 13 0 R] /Parent 221 0 R >> endobj 45 0 obj << /Type/Page /Resources 46 0 R /Contents[11 0 R 4 0 R 51 0 R 13 0 R] /Parent 221 0 R >> endobj 221 0 obj << /Type/Pages /Count 3 /Kids[26 0 R 41 0 R 45 0 R] /Parent 219 0 R >> endobj 53 0 obj << /Type/Page /Resources 54 0 R /Contents[11 0 R 4 0 R 55 0 R 13 0 R] /Parent 222 0 R >> endobj 57 0 obj << /Type/Page /Resources 58 0 R /Contents[11 0 R 4 0 R 59 0 R 13 0 R] /Parent 222 0 R >> endobj 222 0 obj << /Type/Pages /Count 2 /Kids[53 0 R 57 0 R] /Parent 219 0 R >> endobj 61 0 obj << /Type/Page /Resources 62 0 R /Contents[11 0 R 4 0 R 63 0 R 13 0 R] /Parent 223 0 R >> endobj 65 0 obj << /Type/Page /Resources 66 0 R /Contents[11 0 R 4 0 R 67 0 R 13 0 R] /Parent 223 0 R >> endobj 69 0 obj << /Type/Page /Resources 70 0 R /Contents[11 0 R 4 0 R 77 0 R 13 0 R] /Parent 223 0 R >> endobj 223 0 obj << /Type/Pages /Count 3 /Kids[61 0 R 65 0 R 69 0 R] /Parent 219 0 R >> endobj 219 0 obj << /Type/Pages /Count 10 /Kids[220 0 R 221 0 R 222 0 R 223 0 R] /Parent 3 0 R >> endobj 79 0 obj << /Type/Page /Resources 80 0 R /Contents[11 0 R 4 0 R 81 0 R 13 0 R] /Parent 225 0 R >> endobj 83 0 obj << /Type/Page /Resources 84 0 R /Contents[11 0 R 4 0 R 85 0 R 13 0 R] /Parent 225 0 R >> endobj 225 0 obj << /Type/Pages /Count 2 /Kids[79 0 R 83 0 R] /Parent 224 0 R >> endobj 87 0 obj << /Type/Page /Resources 88 0 R /Contents[11 0 R 4 0 R 92 0 R 13 0 R] /Parent 226 0 R >> endobj 94 0 obj << /Type/Page /Resources 95 0 R /Contents[11 0 R 4 0 R 99 0 R 13 0 R] /Parent 226 0 R >> endobj 101 0 obj << /Type/Page /Resources 102 0 R /Contents[11 0 R 4 0 R 103 0 R 13 0 R] /Parent 226 0 R >> endobj 226 0 obj << /Type/Pages /Count 3 /Kids[87 0 R 94 0 R 101 0 R] /Parent 224 0 R >> endobj 105 0 obj << /Type/Page /Resources 106 0 R /Contents[11 0 R 4 0 R 110 0 R 13 0 R] /Parent 227 0 R >> endobj 112 0 obj << /Type/Page /Resources 113 0 R /Contents[11 0 R 4 0 R 114 0 R 13 0 R] /Parent 227 0 R >> endobj 116 0 obj << /Type/Page /Resources 117 0 R /Contents[11 0 R 4 0 R 118 0 R 13 0 R] /Parent 227 0 R >> endobj 227 0 obj << /Type/Pages /Count 3 /Kids[105 0 R 112 0 R 116 0 R] /Parent 224 0 R >> endobj 120 0 obj << /Type/Page /Resources 121 0 R /Contents[11 0 R 4 0 R 122 0 R 13 0 R] /Parent 228 0 R >> endobj 124 0 obj << /Type/Page /Resources 125 0 R /Contents[11 0 R 4 0 R 126 0 R 13 0 R] /Parent 228 0 R >> endobj 128 0 obj << /Type/Page /Resources 129 0 R /Contents[11 0 R 4 0 R 130 0 R 13 0 R] /Parent 228 0 R >> endobj 228 0 obj << /Type/Pages /Count 3 /Kids[120 0 R 124 0 R 128 0 R] /Parent 224 0 R >> endobj 224 0 obj << /Type/Pages /Count 11 /Kids[225 0 R 226 0 R 227 0 R 228 0 R] /Parent 3 0 R >> endobj 132 0 obj << /Type/Page /Resources 133 0 R /Contents[11 0 R 4 0 R 134 0 R 13 0 R] /Parent 230 0 R >> endobj 136 0 obj << /Type/Page /Resources 137 0 R /Contents[11 0 R 4 0 R 138 0 R 13 0 R] /Parent 230 0 R >> endobj 230 0 obj << /Type/Pages /Count 2 /Kids[132 0 R 136 0 R] /Parent 229 0 R >> endobj 140 0 obj << /Type/Page /Resources 141 0 R /Contents[11 0 R 4 0 R 142 0 R 13 0 R] /Parent 231 0 R >> endobj 144 0 obj << /Type/Page /Resources 145 0 R /Contents[11 0 R 4 0 R 146 0 R 13 0 R] /Parent 231 0 R >> endobj 148 0 obj << /Type/Page /Resources 149 0 R /Contents[11 0 R 4 0 R 150 0 R 13 0 R] /Parent 231 0 R >> endobj 231 0 obj << /Type/Pages /Count 3 /Kids[140 0 R 144 0 R 148 0 R] /Parent 229 0 R >> endobj 152 0 obj << /Type/Page /Resources 153 0 R /Contents[11 0 R 4 0 R 154 0 R 13 0 R] /Parent 232 0 R >> endobj 156 0 obj << /Type/Page /Resources 157 0 R /Contents[11 0 R 4 0 R 158 0 R 13 0 R] /Parent 232 0 R >> endobj 232 0 obj << /Type/Pages /Count 2 /Kids[152 0 R 156 0 R] /Parent 229 0 R >> endobj 160 0 obj << /Type/Page /Resources 161 0 R /Contents[11 0 R 4 0 R 162 0 R 13 0 R] /Parent 233 0 R >> endobj 164 0 obj << /Type/Page /Resources 165 0 R /Contents[11 0 R 4 0 R 166 0 R 13 0 R] /Parent 233 0 R >> endobj 168 0 obj << /Type/Page /Resources 169 0 R /Contents[11 0 R 4 0 R 170 0 R 13 0 R] /Parent 233 0 R >> endobj 233 0 obj << /Type/Pages /Count 3 /Kids[160 0 R 164 0 R 168 0 R] /Parent 229 0 R >> endobj 229 0 obj << /Type/Pages /Count 10 /Kids[230 0 R 231 0 R 232 0 R 233 0 R] /Parent 3 0 R >> endobj 172 0 obj << /Type/Page /Resources 173 0 R /Contents[11 0 R 4 0 R 174 0 R 13 0 R] /Parent 235 0 R >> endobj 176 0 obj << /Type/Page /Resources 177 0 R /Contents[11 0 R 4 0 R 178 0 R 13 0 R] /Parent 235 0 R >> endobj 235 0 obj << /Type/Pages /Count 2 /Kids[172 0 R 176 0 R] /Parent 234 0 R >> endobj 180 0 obj << /Type/Page /Resources 181 0 R /Contents[11 0 R 4 0 R 182 0 R 13 0 R] /Parent 236 0 R >> endobj 184 0 obj << /Type/Page /Resources 185 0 R /Contents[11 0 R 4 0 R 189 0 R 13 0 R] /Parent 236 0 R >> endobj 191 0 obj << /Type/Page /Resources 192 0 R /Contents[11 0 R 4 0 R 193 0 R 13 0 R] /Parent 236 0 R >> endobj 236 0 obj << /Type/Pages /Count 3 /Kids[180 0 R 184 0 R 191 0 R] /Parent 234 0 R >> endobj 195 0 obj << /Type/Page /Resources 196 0 R /Contents[11 0 R 4 0 R 197 0 R 13 0 R] /Parent 237 0 R >> endobj 199 0 obj << /Type/Page /Resources 200 0 R /Contents[11 0 R 4 0 R 201 0 R 13 0 R] /Parent 237 0 R >> endobj 203 0 obj << /Type/Page /Resources 204 0 R /Contents[11 0 R 4 0 R 205 0 R 13 0 R] /Parent 237 0 R >> endobj 237 0 obj << /Type/Pages /Count 3 /Kids[195 0 R 199 0 R 203 0 R] /Parent 234 0 R >> endobj 207 0 obj << /Type/Page /Resources 208 0 R /Contents[11 0 R 4 0 R 209 0 R 13 0 R] /Parent 238 0 R >> endobj 211 0 obj << /Type/Page /Resources 212 0 R /Contents[11 0 R 4 0 R 213 0 R 13 0 R] /Parent 238 0 R >> endobj 215 0 obj << /Type/Page /Resources 216 0 R /Contents[11 0 R 4 0 R 217 0 R 13 0 R] /Parent 238 0 R >> endobj 238 0 obj << /Type/Pages /Count 3 /Kids[207 0 R 211 0 R 215 0 R] /Parent 234 0 R >> endobj 234 0 obj << /Type/Pages /Count 11 /Kids[235 0 R 236 0 R 237 0 R 238 0 R] /Parent 3 0 R >> endobj 3 0 obj << /Type/Pages /Count 42 /Kids[219 0 R 224 0 R 229 0 R 234 0 R] /MediaBox[0 0 595 842] >> endobj 11 0 obj << /Length 1 >> stream endstream endobj 13 0 obj << /Length 1 >> stream endstream endobj 4 0 obj << /Length 33 >> stream 1.00028 0 0 1.00028 72 769.82 cm endstream endobj 239 0 obj << >> endobj 240 0 obj null endobj 241 0 obj << >> endobj 2 0 obj << /Type/Catalog /Pages 3 0 R /Outlines 239 0 R /Threads 240 0 R /Names 241 0 R >> endobj xref 0 242 0000000000 65535 f 0000189456 00000 n 0000196252 00000 n 0000195897 00000 n 0000196102 00000 n 0000189620 00000 n 0000002157 00000 n 0000000009 00000 n 0000101259 00000 n 0000101072 00000 n 0000000913 00000 n 0000196002 00000 n 0000001858 00000 n 0000196052 00000 n 0000002124 00000 n 0000189723 00000 n 0000006623 00000 n 0000110786 00000 n 0000110598 00000 n 0000002218 00000 n 0000003135 00000 n 0000127231 00000 n 0000127036 00000 n 0000004751 00000 n 0000005702 00000 n 0000006579 00000 n 0000189908 00000 n 0000012709 00000 n 0000006685 00000 n 0000128784 00000 n 0000128587 00000 n 0000007592 00000 n 0000008583 00000 n 0000130667 00000 n 0000130481 00000 n 0000009560 00000 n 0000145722 00000 n 0000145527 00000 n 0000010304 00000 n 0000011285 00000 n 0000012632 00000 n 0000190013 00000 n 0000013473 00000 n 0000012771 00000 n 0000013396 00000 n 0000190118 00000 n 0000016231 00000 n 0000013535 00000 n 0000148229 00000 n 0000148041 00000 n 0000014503 00000 n 0000015501 00000 n 0000016165 00000 n 0000190311 00000 n 0000018091 00000 n 0000016293 00000 n 0000018025 00000 n 0000190416 00000 n 0000020223 00000 n 0000018153 00000 n 0000020157 00000 n 0000190602 00000 n 0000020815 00000 n 0000020285 00000 n 0000020771 00000 n 0000190707 00000 n 0000022092 00000 n 0000020877 00000 n 0000022026 00000 n 0000190812 00000 n 0000026639 00000 n 0000152132 00000 n 0000151946 00000 n 0000022154 00000 n 0000158172 00000 n 0000157982 00000 n 0000023156 00000 n 0000024092 00000 n 0000026551 00000 n 0000191103 00000 n 0000028505 00000 n 0000026701 00000 n 0000028428 00000 n 0000191208 00000 n 0000028992 00000 n 0000028567 00000 n 0000028948 00000 n 0000191394 00000 n 0000032270 00000 n 0000169345 00000 n 0000169158 00000 n 0000029054 00000 n 0000029995 00000 n 0000032181 00000 n 0000191499 00000 n 0000036326 00000 n 0000171403 00000 n 0000171210 00000 n 0000032332 00000 n 0000033253 00000 n 0000036235 00000 n 0000191604 00000 n 0000039046 00000 n 0000036389 00000 n 0000038955 00000 n 0000191801 00000 n 0000042856 00000 n 0000181187 00000 n 0000180997 00000 n 0000039110 00000 n 0000040044 00000 n 0000042763 00000 n 0000191909 00000 n 0000044197 00000 n 0000042920 00000 n 0000044130 00000 n 0000192017 00000 n 0000045906 00000 n 0000044261 00000 n 0000045815 00000 n 0000192216 00000 n 0000046462 00000 n 0000045970 00000 n 0000046417 00000 n 0000192324 00000 n 0000048825 00000 n 0000046526 00000 n 0000048758 00000 n 0000192432 00000 n 0000051243 00000 n 0000048889 00000 n 0000051130 00000 n 0000192729 00000 n 0000054111 00000 n 0000051307 00000 n 0000054009 00000 n 0000192837 00000 n 0000056413 00000 n 0000054175 00000 n 0000056333 00000 n 0000193028 00000 n 0000058925 00000 n 0000056477 00000 n 0000058823 00000 n 0000193136 00000 n 0000061683 00000 n 0000058989 00000 n 0000061581 00000 n 0000193244 00000 n 0000063952 00000 n 0000061747 00000 n 0000063872 00000 n 0000193443 00000 n 0000066025 00000 n 0000064016 00000 n 0000065923 00000 n 0000193551 00000 n 0000068892 00000 n 0000066089 00000 n 0000068779 00000 n 0000193742 00000 n 0000071507 00000 n 0000068956 00000 n 0000071405 00000 n 0000193850 00000 n 0000074274 00000 n 0000071571 00000 n 0000074172 00000 n 0000193958 00000 n 0000076337 00000 n 0000074338 00000 n 0000076257 00000 n 0000194255 00000 n 0000078928 00000 n 0000076401 00000 n 0000078826 00000 n 0000194363 00000 n 0000081550 00000 n 0000078992 00000 n 0000081448 00000 n 0000194554 00000 n 0000084057 00000 n 0000081614 00000 n 0000083979 00000 n 0000194662 00000 n 0000087826 00000 n 0000186433 00000 n 0000186237 00000 n 0000084121 00000 n 0000084870 00000 n 0000087687 00000 n 0000194770 00000 n 0000089895 00000 n 0000087890 00000 n 0000089802 00000 n 0000194969 00000 n 0000092769 00000 n 0000089959 00000 n 0000092643 00000 n 0000195077 00000 n 0000095216 00000 n 0000092833 00000 n 0000095149 00000 n 0000195185 00000 n 0000096247 00000 n 0000095280 00000 n 0000096202 00000 n 0000195384 00000 n 0000098515 00000 n 0000096311 00000 n 0000098437 00000 n 0000195492 00000 n 0000100129 00000 n 0000098579 00000 n 0000100062 00000 n 0000195600 00000 n 0000101008 00000 n 0000100193 00000 n 0000100941 00000 n 0000191005 00000 n 0000189828 00000 n 0000190223 00000 n 0000190521 00000 n 0000190917 00000 n 0000192631 00000 n 0000191313 00000 n 0000191712 00000 n 0000192125 00000 n 0000192540 00000 n 0000194157 00000 n 0000192945 00000 n 0000193352 00000 n 0000193659 00000 n 0000194066 00000 n 0000195799 00000 n 0000194471 00000 n 0000194878 00000 n 0000195293 00000 n 0000195708 00000 n 0000196184 00000 n 0000196207 00000 n 0000196229 00000 n trailer << /Size 242 /Root 2 0 R /Info 1 0 R >> startxref 196350 %%EOF python-plwm-2.6a+20080530/doc/Makefile0000644000175000017500000000063310035012624015643 0ustar stewstew default: info all: info ps html info: plwm.info ps: plwm.ps pdf: plwm.pdf html: plwm_toc.html clean: rm -f *.{info,ps,html,aux,cp,fn,fns,ky,log,pg,toc,tp,vr,vrs} %.info: %.texi makeinfo --no-split $< %.dvi: %.texi texi2dvi $< # Run it twice to catch new cross-references texi2dvi $< %.ps: %.dvi dvips -o $@ $< %.pdf: %.dvi dvipdfm -o $@ $< %_toc.html: %.texi texi2html -split_node $< python-plwm-2.6a+20080530/doc/plwm.texi0000644000175000017500000023773307435404771016114 0ustar stewstew\input texinfo @c -*-texinfo-*- @c %**start of header @setfilename plwm.info @settitle PLWM -- The Pointless Window Manager @c @setchapternewpage odd @c %**end of header @dircategory Window Managers @direntry * PLWM: (plwm). The Pointless Window Manager @end direntry @ifinfo This file documents the Python window manager PLWM. Copyright 1999-2002 Peter Liljenberg Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies. @ignore Permission is granted to process this file through TeX and print the results, provided the printed document carries a copying permission notice identical to this one except for the removal of this paragraph (this paragraph not being relevant to the printed manual). @end ignore Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided also that the sections entitled ``Copying'' and ``GNU General Public License'' are included exactly as in the original, and provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one. Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation. @end ifinfo @titlepage @title PLWM -- The Pointless Window Manager @author Peter Liljenberg @c The following two commands @c start the copyright page. @page @vskip 0pt plus 1filll Copyright @copyright{} 1999-2002 Peter Liljenberg Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies. Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided also that the sections entitled ``Copying'' and ``GNU General Public License'' are included exactly as in the original, and provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one. Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation. @end titlepage @contents @node Top @top Introduction @sc{plwm}, The Pointless Window Manager, is a collection of window manager primitives written in Python. If you are just interested in running @sc{plwm} you should at least read the chapter on the underlying philosophy, take a look at the chapter on running @sc{plwm} and have @code{README.examplewm} at hand. If you are interested in really using @sc{plwm} it is recommended, even required, to read the rest of the manual. @menu * Philosophy:: Why, oh why? * Running PLWM:: Hints and a sample .xinitrc. * Configuration:: Configuring @sc{plwm}. * Core Classes:: The PLWM kernel. * Event Handling:: How to respond to stimuli. * Core Events:: Internal events. * Client Filters:: Selecting clients. * Extension Modules:: Tasty features. * Utilities:: Useful X programs. * ToDo:: Wouldn't it be nice if... * Credits:: Whodunnit? * Contact Info:: Email and WWW addresses. @end menu @node Philosophy @chapter Philosophy and Excuses @sc{plwm} is not a normal window manager, in fact, it isn't a window manager at all. Instead it is a collection of Python classes which you can use to build your own window manager. You can include the existing features you like and easily write your own extensions to make your @sc{plwm} behave exactly as you want it to. Eventually, you will have a perfect symbiosis of user and window manager, you and the computer will be a beautiful Mensch-Maschine! One of the basic ideas is that the mouse should be banished, and everything should be possible to do without moving your hands from the keyboard. This is the pointless bit of @sc{plwm}. Another of the other basic ideas is to make a window manager which is is pure Unix Philosophy: a bunch of simple tools which can be combined to build a powerful application. The "tools" are Python classes which makes it easy to inherit, extend, mixin and override functionality to get exactly the behaviour that you want. This makes @sc{plwm} extremely configurable by sacrificing ease of configuration: you actually have to write some Python code to get the window manager exactly as you want it. However, if you was moved by the first paragraph, then you're probably already a hacker and will relish in writing your own window manager. A typical @sc{plwm} setup might look rudimentary, even hostile, to people used to the glitz and glamour of more conventional window managers. However, there are a lot of powerful features, making it really user-friendly. Provided that the user is friendly to @sc{plwm}, of course. @node Running PLWM @chapter Running PLWM @sc{plwm}, at least in the @code{examplewm.py} guise, is not very sophisticated when it comes to command line arguments. It only groks three: @table @code @item -display Run @sc{plwm} against another display than @code{$DISPLAY}. @item -debug Enable debug messages. The argument should be a comma-separated list of debug message categories, or empty, meaning enable all messages. @item -version Print the PLWM version. @end table It can be tricky at first to figure out how to have a hacking-friendly X setup, so here is a fragment of my @file{.xinitrc} as an example of a @sc{plwm} environment: @example #!/bin/sh # Redirect error messages to a log file. This is where PLWM # tracebacks will go, so keep an eye on it. exec 1>$HOME/.x11startlog 2>$HOME/.x11startlog # Read resource database from file .Xdefaults xrdb ~/.Xdefaults # Set a solid color for the root window. The modewindow will have the # same background (set with .Xdefaults) and no border, so it will appear # to be a part of the root. Good enough. xsetroot -solid darkolivegreen # Desperately try to start some window manager # As we start it in the background it can exit without shutting down # the entire X server. (plwm || ctwm || twm) & # Instead the X server is kept running by wmm, or if that fails, by # xlogo. To shut down the X server we kill the wmm or xlogo window. wmm || xlogo @end example How to configure @sc{wmm} (@pxref{wmm}) is not obvious at first either, so here's a @file{.wmmrc} too (notice the tabs between the columns): @example plwm plwm & examplewm /home/petli/hack/plwm/examples/examplewm.py & twm twm & @end example The idea is to use a stable @sc{plwm} installed in your @code{$PATH} by default. When you are about to test some freshly hacked feature or a bugfix, simply kill off the running @sc{plwm} (@w{C-M-Esc} in @code{examplewm.py}). This will pop up the @sc{wmm} window, so click on the examplewm button in it to start the development version, using modules from @code{plwm} directories next to @code{examples}. To put the finishing touches to the configuration, we can change some fonts and colors with the @file{~/.Xdefaults} file: @example @group Plwm.outline.font: -*-lucida-bold-r-*-sans-20-*-*-*-*-*-*-* Plwm.border.color: black Plwm.border.focus.color: grey60 Plwm.modewindow.background: darkolivegreen Plwm.modewindow.foreground: white @end group @end example @node Configuration @chapter Configuration Most of the configuration of @sc{plwm} is done by writing a python script using the various window manager modules. @xref{Extension Modules}. However, some small parts of @sc{plwm} can be configured with X resources. The various X resources are defined in the section of their corresponding modules. They have one thing in common though, in that they all start with the name component @code{plwm} and the class component @code{Plwm}. The name component used in lookups will actually be the name of the window manager script, so if your script is called @code{foowm.py}, the resources looked up will be e.g. @code{foowm.border.color} instead of @code{plwm.border.color}. The class component used is controlled by the @code{WindowManager} attribute @code{appclass}, which by default is @code{Plwm}. Summary: in your @file{~/.Xdefaults} or @file{~/.Xresources}, start all @sc{plwm} resources with @code{Plwm.}, unless you know what you're doing. @node Core Classes @chapter Core Classes If you absolutely must point to a single module and call it @sc{plwm}, then you should direct your attention to @code{wmanager}. It contains three classes which implements the fundamental window managing and provides the extension framework. @vtable @asis @item WindowManager This is the central window manager class. A @code{WindowManager} instance manages the windows on a single display, which is provided as an argument to the constructor. @code{WindowManager} also drives the event handling loop. @item Screen This class takes care of catching client windows so we can manage them. When a @code{WindowManager} instance is created it will create a @code{Screen} instance for each physical screen (monitor) connected to the display. Normally, an X server only have one screen, but they can also be multiheaded, i.e. having more than one screen. Each screen has its own root window, and all windows are therefore local to a certain screen and cannot move between different screens. @item Client This class manages a single window. Instances are created when the @code{Screen} instance managing the screen detects that a window has been created. All window operations by extensions should be done using methods on the @code{Client} instance managing that window. @end vtable This gives us the following structure: A single @code{WindowManager} instance manages a certain display, by managing one or more @code{Screen} instances which in turn manages a number of @code{Client} instances. These classes have a number of publicly available attributes, listed in the sections below. Of course, all member attributes are available for you, but if you try to stick to the listed attributes and use the methods provided by the classes to manipulate windows your code will stand a better chance of working with future extensions. The core classes also generates some events which can be useful for mixins. @xref{Core Events}. @menu * WindowManager Public Attributes:: * Screen Public Attributes:: * Client Public Attributes:: @end menu @node WindowManager Public Attributes @section @code{WindowManager} Public Attributes @defivar WindowManager display A @code{Xlib.display.Display} object connecting us to the X server. @end defivar @defivar WindowManager events An @code{EventFetcher} object for this display. @xref{Event Handling}. @end defivar @defivar WindowManager dispatch A global @code{EventDispatcher}. @xref{Event Handling}. @end defivar @defivar WindowManager current_client @defivarx WindowManager focus_client The client currently containing the pointer and the client which has keyboard focus, respectivly. Most of the time these are the same, but certain windows do not use input and will therefore never be focused. Most operations should be performed on current_client. The @code{WindowManager} provides the method @code{set_current_client()} to change this in the proper way. However, to implement some kind of focus scheme you have to use some extension class, see @ref{focus}. @end defivar @defivar WindowManager screens A list of the managed screens. @end defivar @defivar WindowManager screen_nums @defivarx WindowManager screen_roots Mappings from screen numbers or root windows to the corresponding screen object. @end defivar @defivar WindowManager default_screen The default screen, defined when opening the display. @end defivar @defivar WindowManager current_screen The screen currently containing the pointer. This will be maintained by the screens automatically. @end defivar @node Screen Public Attributes @section @code{Screen} Public Attributes @defivar Screen wm The @code{WindowManager} which holds this screen. @end defivar @defivar Screen number The number of this screen. @end defivar @defivar Screen root The root window of this screen. @end defivar @defivar Screen dispatch The @code{EventDispatcher} for the root window. @xref{Event Handling}. @end defivar @defivar Screen info The screen information structure, as returned by the Xlib @code{Display} method @code{screen()}. @end defivar @node Client Public Attributes @section @code{Client} Public Attributes @defivar Client screen @defivarx Client wm The @code{Screen} and @code{WindowManager} instances which contains this client. @end defivar @defivar Client withdrawn Set to true if this client has been withdrawn. A withdrawn client should not be modified further, and has already been removed from the @code{Screen}'s list of clients. @end defivar @defivar Client dispatch The @code{EventDispatcher} for the client window. @xref{Event Handling}. @end defivar @defivar Client current @defivarx Client focused These attributes will be true or false to indicate whether this client is the current one, and if it has focus. @end defivar @node Event Handling @chapter Event Handling Event handling consists of getting events and then distributing them to the event handlers. In @sc{plwm} this is handled by the module @code{event} and its classes @code{EventFetcher} and @code{EventDispatcher}. If the event loop in the @code{WindowManager} is the heart driving the event blood stream, @code{EventFetcher} is the lungs providing fresh events (the oxygene) and @code{EventDispatcher} is the arteries delivering the events to all the working parts of the window manager body. No, that analogue wasn't strictly necessary. @menu * Event Objects:: The blood cells, then. * EventFetcher:: The source of events. * EventDispatcher:: Making things happen with events. @end menu @node Event Objects @section Event Objects The events are Python objects, either X event objects from Python Xlib, or an instance of some other Python class. The only thing required by the event system is that they have at least this attribute: @defivar {Event Objects} type This identifies the event type, and can be any hashable object. For X events this is some integer constants defined in @code{Xlib.X}. For other events this can be the event object class, a unique string, or anything else that is useful. It must however be a hashable object, since the type is used as an index into dictionaries. @end defivar The @code{event} module provides a function for getting new, unique integer event types: @defun new_event_type ( ) Return a new unique integer event type, which does not conflict with the event types of the Xlib. @end defun Additionally the @code{WindowManager} uses one of these attributes to figure out which @code{Screen} and @code{Client} that should take care of the event: @defivar {Event Objects} client A client object this event is about or for. The @code{Screen} managing this client will get the event, and can then in its turn pass the event on to the client itself. @end defivar @defivar {Event Objects} window The window object this event is about or for. The @code{Screen} managing the root window of this window will get the event. If the window corresponds to a managed client, that client can also get the event. @end defivar @defivar {Event Objects} screen The screen object this event is about or for, which will get the event. @end defivar If the event has none of the attributes or it is for an unmanaged screen it will only be passed to the global event handlers. @node EventFetcher @section EventFetcher The @code{EventFetcher} can provide events to the window manager from a number of sources: the X server, timers, files or the window manager itself. Synthetic events are generated by some code in the window manager, typically as an abstraction of some user input. As an example, the @code{focus} module generates @code{ClientFocusOut} and @code{ClientFocusIn} events when the focus change, which it can do as an effect of an @code{X.EnterNotify} event, or a call to @code{focus.move_focus}. Synthetic events have precedence over all other event types. @defmethod EventFetcher put_event ( event ) Insert a synthetic event object. Synthetic events are always returned before any timer or X events, in FIFO order. @end defmethod Timer events are used to do something at a later time. The @code{keys} module uses it to implement a time-out on keyboard grabs, and the @code{mw_clock} module uses it to update a clock display once a minute. Timer events are represented by @code{TimerEvent} objects, and have precedence over file and X events. A timer event object is not reusable, so if something should be done periodically, a new timer event will have to be rescheduled whenever the previous one expires. @defmethod EventFetcher add_timer ( timer ) Add the @code{TimerEvent} object @var{timer} to the list of timers. The expiration time of the event is specified when creating the timer event. The event will be returned when the timer expires unless it is cancelled before that. @end defmethod @deffn Class TimerEvent ( event_type, after = 0, at = 0 ) Create a a timer event that will expire either at a relative time, set with @var{after}, or at a specific time, set with @var{at}. Times are measured in seconds as in the @code{time} module, and can be integers or floating point values. Timer events are identified by its @code{type} member, which are specified with the @var{event_type} argument. @defmethod TimerEvent cancel ( ) Cancel this timer event, if it hasn't expired yet. @end defmethod @end deffn File events can be used to handle non-blocking I/O. They are generated when some file is ready for reading or writing, and is typically used for network services, e.g. by the @code{inspect} module. File events are represented by @code{FileEvent} objects, which remain on the list of watched files until they are explicitly cancelled. File events have the lowest priority, and are preceeded by X events. @defmethod EventFetcher add_file ( file ) Add the @code{FileEvent} object @var{file} to the list of watched files. It will be returned whenever its file is ready for the choosen I/O operation. @end defmethod @deffn Class FileEvent ( event_type, file, mode = None ) Create a file event wathing @var{file}, which could be any object with a @code{fileno()} method. The event is identified by @var{event_type}. @var{mode} is the types of I/O that the caller is interested in, and should be a bitmask of the flags @code{FileEvent.READ}, @code{FileEvent.WRITE}, or @code{FileEvent.EXCEPTION}. If @var{mode} is @code{None}, the @code{mode} attribute of @var{file}, as specified to the @code{open()} call, will be used instead. @defivar FileEvent state When a file event is returned by the event loop, this attribute will be set to a mask of the I/O modes that the file is ready to perform, a subset of the modes waited for. If @code{FileEvent.READ} is set, at least one byte can be read from the file without blocking. If @code{FileEvent.WRITE} is set, at least one byte can be written to the file without blocking. If @code{FileEvent.EXCEPTION} is set, some exceptional I/O has occured, e.g. out-of-band data on a TCP socket. @end defivar @defmethod FileEvent set_mode ( newmode = None, set = 0, @w{clear = 0} ) Change the I/O modes waited for. If @var{newmode} is not @code{None}, the mode will be reset to @var{newmode}, otherwise the old mode will be modifed. Then the flags in the bitmask @var{set} will be added, and the flags in @var{clear} will be removed, in that order. @end defmethod @defmethod FileEvent cancel ( ) Cancel this file event, removing it from the list of watched files. @end defmethod @end deffn @node EventDispatcher @section EventDispatcher Each @code{Screen} and @code{Client} has an @code{EventDispatcher} connected to their root window or client window, respectivly. Additionally, the @code{WindowManager} has an @code{EventDispatcher} which is connected to all the root windows (through the dispatchers of each @code{Screen}). An event is passed to the event handling functions which have been registered for that particular event type. There are three levels of event handlers in a dispatcher: @table @asis @item System Handlers System handlers will always be called, even if grab handlers are installed. They will be called before the other types of handlers in this dispatcher. They are primarily meant to be used by the core classes. @item Grab Handlers Grab handlers override previously installed grab handlers and all normal handlers, but not system handlers. @item Normal Handlers Normal handlers are the most useful type of handlers for extension modules. They will be called only if there are no grab handlers installed for this event type. @end table First of all the handlers in the global dispatcher are called. Then, if the event can be associated with a managed screen through its @code{client}, @code{window}, or @code{screen} attributes the handlers in the dispatcher for that screen are called. Finally, if the event is for a managed window the handlers in the dispatcher for that client are called. Grab handlers do not interfere with the dispatcher sequence directly, but a grab handler do block grab handlers and normal handlers in later dispatchers. System handler are always called, though. An example: Assume that an event for a managed client has been fetched and is about to be passed through the dispatchers. The matching event handlers are the following: in the global dispatcher one system handler and two normal handlers, in the screen dispatcher a grab handler and one normal handler, and in the client dispatcher one system handler, one grab handler and one normal handler. The handlers will be called in this order: global system, global normals, screen grab, client system. The screen normal, client grab and normal handlers will be ignored because of the grab handler in the screen. Handlers are registered with one of these methods: @defmethod EventDispatcher add_handler ( type, handler, [ masks = None ], [ handler_id = None ] ) @defmethodx EventDispatcher add_grab_handler ( type, handler, [ masks = None ], [ handler_id = None ] ) @defmethodx EventDispatcher add_system_handler ( type, handler, [ masks = None ], [ handler_id = None ] ) Add a handler for @var{type} events. @var{handler} is a function which will get one argument, the event object. If @var{masks} is omitted or None the default X event masks for the event type will be set for the @code{EventDispatcher}'s window. Otherwise it should be an event mask or a list or tuple of event masks to set. @var{handler_id} identifies this handler, and defaults to the @var{handler} itself if not provided. @end defmethod @defmethod EventDispatcher remove_handler ( handler_id ) Remove the handler or handlers identified by @var{handler_id}. This will also clear the masks the handlers had installed. @end defmethod Event masks can also be handled manually when necessary. All event masks keep a reference count, so calls to the following functions nest neatly. @defmethod EventDispatcher set_masks ( masks ) Set @var{masks} on the window, without installing any handlers. @var{masks} should be an event mask or a list or tuple of event masks to set. @end defmethod @defmethod EventDispatcher unset_masks ( masks ) Clear @var{masks} on the window. @var{masks} should be an event mask or a list or tuple of event masks to set. @end defmethod @defmethod EventDispatcher block_masks ( masks ) Block @var{masks} on the window. This will prevent any matching X events to be generated on the window until a matching unblock_masks. @var{masks} should be an event mask or a list or tuple of event masks to set. @end defmethod @defmethod EventDispatcher unblock_masks ( masks ) Unblock @var{masks} on the window, allowing the matching X events to be generated. @var{masks} should be an event mask or a list or tuple of event masks to set. @end defmethod @node Core Events @chapter Core Events The core classes can generate a number of internal events. Mostly, these are a result of some X event or user activity. All of these events are defined in the module @code{plwm.wmevents}. @deftp Event AddClient Generated when a client is added. The added client is identified by the event object attribute @code{client}. @end deftp @deftp Event RemoveClient Generated when a client is removed (withdrawn). The removed client is identified by the event object attribute @code{client}. @end deftp @deftp Event QuitWindowManager Generated when the window manager event loop is exited by calling @code{WindowManager.quit}. @end deftp @deftp Event CurrentClientChange Generated when a new client is made current. The event object has two attributes: @code{client} is the new client, or @code{None} if no client is current now. @code{screen} is the screen of the previous current client, or @code{None} if no client was current previously. @end deftp @deftp Event ClientFocusOut Generated when a client loses focus. The event object attribute @code{client} is the now unfocused client. @end deftp @deftp Event ClientFocusIn Generated when a client gets focus. The event object attribute @code{client} is the now focused client. @end deftp @node Client Filters @chapter Client Filters The module @code{plwm.cfilter} defines a number of client filters. These filters can be called with a client as an argument, and returns true if the client matches the filter, or false otherwise. Extension modules can then provide customization with client filters, allowing the user to give certain clients special treatment. Currently, the following filters are defined: @deffn Filter true @deffnx Filter all These filters are true for all clients. @end deffn @deffn Filter false @deffnx Filter none These filters are false for all clients. @end deffn @deffn Filter is_client True if the object is a @code{wmanager.Client} instance. @end deffn @deffn Filter iconified True if the client is iconified. @end deffn @deffn Filter mapped True if the client is mapped, the opposite of iconified. @end deffn @deffn Filter name ( string ) @deffnx Filter re_name ( regexp ) @deffnx Filter glob_name ( pattern ) These filters are true if the client resource name or class is exactly @var{STRING}, or matches the regular expression @var{regexp} or the glob pattern @var{pattern}. @end deffn @deffn Filter title ( string ) @deffnx Filter re_title ( regexp ) @deffnx Filter glob_title ( pattern ) These filters are similar to the name filters above, but matches the client title instead. @end deffn These basic filters can then be assembled into larger, more complex filters using the following logical operations: @deffn Filter And ( filter1, filter2, ..., filterN ) True if all of the subfilters are true. @end deffn @deffn Filter Or ( filter1, filter2, ..., filterN ) True if at least one of the subfilters is true. @end deffn @deffn Filter Not ( filter ) True if @var{filter} is false. @end deffn Here are some examples of compound filters: @example # Match any client that isn't an Emacs Not(name('Emacs')) # Match an iconified xterm: And(iconified, name('XTerm')) # Match an xterm with a root shell (provided that the shell # prompt sets a useful xterm title) And(name('XTerm'), re_title(r'\[root@.*\]')) @end example @node Extension Modules @chapter Extension Modules To actually get a useful window manager one must extend the core classes (@pxref{Core Classes}) with various extension classes. Extension classes take the form of mixin classes, i.e. we just inherit it with the corresponding core class. They will add methods to the core class, and can usually be configured with class attributes. For example, to create a client which highlights the window border when it is focused one could use this fragment: @example class MyClient(wmanager.Client, border.BorderClient): pass @end example Because of the mixin technique, we need some ground rules for naming schemes, configuration and initialization. Finally some extension modules are described in detail. For full extension examples, look in the directory @code{examples} in the distribution tree. @menu * Extension Coding Conventions:: How not to trample on each other's feet. * Extension Classes Initialization:: Mixin initialization methods. * Available Extension Modules:: What have already been written? @end menu @node Extension Coding Conventions @section Extension Coding Conventions Extension classes will need their own member variables and methods, and to avoid classes overwriting the attributes of other classes the following naming scheme should be used: @itemize @bullet @item Each extension module should select a prefix, typically the same as the module name unless that it very unwieldy. @item All member variables and methods used and defined by an extension class should begin with the prefix. @item An exception: methods are allowed to begin with @code{get_} or @code{set_} followed by the prefix. @end itemize @node Extension Classes Initialization @section Extension Classes Initialization Extension classes must be able to initialize themselves. To make this easy the core classes provide some special initializing functions for extension classes. Extension classes should only use these, they must not use the normal Python initialization funktion @code{__init__}. When extending a core class by subclassing it together with a number of extension classes, the core class should be the first base class. The extension classes may have to be ordered among themselves too. When an extended core class is initialized it will traverse the class inheritance tree. When an extension initialization function is found it is called without any arguments except for the object itself. As soon as an extension initialization function is found in a base class, its base classes will not be traversed. @code{WindowManager} provides two extension initialization functions: @ftable @code @item __wm_screen_init__ Called after the display is opened, but before any screens are added. @item __wm_init__ Called after all the screens have been added, i.e. as the very last thing during the initialization of the window manager. @end ftable @code{Screen} also provides two extension initialization functions: @ftable @code @item __screen_client_init__ Called after the root window has been fetched and the @code{EventDispatcher} has been created, but before any clients are added. @item __screen_init__ Called after all the clients have been added, i.e. before the window manager adds the next screen. @end ftable @code{Client} provides only one extension initialization function: @ftable @code @item __client_init__ Called after the client has finished all of its core initialization, i.e. just before the screen will add the next client. @end ftable There are also corresponding finalization methods: @ftable @code @item __wm_del__ @itemx __screen_del__ @itemx __client_del__ These are called just before the object will finish itself off, similar to the @code{__del__} method of objects. (Actually, these methods are called from the @code{__del__} method of the object.) @end ftable @node Available Extension Modules @section Available Extension Modules This is a description of the available extension modules, with information on how to use them and on the interface they provide to other extension modules. @menu * color:: Color lookup and allocation. * font:: Font lookup and allocation. * keys:: Key event handlers. * focus:: Focus management. * border:: Display client borders. * outline:: Draw outlines of windows. * moveresize:: Move and resize clients. * cycle:: Cycle between windows to select one. * views:: Manage views (advanced workspaces). * panes:: Manage windows in panes. * menu:: Display a menu of options. * modewindow:: Display a general information window. * modestatus:: Display window manager status in a modewindow. * mw_clock:: Display current time in a modewindow. * mw_biff:: New mail notification in a modewindow. * mw_apm:: Display laptop battery status in a modewindow. * input:: Read input text from the user, with editing. * inspect:: Allow remote inspection of PLWM internals. @end menu @node color @subsection @code{color} Extension Module @code{color} provides a screen mixin for color handling: @deftp {Screen Mixin} Color @code{Color} handles color allocation. It maintains a cache of allocated colors to reduce @sc{plwm}'s colormap footprint on displays with a low bit depth. @defmethod Color get_color ( color, default = None ) Returns the pixel value corresponding to @var{color}. @var{color} can be a string or tuple of (R, G, B) integers. If the color can't be allocated and @var{default} is provided, @code{get_color} tries to return that color instead. If that fails too, it raises a @code{ColorError} exception. @end defmethod @defmethod Color get_color_res ( res_name, res_class, @w{default = None} ) Return the pixel value for the color defined in the X resource identified by @code{res_name} @code{res_class}. @code{WindowManager.rdb_get} is used to lookup the resource, so the first components of the name and class should be omitted and they should start with @samp{.}. If @code{default} is provided, that name will be used if no matching X resource is found. If omitted, or if the color can't be allocated, @code{ColorError} is raised. @end defmethod @end deftp @node font @subsection @code{font} Extension Module @code{font} provides a window manager mixin for loading fonts: @deftp {WindowManager Mixin} Font Font provides two functions for loading fonts: @defmethod Font get_font ( fontname, default = None ) Returns the font object corresponding to @var{fontname}. If @var{fontname} doesn't match any font, attemt to return the font named @var{default} instead, if @code{default} is provided. If no font can be found, @code{FontError} is raised. @end defmethod @defmethod Font get_font_res ( res_name, res_class, @w{default = None} ) Return the font object corresponding to the X resource identified by @code{res_name} @code{res_class}. @code{WindowManager.rdb_get} is used to lookup the resource, so the first components of the name and class should be omitted and they should start with @samp{.}. If this resource isn't found or doesn't match any font, attempt to return the font named @var{default }instead, if @var{default} is provided. If no font can be found, @var{FontError} is raised @end defmethod @end deftp @node keys @subsection @code{keys} Extension Module @code{keys} provides two classes for handling key events: @code{KeyHandler} and its subclass @code{KeyGrabKeyboard}. @deffn Class KeyHandler ( obj ) Represents a key handler, and should only be used as a base class, never instantiated directly. Instantiate a class derived from KeyHandler to install its key handler. When instantiating, @var{obj} should be a @code{WindowManager}, @code{Screen}, or @code{Client} object. If @var{obj} is a @code{WindowManager} object the key bindings defined will be active on all screens. If @var{obj} is a @code{Screen} object the key bindings will only be active when that screen is the current one. If @var{obj} is a @code{Client} object the key bindings will only be active when that client is focused. @defivar KeyHandler propagate_keys This attribute controls whether this key handler will allow other key handlers to recieve events. If it is true, which is the default, key events will be passed to all currently installed key handlers. If it is false key events will only reach this key handler and other installed handlers will never see them. @end defivar @defivar KeyHandler timeout If this is set to a number, the key handler method @code{_timeout} will be called if no key has been pressed for @code{timeout} number of seconds. This is @code{None} by defalt, meaning that there are no timeout for this keyhandler. @end defivar @defmethod KeyHandler _timeout ( event ) Called when the timeout is reached, if any. @var{event} is the @code{TimerEvent} causing the timeout. Key handlers using a timeout should override this method. @end defmethod @defmethod KeyHandler _cleanup ( ) Uninstall the key handler. This will remove all grabs held by the keyhandler, and remove its event handlers from the event dispatcher. Typically this is called from an overridden @code{_timeout}. @end defmethod @end deffn @deffn Class KeyGrabKeyboard ( obj, time ) This @code{KeyHandler} subclass should be used when the application whishes to grab all key events, not only those corresponding to methods. The @var{obj} argument is the same as for @code{KeyHandler}. @var{time} is the X time of the event which caused this key handler to be installed, typically the @code{time} attribute of the event object. It can also be the constant @code{X.CurrentTime}. This class also changes the defaults for @code{propagate_keys} to false and @code{timeout} to 10 seconds, and provides a @code{_timeout} method which uninstalles the key handler. @end deffn A key handler is created by subclassing @code{KeyHandler} or @code{KeyGrabKeyboard}. All methods defined in the new key handler class represents represents key bindings. When a key event occures that match one of the methods, that method will be called with the event object as the only argument. The name of the method encodes the key event the method is bound to. The syntax looks like this: @example name :== keysym | modifiers '_' keysym keysym :== modifiers :== modifiers '_' modifier | modifier modifier :== 'S' | 'C' | 'M' | 'M1' | 'M2' | 'M3' | 'M4' | 'M5' | 'Any' | 'None' | 'R' @end example In other words, the method name should be a list of modifiers followed by the name of a keysym, all separated by underscores. The keysyms are found in the Python Xlib module @code{Xlib.XK}. The modifiers have the following intepretation: @multitable @columnfractions 0.1 0.9 @item S @tab Shift @item C @tab Control @item M @tab Meta or Alt (interpreted as Mod1) @item M1 @tab Mod1 @item ... @tab ... @item M5 @tab Mod5 @item Any @tab Any modifier state, should not be combined with other modifiers @item None @tab No modifiers, useful for binding to the key @code{9}, or other keysyms which are not valid method names by themselves @item R @tab Bind to the key release event instead of the key press event @end multitable @node focus @subsection @code{focus} Extension Module @code{focus} provides classes to track and control window focus changes. The core classes will generate events when focus changes. The order of the generated events is @code{ClientFocusOut}, @code{CurrentClientChange}, and @code{ClientFocusIn}. @xref{Core Events}. @deftp {WindowManager Mixin} PointToFocus This window manager mixin sets the current client to the one which currently contains the pointer. Most of the time, the current client also has focus. However if the current client don't use input, the previously focused client remains that. @end deftp @deftp {WindowManager Mixin} {SloppyFocus} This is a subclass of @code{FocusHandler} which implements sloppy focus instead of point-to-focus. Sloppy focus means that a client will not loose focus when the pointer moves out to the root window, only when it moves to another client. @end deftp @deftp {WindowManager Mixin} {MoveFocus} This mixin defines a method for moving focus between clients: @defmethod FocusHandler move_focus ( dir ) Move the focus to the next window in direction @code{dir}, which should be one of the constants @code{focus.MOVE_UP}, @code{focus.MOVE_DOWN}, @code{focus.MOVE_LEFT} or @code{focus.MOVE_RIGHT}. Alas, this function is not very intelligent when choosing the next window, and it only works well when all windows are on a horizontal or vertical axis and focus is moved along that axis. @end defmethod @end deftp @node border @subsection @code{border} Extension Module This module provides a client mixin to change window border color depending on focus state. This module requires the @code{color} and @code{focus} modules to work properly. @deftp {Client Mixin} BorderClient Set a border on windows, and change its color depending on focus state. The colors used are set with the X resources @code{plwm.border.color/Plwm.Border.Color} and @code{plwm.border.focus.color/Plwm.Border.Focus.Color}. The defaults for these are "black" and "grey60", respectively. @defivar BorderClient border_default_width The border width in pixels. Default is 3. @end defivar @defivar BorderClient no_border_clients A client filter, used to select clients which should have no border. The default is @code{cfilter.false}, so all clients will have borders. @end defivar @end deftp @node outline @subsection @code{outline} Extension Module This module provides different ways of drawing an outline of windows. All outline classes are client mixins and have the same interface: @defmethod OutlineClient outline_show ( @w{x = None,} @w{y = None,} @w{w = None,} @w{h = None,} @w{name = None} ) Show an outline for this client's window. If an outline already is visible, it will be changed to reflect the arguments. The arguments @var{x}, @var{y}, @var{w} and @var{h} gives the geometry of the outline. If any of these are not provided, the corresponding value from the current window geometry will be used. If @var{name} is provided, that string will be displayed in the middle of the outline. @end defmethod @defmethod OutlineClient outline_hide ( ) Hide the outline, if it is visible. @end defmethod Currently there are two outline classes: @deftp {Client Mixin} XorOutlineClient Draws the outline directly on the display, by xor-ing pixel values. The font used is set with the X resource @code{plwm.outline.font/Plwm.Outline.Font}. The default is @code{fixed}. This is the most efficient outline method, but it has a few problems. If the windows under the outline changes, remains of the outline will still visible when it is hidden. The windows can be restored by e.g. iconifying and deiconifiying, switching to another view and back, or in an Emacs pressing @code{C-l}. A bigger problem is that some combinations of depth, visual and colormap of the root window causes the xor of black to be black. This results in an invisible outline if you have a black background. This can be solved by changing the background colour of the root, or using some other outline method. @end deftp @deftp {Client Mixin} WindowOutlineClient This ``draws'' the outline by creating a set of thin windows, simulating drawing lines on the screen. Any name is displayed by drawing it in a centered window, using the font specified as above. This is less efficient than an xor outline, since eight or nine windows have to be moved and resized if the outline is changed. However, it does not have any of the problems listed for @code{XorOutlineClient}. The colours used is currently hardcoded to black and white. @end deftp @node moveresize @subsection @code{moveresize} Extension Module This module provides functionality for moving and resizing windows. The core functionality is implemented by the abstract base class @code{MoveResize}. It is subclassed by the two classes @code{MoveResizeOpaque} and @code{MoveResizeOutline} which resizes windows by changing the window size, and by drawing an outline of the new size, respectivelly. The latter requires the @code{outline} module. See the code for details on these classes. Most resizing will be done via key handlers, so a template key handler class is provides that simplifies writing your own moving and resizing keyhandler for the currently focused client: @deffn Class MoveResizeKeys ( from_keyhandler, event ) @code{MoveResizeKeys} contains methods for the various move and resize operations. Is should be subclassed, and in the subclass key binding method names should be assigned to the general methods. There are 24 general methods: @multitable @columnfractions .15 .85 @item _move_X @tab Move the client in direction X @item _enlarge_X @tab Enlarge the client in direction X @item _shrink_X @tab Shrink the client from direction X @end multitable The direction is one of eight combinations of the four cardinal points: e, ne, n, nw, w, sw, s and se. Additionally theres two methods for finishing the moveresize: @multitable @columnfractions .15 .85 @item _moveresize_end @tab Finish, actually moving and resizing the client @item _moveresize_abort @tab Abort, leaving client with its old geometry @end multitable By default outline moveresizing is used with the @code{MoveResizeOutline} class. This can be changed by redefining the attribute @code{_moveresize_class} to any subclass of @code{MoveResize}. A small @code{MoveResizeKeys} subclass example: @example class MyMRKeys(MoveResizeKeys): _moveresize_class = MoveResizeOpaque KP_Left = MoveResizeKeys._move_w KP_Right = MoveResizeKeys._move_e KP_Up = MoveResizeKeys._move_n KP_Down = MoveResizeKeys._move_s KP_Begin = MoveResizeKeys._moveresize_end Escape = MoveResizeKeys._moveresize_abort @end example This would be invoked like this in a keyhandler event method in your basic keyhandler: @example def KP_Begin(self, evt): MyMRKeys(self, evt) @end example @end deffn @code{MoveResize} generates events during operation. The @code{type} attribute for all these are the event class, and the @code{client} attribute is the affected client. @code{MoveResizeStart} is generated when the moveresize is started, @code{MoveResizeEnd} when it ends and the window geometry is changed, and @code{MoveResizeEnd} when it is aborted and the window geometry is left unchanged. @code{MoveResizeDo} is generated for each change in window geometry during moveresize. It has four attributes denoting the current geometry: @code{x}, @code{y}, @code{width} and @code{height}. @node cycle @subsection @code{cycle} Extension Module @code{cycle} provides classes for cycling among windows to select one of them to be activated. This is performed by an abstract base class: @deffn Class Cycle ( screen, client_filter ) Cycle among the windows on @var{screen} matching @var{client_filter}. @defmethod Cycle next ( ) Cycle to the next window. @end defmethod @defmethod Cycle previous ( ) Cycle to the previous window. @end defmethod @defmethod Cycle end ( ) Finish and activating the selected window. @end defmethod @defmethod Cycle abort ( ) Abort, not activating the selected window. @end defmethod @end deffn This is implemented by two subclasses: @code{CycleActive} which cycles among windows by activating them in turn, and @code{CycleOutline} which cycle among windows by drawing an outline of the currently selected window. The latter requires the @code{outline} extension. To simplify writing a key handler for cycling, a template key handler is provided: @deffn Class CycleKeys ( keyhandler, event ) Cycle among the windows on the current screen matching the client filter specified by the attribute @code{_cycle_filter}. This is @code{cfilter.true} by default, cycling among all windows. The cycle method is specified by the attribute @code{_cycle_class}, which by default is @code{CycleOutline}. CycleKeys defines a number of event handler methods: @multitable @columnfractions .15 .85 @item _cycle_next @tab Cycle to the next client @item _cycle_previous @tab Cycle to the previous client @item _cycle_end @tab Finish, selecting the current client @item _cycle_abort @tab Abort, reverting to the previous state (if possible) @end multitable A small @code{CycleKeys} subclass example: @example class MyCycleKeys(CycleKeys): _cycle_class = CycleActivate _cycle_filter = cfilter.Not(cfilter.iconified) Tab = CycleKeys._cycle_next C_Tab = CycleKeys._cycle_next S_Tab = CycleKeys._cycle_previous S_C_Tab = CycleKeys._cycle_previous Return = CycleKeys._cycle_end Escape = CycleKeys._cycle_abort @end example To activate your cycle keys, write a keyhandler event method like this in your basic keyhandler: @example def C_Tab(self, evt): MyCycleKeys(self, evt) @end example @end deffn @node views @subsection @code{views} Extension Module Views are @sc{plwm}'s "workspaces". Many window manager have the concept of workspaces, or virtual screens. They give the illusion of having several screens, although only one of them can be displayed at a given time on the physical screen. This is done by iconifying the windows not visible on a workspace when that workspace is displayed. Views does this too, and more. A view can be seen as a projection of the avialable windows onto the screen in a certain configuration. Views not only remembers which windows are visible on them, but also their geometry and stacking order. This means that the same window can appear on several views in a different place, even with a different size, on each view. Views also remembers the pointer position when swapping to another view, and restores it when the view is activated again. All information about the view configuration is stored when @sc{plwm} exits, so it can be restored after a restart. Additionally, you can create views dynamically when you need them, and when they are no longer needed they will be destroyed. A view is considered to be unneeded if it is empty when you switch to another view. All this is handled by the screen mixin @code{ViewHandler}: @deftp {Screen Mixin} ViewHandler @defivar ViewHandler view_always_visible_clients A client filter matching the client windows that should be visible on all views, irrespective of view configuration. When determining if a view is empty so it can be deleted, these windows will be ignored. The default value is @code{cfilter.false}. @end defivar @defmethod ViewHandler view_new ( copyconf = 0 ) Create a new view and switch to it. If @var{copyconf} is true, the window configuration of the current view will be copied, otherwise the new view will be empty. @end defmethod @defmethod ViewHandler view_next ( ) Switch to the next view. @end defmethod @defmethod ViewHandler view_prev ( ) Switch to the previous view. @end defmethod @defmethod ViewHandler view_goto ( index, noexc = 0 ) Switch to view number @var{index}, counting from 0. If @var{noexc} is false @code{IndexError} will be raised if @var{index} is out or range. If @var{noexc} is true, quitely return. @end defmethod @defmethod ViewHandler view_find_with_client ( clients ) Switch to the next view where there is a visible client matching the client filter @var{clients}. Beeps if there none. @end defmethod @defmethod ViewHandler view_tag ( tag ) Set a tag on the current view. @var{tag} can be any string. @end defmethod @defmethod ViewHandler view_find_tag ( tag ) Switch to the next view with tag @var{tag}. Beeps if there is none. @end defmethod @end deftp If there is a modewindow, one can use the mixin @code{XMW_ViewHandler} instead of @code{ViewHandler} to get information on the current view number and tags in the modewindow. @node menu @subsection @code{menu} Extension Module The @code{menu} module provides a screen mixin to display a menu of options for the user to select from, as well as a pair of keyboard handler templates for the menu. There is only one menu for each screen, but the available options may be changed each time it is displayed. To the user, there appear to be many menus, but only one may be displayed at a time. @deftp {Screen Mixin} screenMenu Provides the menu window for each screen. The look of the window is controlled by the following class variables: @multitable {menu_borderwidth} {9x15bold} {Background color for the menu window.} @item Variable @tab Default @tab Description @item menu_fontname @tab 9x15bold @tab Font for menu options. @item menu_foreground @tab black @tab Foreground color for the menu window. @item menu_background @tab white @tab Background color for the menu window. @item menu_borderwidth @tab 3 @tab Border for the men window. @item menu_handler @tab MenuKeyHandler @tab Keyboard handler for menus. @end multitable @defmethod screenMenu menu_make ( labels, align = 'center' ) Creates a menu window from @var{labels}, which must be a sequence of strings. The strings will be aligned in the window according to the value of @var{align}, which may be @code{'left'}, @code{'right'} or @code{'center'}. The @var{width} and @var{height} of the resulting window are returned as a tuple for use in calculating the menu placement. @end defmethod @defmethod screenMenu menu_run ( x, y, action ) @var{x} and @var{y} are the coordinates the menu should be placed at. @var{action} is a callable argument that will be invoked with the string used for the label the user selected. If the user aborts the menu, action will not be invoked. @end defmethod A simple example of a menu with dictionary of functions might be: @example class MyFunctionMenu: def __init__(self, screen, dict): self.dict = dict labels = dict.keys() labels.sort() width, height = screen.menu_make(labels) # Center the menu screen.menu_run((screen.root_width - width) / 2, (screen.root_height - height) / 2, self) def __call__(self, choice): self.dict[choice]() @end example @end deftp Making selections and aborting the menu are done via key handlers @xref{keys}, and two template key handlers are provided for menu selections: @deffn Class MenuKeyHandler @code{MenuKeyHandler} provides the methods @code{_up}, @code{_down}, @code{_do} and @code{_abort}. These move the current selection, pass the current selection to the @var{action} object passed to @code{menu_run}, and abort the menu taking no action. A binding with Emacs keys might look like: @example class MyMenuKeys(MenuKeyHandler): C_p = MenuKeyHandler._up C_n = MenuKeyHandler._down Return = MenuKeyHandler._do C_g = MenuKeyHandler._abort @end example @end deffn @deffn Class MenuCharHandler @code{MenuCharHandler} adds the @code{_goto} method, which moves the current selection to the first label that starts with the a character greater than or equal to the typed key. It then binds the keys @code{a} to @code{z} and @code{0} to @code{9} to _goto. This lets the user select labels by their first character if @code{MenuCharHandler} is used instead of @code{MenuKeyHandler}. @end deffn To have menus on your screen use your menu keys, you would add the @code{screenMenu} mixin and set the @code{menu_handler} class variable: @example class MyScreen(Screen, screenMenu): menu_handler = MyMenuKeys @end example @node panes @subsection @code{panes} Extension Module The @code{panes} mixins provide an alternative method of managing windows. Rather than wrapping each window in a frame which is manipulated to manipulate the window, windows are placed in "panes", and the only thing the user can do to windows in a pane is circulate through them. When a window is placed in a pane, it will be resized to the largest size that it can handle which will fit in that pane. However, panes can be split into two parts at whatever fraction of the full pane the user chooses, so that panes can be created with nearly arbitrary geometry. Panes do not overlap, and every pixel on the screen is in a pane. See @file{examples/plpwm.py} for an example of using panes to build a window manager. @deftp Class Pane @defmethod Pane add_window ( client ) Adds the clients window to the current pane. It will become the top window in the pane. If the current top window in the pane has focus, the new top window will get focus. @end defmethod @defmethod Pane iconify_window ( ) Iconifies the panes active window. @end defmethod @defmethod Pane force_window ( ) Resize the window again. This actually resizes the window down then back up, and is useful if the application doesn't realize how big the window really is. This is most often seen in programs started in an xterm by the xterm command. @end defmethod @defmethod Pane next_window ( ) Make the next window associated with this pane the top window. @end defmethod @defmethod Pane prev_window ( ) Make the previous window associated with this pane the top window. When a window is added to a pane, the window that was the top window becomes the previous window. @end defmethod @defmethod Pane horizontal_split ( fraction = .5 ) Split the current pane into two halves horizontally. The new pane will get @var{fraction} of the current panes height at the bottom of the current pane, and will become the active pane. @end defmethod @defmethod Pane vertical_split ( fraction = .5 ) Split the current pane into two halves vertically. The new pane will get @var{fraction} of the current panes width at the right of the current pane, and will become the active pane. @end defmethod @defmethod Pane maximize ( ) Make the current pane occupy the entire screen, removing all other panes. @end defmethod @end deftp @deffn Filter panelfilter ( pane ) True if the client is in the given pane. @end deffn @deftp {WindowManager Mixin} panesManager The @code{panesManager} mixin adds panes and pane manipulation to the window manager. @defivar panesManager panes_list The list of panes managed by this windowmanager. @end defivar @defivar panesManager panes_current The index of the pane containing the currently active window, also known as the active pane. @end defivar @defivar panesManager panes_window_gravity The gravity to be used for normal windows in this pane. @end defivar @defivar panesManager panes_maxsize_gravity The gravity to be used for windows with maxsize hints in this pane. @end defivar @defivar panesManager panes_transient_gravity The gravity to be used for transient windows in this pane. @end defivar @defmethod panesManager panes_goto ( index ) Make the pane at @var{index} in @code{panes_list} the active pane. @end defmethod @defmethod panesManager panes_activate ( pane ) Make @var{pane} the active pane. @end defmethod @defmethod panesManager panes_next ( ) Make the next pane in the list the active pane. If the last pane is the active pane, make pane 0 the active pane. @end defmethod @defmethod panesManager panes_prev ( ) Make the previous pane in the list the active pane. If pane 0 was active, make the last pane in the list active. @end defmethod @defmethod panesManager panes_number ( number ) Rearrange @code{panes_list} so that the active pane is pane @var{number}. This is done by exchanging the list positions of pane @var{number} and pane @code{panes_current}. @end defmethod @defmethod panesManager panes_save ( ) Save the state of all clients by building a dictionary of which pane they are associated with. @end defmethod @defmethod panesManager panes_restore ( ) Put all clients back in the pane they were in when panes_save was last invoked, if possible. If the pane doesn't exist or is on a different screen from the window, the restore isn't possible. @end defmethod @end deftp @deffn {Screen Mixin} panesScreen This mixin causes the first pane to be created on each screen being managed. It has no variables or methods useful to the user, but you must mix it into your screen class if you want to use panes. @end deffn @deffn {Client Mixin} panesClient This mixin passes client events to the pane that the client's window is associated with. It has no variables or methods useful to the user, but you must mix it into your client class if you want to use panes. @end deffn @node modewindow @subsection @code{modewindow} Extension Module The @code{modewindow} module provides a screen mixin to display a window containing general window manager information, and a class representing this information. The name of the module derives from the mode-line in Emacs, which has a similar function. @deftp {Screen Mixin} ModeWindowScreen Displays a mode window on the screen. The look of the window is controlled with the following X resources: @table @asis @item plwm.modewindow.foreground/Plwm.ModeWindow.Foreground @itemx plwm.modewindow.background/Plwm.ModeWindow.Background These set the colors to be used by the modewindow. Defaults are black foreground and white background. @item plwm.modewindow.font/Plwm.ModeWindow.Font The font to use in the modewindow. Default is fixed. @end table @defivar ModeWindowScreen modewindow_pos Controls the position of the mode window. Can either be @code{modewindow.TOP} or @code{modewindow.BOTTOM}. @end defivar @defmethod ModeWindowScreen modewindow_add_message ( message ) Add @var{message}, which must be a @code{Message} object, to this mode window. A single message object can be added to several mode windows. @end defmethod @defmethod ModeWindowScreen modewindow_remove_message ( message ) Remove @var{message} from this modewindow. @end defmethod @end deftp @deffn Class Message ( position, @w{justification = modewindow.CENTER,} @w{nice = 0,} @w{text = None} ) Represents a single message to be displayed in one or more mode windows. @var{position} is the horizontal position for this message in the modewindow, and should be a float in the range @w{[0.0, 1.0]}. The message text is drawn at this point according to @var{justification}, which should be one of the values @code{modewindow.LEFT}, @code{modewindow.CENTER} or @code{modewindow.RIGHT}. @var{nice} is currently not used, but is meant to be used to avoid message overlaps by shuffling less important messages around. Finally, @var{text} is the initial text of this message, where @code{None} means an empty message. @defmethod Message set_text ( text ) Change the text of this message to @var{text}. All affected mode windows will be redrawn, if necessary. @end defmethod @end deffn @node modestatus @subsection @code{modestatus} Extension Module @code{modestatus} is a layer on top of @code{modewindow}, providing a way to display the current status of the window manager, e.g. the focused window, the geometry of a window during resize, and more. @deftp {Screen Mixin} ModeStatus Add a status message to the center of this screen's mode window. The status message is really a stack of different messages, where the top-most message is currently displayed. @defmethod ModeStatus modestatus_set_default ( text ) Set the default text to be displayed when there is no special status to @var{text}. @end defmethod @defmethod ModeStatus modestatus_new ( text = '' ) Push a new message on to the status message stack. A @code{ModeText} object will be returned, which will have @var{text} as the initial message. @end defmethod @end deftp @deffn Class ModeText This class should never be instantiated directly, only through @code{ModeStatus.modestatus_new}. @defmethod ModeText set ( text ) Set the text of this status message to @var{text}. If this is the top-most message, the new text will be displayed. @end defmethod @defmethod ModeText pop ( ) Remove this message from the message stack. If this was the top-most message, the previous message will be displayed instead. @end defmethod @end deffn This module also provides some mixins that use the mode status functionality: @deftp {Client Mixin} ModeFocusedTitle Display the currently focused client's title as the default message. @end deftp @deftp {Screen Mixin} ModeMoveResize When moving and resizing, display the title of the displayed window and the current geometry. The format of the message is controlled by an X resource with name @code{plwm.moveResize.modeFormat} and class @code{Plwm.MoveResize.ModeFormat}. It should be a Python format string, where @code{%(title)s} will be replaced with the client title, and @code{%(geometry)s} with the current geometry. The default format is @code{%(title)s [%(geometry)s]}. @end deftp @node mw_clock @subsection @code{mw_clock} Extension Module @deftp {WindowManager Mixin} ModeWindowClock This mixin displays the current time in all mode windows. It is updated once a minute. The format is a @code{time.strftime} format, by default @code{%H:%M}. It can be changed with an X resource with name @code{plwm.modewindow.clock.format} and class @code{Plwm.ModeWindow.Clock.Format}. @defivar ModeWindowClock mw_clock_position The position of the time message in the mode window, default 1.0. @end defivar @defivar ModeWindowClock mw_clock_justification The justification of the time message, default is @code{modewindow.RIGHT}. @end defivar @end deftp @node mw_biff @subsection @code{mw_biff} Extension Module This module provides two different mail notifications mixins, which both use the mode windows and beeping for notification. They assume that new mail is stored in @code{$MAIL}, and removed from it when read. This works well with the behaviour of Gnus with the nnmail backend. When @code{$MAIL} is empty or non-existent, no message is displayed. When new mail arrives the message @samp{New mail} is displayed, and the speaker beeps. If @code{$MAIL} is accessed without being emptied, the message is changed to @samp{Mail}. When @code{$MAIL} is emptied, the message is removed again. The messages can be changed with X resources. The X resource with name @code{plwm.modewindow.newMail.text} and class @code{Plwm.ModeWindow.NewMail.Text} controls the new mail message, and the resource with name @code{plwm.modewindow.Mail.text} and class @code{Plwm.ModeWindow.Mail.Text} controls the mail exists message. Changing the mail exists message to @samp{} could be useful if one uses a mail client that leaves read mail in @code{$MAIL}. @deftp {WindowManager Mixin} ModeWindowBiff Displays a mail notification message in all mode windows. @defivar ModeWindowBiff mw_biff_position The position of the mail message in the mode window, default 0.0. @end defivar @defivar ModeWindowBiff mw_biff_justification The justification of the mail message, default is @code{modewindow.LEFT}. @end defivar @end deftp If the mailspool is mounted over NFS, it might be unadvisable to access it from the window manager. If the NFS server should freeze, the entire window manager would be unusable until the server recovers. Therefore a threaded biff mixin is provided: @deftp {WindowManager Mixin} ThreadedModeWindowBiff This subclasses @code{ModeWindowBiff}, with the change that access to @code{$MAIL} is done in a separate thread. As @sc{plwm} certainly isn't thread-safe, all interaction with the rest of the window manager modules is done in the main thread. Communication between the mailspool access thread and the main thread is done without locks or semaphores. The main thread polls at regular intervals whether the access thread has finished yet, and if so, updates the mode window. This uses the property of Python threading that only one thread at a time may access Python objects. If this would ever change, this class might break. @end deftp @node mw_apm @subsection @code{mw_apm} Extension Module @deftp {WindowManager Mixin} ModeWindowAPM This mixin displays the battery status in all mode windows. There is a generic interface for fetching the status, making it easy to port this module to different @sc{apm} systems. Currently, the only supported system is the special file @file{/proc/apm} of Linux systems. @defivar ModeWindowClock mw_apm_position The position of the battery status in the mode window, default 0.2. @end defivar @defivar ModeWindowClock mw_apm_justification The justification of the battery status, default is @code{modewindow.RIGHT}. @end defivar @end deftp @node input @subsection @code{input} Extensions Module @code{Input} provides tools to let the window manager read a line of input from the user and act on it. It provides a subclass of @code{KeyGrabKeyboard} (@pxref{keys}) for configuration, and two classes to read input. @deftp Class InputKeyHandler ( handler, displayer ) Creates a key handler class for editing input. The @var{handler} is any object acceptable to @code{KeyGrabKeyboard}. @var{displyer}@code{.show(@var{left}, @var{right})} is called to display the two strings with a curser between them. @var{displayer}@code{.do(@var{text})} is called when the user is through editing the input. @var{displayer}@code{.abort()} is called if the user aborts the operation. @code{InputKeyHandler} provides the following methods that may be bound to keystrokes like any other keyhander. @xref{keys}. @defmethod InputKeyHandler _insert ( event ) The key pressed to generate @code{event} is inserted into the buffer at the cursor. All the characters of the latin1 character set are bound to this by default. @end defmethod @defmethod InputKeyHandler _forw (event) Move the cursor forward one character in the edited text. @end defmethod @defmethod InputKeyHandler _back (event) Move the cursor backward one character in the edited text. @end defmethod @defmethod InputKeyHandler _delforw (event) Delete the character in front of the cursor in the edited text. @end defmethod @defmethod InputKeyHandler _back (event) Delete the character behind of the cursor in the edited text. @end defmethod @defmethod InputKeyHandler _end (event) Move the cursor to the end of the edited text. @end defmethod @defmethod InputKeyHandler _begin (event) Move the cursor to the beginning of the edited text. @end defmethod @defmethod InputKeyHandler _back (event) Delete all the characters from the cursor to the end of the edited text. @end defmethod @defmethod InputKeyHandler _paste (event) If text is selected, it will be inserted into the edited text at the cursor, as if typed by the user. For this to work, the @code{handler} must be an instance of @code{wmanager.Window}. If that is not the case, or no text is currently selected, this does nothing. @end defmethod @defmethod InputKeyHandler _done (event) Finishes the action, and calls @code{displayer.do} passing it the edited text. @end defmethod @defmethod InputKeyHander _abort (event) Aborts the input operation, callgin @code{displayer.abort()}. @end defmethod @end deftp @deftp Class inputWindow ( prompt, screen, @w{length = 30}) @code{inputWindow} is a class that creates a window on @var{screen} and uses an @code{InputKeyHandler} to read input from the user. The user is prompted with @var{prompt}. Space is left for @var{length} extra characterfs to display in the window, but it will scroll to keep the cursor always in view. @defivar inputWindow fontname @defivarx inputWindow foreground @defivarx inputWindow background @defivarx inputWindow borderwidth The attributes of the window used to read the input. The defaults are 9x15 for the @var{fontname}, a black @var{foreground} on a white @var{background}, and a @var{borderwidth} of 3. @end defivar @defivar inputWindow height @defivarx inputWindow width The @var{height} and @var{width} of the window will be available as attributes after the @code{inputWindow} is instantiated. @end defivar @defmethod inputWindow read ( action, handlertype, @w{x = 0}, @w{y = 0} ) The read method of the inputWindow is called to read text from user and act on it. @var{handlertype} should be a subclass of @code{InputKeyHandler} with appropriate bindings for editing the text. @var{action} will be invoked with the edited text as it's sole argument when @var{handlertypes}'s @code{_done} action is invoked. @end defmethod @end deftp @deftp Class modeInput ( prompt, screen ) An @code{modeInput} object uses the @code{modewindow} on @var{screen} to read input from the user. @xref{modewindow}. It has an @code{read} method that takes the same arguments as the @code{read} method of @code{inputWindow}, except that x and y are ignored. @end deftp @node inspect @subsection @code{inspect} Extension Module @code{Inspect} allows a special client to remotly connect to the running window manager. The client can then execute Python statements in the context of the window manager. This can be used to inspect the internal state of the window manager, change it, or whatever you might come up with. The server side is implemented by a window manager mixin. For documentation on the client program, see @ref{inspect_plwm}. @deftp {Window Manager Mixin} InspectServer The inspect server can be enabled or disabled. When it is enabled, the message @code{[Inspect]} is showed in the modewindow. When inspect clients connect it will change to also show the number of connected clients. @defivar inspect_enabled_at_start Whether the inspect server should be enabled at startup. Default value is false. @end defivar @defmethod InspectServer inspect_enable ( ) Enable the inspect server. @end defmethod @defmethod InspectServer inspect_disable ( force = 0 ) Disable the inspect server. If @var{force} is false disabling will fail if clients are connected, which will be indicated by a beep. If @var{force} is true the clients will be disconnected and the inspect server disabled. @end defmethod @defmethod InspectServer inspect_toggle ( force = 0 ) Toggle the inspect server on or off. @var{force} is only used for disabling, and has the same meaning as for @code{inspect_disable}. @end defmethod @end deftp The inspect server is as secure as your X display. The inspect server announces the @sc{tcp} port it listens on in a property on the root window. In the same property it also stores a random, 31-bit cookie. To be able to connect to the inspect server the client must fetch this property to find the port to connect to, and the cookie to send as authorization. Therefore only those with access to your X display, thanks to xhost or xauth, can connect to the inspect server. @node Utilities @chapter Utilities The following small X programs can be seen as a first step towards making @sc{plwm} a full pointless desktop to rival @sc{gnome} and @sc{kde}. Well, maybe not. @menu * wmm:: The Window Manager Manager. * inspect_plwm:: @sc{plwm} remote inspection client. @end menu @node wmm @section wmm wmm, the Window Manager Manager, is a utility to simplify testing your freshly hacked window manager. It opens a small window containing one or more buttons. When a button is clicked, a command is executed. The window manager managing feature is that if wmm is iconified by the running window manager it will be mapped when the window manager exits or crashes. When wmm detects that it has been mapped it will raise itself to the top of all the other windows. This ensures that it will be possible to click on one of its buttons to start another window manager, and thus be able to fix the bug which lurked in your window manager. wmm is configured with @code{~/.wmmrc}. For each non-blank line, not starting with a #, wmm will create a button. Each line should consist of two tab-separated fields, the first is the button label and the second is the command to run when the button is clicked (it should probably end with an @code{&} so WMM isn't locked). If the second field is missing, wmm will instead quit when the button is clicked. @node inspect_plwm @section inspect_plwm This utility is used to connect to the inspect server of a running window manager. The client must be able to connect to the display that the window manager is running on to be able to connect to the inspect server. The display is fetched from the @code{$DISPLAY} variable or the command line option @code{-display}. When successfully connected, a welcome message is displayed by the window manager and a common Python prompt is shown. Python expressions and statements can now be entered. They will be evaluated in the window manager, and the results or tracebacks will be printed. The code will be evalated in an environment containing all builtin functions and the variable @code{wm}, which points to the WindowManager object representing the window manager. An example session (long lines have been wrapped): @example [petli@@sid petli]$ inspect_plwm Welcome to PLWM at :0 >>> wm <__main__.PLWM instance at 82148a8> >>> wm.default_screen.clients.values() [<__main__.MyClient instance at 822ec28>, <__main__.MyClient instance at 8215ce8>, <__main__.MyClient instance at 8217370>, <__main__.MyClient instance at 822bfc0>] >>> import sys >>> map(lambda c: sys.stdout.write(c.get_title() + '\n'), wm.default_screen.clients.values()) xterm xterm WMManager emacs@@sid.cendio.se [None, None, None, None] >>> @end example Note that modules can be imported, and that sys.stdout and sys.stderr will output in the terminal window inspect_plwm is running in, even though the expressions are evaluated inside the window manager. Multi-line statements can also be written with a small kludge: The lines must start with exactly one space, and one signals that the suite is finished by entering an empty line (without any space at all). Example: @example >>> for c in wm.default_screen.clients.values(): ... print c.get_title(), c.geometry() ... xterm (516, 30, 502, 732, 3) xterm (0, 30, 508, 732, 3) WMManager (100, 0, 63, 71, 3) emacs@@sid.cendio.se (192, 18, 632, 744, 3) >>> @end example @node ToDo @chapter ToDo Known bugs: @itemize @bullet @item None, right now. But there are probably still memory leaks, subversive behaviour and smelly code here and there. @end itemize Fairly simple improvements: @itemize @bullet @item X resources: get rid of them. Or at least use Client/Screen/WindowManager attributes in the first place, falling back on resources for backward compitability. @item modewin: Teach it to avoid overlapping texts. Currently it will only write overlapping texts on top on each other, but it'd be nice if it was intelligent enough to shuffle the texts around to avoid it. @item Being able to dynamically turn on and off debugging in a running @sc{plwm}. Most of the framework is there, it only needs some key bindings. @end itemize More advanced fixes and features: @itemize @bullet @item Improve inspection to allow pdb debugging. @item Provide a class which can represent the layout of the windows in such a way that one can easily ask for things like "the window to the left of this window", "the first window edge we run into moving this window upwards" or "the visible parts of this window". @item Real frames around the client windows. This requires reparenting the windows and thus quite a number of modifications to wmanager.Client. @item Maybe some people would like mouse support? @item Other focus methods, such as click-to-focus. @end itemize Real out-of-this-time features: @itemize @bullet @item Support for controlling specific clients from the keyboard. Ex: pressing tab in a Netscape window would move the pointer to the next hyperlink in the document. Update: Okay, Netscape 6.1 actually have tabbing between links. There are probably still uses for this idea, and the modification to @code{keys} in 2.3 was done to make this possible. @item Rewrite the whole thing as threaded collection of agents, or something. @sc{plwm} is beginning to feel quite bulky. @end itemize Misc stuff: @itemize @bullet @item Replace autoconf script with Distutils script. Or maybe a combination of both. @item Change the window script idea around, so that a small script is installed as @code{plwm} which then simply sources @file{~/.plwm.py}. @end itemize @node Credits @chapter Credits @sc{plwm} was born late one night in the spring of 1999 when Peter Liljenberg and Morgan Eklöf, as our habit is, enjoyed music and conversation. After some general window manager discussions and consensus on the beauty of using Python instead of yet another configuration file language, the name "the Pointless Window Manager" popped up. It was so good that we just had to implement it. When it came to hacking, Peter was more inclined to let the work (or life) suffer and as a result have written all of the code. Henrik Rindlöw has helped with ironing out bugs triggered in multiheaded environments. By being the first other active user of @sc{plwm} he has also found quite a number of more normal bugs. Our now former employer @uref{http://www.cendio.se/, Cendio Systems} deserves a paragraph here. The employee contracts explicitly mentioned that we were allowed to develop non-work-related GPL'd programs using the company's computers in our spare time, and keep the copyright. Nice. Now I can admit that quite a lot of work time also went into the development... Mike Meyer wrote the very interesting extension modules @code{panes} and @code{menu}, and the corresponding example window manager @code{plpwm}. @node Contact Info @chapter Contact Info Bug reports, feature requests, new modules, bug fixes, etc, should go to Peter Liljenberg . New versions of @sc{plwm} will be announced on @uref{http://plwm.sourceforge.net/, the @sc{plwm} website} and on @uref{http://www.freshmeat.net/, Freshmeat}. @sc{plwm} is a SourceForge project. Mailinglists, bug tracking and public CVS access can be found at @uref{http://sourceforge.net/project/plwm/, the project page}. @bye