summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorDana Jansens <danakj@orodu.net>2003-03-16 21:11:39 +0000
committerDana Jansens <danakj@orodu.net>2003-03-16 21:11:39 +0000
commitf8a47de5ec444c452093371e3db16857eb39a490 (patch)
tree31db2567842d98232775f9980f7a8d2586c0ac71 /python
parent8ba0586bcbdc7fe9648f1063812126d71a041670 (diff)
merge the C branch into HEAD
Diffstat (limited to 'python')
-rw-r--r--python/.cvsignore2
-rw-r--r--python/Makefile.am11
-rw-r--r--python/buttonmap.py76
-rw-r--r--python/config.py242
-rw-r--r--python/focus.py66
-rw-r--r--python/historyplacement.py164
-rw-r--r--python/keymap.py17
-rw-r--r--python/motion.py81
-rw-r--r--python/rc.py114
-rw-r--r--python/stackedcycle.py216
-rw-r--r--python/windowplacement.py47
11 files changed, 1036 insertions, 0 deletions
diff --git a/python/.cvsignore b/python/.cvsignore
new file mode 100644
index 00000000..282522db
--- /dev/null
+++ b/python/.cvsignore
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
diff --git a/python/Makefile.am b/python/Makefile.am
new file mode 100644
index 00000000..ebb3812e
--- /dev/null
+++ b/python/Makefile.am
@@ -0,0 +1,11 @@
+scriptdir=$(libdir)/openbox/python
+
+SUBDIRS =
+
+script_PYTHON = rc.py keymap.py buttonmap.py config.py motion.py \
+ historyplacement.py windowplacement.py stackedcycle.py focus.py
+
+MAINTAINERCLEANFILES= Makefile.in
+
+distclean-local:
+ $(RM) *\~ *.orig *.rej .\#*
diff --git a/python/buttonmap.py b/python/buttonmap.py
new file mode 100644
index 00000000..1a20281e
--- /dev/null
+++ b/python/buttonmap.py
@@ -0,0 +1,76 @@
+from input import Pointer
+
+def set(map):
+ """Set your buttonmap. Functions in the button map should all take a single
+ argument, a Client object, except for functions for Action_Motion events,
+ who should take 2 arguments, a PointerData object and a Client object."""
+ global _press_map, _release_map, _click_map, _doubleclick_map, _motion_map
+ Pointer.clearBinds()
+ _press_map = []
+ _release_map = []
+ _click_map = []
+ _doubleclick_map = []
+ _motion_map = []
+ for button, context, action, func in map:
+ if (action == Pointer.Action_Press):
+ _press_map.append((button, context, func))
+ mapfunc = press_run
+ if (action == Pointer.Action_Release):
+ _release_map.append((button, context, func))
+ mapfunc = release_run
+ if (action == Pointer.Action_Click):
+ _click_map.append((button, context, func))
+ mapfunc = click_run
+ if (action == Pointer.Action_DoubleClick):
+ _doubleclick_map.append((button, context, func))
+ mapfunc = doubleclick_run
+ if (action == Pointer.Action_Motion):
+ _motion_map.append((button, context, func))
+ mapfunc = motion_run
+ Pointer.bind(button, context, action, mapfunc)
+
+def press_run(ptrdata, client):
+ """Run a button press event through the buttonmap"""
+ button = ptrdata.button
+ context = ptrdata.context
+ for but, cont, func in _press_map:
+ if (but == button and cont == context):
+ func(client)
+
+def release_run(ptrdata, client):
+ """Run a button release event through the buttonmap"""
+ button = ptrdata.button
+ context = ptrdata.context
+ for but, cont, func in _release_map:
+ if (but == button and cont == context):
+ func(client)
+
+def click_run(ptrdata, client):
+ """Run a button click event through the buttonmap"""
+ button = ptrdata.button
+ context = ptrdata.context
+ for but, cont, func in _click_map:
+ if (but == button and cont == context):
+ func(client)
+
+def doubleclick_run(ptrdata, client):
+ """Run a button doubleclick event through the buttonmap"""
+ button = ptrdata.button
+ context = ptrdata.context
+ for but, cont, func in _doubleclick_map:
+ if (but == button and cont == context):
+ func(client)
+
+def motion_run(ptrdata, client):
+ """Run a pointer motion event through the buttonmap"""
+ button = ptrdata.button
+ context = ptrdata.context
+ for but, cont, func in _motion_map:
+ if (but == button and cont == context):
+ func(ptrdata, client)
+
+_press_map = ()
+_release_map = ()
+_click_map = ()
+_doubleclick_map = ()
+_motion_map = ()
diff --git a/python/config.py b/python/config.py
new file mode 100644
index 00000000..2ab22f96
--- /dev/null
+++ b/python/config.py
@@ -0,0 +1,242 @@
+# Openbox's config system. Please use the defined functions instead of
+# accessing the internal data structures directly, for the sake of us all.
+
+def add(modulename, name, friendlyname, description, type, default,
+ **keywords):
+ """Add a variable to the configuration system.
+
+ Add a variable to the configuration system for a module.
+ modulename - The name of the module, e.g. 'focus'
+ name - The name of the variable, e.g. 'my_variable'
+ friendlyname - The user-friendly name of the variable, e.g. 'My Variable'
+ description - The detailed destription of the variable, e.g. 'Does Things'
+ type - The type of the variable, one of:
+ - 'boolean'
+ - 'enum'
+ - 'integer'
+ - 'string'
+ - 'file'
+ - 'function'
+ - 'object'
+ default - The default value for the variable, e.g. 300
+ keywords - Extra keyword=value pairs to further define the variable. These
+ can be:
+ - For 'enum' types:
+ - options : A list of possible options for the variable.
+ This *must* be set for all enum variables.
+ - For 'integer' types:
+ - min : The minimum value for the variable.
+ - max : The maximum value for the variable.
+ """
+ modulename = str(modulename).lower()
+ name = str(name).lower()
+ friendlyname = str(friendlyname)
+ description = str(description)
+ type = str(type).lower()
+
+ # make sure the sub-dicts exist
+ try:
+ _settings[modulename]
+ try:
+ _settings[modulename][name]
+ except KeyError:
+ _settings[modulename][name] = {}
+ except KeyError:
+ _settings[modulename] = {}
+ _settings[modulename][name] = {}
+
+ # add the keywords first as they are used for the tests in set()
+ for key,value in zip(keywords.keys(), keywords.values()):
+ _settings[modulename][name][key] = value
+
+ _settings[modulename][name]['name'] = friendlyname
+ _settings[modulename][name]['description'] = description
+ _settings[modulename][name]['type'] = type
+ _settings[modulename][name]['default'] = default
+
+ # put it through the tests
+ try:
+ set(modulename, name, default)
+ except:
+ del _settings[modulename][name]
+ import sys
+ raise sys.exc_info()[0], sys.exc_info()[1] # re-raise it
+
+def set(modulename, name, value):
+ """Set a variable's value.
+
+ Sets the value for a variable of the specified module.
+ modulename - The name of the module, e.g. 'focus'
+ name - The name of the variable, e.g. 'my_variable'
+ value - The new value for the variable.
+ """
+ modulename = str(modulename).lower()
+ name = str(name).lower()
+
+ # proper value checking for 'boolean's
+ if _settings[modulename][name]['type'] == 'boolean':
+ if not (value == 0 or value == 1):
+ raise ValueError, 'Attempted to set ' + name + ' to a value of '+\
+ str(value) + ' but boolean variables can only contain 0 or'+\
+ ' 1.'
+
+ # proper value checking for 'enum's
+ elif _settings[modulename][name]['type'] == 'enum':
+ options = _settings[modulename][name]['options']
+ if not value in options:
+ raise ValueError, 'Attempted to set ' + name + ' to a value of '+\
+ str(value) + ' but this is not one of the possible values '+\
+ 'for this enum variable. Possible values are: ' +\
+ str(options) + "."
+
+ # min/max checking for 'integer's
+ elif _settings[modulename][name]['type'] == 'integer':
+ try:
+ min = _settings[modulename][name]['min']
+ if value < min:
+ raise ValueError, 'Attempted to set ' + name + ' to a value '+\
+ ' of ' + str(value) + ' but it has a minimum value ' +\
+ ' of ' + str(min) + '.'
+ except KeyError: pass
+ try:
+ max = _settings[modulename][name]['max']
+ if value > max:
+ raise ValueError, 'Attempted to set ' + name + ' to a value '+\
+ ' of ' + str(value) + ' but it has a maximum value ' +\
+ ' of ' + str(min) + '.'
+ except KeyError: pass
+
+ _settings[modulename][name]['value'] = value
+
+def reset(modulename, name):
+ """Reset a variable to its default value.
+
+ Resets the value for a variable in the specified module back to its
+ original (default) value.
+ modulename - The name of the module, e.g. 'focus'
+ name - The name of the variable, e.g. 'my_variable'
+ """
+ modulename = str(modulename).lower()
+ name = str(name).lower()
+ _settings[modulename][name]['value'] = \
+ _settings[modulename][name]['default']
+
+def get(modulename, name):
+ """Returns the value of a variable.
+
+ Returns the current value for a variable in the specified module.
+ modulename - The name of the module, e.g. 'focus'
+ name - The name of the variable, e.g. 'my variable'
+ """
+ modulename = str(modulename).lower()
+ name = str(name).lower()
+ return _settings[modulename][name]['value']
+
+#---------------------------- Internals ---------------------------
+
+"""The main configuration dictionary, which holds sub-dictionaries for each
+ module.
+
+ The format for entries in here like this (for a string):
+ _settings['modulename']['varname']['name'] = 'Text Label'
+ _settings['modulename']['varname']['description'] = 'Does this'
+ _settings['modulename']['varname']['type'] = 'string'
+ _settings['modulename']['varname']['default'] = 'Foo'
+ _settings['modulename']['varname']['value'] = 'Foo'
+ # 'value' should always be initialized to the same
+ # value as the 'default' field!
+
+ Here's an example of an enum:
+ _settings['modulename']['varname']['name'] = 'My Enum Variable'
+ _settings['modulename']['varname']['description'] = 'Does Enum-like things.'
+ _settings['modulename']['varname']['type'] = 'enum'
+ _settings['modulename']['varname']['default'] = \
+ _settings['modulename']['varname']['value'] = [ 'Blue', 'Green', 'Pink' ]
+
+ And Here's an example of an integer with bounds:
+ _settings['modulename']['varname']['name'] = 'A Bounded Integer'
+ _settings['modulename']['varname']['description'] = 'A fierce party animal!'
+ _settings['modulename']['varname']['type'] = 'integer'
+ _settings['modulename']['varname']['default'] = \
+ _settings['modulename']['varname']['value'] = 0
+ _settings['modulename']['varname']['min'] = 0
+ _settings['modulename']['varname']['max'] = 49
+
+ Hopefully you get the idea.
+ """
+_settings = {}
+
+"""Valid values for a variable's type."""
+_types = [ 'boolean', # Boolean types can only hold a value of 0 or 1.
+
+ 'enum', # Enum types hold a value from a list of possible values.
+ # An 'options' field *must* be provided for enums,
+ # containing a list of possible values for the variable.
+
+ 'integer', # Integer types hold a single number, as well as a 'min'
+ # and 'max' property.
+ # If the 'min' or 'max' is ignore then bounds checking
+ # will not be performed in that direction.
+
+ 'string', # String types hold a text string.
+
+ 'file', # File types hold a file object.
+
+ 'function',# Function types hold any callable object.
+
+ 'object' # Object types can hold any python object.
+ ];
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#############################################################################
+### Options that can be changed to adjust the behavior of Openbox. ###
+#############################################################################
+
+THEME = "/usr/local/share/openbox/styles/fieron2"
+"""The theme used to decorate everything."""
+
+#TITLEBAR_LAYOUT = [ "icon", "title", "alldesktops", "iconify", "maximize", "close" ]
+TITLEBAR_LAYOUT = "DITMC"
+"""The layout of the buttons/label on client titlebars, can be made up of the
+following:
+ I - iconify button
+ L - text label
+ M - maximize button,
+ D - all-desktops button
+ C - close button
+If no 'L' is included in the string, one will be added to the end by
+Openbox."""
+
+DOUBLE_CLICK_DELAY = 300
+"""The number of milliseconds in which 2 clicks are perceived as a
+double-click."""
+
+DRAG_THRESHOLD = 3
+"""The amount of pixels that you have to drag the mouse before motion events
+will start occuring."""
+
+DESKTOP_NAMES = ["one", "two", "three", "four", "five", "six", "seven", \
+ "eight", "nine", "ten", "eleven", "twelve"]
+"""The name of each desktop."""
+
+NUMBER_OF_DESKTOPS = 4
+"""The number of desktops/workspaces which can be scrolled between."""
+
+#############################################################################
+
+print "Loaded config.py"
diff --git a/python/focus.py b/python/focus.py
new file mode 100644
index 00000000..9cf37e63
--- /dev/null
+++ b/python/focus.py
@@ -0,0 +1,66 @@
+###########################################################################
+### Functions for helping out with your window focus. ###
+###########################################################################
+
+import config, ob, hooks
+
+export_functions = ()
+
+config.add('focus',
+ 'avoid_skip_taskbar',
+ 'Avoid SkipTaskbar Windows',
+ "Don't focus windows which have requested to not be displayed " + \
+ "in taskbars. You will still be able to focus the windows, but " + \
+ "not through cycling, and they won't be focused as a fallback " + \
+ "if 'Focus Fallback' is enabled.",
+ 'boolean',
+ 1)
+
+config.add('focus',
+ 'fallback',
+ 'Focus Fallback',
+ "Send focus somewhere when nothing is left with the focus, if " + \
+ "possible.",
+ 'boolean',
+ 1)
+
+# maintain a list of clients, stacked in focus order
+_clients = []
+_skip = 0
+
+def focusable(client, desktop):
+ if not client.normal(): return False
+ if not (client.canFocus() or client.focusNotify()): return False
+ if client.iconic(): return False
+ if config.get('focus', 'avoid_skip_taskbar') and \
+ client.skipTaskbar(): return False
+
+ desk = client.desktop()
+ if not (desk == 0xffffffff or desk == desktop): return False
+
+ return True
+
+def _focused(client):
+ global _clients, _skip
+
+ if _skip:
+ _skip -= 1
+ return
+
+ if client:
+ # move it to the top
+ _clients.remove(client)
+ _clients.insert(0, client)
+ elif config.get('focus', 'fallback'):
+ # pass around focus
+ desktop = ob.Openbox.desktop()
+ for c in _clients:
+ if focusable(c, desktop):
+ c.focus()
+ break
+
+hooks.managed.append(lambda c: _clients.append(c))
+hooks.closed.append(lambda c: _clients.remove(c))
+hooks.focused.append(_focused)
+
+print "Loaded focus.py"
diff --git a/python/historyplacement.py b/python/historyplacement.py
new file mode 100644
index 00000000..0421a223
--- /dev/null
+++ b/python/historyplacement.py
@@ -0,0 +1,164 @@
+##############################################################################
+### The history window placement algorithm. ebind historyplacement.place ###
+### to the ob.EventAction.PlaceWindow event to use it. ###
+##############################################################################
+
+import windowplacement, config, hooks
+
+def place(data):
+ """Place a window usingthe history placement algorithm."""
+ _place(data)
+
+export_functions = place
+
+##############################################################################
+
+config.add('historyplacement',
+ 'ignore_requested_positions',
+ 'Ignore Requested Positions',
+ "When true, the placement algorithm will attempt to place " + \
+ "windows even when they request a position (like XMMS can)." + \
+ "Note this only applies to 'normal' windows, not to special " + \
+ "cases like desktops and docks.",
+ 'boolean',
+ 0)
+config.add('historyplacement',
+ 'dont_duplicate',
+ "Don't Diplicate",
+ "When true, if 2 copies of the same match in history are to be " + \
+ "placed before one of them is closed (so it would be placed " + \
+ "over-top of the last one), this will cause the second window to "+\
+ "not be placed via history, and the 'Fallback Algorithm' will be "+\
+ "used instead.",
+ 'boolean',
+ 1)
+config.add('historyplacement',
+ 'filename',
+ 'History Database Filename',
+ "The name of the file where history data will be stored. The " + \
+ "number of the screen is appended onto this name. The file will " +\
+ "be placed in ~/.openbox/.",
+ 'string',
+ 'historydb')
+config.add('historyplacement',
+ 'fallback',
+ 'Fallback Algorithm',
+ "The window placement algorithm that will be used when history " + \
+ "placement does not have a place for the window.",
+ 'enum',
+ windowplacement.random,
+ options = windowplacement.export_functions)
+
+###########################################################################
+
+###########################################################################
+### Internal stuff, should not be accessed outside the module. ###
+###########################################################################
+
+import ob, os, string
+
+_data = []
+
+class _State:
+ def __init__(self, resname, resclass, role, x, y):
+ self.resname = resname
+ self.resclass = resclass
+ self.role = role
+ self.x = x
+ self.y = y
+ self.placed = 0
+ def __eq__(self, other):
+ if self.resname == other.resname and \
+ self.resclass == other.resclass and \
+ self.role == other.role:
+ return 1
+ return 0
+
+def _load():
+ global _data
+ try:
+ file = open(os.environ['HOME'] + '/.openbox/' + \
+ config.get('historyplacement', 'filename') + \
+ "." + str(ob.Openbox.screenNumber()), 'r')
+ # read data
+ for line in file.readlines():
+ line = line[:-1] # drop the '\n'
+ try:
+ s = string.split(line, '\0')
+ state = _State(s[0], s[1], s[2],
+ int(s[3]), int(s[4]))
+
+ _data.append(state)
+
+ except ValueError: pass
+ except IndexError: pass
+ file.close()
+ except IOError: pass
+
+def _save():
+ file = open(os.environ['HOME'] + '/.openbox/'+ \
+ config.get('historyplacement', 'filename') + \
+ "." + str(ob.Openbox.screenNumber()), 'w')
+ if file:
+ for i in _data:
+ file.write(i.resname + '\0' +
+ i.resclass + '\0' +
+ i.role + '\0' +
+ str(i.x) + '\0' +
+ str(i.y) + '\n')
+ file.close()
+
+def _create_state(client):
+ area = client.area()
+ return _State(client.resName(), client.resClass(),
+ client.role(), area[0], area[1])
+
+def _place(client):
+ state = _create_state(client)
+ try:
+ print "looking for : " + state.resname + " : " + \
+ state.resclass + " : " + state.role
+ try:
+ i = _data.index(state)
+ except ValueError:
+ print "No match in history"
+ else:
+ coords = _data[i] # get the equal element
+ print "Found in history ("+str(coords.x)+","+\
+ str(coords.y)+")"
+ if not (config.get('historyplacement', 'dont_duplicate') \
+ and coords.placed):
+ coords.placed = 1
+ if ob.Openbox.state() != ob.State.Starting:
+# if not (config.get('historyplacement', 'ignore_requested_positions') \
+# and data.client.normal()):
+# if data.client.positionRequested(): return
+ ca = client.area()
+ client.setArea((coords.x, coords.y, ca[2], ca[3]))
+ return
+ else:
+ print "Already placed another window there"
+ except TypeError:
+ pass
+ fallback = config.get('historyplacement', 'fallback')
+ if fallback: fallback(client)
+
+def _save_window(client):
+ global _data
+ state = _create_state(client)
+ print "looking for : " + state.resname + " : " + state.resclass + \
+ " : " + state.role
+
+ try:
+ print "replacing"
+ i = _data.index(state)
+ _data[i] = state # replace it
+ except ValueError:
+ print "appending"
+ _data.append(state)
+
+hooks.startup.append(_load)
+hooks.shutdown.append(_save)
+hooks.closed.append(_save_window)
+
+print "Loaded historyplacement.py"
diff --git a/python/keymap.py b/python/keymap.py
new file mode 100644
index 00000000..569dc92b
--- /dev/null
+++ b/python/keymap.py
@@ -0,0 +1,17 @@
+from input import Keyboard
+
+def set(map):
+ """Set your keymap"""
+ global _map
+ Keyboard.clearBinds()
+ for key, func in map:
+ Keyboard.bind(key, run)
+ _map = map
+
+def run(keydata, client):
+ """Run a key press event through the keymap"""
+ for key, func in _map:
+ if (keydata.keychain == key):
+ func(keydata, client)
+
+_map = ()
diff --git a/python/motion.py b/python/motion.py
new file mode 100644
index 00000000..64e04947
--- /dev/null
+++ b/python/motion.py
@@ -0,0 +1,81 @@
+import config, hooks, ob
+from input import Pointer
+
+config.add('motion',
+ 'edge_resistance',
+ 'Edge Resistance',
+ "The amount of resistance to provide to moving a window past a " + \
+ "screen boundary. Specify a value of 0 to disable edge resistance.",
+ 'integer',
+ 10,
+ min = 0)
+
+def move(ptrdata, client):
+ def mymove(ptrdata, client):
+ global _moving, _last_pos
+ if ptrdata.action == Pointer.Action_Release:
+ _moveclient.setArea(_moveclient.area(), True) # finalize the move
+ _moving = False
+ Pointer.ungrab()
+ elif ptrdata.action == Pointer.Action_Motion:
+ pos = ptrdata.pos
+
+ x = _pcarea[0] + pos[0] - _presspos[0]
+ y = _pcarea[1] + pos[1] - _presspos[1]
+
+ resist = config.get('motion', 'edge_resistance')
+ if resist:
+ ca = _moveclient.area()
+ w, h = ca[2], ca[3]
+ # use the area based on the struts
+ sa = ob.Openbox.screenArea(_moveclient.desktop())
+ l, t = sa[0], sa[1]
+ r = l+ sa[2] - w
+ b = t+ sa[3] - h
+ # left screen edge
+ if _last_pos[0] >= pos[0] and x < l and x >= l - resist:
+ x = l
+ # right screen edge
+ if _last_pos[0] <= pos[0] and x > r and x <= r + resist:
+ x = r
+ # top screen edge
+ if _last_pos[1] >= pos[1] and y < t and y >= t - resist:
+ y = t
+ # right screen edge
+ if _last_pos[1] <= pos[1] and y > b and y <= b + resist:
+ y = b
+
+ _moveclient.setArea((x, y, _pcarea[2], _pcarea[3]), False)
+ _last_pos = pos
+
+ global _last_pos, _moving, _pcarea, _presspos, _moveclient
+ if not _moving:
+ _moving = True
+ _pcarea = ptrdata.pressclientarea
+ _presspos = ptrdata.presspos
+ _last_pos = _presspos
+ _moveclient = client
+ Pointer.grab(mymove)
+ mymove(ptrdata, client)
+
+def resize(ptrdata, client):
+ x, y = ptrdata.pos
+ px, py = ptrdata.presspos
+ cx, cy, cw, ch = ptrdata.pressclientarea
+ dx = x - px
+ dy = y - py
+ if px < cx + cw / 2: # left side
+ dx *= -1
+ cx -= dx
+ if py < cy + ch / 2: # top side
+ dy *= -1
+ cy -= dy
+ cw += dx
+ ch += dy
+ client.setArea((cx, cy, cw, ch))
+
+_moving = False
+_moveclient = 0
+_last_pos = ()
+_pcarea = ()
+_presspos = ()
diff --git a/python/rc.py b/python/rc.py
new file mode 100644
index 00000000..e3cf76fd
--- /dev/null
+++ b/python/rc.py
@@ -0,0 +1,114 @@
+import hooks, ob, keymap, buttonmap, os, sys, input, motion, historyplacement
+import stackedcycle
+from input import Pointer
+
+hooks.managed.append(historyplacement.place)
+
+_grab = 0
+def printshit(keydata, client):
+ global _grab
+ print "shit"
+ _grab = not _grab
+ print _grab
+ def gfunc(data, client=None): pass
+ if _grab:
+ input.Keyboard.grab(gfunc)
+ input.Pointer.grab(gfunc)
+ else:
+ input.Keyboard.ungrab()
+ input.Pointer.ungrab()
+
+def myexec(prog):
+ print "execing: ", prog
+ if (os.fork() == 0):
+ try:
+ os.setsid()
+ os.execl("/bin/sh", "/bin/sh", "-c", prog)
+ except:
+ print str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1])
+ try:
+ print "failed to execute '" + prog + "'"
+ except:
+ print str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1])
+ os._exit(0)
+
+def myactivate(c):
+ if ob.Openbox.showingDesktop():
+ ob.Openbox.setShowingDesktop(False)
+ if c.iconic():
+ c.setIconic(False)
+ elif not c.visible():
+ # if its not visible for other reasons, then don't mess with it
+ return
+ if c.shaded():
+ c.setShaded(False)
+ c.focus()
+ c.raiseWindow()
+
+hooks.requestactivate.append(myactivate)
+
+def myfocus(c):
+ if c and c.normal(): c.focus()
+
+#hooks.showwindow.append(myfocus)
+hooks.pointerenter.append(myfocus)
+
+hooks.visible.append(myfocus)
+
+mykmap=((("C-a", "d"), printshit),
+ (("C-Tab",), stackedcycle.next),
+ (("C-S-Tab",), stackedcycle.previous),
+ (("C-space",), lambda k, c: myexec("xterm")))
+keymap.set(mykmap)
+
+def mytogglesticky(client):
+ if client.desktop() == 0xffffffff: d = ob.Openbox.desktop()
+ else: d = 0xffffffff
+ client.setDesktop(d)
+
+mybmap=(("1", "maximize", Pointer.Action_Click,
+ lambda c: c.setMaximized(not c.maximized())),
+ ("2", "maximize", Pointer.Action_Click,
+ lambda c: c.setMaximizedVert(not c.maximizedVert())),
+ ("3", "maximize", Pointer.Action_Click,
+ lambda c: c.setMaximizedHorz(not c.maximizedHorz())),
+ ("1", "alldesktops", Pointer.Action_Click, mytogglesticky),
+ ("1", "iconify", Pointer.Action_Click,
+ lambda c: c.setIconic(True)),
+ ("1", "icon", Pointer.Action_DoubleClick, ob.Client.close),
+ ("1", "close", Pointer.Action_Click, ob.Client.close),
+ ("1", "titlebar", Pointer.Action_Motion, motion.move),
+ ("1", "handle", Pointer.Action_Motion, motion.move),
+ ("Mod1-1", "frame", Pointer.Action_Click, ob.Client.raiseWindow),
+ ("Mod1-1", "frame", Pointer.Action_Motion, motion.move),
+ ("1", "titlebar", Pointer.Action_Press, ob.Client.raiseWindow),
+ ("1", "handle", Pointer.Action_Press, ob.Client.raiseWindow),
+ ("1", "client", Pointer.Action_Press, ob.Client.raiseWindow),
+ ("2", "titlebar", Pointer.Action_Press, ob.Client.lowerWindow),
+ ("2", "handle", Pointer.Action_Press, ob.Client.lowerWindow),
+ ("Mod1-3", "frame", Pointer.Action_Click, ob.Client.lowerWindow),
+ ("Mod1-3", "frame", Pointer.Action_Motion, motion.resize),
+ ("1", "blcorner", Pointer.Action_Motion, motion.resize),
+ ("1", "brcorner", Pointer.Action_Motion, motion.resize),
+ ("1", "titlebar", Pointer.Action_Press, ob.Client.focus),
+ ("1", "handle", Pointer.Action_Press, ob.Client.focus),
+ ("1", "client", Pointer.Action_Press, ob.Client.focus),
+ ("1", "titlebar", Pointer.Action_DoubleClick,
+ lambda c: c.setShaded(not c.shaded())),
+ ("4", "titlebar", Pointer.Action_Click,
+ lambda c: c.setShaded(True)),
+ ("5", "titlebar", Pointer.Action_Click,
+ lambda c: c.setShaded(False)),
+ ("4", "root", Pointer.Action_Click,
+ lambda c: ob.Openbox.setNextDesktop()),
+ ("5", "root", Pointer.Action_Click,
+ lambda c: ob.Openbox.setPreviousDesktop()),
+ ("Mod1-4", "frame", Pointer.Action_Click,
+ lambda c: ob.Openbox.setNextDesktop()),
+ ("Mod1-5", "frame", Pointer.Action_Click,
+ lambda c: ob.Openbox.setPreviousDesktop()),
+ ("Mod1-4", "root", Pointer.Action_Click,
+ lambda c: ob.Openbox.setNextDesktop()),
+ ("Mod1-5", "root", Pointer.Action_Click,
+ lambda c: ob.Openbox.setPreviousDesktop()))
+buttonmap.set(mybmap)
diff --git a/python/stackedcycle.py b/python/stackedcycle.py
new file mode 100644
index 00000000..4f7f71da
--- /dev/null
+++ b/python/stackedcycle.py
@@ -0,0 +1,216 @@
+import ob, config, hooks, focus
+from input import Pointer, Keyboard
+
+config.add('stackedcycle',
+ 'activate_while_cycling',
+ 'Activate While Cycling',
+ "If this is True then windows will be activated as they are" + \
+ "highlighted in the cycling list (except iconified windows).",
+ 'boolean',
+ True)
+
+config.add('stackedcycle',
+ 'raise_window',
+ 'Raise After Cycling',
+ "If this is True, the selected window will be raised as well as " +\
+ "focused.",
+ 'boolean',
+ True)
+
+config.add('stackedcycle',
+ 'include_all_desktops',
+ 'Include Windows From All Desktops',
+ "If this is True then windows from all desktops will be included" +\
+ " in the stacking list.",
+ 'boolean',
+ False)
+
+config.add('stackedcycle',
+ 'include_icons',
+ 'Include Icons',
+ "If this is True then windows which are iconified on the current" +\
+ " desktop will be included in the stacking list.",
+ 'boolean',
+ False)
+
+config.add('stackedcycle',
+ 'include_icons_all_desktops',
+ 'Include Icons From All Desktops',
+ "If this is True then windows which are iconified from all " +\
+ "desktops will be included in the stacking list (if Include Icons"+\
+ " is also True).",
+ 'boolean',
+ True)
+
+config.add('stackedcycle',
+ 'include_omnipresent',
+ 'Include Omnipresent Windows',
+ "If this is True then windows which are on all-desktops at once " +\
+ "will be included in the stacking list.",
+ 'boolean',
+ True)
+
+def next(keydata, client): _cycle(keydata, client, True)
+def previous(keydata, client): _cycle(keydata, client, False)
+
+def _shouldAdd(client):
+ """Determines if a client should be added to the cycling list."""
+ curdesk = ob.Openbox.desktop()
+ desk = client.desktop()
+
+ if not (client.normal() and client.canFocus()): return False
+ if config.get('focus', 'avoid_skip_taskbar') and client.skipTaskbar():
+ return False
+
+ if client.iconic():
+ if config.get('stackedcycle', 'include_icons'):
+ if config.get('stackedcycle', 'include_icons_all_desktops'):
+ return True
+ if desk == curdesk: return True
+ return False
+ if config.get('stackedcycle', 'include_omnipresent') and \
+ desk == 0xffffffff: return True
+ if config.get('stackedcycle', 'include_all_desktops'): return True
+ if desk == curdesk: return True
+
+ return False
+
+def _populateItems():
+ global _items
+ # get the list of clients, keeping iconic windows at the bottom
+ _items = []
+ iconic_clients = []
+ for c in focus._clients:
+ if _shouldAdd(c):
+ if c.iconic(): iconic_clients.append(c)
+ else: _items.append(c)
+ _items.extend(iconic_clients)
+
+def _populate():
+ global _pos, _items
+ try:
+ current = _items[_pos]
+ except IndexError:
+ current = None
+ oldpos = _pos
+ _pos = -1
+
+ _populateItems()
+
+ i = 0
+ for item in _items:
+ # current item might have shifted after a populateItems()
+ # call, so we need to do this test.
+ if current == item:
+ _pos = i
+ i += 1
+
+ # The item we were on might be gone entirely
+ if _pos < 0:
+ # try stay at the same spot in the menu
+ if oldpos >= len(_items):
+ _pos = len(_items) - 1
+ else:
+ _pos = oldpos
+
+
+def _activate(final):
+ """
+ Activates (focuses and, if the user requested it, raises a window).
+ If final is True, then this is the very last window we're activating
+ and the user has finished cycling.
+ """
+ print "_activate"
+ try:
+ client = _items[_pos]
+ except IndexError: return # empty list
+ print client
+
+ # move the to client's desktop if required
+ if not (client.iconic() or client.desktop() == 0xffffffff or \
+ client.desktop() == ob.Openbox.desktop()):
+ ob.Openbox.setDesktop(client.desktop())
+
+ if final or not client.iconic():
+ if final: r = config.get('stackedcycle', 'raise_window')
+ else: r = False
+ client.focus(True)
+ if final and client.shaded(): client.setShaded(False)
+ print "final", final, "raising", r
+ if r: client.raiseWindow()
+ if not final:
+ focus._skip += 1
+
+def _cycle(keydata, client, forward):
+ global _cycling, _state, _pos, _inititem, _items
+
+ if not _cycling:
+ _items = [] # so it doesnt try start partway through the list
+ _populate()
+
+ if not _items: return # don't bother doing anything
+
+ Keyboard.grab(_grabfunc)
+ # the pointer grab causes pointer events during the keyboard grab
+ # to go away, which means we don't get enter notifies when the
+ # popup disappears, screwing up the focus
+ Pointer.grabPointer(True)
+
+ _cycling = True
+ _state = keydata.state
+ _pos = 0
+ _inititem = _items[_pos]
+
+ if forward:
+ _pos += 1
+ else:
+ _pos -= 1
+ # wrap around
+ if _pos < 0: _pos = len(_items) - 1
+ elif _pos >= len(_items): _pos = 0
+ if config.get('stackedcycle', 'activate_while_cycling'):
+ _activate(False) # activate, but dont deiconify/unshade/raise
+
+def _grabfunc(keydata, client):
+ global _cycling
+
+ done = False
+ notreverting = True
+ # have all the modifiers this started with been released?
+ if not _state & keydata.state:
+ done = True
+ elif keydata.press:
+ # has Escape been pressed?
+ if keydata.keychain == "Escape":
+ done = True
+ notreverting = False
+ # revert
+ try:
+ _pos = _items.index(_inititem)
+ except:
+ _pos = -1
+ # has Enter been pressed?
+ elif keydata.keychain == "Return":
+ done = True
+
+ if done:
+ # activate, and deiconify/unshade/raise
+ _activate(notreverting)
+ _cycling = False
+ Keyboard.ungrab()
+ Pointer.grabPointer(False)
+
+
+_cycling = False
+_pos = 0
+_inititem = None
+_items = []
+_state = 0
+
+def _newwin(data):
+ if _cycling: _populate()
+def _closewin(data):
+ if _cycling: _populate()
+
+hooks.managed.append(_newwin)
+hooks.closed.append(_closewin)
diff --git a/python/windowplacement.py b/python/windowplacement.py
new file mode 100644
index 00000000..56b5320d
--- /dev/null
+++ b/python/windowplacement.py
@@ -0,0 +1,47 @@
+############################################################################
+### Window placement algorithms, choose one of these and ebind it to the ###
+### ob.EventAction.PlaceWindow event. ###
+### ###
+### Also see historyplacement.py for the history placement module which ###
+### provides an algorithm that can be used in place of, or alongside, ###
+### these. ###
+############################################################################
+
+import ob
+from random import Random
+
+def random(client):
+ """Place windows randomly around the screen."""
+ if ob.Openbox.state() == ob.State.Starting: return
+ #if data.client.positionRequested(): return
+ cx, cy, cw, ch = client.area()
+ sx, sy, sw, sh = ob.Openbox.screenArea(client.desktop())
+ global _rand
+ x = Random().randrange(sx, sw - cw - 1)
+ y = Random().randrange(sy, sh - ch - 1)
+ client.setArea((x, y, cw, ch))
+
+def cascade(client):
+ """Place windows in a cascading order from top-left to bottom-right."""
+ if ob.Openbox.state() == ob.State.Starting: return
+ #if data.client.positionRequested(): return
+ cx, cy, cw, ch = client.area()
+ sx, sy, sw, sh = ob.Openbox.screenArea(client.desktop())
+ width = sw - cw
+ height = sh - ch
+ global _cascade_x, _cascade_y
+ if _cascade_x < sx or _cascade_y < sy or \
+ _cascade_x >= width or _cascade_y >= height:
+ _cascade_x = sx
+ _cascade_y = sy
+ client.setArea((_cascade_x, _cascade_y, cw, ch))
+ frame_size = client.frameSize()
+ _cascade_x += frame_size[1]
+ _cascade_y += frame_size[1]
+
+_cascade_x = 0
+_cascade_y = 0
+
+export_functions = random, cascade
+
+print "Loaded windowplacement.py"