fluxbox/src/FocusControl.cc

683 lines
23 KiB
C++
Raw Normal View History

// FocusControl.hh
// Copyright (c) 2006 Fluxbox Team (fluxgen at fluxbox dot org)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
// $Id$
#include "FocusControl.hh"
#include "Screen.hh"
#include "Window.hh"
#include "WinClient.hh"
#include "Workspace.hh"
#include "fluxbox.hh"
#include "FbWinFrameTheme.hh"
#include <string>
#include <cassert>
#include <iostream>
using std::cerr;
using std::endl;
using std::string;
WinClient *FocusControl::s_focused_window = 0;
FocusControl::FocusControl(BScreen &screen):
m_screen(screen),
m_focus_model(screen.resourceManager(),
CLICKFOCUS,
screen.name()+".focusModel",
screen.altName()+".FocusModel"),
m_tab_focus_model(screen.resourceManager(),
CLICKTABFOCUS,
screen.name()+".tabFocusModel",
screen.altName()+".TabFocusModel"),
m_focus_last(screen.resourceManager(), true,
screen.name()+".focusLastWindow",
screen.altName()+".FocusLastWindow"),
m_focus_new(screen.resourceManager(), true,
screen.name()+".focusNewWindows",
screen.altName()+".FocusNewWindows"),
m_cycling_focus(false),
m_cycling_last(0) {
m_cycling_window = m_focused_list.end();
}
// true if the windows should be skiped else false
bool doSkipWindow(const WinClient &winclient, int opts) {
const FluxboxWindow *win = winclient.fbwindow();
return (!win ||
// skip if stuck
(opts & FocusControl::CYCLESKIPSTUCK) != 0 && win->isStuck() ||
// skip if not active client (i.e. only visit each fbwin once)
(opts & FocusControl::CYCLEGROUPS) != 0 && win->winClient().window() != winclient.window() ||
// skip if shaded
(opts & FocusControl::CYCLESKIPSHADED) != 0 && win->isShaded() ||
// skip if hidden
win->isFocusHidden()
);
}
void FocusControl::prevFocus(int opts) {
int num_windows = m_screen.currentWorkspace()->numberOfWindows();
if (num_windows < 1)
return;
if (!(opts & CYCLELINEAR)) {
if (!m_cycling_focus) {
m_cycling_focus = true;
m_cycling_window = m_focused_list.end();
m_cycling_last = 0;
} else {
// already cycling, so restack to put windows back in their proper order
m_screen.layerManager().restack();
}
// if it is stacked, we want the highest window in the focused list
// that is on the same workspace
FocusedWindows::iterator it = m_cycling_window;
FocusedWindows::iterator it_end = m_focused_list.end();
while (true) {
--it;
if (it == it_end) {
it = m_focused_list.end();
--it;
}
// give up [do nothing] if we reach the current focused again
if ((*it) == (*m_cycling_window)) {
break;
}
FluxboxWindow *fbwin = (*it)->fbwindow();
if (fbwin && !fbwin->isIconic() &&
(fbwin->isStuck()
|| fbwin->workspaceNumber() == m_screen.currentWorkspaceID())) {
// either on this workspace, or stuck
// keep track of the originally selected window in a set
WinClient &last_client = fbwin->winClient();
if (! (doSkipWindow(**it, opts) || !fbwin->setCurrentClient(**it)) ) {
// moved onto a new fbwin
if (!m_cycling_last || m_cycling_last->fbwindow() != fbwin) {
if (m_cycling_last) {
// set back to orig current Client in that fbwin
m_cycling_last->fbwindow()->setCurrentClient(*m_cycling_last, false);
}
m_cycling_last = &last_client;
}
fbwin->tempRaise();
break;
}
}
}
m_cycling_window = it;
} else { // not stacked cycling
Workspace &wksp = *m_screen.currentWorkspace();
Workspace::Windows &wins = wksp.windowList();
Workspace::Windows::iterator it = wins.begin();
FluxboxWindow *focused_group = 0;
// start from the focused window
bool have_focused = false;
WinClient *focused = focusedWindow();
if (focused != 0) {
if (focused->screen().screenNumber() == m_screen.screenNumber()) {
have_focused = true;
focused_group = focused->fbwindow();
}
}
if (!have_focused) {
focused_group = (*it);
} else {
//get focused window iterator
for (; it != wins.end() && (*it) != focused_group; ++it)
continue;
}
do {
if (it == wins.begin())
it = wins.end();
--it;
// see if the window should be skipped
if (! (doSkipWindow((*it)->winClient(), opts) || !(*it)->setInputFocus()) )
break;
} while ((*it) != focused_group);
if ((*it) != focused_group && it != wins.end())
(*it)->raise();
}
}
void FocusControl::addFocusFront(WinClient &client) {
m_focused_list.push_front(&client);
}
void FocusControl::addFocusBack(WinClient &client) {
m_focused_list.push_back(&client);
}
void FocusControl::stopCyclingFocus() {
// nothing to do
if (!m_cycling_focus)
return;
m_cycling_focus = false;
m_cycling_last = 0;
// put currently focused window to top
// the iterator may be invalid if the window died
// in which case we'll do a proper revert focus
if (m_cycling_window != m_focused_list.end()) {
WinClient *client = *m_cycling_window;
m_focused_list.erase(m_cycling_window);
m_focused_list.push_front(client);
client->fbwindow()->raise();
} else {
revertFocus(m_screen);
}
}
/**
* Used to find out which window was last focused on the given workspace
* If workspace is outside the ID range, then the absolute last focused window
* is given.
*/
WinClient *FocusControl::lastFocusedWindow(int workspace) {
if (m_focused_list.empty()) return 0;
if (workspace < 0 || workspace >= (int) m_screen.numberOfWorkspaces())
return m_focused_list.front();
FocusedWindows::iterator it = m_focused_list.begin();
FocusedWindows::iterator it_end = m_focused_list.end();
for (; it != it_end; ++it) {
if ((*it)->fbwindow() &&
(((int)(*it)->fbwindow()->workspaceNumber()) == workspace
&& !(*it)->fbwindow()->isIconic()
&& (!(*it)->fbwindow()->isStuck() || (*it)->fbwindow()->isFocused())))
// only give focus to a stuck window if it is currently focused
// otherwise they tend to override normal workspace focus
return *it;
}
return 0;
}
/**
* Used to find out which window was last active in the given group
* If ignore_client is given, it excludes that client.
* Stuck, iconic etc don't matter within a group
*/
WinClient *FocusControl::lastFocusedWindow(FluxboxWindow &group, WinClient *ignore_client) {
if (m_focused_list.empty()) return 0;
FocusedWindows::iterator it = m_focused_list.begin();
FocusedWindows::iterator it_end = m_focused_list.end();
for (; it != it_end; ++it) {
if (((*it)->fbwindow() == &group) &&
(*it) != ignore_client)
return *it;
}
return 0;
}
void FocusControl::nextFocus(int opts) {
const int num_windows = m_screen.currentWorkspace()->numberOfWindows();
if (num_windows < 1)
return;
if (!(opts & CYCLELINEAR)) {
if (!m_cycling_focus) {
m_cycling_focus = True;
m_cycling_window = m_focused_list.begin();
m_cycling_last = 0;
} else {
// already cycling, so restack to put windows back in their proper order
m_screen.layerManager().restack();
}
// if it is stacked, we want the highest window in the focused list
// that is on the same workspace
FocusedWindows::iterator it = m_cycling_window;
const FocusedWindows::iterator it_end = m_focused_list.end();
while (true) {
++it;
if (it == it_end) {
it = m_focused_list.begin();
}
// give up [do nothing] if we reach the current focused again
if ((*it) == (*m_cycling_window)) {
break;
}
FluxboxWindow *fbwin = (*it)->fbwindow();
if (fbwin && !fbwin->isIconic() &&
(fbwin->isStuck()
|| fbwin->workspaceNumber() == m_screen.currentWorkspaceID())) {
// either on this workspace, or stuck
// keep track of the originally selected window in a set
WinClient &last_client = fbwin->winClient();
if (! (doSkipWindow(**it, opts) || !fbwin->setCurrentClient(**it)) ) {
// moved onto a new fbwin
if (!m_cycling_last || m_cycling_last->fbwindow() != fbwin) {
if (m_cycling_last) {
// set back to orig current Client in that fbwin
m_cycling_last->fbwindow()->setCurrentClient(*m_cycling_last, false);
}
m_cycling_last = &last_client;
}
fbwin->tempRaise();
break;
}
}
}
m_cycling_window = it;
} else { // not stacked cycling
// I really don't like this, but evidently some people use it(!)
Workspace &wksp = *m_screen.currentWorkspace();
Workspace::Windows &wins = wksp.windowList();
Workspace::Windows::iterator it = wins.begin();
FluxboxWindow *focused_group = 0;
// start from the focused window
bool have_focused = false;
WinClient *focused = focusedWindow();
if (focused != 0) {
if (focused->screen().screenNumber() == m_screen.screenNumber()) {
have_focused = true;
focused_group = focused->fbwindow();
}
}
if (!have_focused) {
focused_group = (*it);
} else {
// get focused window iterator
for (; it != wins.end() && (*it) != focused_group; ++it)
continue;
}
do {
++it;
if (it == wins.end())
it = wins.begin();
// see if the window should be skipped
if (! (doSkipWindow((*it)->winClient(), opts) || !(*it)->setInputFocus()) )
break;
} while ((*it) != focused_group);
if ((*it) != focused_group && it != wins.end())
(*it)->raise();
}
}
void FocusControl::raiseFocus() {
bool have_focused = false;
// set have_focused if the currently focused window
// is on this screen
if (focusedWindow()) {
if (focusedWindow()->screen().screenNumber() == m_screen.screenNumber()) {
have_focused = true;
}
}
// if we have a focused window on this screen and
// number of windows is greater than one raise the focused window
if (m_screen.currentWorkspace()->numberOfWindows() > 1 && have_focused)
focusedWindow()->raise();
}
void FocusControl::setScreenFocusedWindow(WinClient &win_client) {
cerr<<__FUNCTION__<<endl;
// raise newly focused window to the top of the focused list
if (!m_cycling_focus) { // don't change the order if we're cycling
m_focused_list.remove(&win_client);
m_focused_list.push_front(&win_client);
m_cycling_window = m_focused_list.begin();
}
}
void FocusControl::setFocusModel(FocusModel model) {
m_focus_model = model;
}
void FocusControl::setTabFocusModel(TabFocusModel model) {
m_tab_focus_model = model;
}
void FocusControl::dirFocus(FluxboxWindow &win, FocusDir dir) {
// change focus to the window in direction dir from the given window
// we scan through the list looking for the window that is "closest"
// in the given direction
FluxboxWindow *foundwin = 0;
int weight = 999999, exposure = 0; // extreme values
int borderW = m_screen.winFrameTheme().border().width(),
top = win.y(),
bottom = win.y() + win.height() + 2*borderW,
left = win.x(),
right = win.x() + win.width() + 2*borderW;
Workspace::Windows &wins = m_screen.currentWorkspace()->windowList();
Workspace::Windows::iterator it = wins.begin();
for (; it != wins.end(); ++it) {
if ((*it) == &win
|| (*it)->isIconic()
|| (*it)->isFocusHidden()
|| !(*it)->winClient().acceptsFocus())
continue; // skip self
// we check things against an edge, and within the bounds (draw a picture)
int edge=0, upper=0, lower=0, oedge=0, oupper=0, olower=0;
int otop = (*it)->y(),
// 2 * border = border on each side
obottom = (*it)->y() + (*it)->height() + 2*borderW,
oleft = (*it)->x(),
// 2 * border = border on each side
oright = (*it)->x() + (*it)->width() + 2*borderW;
// check if they intersect
switch (dir) {
case FOCUSUP:
edge = obottom;
oedge = bottom;
upper = left;
oupper = oleft;
lower = right;
olower = oright;
break;
case FOCUSDOWN:
edge = top;
oedge = otop;
upper = left;
oupper = oleft;
lower = right;
olower = oright;
break;
case FOCUSLEFT:
edge = oright;
oedge = right;
upper = top;
oupper = otop;
lower = bottom;
olower = obottom;
break;
case FOCUSRIGHT:
edge = left;
oedge = oleft;
upper = top;
oupper = otop;
lower = bottom;
olower = obottom;
break;
}
if (oedge < edge)
continue; // not in the right direction
if (olower <= upper || oupper >= lower) {
// outside our horz bounds, get a heavy weight penalty
int myweight = 100000 + oedge - edge + abs(upper-oupper)+abs(lower-olower);
if (myweight < weight) {
foundwin = *it;
exposure = 0;
weight = myweight;
}
} else if ((oedge - edge) < weight) {
foundwin = *it;
weight = oedge - edge;
exposure = ((lower < olower)?lower:olower) - ((upper > oupper)?upper:oupper);
} else if (foundwin && oedge - edge == weight) {
int myexp = ((lower < olower)?lower:olower) - ((upper > oupper)?upper:oupper);
if (myexp > exposure) {
foundwin = *it;
// weight is same
exposure = myexp;
}
} // else not improvement
}
if (foundwin)
foundwin->setInputFocus();
}
void FocusControl::removeClient(WinClient &client) {
WinClient *cyc = 0;
if (m_cycling_window != m_focused_list.end())
cyc = *m_cycling_window;
m_focused_list.remove(&client);
if (cyc == &client) {
m_cycling_window = m_focused_list.end();
}
if (m_cycling_last == &client)
m_cycling_last = 0;
}
/**
* This function is called whenever we aren't quite sure what
* focus is meant to be, it'll make things right ;-)
* last_focused is set to something if we want to make use of the
* previously focused window (it must NOT be set focused now, it
* is probably dying).
*
* ignore_event means that it ignores the given event until
* it gets a focusIn
*/
void FocusControl::revertFocus(BScreen &screen) {
cerr<<__FUNCTION__<<endl;
// Relevant resources:
// resource.focus_last = whether we focus last focused when changing workspace
// BScreen::FocusModel = sloppy, click, whatever
WinClient *next_focus =
screen.focusControl().lastFocusedWindow(screen.currentWorkspaceID());
// if setting focus fails, or isn't possible, fallback correctly
if (!(next_focus && next_focus->fbwindow() &&
next_focus->fbwindow()->setCurrentClient(*next_focus, true))) {
setFocusedWindow(0); // so we don't get dangling m_focused_window pointer
switch (screen.focusControl().focusModel()) {
case FocusControl::MOUSEFOCUS:
XSetInputFocus(screen.rootWindow().display(),
PointerRoot, None, CurrentTime);
break;
case FocusControl::CLICKFOCUS:
screen.rootWindow().setInputFocus(RevertToPointerRoot, CurrentTime);
break;
}
}
}
/*
* Like revertFocus, but specifically related to this window (transients etc)
* if full_revert, we fallback to a full revertFocus if we can't find anything
* local to the client.
* If unfocus_frame is true, we won't focus anything in the same frame
* as the client.
*
* So, we first prefer to choose a transient parent, then the last
* client in this window, and if no luck (or unfocus_frame), then
* we just use the normal revertFocus on the screen.
*
* assumption: client has focus
*/
void FocusControl::unfocusWindow(WinClient &client,
bool full_revert,
bool unfocus_frame) {
cerr<<__FUNCTION__<<endl;
// go up the transient tree looking for a focusable window
FluxboxWindow *fbwin = client.fbwindow();
if (fbwin == 0)
unfocus_frame = false;
WinClient *trans_parent = client.transientFor();
while (trans_parent) {
if (trans_parent->fbwindow() && // can't focus if no fbwin
(!unfocus_frame || trans_parent->fbwindow() != fbwin) && // can't be this window
trans_parent->fbwindow()->isVisible() &&
trans_parent->fbwindow()->setCurrentClient(*trans_parent,
s_focused_window == &client)) {
return;
}
trans_parent = trans_parent->transientFor();
}
if (fbwin == 0)
return; // nothing more we can do
BScreen &screen = fbwin->screen();
if (!unfocus_frame) {
WinClient *last_focus = screen.focusControl().lastFocusedWindow(*fbwin, &client);
if (last_focus != 0 &&
fbwin->setCurrentClient(*last_focus,
s_focused_window == &client)) {
return;
}
}
if (full_revert && s_focused_window == &client)
revertFocus(screen);
}
void FocusControl::setFocusedWindow(WinClient *client) {
BScreen *screen = client ? &client->screen() : 0;
BScreen *old_screen =
FocusControl::focusedWindow() ?
&FocusControl::focusedWindow()->screen() : 0;
#ifdef DEBUG
cerr<<__FILE__<<endl;
cerr<<"------------------"<<endl;
cerr<<"Setting Focused window = "<<client<<endl;
if (client != 0 && client->fbwindow() != 0)
cerr<<"title: "<<client->fbwindow()->title()<<endl;
cerr<<"Current Focused window = "<<s_focused_window<<endl;
cerr<<"------------------"<<endl;
#endif // DEBUG
WinClient *old_client = 0;
// Update the old focused client to non focus
// check if s_focused_window is valid
if (s_focused_window != 0 &&
Fluxbox::instance()->validateClient(s_focused_window)) {
old_client = s_focused_window;
if (old_client->fbwindow()) {
FluxboxWindow *old_win = old_client->fbwindow();
if (!client || client->fbwindow() != old_win)
old_win->setFocusFlag(false);
}
} else {
s_focused_window = 0;
}
if (client && client->fbwindow() && !client->fbwindow()->isIconic()) {
// screen should be ok
FluxboxWindow *win = client->fbwindow();
s_focused_window = client; // update focused window
win->setCurrentClient(*client,
false); // don't set inputfocus
win->setFocusFlag(true); // set focus flag
} else
s_focused_window = 0;
// update AtomHandlers and/or other stuff...
Fluxbox::instance()->updateFocusedWindow(screen, old_screen);
}
////////////////////// FocusControl RESOURCES
template<>
std::string FbTk::Resource<FocusControl::FocusModel>::getString() const {
switch (m_value) {
case FocusControl::MOUSEFOCUS:
return string("MouseFocus");
case FocusControl::CLICKFOCUS:
return string("ClickFocus");
}
// default string
return string("ClickFocus");
}
template<>
void FbTk::Resource<FocusControl::FocusModel>::
setFromString(char const *strval) {
if (strcasecmp(strval, "MouseFocus") == 0)
m_value = FocusControl::MOUSEFOCUS;
else if (strcasecmp(strval, "ClickToFocus") == 0)
m_value = FocusControl::CLICKFOCUS;
else
setDefaultValue();
}
template<>
std::string FbTk::Resource<FocusControl::TabFocusModel>::getString() const {
switch (m_value) {
case FocusControl::MOUSETABFOCUS:
return string("SloppyTabFocus");
case FocusControl::CLICKTABFOCUS:
return string("ClickToTabFocus");
}
// default string
return string("ClickToTabFocus");
}
template<>
void FbTk::Resource<FocusControl::TabFocusModel>::
setFromString(char const *strval) {
if (strcasecmp(strval, "SloppyTabFocus") == 0 )
m_value = FocusControl::MOUSETABFOCUS;
else if (strcasecmp(strval, "ClickToTabFocus") == 0)
m_value = FocusControl::CLICKTABFOCUS;
else
setDefaultValue();
}