#!/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