From 51a41b9b765dbcf76ec09324f533d8c8edd46e37 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Tue, 4 Feb 2003 08:51:47 +0000 Subject: [PATCH 1/1] all new stacked cycling code. so much sexy. add an openbox_active_window client message that lets you specify if you want the window to be raised and/or unshaded. --- otk/property.cc | 1 + otk/property.hh | 1 + scripts/Makefile.am | 2 +- scripts/defaults.py | 5 +- scripts/focus.py | 333 ++++++++-------------------------------- scripts/stackedcycle.py | 214 ++++++++++++++++++++++++++ src/client.cc | 9 +- 7 files changed, 293 insertions(+), 272 deletions(-) create mode 100644 scripts/stackedcycle.py diff --git a/otk/property.cc b/otk/property.cc index a0404f68..39626911 100644 --- a/otk/property.cc +++ b/otk/property.cc @@ -136,6 +136,7 @@ void Property::initialize() create("_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"); atoms.openbox_premax = create("_OPENBOX_PREMAX"); + atoms.openbox_active_window = create("_OPENBOX_ACTIVE_WINDOW"); } void Property::set(Window win, Atom atom, Atom type, unsigned char* data, diff --git a/otk/property.hh b/otk/property.hh index e38bb23a..1dc08937 100644 --- a/otk/property.hh +++ b/otk/property.hh @@ -125,6 +125,7 @@ struct Atoms { Atom kde_net_wm_window_type_override; Atom openbox_premax; + Atom openbox_active_window; }; diff --git a/scripts/Makefile.am b/scripts/Makefile.am index fb5dd2f0..16a61f13 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -2,7 +2,7 @@ scriptdir = $(libdir)/openbox/python MAINTAINERCLEANFILES = Makefile.in script_DATA = config.py defaults.py focus.py callbacks.py \ focusmodel.py windowplacement.py behavior.py motion.py \ - historyplacement.py + historyplacement.py stackedcycle.py EXTRA_DIST = $(script_DATA) distclean-local: diff --git a/scripts/defaults.py b/scripts/defaults.py index 922f472c..7fd2273d 100644 --- a/scripts/defaults.py +++ b/scripts/defaults.py @@ -1,4 +1,5 @@ import focus # add some default focus handling and cycling functions +import stackedcycle # functions for doing stacked 'kde-style' cycling import focusmodel # default focus models import behavior # defines default behaviors for interaction with windows import callbacks # a lib of functions that can be used as binding callbacks @@ -33,8 +34,8 @@ ob.mbind("Left", ob.MouseContext.Root, ob.MouseAction.Click, ob.kbind(["A-F4"], ob.KeyContext.All, callbacks.close) # focus bindings -ob.kbind(["A-Tab"], ob.KeyContext.All, focus.focus_next_stacked) -ob.kbind(["A-S-Tab"], ob.KeyContext.All, focus.focus_prev_stacked) +ob.kbind(["A-Tab"], ob.KeyContext.All, stackedcycle.next) +ob.kbind(["A-S-Tab"], ob.KeyContext.All, stackedcycle.previous) # desktop changing bindings ob.kbind(["C-1"], ob.KeyContext.All, lambda(d): callbacks.change_desktop(d, 0)) diff --git a/scripts/focus.py b/scripts/focus.py index 6bb64992..172c1658 100644 --- a/scripts/focus.py +++ b/scripts/focus.py @@ -4,39 +4,31 @@ ########################################################################### ### Options that affect the behavior of the focus module. ### -### ### -# cycle_raise - raise the window also when it is focused ### -cycle_raise = 1 ### -# avoid_skip_taskbar - 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 ### -### 'fallback' is enabled. ### -avoid_skip_taskbar = 1 ### -# stacked_cycle_raise - raise as you cycle in stacked mode ### -stacked_cycle_raise = 0 ### -# stacked_cycle_popup_list - show a pop-up list of windows while ### -### cycling ### -stacked_cycle_popup_list = 1 ### -# send focus somewhere when nothing is left with the focus, if possible ### -fallback = 0 ### -### ### -### ### -# Provides: ### -# def focus_next_stacked(data, forward=1): ### -# """Focus the next (or previous, with forward=0) window in a stacked ### -# order.""" ### -# def focus_prev_stacked(data): ### -# """Focus the previous window in a stacked order.""" ### -# def focus_next(data, num=1, forward=1): ### -# """Focus the next (or previous, with forward=0) window in a linear ### -# order.""" ### -# def focus_prev(data, num=1): ### -# """Focus the previous window in a linear order.""" ### -### ### -# All of these functions call be used as callbacks for bindings ### -# directly. ### -### ### +########################################################################### +avoid_skip_taskbar = 1 +"""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 'fallback' is enabled.""" +raise_window = 1 +"""When cycling focus, raise the window chosen as well as focusing it. This + does not affect fallback focusing behavior.""" +fallback = 0 +"""Send focus somewhere when nothing is left with the focus, if possible.""" +########################################################################### + +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 otk @@ -44,237 +36,51 @@ import ob # maintain a list of clients, stacked in focus order _clients = [] -# maintaint he current focused window -_doing_stacked = 0 +_disable = 0 def _focusable(client, desktop): - if not (avoid_skip_taskbar and client.skipTaskbar()) and \ - (client.desktop() == desktop or client.desktop() == 0xffffffff) and \ - client.normal() and (client.canFocus() or client.focusNotify()): - return 1 - return 0 + if not client.normal(): return 0 + if not (client.canFocus() or client.focusNotify()): return 0 + if avoid_skip_taskbar and client.skipTaskbar(): return 0 -def _new_win(data): - global _clients - global _doing_stacked - global _cyc_w; + desk = client.desktop() + if not (desk == 0xffffffff or desk == desktop): return 0 - if _doing_stacked: - _clients.insert(_clients.index(_cyc_w), data.client.window()) - _create_popup_list(data) - _hilite_popup_list(data) - else: - if not len(_clients): - _clients.append(data.client.window()) - else: - _clients.insert(1, data.client.window()) # insert in 2nd slot - -def _close_win(data): - global _clients - global _cyc_w; - global _doing_stacked - - if not _doing_stacked: - # not in the middle of stacked cycling, so who cares - _clients.remove(data.client.window()) - else: - # have to fix the cycling if we remove anything - win = data.client.window() - if _cyc_w == win: - _do_stacked_cycle(data, 1) # cycle off the window first, forward - _clients.remove(win) - _create_popup_list(data) + return 1 def _focused(data): global _clients - global _doing_stacked - global _cyc_w + + if _disable: return if data.client: - if not _doing_stacked: # only move the window when we're not cycling - win = data.client.window() - # move it to the top - _clients.remove(win) - _clients.insert(0, win) - else: # if we are cycling, then update our pointer - _cyc_w = data.client.window() - _hilite_popup_list(data) + win = data.client.window() + # move it to the top + _clients.remove(win) + _clients.insert(0, win) elif fallback: # pass around focus - desktop = ob.openbox.screen(_cyc_screen).desktop() + desktop = ob.openbox.screen(data.screen).desktop() for w in _clients: client = ob.openbox.findClient(w) if client and _focusable(client, desktop) and client.focus(): break - if _doing_stacked: - _cyc_w = 0 - _hilite_popup_list(data) - -_cyc_mask = 0 -_cyc_key = 0 -_cyc_w = 0 # last window cycled to -_cyc_screen = 0 - -def _do_stacked_cycle(data, forward): - global _cyc_w - global stacked_cycle_raise - global _clients - clients = _clients[:] # make a copy - - if not forward: - clients.reverse() - - try: - i = clients.index(_cyc_w) + 1 - except ValueError: - i = 1 - clients = clients[i:] + clients[:i] +def _newwindow(data): + _clients.append(data.client.window()) - desktop = ob.openbox.screen(data.screen).desktop() - for w in clients: - client = ob.openbox.findClient(w) - - if client and _focusable(client, desktop) and client.focus(): - if stacked_cycle_raise: - ob.openbox.screen(data.screen).raiseWindow(client) - return - -def _focus_stacked_ungrab(data): - global _cyc_mask; - global _cyc_key; - global _doing_stacked; - - if data.action == ob.KeyAction.Release: - # have all the modifiers this started with been released? - if not _cyc_mask & data.state: - _destroy_popup_list() - ob.kungrab() - ob.mungrab() - _doing_stacked = 0; - client = ob.openbox.findClient(_cyc_w) - if client: - data.client = client - #_focused(data) # resort the list as appropriate - if cycle_raise: - ob.openbox.screen(data.screen).raiseWindow(client) - -_list_widget = 0 -_list_labels = [] -_list_windows = [] - -def _hilite_popup_list(data): - global _cyc_w, _doing_stacked - global _list_widget, _list_labels, _list_windows - found = 0 +def _closewindow(data): + try: + focus._clients.remove(data.client.window()) + except ValueError: pass - if not _list_widget and _doing_stacked: - _create_popup_list(data) - - if _list_widget: - i = 0 - for w in _list_windows: - if w == _cyc_w: - _list_labels[i].focus() - found = 1 - else: - _list_labels[i].unfocus() - i += 1 - if not found: - _create_popup_list(data) +ob.ebind(ob.EventAction.NewWindow, _newwindow) +ob.ebind(ob.EventAction.CloseWindow, _closewindow) +ob.ebind(ob.EventAction.Focus, _focused) -def _destroy_popup_list(): - global _list_widget, _list_labels, _list_windows - if _list_widget: - _list_windows = [] - _list_labels = [] - _list_widget = 0 - -def _create_popup_list(data): +def _cycle(data, num, forward): global avoid_skip_taskbar - global _list_widget, _list_labels, _list_windows, _clients - - if _list_widget: - _destroy_popup_list() - style = ob.openbox.screen(data.screen).style() - _list_widget = otk.Widget(ob.openbox, style, - otk.Widget.Vertical, 0, - style.bevelWidth(), 1) - t = style.titlebarFocusBackground() - _list_widget.setTexture(t) - - titles = [] - font = style.labelFont() - height = font.height() - longest = 0 - desktop = ob.openbox.screen(data.screen).desktop() - for c in _clients: - client = ob.openbox.findClient(c) - if client and _focusable(client, desktop): - t = client.title() - if len(t) > 50: # limit the length of titles - t = t[:24] + "..." + t[-24:] - titles.append(t) - _list_windows.append(c) - l = font.measureString(t) - if l > longest: longest = l - if len(titles) > 1: - for t in titles: - w = otk.FocusLabel(_list_widget) - w.fitSize(longest, height) - w.setText(t) - w.unfocus() - _list_labels.append(w) - _list_widget.update() - area = otk.display.screenInfo(data.screen).rect() - _list_widget.move(area.x() + (area.width() - - _list_widget.width()) / 2, - area.y() + (area.height() - - _list_widget.height()) / 2) - _list_widget.show(1) - else: - _destroy_popup_list() # nothing (or only 1) to list - -def focus_next_stacked(data, forward=1): - """Focus the next (or previous, with forward=0) window in a stacked - order.""" - global _cyc_mask - global _cyc_key - global _cyc_w - global _cyc_screen - global _doing_stacked - - if _doing_stacked: - if _cyc_key == data.key: - _do_stacked_cycle(data,forward) - else: - _cyc_mask = data.state - _cyc_key = data.key - _cyc_w = 0 - _cyc_screen = data.screen - _doing_stacked = 1 - - global stacked_cycle_popup_list - if stacked_cycle_popup_list: - _create_popup_list(data) - - ob.kgrab(data.screen, _focus_stacked_ungrab) - # 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) - focus_next_stacked(data, forward) # start with the first press - -def focus_prev_stacked(data): - """Focus the previous window in a stacked order.""" - focus_next_stacked(data, forward=0) - -def focus_next(data, num=1, forward=1): - """Focus the next (or previous, with forward=0) window in a linear - order.""" - global avoid_skip_taskbar - screen = ob.openbox.screen(data.screen) count = screen.clientCount() @@ -298,29 +104,20 @@ def focus_next(data, num=1, forward=1): if forward: target = 0 else: target = count - 1 - t = target - desktop = screen.desktop() - while 1: - client = screen.client(t) - if client and _focusable(client, desktop) and client.focus(): - if cycle_raise: - 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 - -def focus_prev(data, num=1): - """Focus the previous window in a linear order.""" - focus_next(data, num, forward=0) - - -ob.ebind(ob.EventAction.NewWindow, _new_win) -ob.ebind(ob.EventAction.CloseWindow, _close_win) -ob.ebind(ob.EventAction.Focus, _focused) - + t = target + desktop = screen.desktop() + while 1: + client = screen.client(t) + if client and _focusable(client, desktop) and client.focus(): + if cycle_raise: + 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 focus.py" diff --git a/scripts/stackedcycle.py b/scripts/stackedcycle.py new file mode 100644 index 00000000..a022c0cc --- /dev/null +++ b/scripts/stackedcycle.py @@ -0,0 +1,214 @@ +########################################################################### +### Functions for cycling focus (in a 'stacked' order) between windows. ### +########################################################################### + +########################################################################### +### Options that affect the behavior of the stackedcycle module. ### +### Also see the options in the focus module. ### +########################################################################### +include_all_desktops = 0 +include_icons = 1 +include_omnipresent = 1 +title_size_limit = 80 +activate_while_cycling = 1 +########################################################################### + +def next(data): + """Focus the next window.""" + _o.cycle(data, 1) + +def previous(data): + """Focus the previous window.""" + _o.cycle(data, 0) + +########################################################################### +########################################################################### + +########################################################################### +### Internal stuff, should not be accessed outside the module. ### +########################################################################### + +import otk +import ob +import focus + +class cycledata: + def __init__(self): + self.cycling = 0 + + def createpopup(self): + self.style = self.screen.style() + self.widget = otk.Widget(ob.openbox, self.style, otk.Widget.Vertical, + 0, self.style.bevelWidth(), 1) + self.widget.setTexture(self.style.titlebarFocusBackground()) + + 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 include_icons and client.iconic(): return 1 + 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 + self.clients = [] + print focus._clients + for i in focus._clients: + c = ob.openbox.findClient(i) + if c: self.clients.append(c) + + font = self.style.labelFont() + longest = 0 + height = font.height() + + # 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.FocusLabel(self.widget) + if current and c.window() == current.window(): + print "found " + str(i) + self.menupos = i + w.focus() + else: + w.unfocus() + self.menuwidgets.append(w) + + if c.iconic(): t = c.iconTitle() + else: t = c.title() + 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:] + length = font.measureString(t) + if length > longest: longest = length + 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 + + # fit to the largest item in the menu + for w in self.menuwidgets: + w.fitSize(longest, height) + + # show or hide the list and its child widgets + if len(self.clients) > 1: + area = self.screeninfo.rect() + self.widget.update() + self.widget.move(area.x() + (area.width() - + self.widget.width()) / 2, + area.y() + (area.height() - + self.widget.height()) / 2) + 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.Property_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 = focus.raise_window + else: r = 0 + ob.send_client_msg(self.screeninfo.rootWindow(), + otk.Property_atoms().openbox_active_window, + client.window(), final, r) + + def cycle(self, data, forward): + if not self.cycling: + self.cycling = 1 + focus._disable = 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() + + ob.kgrab(self.screen.number(), _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(self.screen.number()) + + self.menuwidgets[self.menupos].unfocus() + 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].focus() + if activate_while_cycling: + self.activatetarget(0) # activate, but dont deiconify/unshade/raise + + def grabfunc(self, data): + if data.action == ob.KeyAction.Release: + # have all the modifiers this started with been released? + if not self.state & data.state: + self.cycling = 0 + focus._disable = 0 + self.activatetarget(1) # activate, and deiconify/unshade/raise + self.destroypopup() + 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() diff --git a/src/client.cc b/src/client.cc index 1312cac8..d42a962a 100644 --- a/src/client.cc +++ b/src/client.cc @@ -1092,9 +1092,16 @@ void Client::clientMessageHandler(const XClientMessageEvent &e) setDesktop(openbox->screen(_screen)->desktop()); if (_shaded) shade(false); - // XXX: deiconify focus(); openbox->screen(_screen)->raiseWindow(this); + } else if (e.message_type == otk::Property::atoms.openbox_active_window) { + if (_iconic) + setDesktop(openbox->screen(_screen)->desktop()); + if (e.data.l[0] && _shaded) + shade(false); + focus(); + if (e.data.l[1]) + openbox->screen(_screen)->raiseWindow(this); } } -- 2.45.2