diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/.cvsignore | 2 | ||||
| -rw-r--r-- | python/Makefile.am | 11 | ||||
| -rw-r--r-- | python/buttonmap.py | 76 | ||||
| -rw-r--r-- | python/config.py | 242 | ||||
| -rw-r--r-- | python/focus.py | 66 | ||||
| -rw-r--r-- | python/historyplacement.py | 164 | ||||
| -rw-r--r-- | python/keymap.py | 17 | ||||
| -rw-r--r-- | python/motion.py | 81 | ||||
| -rw-r--r-- | python/rc.py | 114 | ||||
| -rw-r--r-- | python/stackedcycle.py | 216 | ||||
| -rw-r--r-- | python/windowplacement.py | 47 |
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" |
