#!/usr/bin/python # # 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], '..')] import plwm.xlibpath ###END SETUP PATH import time from Xlib import X, XK, Xatom from plwm import wmanager, focus, keys, \ deltamove, outline, \ border, color, font, views, \ modewindow, modestatus, \ mw_clock, mw_biff, mw_apm, \ inspect, misc, input from plwm.cycle import CycleKeys, CycleActivate from plwm.moveresize import MoveResizeKeys from plwm.cfilter import * delta = deltamove.DeltaMove() # Netscape 6.1 have forgotten how to scroll backwards with the backspace # key. Relearn it, using the XTEST extension. Oops: naturally this also # affected backspace in text edit fields. Disable entirely until this # can be fixed... class MozillaKeys(keys.KeyHandler): def __init__(self, obj): keys.KeyHandler.__init__(self, obj) self.pageup_code = self.wm.display.keysym_to_keycode(XK.XK_Prior) # Disabled for now, as the grabbing means that the faked input will go # back to us and not to the Netscape window. Therefore, only generate # event at KeyRelease until I figure out a way around this. # def Any_BackSpace(self, evt): # if self.pageup_code: # self.wm.display.xtest_fake_input(X.KeyPress, self.pageup_code) def R_Any_BackSpace(self, evt): if self.pageup_code: self.wm.display.xtest_fake_input(X.KeyPress, self.pageup_code) self.wm.display.xtest_fake_input(X.KeyRelease, self.pageup_code, 5) class TeachMozillaBackspace: def __client_init__(self): if self.res_class == 'Mozilla-bin': MozillaKeys(self) class MyMozillaPopupKeymap(misc.MozillaPopupKeymap): M5_a = misc.MozillaPopupKeymap._accept _key_name = 'M5-a' class MyClient(wmanager.Client, outline.XorOutlineClient, # outline.WindowOutlineClient, border.BorderClient, modestatus.ModeFocusedTitleClient, misc.InhibitMozillaPopups, misc.InitialKeepOnScreenClient, focus.JumpstartClient, # TeachMozillaBackspace, ): no_border_clients = none start_iconified_clients = name('WMManager') default_pointer_pos = {'Emacs': (-1, 0), 'XTerm': (-1, 0)} mozpopup_keymap = MyMozillaPopupKeymap class MyScreen(wmanager.Screen, color.Color, modewindow.ModeWindowScreen, modestatus.ModeStatus, modestatus.ModeMoveResize, views.XMW_ViewHandler, modestatus.ModeFocusedTitleScreen): view_always_visible_clients = none class WMConfig: def __wm_init__(self): BasicKeys(self) self.dispatch.add_handler('cmdevent', cmdhandler) class PLWM(wmanager.WindowManager, focus.SloppyFocus, focus.MoveFocus, font.Font, mw_clock.ModeWindowClock, # mw_biff.ThreadedModeWindowBiff, mw_apm.ModeWindowAPM, inspect.InspectServer, WMConfig): mw_apm_position = 0 mw_apm_justification = modewindow.LEFT client_class = MyClient screen_class = MyScreen def cmdhandler(evt): print 'Exit:', evt.exitstatus(), 'Signal:', evt.termsig() 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 -geometry 80x50+200+100') def C_S_F1(self, evt): self.wm.current_screen.view_new() self.wm.system('xterm -geometry 80x50+200+100') 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('Netscape'), name('Mozilla-bin'))) def S_F3(self, evt): self.wm.system('netscape') def C_S_F3(self, evt): self.wm.current_screen.view_new() self.wm.system('netscape') def F4(self, evt): self.wm.current_screen.view_find_with_client(Or(name('xpdf'), name('soffice'))) 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 F7(self, evt): self.wm.current_screen.view_find_tag('F7') def S_F7(self, evt): self.wm.current_screen.view_tag('F7') def F8(self, evt): self.wm.current_screen.view_find_tag('F8') def S_F8(self, evt): self.wm.current_screen.view_tag('F8') 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) # def F12(self, evt): # clients = self.wm.query_clients() # f = name('titrax') # for c in clients: # if f(c): # c.deiconify() # c.activate() # return # else: # self.wm.system('/opt/local/bin/titrax') 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)