// FocusControl.cc // 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(); int safety_counter = 0; while (true) { ++it; if (it == it_end) { it = m_focused_list.begin(); safety_counter++; if (safety_counter > 3) break; } // 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; } int safety_counter = 0; do { ++it; if (it == wins.end()) { it = wins.begin(); safety_counter++; if (safety_counter > 3) break; } // 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) { // 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) { // 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) { // 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<<"------------------"<<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 namespace FbTk { 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(); } } // end namespace FbTk