#!/usr/bin/python
#
# petliwm.py -- My PLWM "configuration"
#
# Copyright (C) 1999-2002 Peter Liljenberg <petli@ctrl-c.liu.se>
#
# 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)
syntax highlighted by Code2HTML, v. 0.9.1