add the new cycle module with super snazzy new Cycle classes. yay KatanaLynx!
This commit is contained in:
parent
136c9c078d
commit
671230f83e
5 changed files with 499 additions and 337 deletions
|
@ -2,7 +2,7 @@ scriptdir = $(libdir)/openbox/python
|
||||||
MAINTAINERCLEANFILES = Makefile.in
|
MAINTAINERCLEANFILES = Makefile.in
|
||||||
script_PYTHON = config.py defaults.py focus.py callbacks.py \
|
script_PYTHON = config.py defaults.py focus.py callbacks.py \
|
||||||
focusmodel.py windowplacement.py behavior.py motion.py \
|
focusmodel.py windowplacement.py behavior.py motion.py \
|
||||||
historyplacement.py stackedcycle.py focuscycle.py
|
historyplacement.py cycle.py
|
||||||
|
|
||||||
distclean-local:
|
distclean-local:
|
||||||
$(RM) *\~ .\#*
|
$(RM) *\~ .\#*
|
||||||
|
|
486
scripts/cycle.py
Normal file
486
scripts/cycle.py
Normal file
|
@ -0,0 +1,486 @@
|
||||||
|
import ob, otk
|
||||||
|
class _Cycle:
|
||||||
|
"""
|
||||||
|
This is a basic cycling class for anything, from xOr's stackedcycle.py,
|
||||||
|
that pops up a cycling menu when there's more than one thing to be cycled
|
||||||
|
to.
|
||||||
|
An example of inheriting from and modifying this class is _CycleWindows,
|
||||||
|
which allows users to cycle around windows.
|
||||||
|
|
||||||
|
This class could conceivably be used to cycle through anything -- desktops,
|
||||||
|
windows of a specific class, XMMS playlists, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""This specifies a rough limit of characters for the cycling list titles.
|
||||||
|
Titles which are larger will be chopped with an elipsis in their
|
||||||
|
center."""
|
||||||
|
TITLE_SIZE_LIMIT = 80
|
||||||
|
|
||||||
|
"""If this is non-zero then windows will be activated as they are
|
||||||
|
highlighted in the cycling list (except iconified windows)."""
|
||||||
|
ACTIVATE_WHILE_CYCLING = 0
|
||||||
|
|
||||||
|
"""If this is true, we start cycling with the next (or previous) thing
|
||||||
|
selected."""
|
||||||
|
START_WITH_NEXT = 1
|
||||||
|
|
||||||
|
"""If this is true, a popup window will be displayed with the options
|
||||||
|
while cycling."""
|
||||||
|
SHOW_POPUP = 1
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize an instance of this class. Subclasses should
|
||||||
|
do any necessary event binding in their constructor as well.
|
||||||
|
"""
|
||||||
|
self.cycling = 0 # internal var used for going through the menu
|
||||||
|
self.items = [] # items to cycle through
|
||||||
|
|
||||||
|
self.widget = None # the otk menu widget
|
||||||
|
self.menuwidgets = [] # labels in the otk menu widget TODO: RENAME
|
||||||
|
|
||||||
|
def createPopup(self):
|
||||||
|
"""Creates the cycling popup menu.
|
||||||
|
"""
|
||||||
|
self.widget = otk.Widget(self.screen.number(), ob.openbox,
|
||||||
|
otk.Widget.Vertical, 0, 1)
|
||||||
|
|
||||||
|
def destroyPopup(self):
|
||||||
|
"""Destroys (or rather, cleans up after) the cycling popup menu.
|
||||||
|
"""
|
||||||
|
self.menuwidgets = []
|
||||||
|
self.widget = 0
|
||||||
|
|
||||||
|
def populateItems(self):
|
||||||
|
"""Populate self.items with the appropriate items that can currently
|
||||||
|
be cycled through. self.items may be cleared out before this
|
||||||
|
method is called.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def menuLabel(self, item):
|
||||||
|
"""Return a string indicating the menu label for the given item.
|
||||||
|
Don't worry about title truncation.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def itemEqual(self, item1, item2):
|
||||||
|
"""Compare two items, return 1 if they're "equal" for purposes of
|
||||||
|
cycling, and 0 otherwise.
|
||||||
|
"""
|
||||||
|
# suggestion: define __eq__ on item classes so that this works
|
||||||
|
# in the general case. :)
|
||||||
|
return item1 == item2
|
||||||
|
|
||||||
|
def populateLists(self):
|
||||||
|
"""Populates self.items and self.menuwidgets, and then shows and
|
||||||
|
positions the cycling popup. You probably shouldn't mess with
|
||||||
|
this function; instead, see populateItems and menuLabel.
|
||||||
|
"""
|
||||||
|
self.widget.hide()
|
||||||
|
|
||||||
|
try:
|
||||||
|
current = self.items[self.menupos]
|
||||||
|
except IndexError:
|
||||||
|
current = None
|
||||||
|
oldpos = self.menupos
|
||||||
|
self.menupos = -1
|
||||||
|
|
||||||
|
self.items = []
|
||||||
|
self.populateItems()
|
||||||
|
|
||||||
|
# make the widgets
|
||||||
|
i = 0
|
||||||
|
self.menuwidgets = []
|
||||||
|
for i in range(len(self.items)):
|
||||||
|
c = self.items[i]
|
||||||
|
|
||||||
|
w = otk.Label(self.widget)
|
||||||
|
# current item might have shifted after a populateItems()
|
||||||
|
# call, so we need to do this test.
|
||||||
|
if current and self.itemEqual(c, current):
|
||||||
|
self.menupos = i
|
||||||
|
w.setHilighted(1)
|
||||||
|
self.menuwidgets.append(w)
|
||||||
|
|
||||||
|
t = self.menuLabel(c)
|
||||||
|
# TODO: maybe subclasses will want to truncate in different ways?
|
||||||
|
if len(t) > self.TITLE_SIZE_LIMIT: # limit the length of titles
|
||||||
|
t = t[:self.TITLE_SIZE_LIMIT / 2 - 2] + "..." + \
|
||||||
|
t[0 - self.TITLE_SIZE_LIMIT / 2 - 2:]
|
||||||
|
w.setText(t)
|
||||||
|
|
||||||
|
# The item we were on might be gone entirely
|
||||||
|
if self.menupos < 0:
|
||||||
|
# try stay at the same spot in the menu
|
||||||
|
if oldpos >= len(self.items):
|
||||||
|
self.menupos = len(self.items) - 1
|
||||||
|
else:
|
||||||
|
self.menupos = oldpos
|
||||||
|
|
||||||
|
# find the size for the popup
|
||||||
|
width = 0
|
||||||
|
height = 0
|
||||||
|
for w in self.menuwidgets:
|
||||||
|
size = w.minSize()
|
||||||
|
if size.width() > width: width = size.width()
|
||||||
|
height += size.height()
|
||||||
|
|
||||||
|
# show or hide the list and its child widgets
|
||||||
|
if len(self.items) > 1:
|
||||||
|
size = self.screeninfo.size()
|
||||||
|
self.widget.moveresize(otk.Rect((size.width() - width) / 2,
|
||||||
|
(size.height() - height) / 2,
|
||||||
|
width, height))
|
||||||
|
if self.SHOW_POPUP: self.widget.show(1)
|
||||||
|
|
||||||
|
def activateTarget(self, 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.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setDataInfo(self, data):
|
||||||
|
"""Retrieve and/or calculate information when we start cycling,
|
||||||
|
preferably caching it. Data is what's given to callback functions.
|
||||||
|
"""
|
||||||
|
self.screen = ob.openbox.screen(data.screen)
|
||||||
|
self.screeninfo = otk.display.screenInfo(data.screen)
|
||||||
|
|
||||||
|
def chooseStartPos(self):
|
||||||
|
"""Set self.menupos to a number between 0 and len(self.items) - 1.
|
||||||
|
By default the initial menupos is 0, but this can be used to change
|
||||||
|
it to some other position."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cycle(self, data, forward):
|
||||||
|
"""Does the actual job of cycling through windows. data is a callback
|
||||||
|
parameter, while forward is a boolean indicating whether the
|
||||||
|
cycling goes forwards (true) or backwards (false).
|
||||||
|
"""
|
||||||
|
|
||||||
|
initial = 0
|
||||||
|
|
||||||
|
if not self.cycling:
|
||||||
|
ob.kgrab(data.screen, self.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
|
||||||
|
ob.mgrab(data.screen)
|
||||||
|
|
||||||
|
self.cycling = 1
|
||||||
|
self.state = data.state
|
||||||
|
self.menupos = 0
|
||||||
|
|
||||||
|
self.setDataInfo(data)
|
||||||
|
|
||||||
|
self.createPopup()
|
||||||
|
self.items = [] # so it doesnt try start partway through the list
|
||||||
|
self.populateLists()
|
||||||
|
|
||||||
|
self.chooseStartPos()
|
||||||
|
self.initpos = self.menupos
|
||||||
|
|
||||||
|
initial = 1
|
||||||
|
|
||||||
|
if not self.items: return # don't bother doing anything
|
||||||
|
|
||||||
|
self.menuwidgets[self.menupos].setHighlighted(0)
|
||||||
|
|
||||||
|
if initial and not self.START_WITH_NEXT:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if forward:
|
||||||
|
self.menupos += 1
|
||||||
|
else:
|
||||||
|
self.menupos -= 1
|
||||||
|
# wrap around
|
||||||
|
if self.menupos < 0: self.menupos = len(self.items) - 1
|
||||||
|
elif self.menupos >= len(self.items): self.menupos = 0
|
||||||
|
self.menuwidgets[self.menupos].setHighlighted(1)
|
||||||
|
if self.ACTIVATE_WHILE_CYCLING:
|
||||||
|
self.activateTarget(0) # activate, but dont deiconify/unshade/raise
|
||||||
|
|
||||||
|
def grabfunc(self, data):
|
||||||
|
"""A callback method that grabs away all keystrokes so that navigating
|
||||||
|
the cycling menu is possible."""
|
||||||
|
done = 0
|
||||||
|
notreverting = 1
|
||||||
|
# have all the modifiers this started with been released?
|
||||||
|
if not self.state & data.state:
|
||||||
|
done = 1
|
||||||
|
elif data.action == ob.KeyAction.Press:
|
||||||
|
# has Escape been pressed?
|
||||||
|
if data.key == "Escape":
|
||||||
|
done = 1
|
||||||
|
notreverting = 0
|
||||||
|
# revert
|
||||||
|
self.menupos = self.initpos
|
||||||
|
# has Enter been pressed?
|
||||||
|
elif data.key == "Return":
|
||||||
|
done = 1
|
||||||
|
|
||||||
|
if done:
|
||||||
|
# activate, and deiconify/unshade/raise
|
||||||
|
self.activateTarget(notreverting)
|
||||||
|
self.destroyPopup()
|
||||||
|
self.cycling = 0
|
||||||
|
ob.kungrab()
|
||||||
|
ob.mungrab()
|
||||||
|
|
||||||
|
def next(self, data):
|
||||||
|
"""Focus the next window."""
|
||||||
|
if not data.state:
|
||||||
|
raise RuntimeError("next must be bound to a key" +
|
||||||
|
"combination with at least one modifier")
|
||||||
|
self.cycle(data, 1)
|
||||||
|
|
||||||
|
def previous(self, data):
|
||||||
|
"""Focus the previous window."""
|
||||||
|
if not data.state:
|
||||||
|
raise RuntimeError("previous must be bound to a key" +
|
||||||
|
"combination with at least one modifier")
|
||||||
|
self.cycle(data, 0)
|
||||||
|
|
||||||
|
#---------------------- Window Cycling --------------------
|
||||||
|
import focus
|
||||||
|
class _CycleWindows(_Cycle):
|
||||||
|
"""
|
||||||
|
This is a basic cycling class for Windows.
|
||||||
|
|
||||||
|
An example of inheriting from and modifying this class is _ClassCycleWindows,
|
||||||
|
which allows users to cycle around windows of a certain application
|
||||||
|
name/class only.
|
||||||
|
|
||||||
|
This class has an underscored name because I use the singleton pattern
|
||||||
|
(so CycleWindows is an actual instance of this class). This doesn't have
|
||||||
|
to be followed, but if it isn't followed then the user will have to create
|
||||||
|
their own instances of your class and use that (not always a bad thing).
|
||||||
|
|
||||||
|
An example of using the CycleWindows singleton:
|
||||||
|
|
||||||
|
from cycle import CycleWindows
|
||||||
|
CycleWindows.INCLUDE_ICONS = 0 # I don't like cycling to icons
|
||||||
|
ob.kbind(["A-Tab"], ob.KeyContext.All, CycleWindows.next)
|
||||||
|
ob.kbind(["A-S-Tab"], ob.KeyContext.All, CycleWindows.previous)
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""If this is non-zero then windows from all desktops will be included in
|
||||||
|
the stacking list."""
|
||||||
|
INCLUDE_ALL_DESKTOPS = 0
|
||||||
|
|
||||||
|
"""If this is non-zero then windows which are iconified on the current
|
||||||
|
desktop will be included in the stacking list."""
|
||||||
|
INCLUDE_ICONS = 1
|
||||||
|
|
||||||
|
"""If this is non-zero then windows which are iconified from all desktops
|
||||||
|
will be included in the stacking list."""
|
||||||
|
INCLUDE_ICONS_ALL_DESKTOPS = 1
|
||||||
|
|
||||||
|
"""If this is non-zero then windows which are on all-desktops at once will
|
||||||
|
be included."""
|
||||||
|
INCLUDE_OMNIPRESENT = 1
|
||||||
|
|
||||||
|
"""A better default for window cycling than generic cycling."""
|
||||||
|
ACTIVATE_WHILE_CYCLING = 1
|
||||||
|
|
||||||
|
"""When cycling focus, raise the window chosen as well as focusing it."""
|
||||||
|
RAISE_WINDOW = 1
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
_Cycle.__init__(self)
|
||||||
|
|
||||||
|
def newwindow(data):
|
||||||
|
if self.cycling: self.populateLists()
|
||||||
|
def closewindow(data):
|
||||||
|
if self.cycling: self.populateLists()
|
||||||
|
|
||||||
|
ob.ebind(ob.EventAction.NewWindow, newwindow)
|
||||||
|
ob.ebind(ob.EventAction.CloseWindow, closewindow)
|
||||||
|
|
||||||
|
def shouldAdd(self, client):
|
||||||
|
"""Determines if a client should be added to the cycling list."""
|
||||||
|
curdesk = self.screen.desktop()
|
||||||
|
desk = client.desktop()
|
||||||
|
|
||||||
|
if not client.normal(): return 0
|
||||||
|
if not (client.canFocus() or client.focusNotify()): return 0
|
||||||
|
if focus.AVOID_SKIP_TASKBAR and client.skipTaskbar(): return 0
|
||||||
|
|
||||||
|
if client.iconic():
|
||||||
|
if self.INCLUDE_ICONS:
|
||||||
|
if self.INCLUDE_ICONS_ALL_DESKTOPS: return 1
|
||||||
|
if desk == curdesk: return 1
|
||||||
|
return 0
|
||||||
|
if self.INCLUDE_OMNIPRESENT and desk == 0xffffffff: return 1
|
||||||
|
if self.INCLUDE_ALL_DESKTOPS: return 1
|
||||||
|
if desk == curdesk: return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def populateItems(self):
|
||||||
|
# get the list of clients, keeping iconic windows at the bottom
|
||||||
|
iconic_clients = []
|
||||||
|
for c in focus._clients:
|
||||||
|
if self.shouldAdd(c):
|
||||||
|
if c.iconic(): iconic_clients.append(c)
|
||||||
|
else: self.items.append(c)
|
||||||
|
self.items.extend(iconic_clients)
|
||||||
|
|
||||||
|
def menuLabel(self, client):
|
||||||
|
if client.iconic(): t = '[' + client.iconTitle() + ']'
|
||||||
|
else: t = client.title()
|
||||||
|
|
||||||
|
if self.INCLUDE_ALL_DESKTOPS:
|
||||||
|
d = client.desktop()
|
||||||
|
if d == 0xffffffff: d = self.screen.desktop()
|
||||||
|
t = self.screen.desktopName(d) + " - " + t
|
||||||
|
|
||||||
|
return t
|
||||||
|
|
||||||
|
def itemEqual(self, client1, client2):
|
||||||
|
return client1.window() == client2.window()
|
||||||
|
|
||||||
|
def activateTarget(self, 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."""
|
||||||
|
try:
|
||||||
|
client = self.items[self.menupos]
|
||||||
|
except IndexError: return # empty list
|
||||||
|
|
||||||
|
# move the to client's desktop if required
|
||||||
|
if not (client.iconic() or client.desktop() == 0xffffffff or \
|
||||||
|
client.desktop() == self.screen.desktop()):
|
||||||
|
root = self.screeninfo.rootWindow()
|
||||||
|
ob.send_client_msg(root, otk.atoms.net_current_desktop,
|
||||||
|
root, client.desktop())
|
||||||
|
|
||||||
|
# send a net_active_window message for the target
|
||||||
|
if final or not client.iconic():
|
||||||
|
if final: r = self.RAISE_WINDOW
|
||||||
|
else: r = 0
|
||||||
|
ob.send_client_msg(self.screeninfo.rootWindow(),
|
||||||
|
otk.atoms.openbox_active_window,
|
||||||
|
client.window(), final, r)
|
||||||
|
if not final:
|
||||||
|
focus._skip += 1
|
||||||
|
|
||||||
|
# The singleton.
|
||||||
|
CycleWindows = _CycleWindows()
|
||||||
|
|
||||||
|
#---------------------- Window Cycling --------------------
|
||||||
|
import focus
|
||||||
|
class _CycleWindowsLinear(_CycleWindows):
|
||||||
|
"""
|
||||||
|
This class is an example of how to inherit from and make use of the
|
||||||
|
_CycleWindows class. This class also uses the singleton pattern.
|
||||||
|
|
||||||
|
An example of using the CycleWindowsLinear singleton:
|
||||||
|
|
||||||
|
from cycle import CycleWindowsLinear
|
||||||
|
CycleWindows.ALL_DESKTOPS = 1 # I want all my windows in the list
|
||||||
|
ob.kbind(["A-Tab"], ob.KeyContext.All, CycleWindowsLinear.next)
|
||||||
|
ob.kbind(["A-S-Tab"], ob.KeyContext.All, CycleWindowsLinear.previous)
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""When cycling focus, raise the window chosen as well as focusing it."""
|
||||||
|
RAISE_WINDOW = 0
|
||||||
|
|
||||||
|
"""If this is true, a popup window will be displayed with the options
|
||||||
|
while cycling."""
|
||||||
|
SHOW_POPUP = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
_CycleWindows.__init__(self)
|
||||||
|
|
||||||
|
def shouldAdd(self, client):
|
||||||
|
"""Determines if a client should be added to the cycling list."""
|
||||||
|
curdesk = self.screen.desktop()
|
||||||
|
desk = client.desktop()
|
||||||
|
|
||||||
|
if not client.normal(): return 0
|
||||||
|
if not (client.canFocus() or client.focusNotify()): return 0
|
||||||
|
if focus.AVOID_SKIP_TASKBAR and client.skipTaskbar(): return 0
|
||||||
|
|
||||||
|
if client.iconic(): return 0
|
||||||
|
if self.INCLUDE_OMNIPRESENT and desk == 0xffffffff: return 1
|
||||||
|
if self.INCLUDE_ALL_DESKTOPS: return 1
|
||||||
|
if desk == curdesk: return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def populateItems(self):
|
||||||
|
# get the list of clients, keeping iconic windows at the bottom
|
||||||
|
iconic_clients = []
|
||||||
|
for i in range(self.screen.clientCount()):
|
||||||
|
c = self.screen.client(i)
|
||||||
|
if self.shouldAdd(c):
|
||||||
|
self.items.append(c)
|
||||||
|
|
||||||
|
def chooseStartPos(self):
|
||||||
|
if focus._clients:
|
||||||
|
t = focus._clients[0]
|
||||||
|
for i,c in zip(range(len(self.items)), self.items):
|
||||||
|
if self.itemEqual(c, t):
|
||||||
|
self.menupos = i
|
||||||
|
break
|
||||||
|
|
||||||
|
def menuLabel(self, client):
|
||||||
|
t = client.title()
|
||||||
|
|
||||||
|
if self.INCLUDE_ALL_DESKTOPS:
|
||||||
|
d = client.desktop()
|
||||||
|
if d == 0xffffffff: d = self.screen.desktop()
|
||||||
|
t = self.screen.desktopName(d) + " - " + t
|
||||||
|
|
||||||
|
return t
|
||||||
|
|
||||||
|
# The singleton.
|
||||||
|
CycleWindowsLinear = _CycleWindowsLinear()
|
||||||
|
|
||||||
|
#----------------------- Desktop Cycling ------------------
|
||||||
|
class _CycleDesktops(_Cycle):
|
||||||
|
"""
|
||||||
|
Example of usage:
|
||||||
|
|
||||||
|
from cycle import CycleDesktops
|
||||||
|
ob.kbind(["W-d"], ob.KeyContext.All, CycleDesktops.next)
|
||||||
|
ob.kbind(["W-S-d"], ob.KeyContext.All, CycleDesktops.previous)
|
||||||
|
"""
|
||||||
|
class Desktop:
|
||||||
|
def __init__(self, name, index):
|
||||||
|
self.name = name
|
||||||
|
self.index = index
|
||||||
|
def __eq__(self, other):
|
||||||
|
return other.index == self.index
|
||||||
|
|
||||||
|
START_WITH_NEXT = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
_Cycle.__init__(self)
|
||||||
|
|
||||||
|
def populateItems(self):
|
||||||
|
for i in range(self.screen.numDesktops()):
|
||||||
|
self.items.append(
|
||||||
|
_CycleDesktops.Desktop(self.screen.desktopName(i), i))
|
||||||
|
|
||||||
|
def menuLabel(self, desktop):
|
||||||
|
return desktop.name
|
||||||
|
|
||||||
|
def chooseStartPos(self):
|
||||||
|
self.menupos = self.screen.desktop()
|
||||||
|
|
||||||
|
def activateTarget(self, final):
|
||||||
|
# TODO: refactor this bit
|
||||||
|
try:
|
||||||
|
desktop = self.items[self.menupos]
|
||||||
|
except IndexError: return
|
||||||
|
|
||||||
|
root = self.screeninfo.rootWindow()
|
||||||
|
ob.send_client_msg(root, otk.atoms.net_current_desktop,
|
||||||
|
root, desktop.index)
|
||||||
|
|
||||||
|
CycleDesktops = _CycleDesktops()
|
||||||
|
|
||||||
|
print "Loaded cycle.py"
|
|
@ -38,16 +38,20 @@ ob.kbind(["A-F4"], ob.KeyContext.All, callbacks.close)
|
||||||
ob.kbind(["W-d"], ob.KeyContext.All, callbacks.toggle_show_desktop)
|
ob.kbind(["W-d"], ob.KeyContext.All, callbacks.toggle_show_desktop)
|
||||||
|
|
||||||
# focus bindings
|
# focus bindings
|
||||||
import stackedcycle # functions for doing stacked 'kde-style' cycling
|
|
||||||
ob.kbind(["A-Tab"], ob.KeyContext.All, stackedcycle.next)
|
|
||||||
ob.kbind(["A-S-Tab"], ob.KeyContext.All, stackedcycle.previous)
|
|
||||||
|
|
||||||
# if you want linear cycling instead of stacked cycling, comment out the focus
|
from cycle import CycleWindows
|
||||||
|
ob.kbind(["A-Tab"], ob.KeyContext.All, CycleWindows.next)
|
||||||
|
ob.kbind(["A-S-Tab"], ob.KeyContext.All, CycleWindows.previous)
|
||||||
|
|
||||||
|
# if you want linear cycling instead of stacked cycling, comment out the two
|
||||||
# bindings above, and use these instead.
|
# bindings above, and use these instead.
|
||||||
#import focuscycle # functions for doing linear cycling
|
#from cycle import CycleWindowsLinear
|
||||||
#focuscycle.RAISE_WINDOW = 0 # don't raise windows when they're activated
|
#ob.kbind(["A-Tab"], ob.KeyContext.All, CycleWindows.next)
|
||||||
#ob.kbind(["A-Tab"], ob.KeyContext.All, focuscycle.next)
|
#ob.kbind(["A-S-Tab"], ob.KeyContext.All, CycleWindows.previous)
|
||||||
#ob.kbind(["A-S-Tab"], ob.KeyContext.All, focuscycle.previous)
|
|
||||||
|
from cycle import CycleDesktops
|
||||||
|
ob.kbind(["C-Tab"], ob.KeyContext.All, CycleDesktops.next)
|
||||||
|
ob.kbind(["C-S-Tab"], ob.KeyContext.All, CycleDesktops.previous)
|
||||||
|
|
||||||
# desktop changing bindings
|
# desktop changing bindings
|
||||||
ob.kbind(["C-1"], ob.KeyContext.All, lambda(d): callbacks.change_desktop(d, 0))
|
ob.kbind(["C-1"], ob.KeyContext.All, lambda(d): callbacks.change_desktop(d, 0))
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
###########################################################################
|
|
||||||
### Functions for cycling focus (in a 'linear' order) between windows. ###
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
### Options that affect the behavior of the focuscycle module. ###
|
|
||||||
###########################################################################
|
|
||||||
RAISE_WINDOW = 1
|
|
||||||
"""When cycling focus, raise the window chosen as well as focusing it. This
|
|
||||||
does not affect fallback focusing behavior."""
|
|
||||||
# See focus.AVOID_SKIP_TASKBAR
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
def next(data, num=1):
|
|
||||||
"""Focus the next window."""
|
|
||||||
_cycle(data, num, 1)
|
|
||||||
|
|
||||||
def previous(data, num=1):
|
|
||||||
"""Focus the previous window."""
|
|
||||||
_cycle(data, num, 0)
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
### Internal stuff, should not be accessed outside the module. ###
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
import ob
|
|
||||||
import focus
|
|
||||||
|
|
||||||
def _cycle(data, num, forward):
|
|
||||||
screen = ob.openbox.screen(data.screen)
|
|
||||||
count = screen.clientCount()
|
|
||||||
|
|
||||||
if not count: return # no clients
|
|
||||||
|
|
||||||
target = 0
|
|
||||||
if data.client:
|
|
||||||
client_win = data.client.window()
|
|
||||||
found = 0
|
|
||||||
r = range(count)
|
|
||||||
if not forward:
|
|
||||||
r.reverse()
|
|
||||||
for i in r:
|
|
||||||
if found:
|
|
||||||
target = i
|
|
||||||
found = 2
|
|
||||||
break
|
|
||||||
elif screen.client(i).window() == client_win:
|
|
||||||
found = 1
|
|
||||||
if found == 1: # wraparound
|
|
||||||
if forward: target = 0
|
|
||||||
else: target = count - 1
|
|
||||||
|
|
||||||
t = target
|
|
||||||
desktop = screen.desktop()
|
|
||||||
while 1:
|
|
||||||
client = screen.client(t)
|
|
||||||
if client and focus._focusable(client, desktop) and client.focus():
|
|
||||||
if RAISE_WINDOW:
|
|
||||||
screen.raiseWindow(client)
|
|
||||||
return
|
|
||||||
if forward:
|
|
||||||
t += num
|
|
||||||
if t >= count: t -= count
|
|
||||||
else:
|
|
||||||
t -= num
|
|
||||||
if t < 0: t += count
|
|
||||||
if t == target: return # nothing to focus
|
|
||||||
|
|
||||||
print "Loaded focuscycle.py"
|
|
|
@ -1,256 +0,0 @@
|
||||||
###########################################################################
|
|
||||||
### Functions for cycling focus (in a 'stacked' order) between windows. ###
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
### Options that affect the behavior of the stackedcycle module. ###
|
|
||||||
###########################################################################
|
|
||||||
INCLUDE_ALL_DESKTOPS = 0
|
|
||||||
"""If this is non-zero then windows from all desktops will be included in
|
|
||||||
the stacking list."""
|
|
||||||
INCLUDE_ICONS = 1
|
|
||||||
"""If this is non-zero then windows which are iconified on the current desktop
|
|
||||||
will be included in the stacking list."""
|
|
||||||
INCLUDE_ICONS_ALL_DESKTOPS = 1
|
|
||||||
"""If this is non-zero then windows which are iconified from all desktops
|
|
||||||
will be included in the stacking list."""
|
|
||||||
INCLUDE_OMNIPRESENT = 1
|
|
||||||
"""If this is non-zero then windows which are on all-desktops at once will
|
|
||||||
be included."""
|
|
||||||
TITLE_SIZE_LIMIT = 80
|
|
||||||
"""This specifies a rough limit of characters for the cycling list titles.
|
|
||||||
Titles which are larger will be chopped with an elipsis in their
|
|
||||||
center."""
|
|
||||||
ACTIVATE_WHILE_CYCLING = 1
|
|
||||||
"""If this is non-zero then windows will be activated as they are
|
|
||||||
highlighted in the cycling list (except iconified windows)."""
|
|
||||||
# See focus.AVOID_SKIP_TASKBAR
|
|
||||||
# See focuscycle.RAISE_WINDOW
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
def next(data):
|
|
||||||
"""Focus the next window."""
|
|
||||||
if not data.state:
|
|
||||||
raise RuntimeError("stackedcycle.next must be bound to a key" +
|
|
||||||
"combination with at least one modifier")
|
|
||||||
_o.cycle(data, 1)
|
|
||||||
|
|
||||||
def previous(data):
|
|
||||||
"""Focus the previous window."""
|
|
||||||
if not data.state:
|
|
||||||
raise RuntimeError("stackedcycle.previous must be bound to a key" +
|
|
||||||
"combination with at least one modifier")
|
|
||||||
_o.cycle(data, 0)
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
### Internal stuff, should not be accessed outside the module. ###
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
import otk
|
|
||||||
import ob
|
|
||||||
import focus
|
|
||||||
import focuscycle
|
|
||||||
|
|
||||||
class _cycledata:
|
|
||||||
def __init__(self):
|
|
||||||
self.cycling = 0
|
|
||||||
|
|
||||||
def createpopup(self):
|
|
||||||
self.widget = otk.Widget(self.screen.number(), ob.openbox,
|
|
||||||
otk.Widget.Vertical, 0, 1)
|
|
||||||
|
|
||||||
def destroypopup(self):
|
|
||||||
self.menuwidgets = []
|
|
||||||
self.widget = 0
|
|
||||||
|
|
||||||
def shouldadd(self, client):
|
|
||||||
"""Determines if a client should be added to the list."""
|
|
||||||
curdesk = self.screen.desktop()
|
|
||||||
desk = client.desktop()
|
|
||||||
|
|
||||||
if not client.normal(): return 0
|
|
||||||
if not (client.canFocus() or client.focusNotify()): return 0
|
|
||||||
if focus.AVOID_SKIP_TASKBAR and client.skipTaskbar(): return 0
|
|
||||||
|
|
||||||
if client.iconic():
|
|
||||||
if INCLUDE_ICONS:
|
|
||||||
if INCLUDE_ICONS_ALL_DESKTOPS: return 1
|
|
||||||
if desk == curdesk: return 1
|
|
||||||
return 0
|
|
||||||
if INCLUDE_OMNIPRESENT and desk == 0xffffffff: return 1
|
|
||||||
if INCLUDE_ALL_DESKTOPS: return 1
|
|
||||||
if desk == curdesk: return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def populatelist(self):
|
|
||||||
"""Populates self.clients and self.menuwidgets, and then shows and
|
|
||||||
positions the cycling popup."""
|
|
||||||
|
|
||||||
self.widget.hide()
|
|
||||||
|
|
||||||
try:
|
|
||||||
current = self.clients[self.menupos]
|
|
||||||
except IndexError: current = 0
|
|
||||||
oldpos = self.menupos
|
|
||||||
self.menupos = -1
|
|
||||||
|
|
||||||
# get the list of clients, keeping iconic windows at the bottom
|
|
||||||
self.clients = []
|
|
||||||
iconic_clients = []
|
|
||||||
for c in focus._clients:
|
|
||||||
if c.iconic(): iconic_clients.append(c)
|
|
||||||
else: self.clients.append(c)
|
|
||||||
self.clients.extend(iconic_clients)
|
|
||||||
|
|
||||||
# make the widgets
|
|
||||||
i = 0
|
|
||||||
self.menuwidgets = []
|
|
||||||
while i < len(self.clients):
|
|
||||||
c = self.clients[i]
|
|
||||||
if not self.shouldadd(c):
|
|
||||||
# make the clients and menuwidgets lists match
|
|
||||||
self.clients.pop(i)
|
|
||||||
continue
|
|
||||||
|
|
||||||
w = otk.Label(self.widget)
|
|
||||||
if current and c.window() == current.window():
|
|
||||||
self.menupos = i
|
|
||||||
w.setHighlighted(1)
|
|
||||||
self.menuwidgets.append(w)
|
|
||||||
|
|
||||||
if c.iconic(): t = c.iconTitle()
|
|
||||||
else: t = c.title()
|
|
||||||
|
|
||||||
if INCLUDE_ALL_DESKTOPS:
|
|
||||||
d = c.desktop()
|
|
||||||
if d == 0xffffffff: d = self.screen.desktop()
|
|
||||||
t = self.screen.desktopName(d) + " - " + t
|
|
||||||
|
|
||||||
if len(t) > TITLE_SIZE_LIMIT: # limit the length of titles
|
|
||||||
t = t[:TITLE_SIZE_LIMIT / 2 - 2] + "..." + \
|
|
||||||
t[0 - TITLE_SIZE_LIMIT / 2 - 2:]
|
|
||||||
w.setText(t)
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# the window we were on may be gone
|
|
||||||
if self.menupos < 0:
|
|
||||||
# try stay at the same spot in the menu
|
|
||||||
if oldpos >= len(self.clients):
|
|
||||||
self.menupos = len(self.clients) - 1
|
|
||||||
else:
|
|
||||||
self.menupos = oldpos
|
|
||||||
|
|
||||||
# find the size for the popup
|
|
||||||
width = 0
|
|
||||||
height = 0
|
|
||||||
for w in self.menuwidgets:
|
|
||||||
size = w.minSize()
|
|
||||||
if size.width() > width: width = size.width()
|
|
||||||
height += size.height()
|
|
||||||
|
|
||||||
# show or hide the list and its child widgets
|
|
||||||
if len(self.clients) > 1:
|
|
||||||
size = self.screeninfo.size()
|
|
||||||
self.widget.moveresize(otk.Rect((size.width() - width) / 2,
|
|
||||||
(size.height() - height) / 2,
|
|
||||||
width, height))
|
|
||||||
self.widget.show(1)
|
|
||||||
|
|
||||||
def activatetarget(self, final):
|
|
||||||
try:
|
|
||||||
client = self.clients[self.menupos]
|
|
||||||
except IndexError: return # empty list makes for this
|
|
||||||
|
|
||||||
# move the to client's desktop if required
|
|
||||||
if not (client.iconic() or client.desktop() == 0xffffffff or \
|
|
||||||
client.desktop() == self.screen.desktop()):
|
|
||||||
root = self.screeninfo.rootWindow()
|
|
||||||
ob.send_client_msg(root, otk.atoms.net_current_desktop,
|
|
||||||
root, client.desktop())
|
|
||||||
|
|
||||||
# send a net_active_window message for the target
|
|
||||||
if final or not client.iconic():
|
|
||||||
if final: r = focuscycle.RAISE_WINDOW
|
|
||||||
else: r = 0
|
|
||||||
ob.send_client_msg(self.screeninfo.rootWindow(),
|
|
||||||
otk.atoms.openbox_active_window,
|
|
||||||
client.window(), final, r)
|
|
||||||
if not final:
|
|
||||||
focus._skip += 1
|
|
||||||
|
|
||||||
def cycle(self, data, forward):
|
|
||||||
if not self.cycling:
|
|
||||||
ob.kgrab(data.screen, _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
|
|
||||||
ob.mgrab(data.screen)
|
|
||||||
|
|
||||||
self.cycling = 1
|
|
||||||
self.state = data.state
|
|
||||||
self.screen = ob.openbox.screen(data.screen)
|
|
||||||
self.screeninfo = otk.display.screenInfo(data.screen)
|
|
||||||
self.menupos = 0
|
|
||||||
self.createpopup()
|
|
||||||
self.clients = [] # so it doesnt try start partway through the list
|
|
||||||
self.populatelist()
|
|
||||||
|
|
||||||
if not len(self.clients): return # don't both doing anything
|
|
||||||
|
|
||||||
self.menuwidgets[self.menupos].setHighlighted(0)
|
|
||||||
if forward:
|
|
||||||
self.menupos += 1
|
|
||||||
else:
|
|
||||||
self.menupos -= 1
|
|
||||||
# wrap around
|
|
||||||
if self.menupos < 0: self.menupos = len(self.clients) - 1
|
|
||||||
elif self.menupos >= len(self.clients): self.menupos = 0
|
|
||||||
self.menuwidgets[self.menupos].setHighlighted(1)
|
|
||||||
if ACTIVATE_WHILE_CYCLING:
|
|
||||||
self.activatetarget(0) # activate, but dont deiconify/unshade/raise
|
|
||||||
|
|
||||||
def grabfunc(self, data):
|
|
||||||
done = 0
|
|
||||||
notreverting = 1
|
|
||||||
# have all the modifiers this started with been released?
|
|
||||||
if not self.state & data.state:
|
|
||||||
done = 1
|
|
||||||
elif data.action == ob.KeyAction.Press:
|
|
||||||
# has Escape been pressed?
|
|
||||||
if data.key == "Escape":
|
|
||||||
done = 1
|
|
||||||
notreverting = 0
|
|
||||||
# revert
|
|
||||||
self.menupos = 0
|
|
||||||
# has Enter been pressed?
|
|
||||||
elif data.key == "Return":
|
|
||||||
done = 1
|
|
||||||
|
|
||||||
if done:
|
|
||||||
# activate, and deiconify/unshade/raise
|
|
||||||
self.activatetarget(notreverting)
|
|
||||||
self.destroypopup()
|
|
||||||
self.cycling = 0
|
|
||||||
ob.kungrab()
|
|
||||||
ob.mungrab()
|
|
||||||
|
|
||||||
def _newwindow(data):
|
|
||||||
if _o.cycling: _o.populatelist()
|
|
||||||
|
|
||||||
def _closewindow(data):
|
|
||||||
if _o.cycling: _o.populatelist()
|
|
||||||
|
|
||||||
def _grabfunc(data):
|
|
||||||
_o.grabfunc(data)
|
|
||||||
|
|
||||||
ob.ebind(ob.EventAction.NewWindow, _newwindow)
|
|
||||||
ob.ebind(ob.EventAction.CloseWindow, _closewindow)
|
|
||||||
|
|
||||||
_o = _cycledata()
|
|
||||||
|
|
||||||
print "Loaded stackedcycle.py"
|
|
Loading…
Reference in a new issue