// Window.cc for Fluxbox Window Manager // Copyright (c) 2001 - 2003 Henrik Kinnunen (fluxgen at users.sourceforge.net) // // Window.cc for Blackbox - an X11 Window manager // Copyright (c) 1997 - 2000 Brad Hughes (bhughes at tcac.net) // // 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: Window.cc,v 1.263 2004/01/18 19:14:08 fluxgen Exp $ #include "Window.hh" #include "WinClient.hh" #include "I18n.hh" #include "fluxbox.hh" #include "Screen.hh" #include "FbWinFrameTheme.hh" #include "MenuTheme.hh" #include "FbAtoms.hh" #include "RootTheme.hh" #include "Workspace.hh" #include "LayerMenu.hh" #include "FbWinFrame.hh" #include "WinButton.hh" #include "WinButtonTheme.hh" #include "SendToMenu.hh" #include "FbTk/StringUtil.hh" #include "FbTk/TextButton.hh" #include "FbTk/Compose.hh" #include "FbTk/EventManager.hh" #include "FbTk/MultiButtonMenuItem.hh" #include "FbTk/KeyUtil.hh" #ifdef HAVE_CONFIG_H #include "config.h" #endif // HAVE_CONFIG_H #ifdef SHAPE #include <X11/extensions/shape.h> #endif // SHAPE //use GNU extensions #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif // _GNU_SOURCE #include <X11/Xatom.h> #include <X11/keysym.h> #include <cstring> #include <cstdio> #include <iostream> #include <cassert> #include <functional> #include <algorithm> using namespace std; namespace { void grabButton(Display *display, unsigned int button, Window window, Cursor cursor) { const int numlock = FbTk::KeyUtil::instance().numlock(); const int capslock = FbTk::KeyUtil::instance().capslock(); const int scrolllock = FbTk::KeyUtil::instance().scrolllock(); // Grab with Mod1 and with all lock modifiers // (num, scroll and caps) //numlock XGrabButton(display, button, Mod1Mask|numlock, window, True, ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, cursor); //scrolllock XGrabButton(display, button, Mod1Mask|scrolllock, window, True, ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, cursor); //capslock XGrabButton(display, button, Mod1Mask|capslock, window, True, ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, cursor); //capslock+numlock XGrabButton(display, Button1, Mod1Mask|capslock|numlock, window, True, ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, cursor); //capslock+scrolllock XGrabButton(display, button, Mod1Mask|capslock|scrolllock, window, True, ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, cursor); //capslock+numlock+scrolllock XGrabButton(display, button, Mod1Mask|capslock|numlock|scrolllock, window, True, ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, cursor); //numlock+scrollLock XGrabButton(display, button, Mod1Mask|numlock|scrolllock, window, True, ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, cursor); } // X event scanner for enter/leave notifies - adapted from twm typedef struct scanargs { Window w; Bool leave, inferior, enter; } scanargs; // look for valid enter or leave events (that may invalidate the earlier one we are interested in) static Bool queueScanner(Display *, XEvent *e, char *args) { if (e->type == LeaveNotify && e->xcrossing.window == ((scanargs *) args)->w && e->xcrossing.mode == NotifyNormal) { ((scanargs *) args)->leave = true; ((scanargs *) args)->inferior = (e->xcrossing.detail == NotifyInferior); } else if (e->type == EnterNotify && e->xcrossing.mode == NotifyUngrab) ((scanargs *) args)->enter = true; return false; } /// returns the deepest transientFor, asserting against a close loop WinClient *getRootTransientFor(WinClient *client) { while (client->transientFor()) { assert(client != client->transientFor()); client = client->transientFor(); } return client; } /// raise window and do the same for each transient of the current window void raiseFluxboxWindow(FluxboxWindow &win) { if (win.oplock) return; win.oplock = true; // we need to lock actual restacking so that raising above active transient // won't do anything nasty if (!win.winClient().transientList().empty()) win.screen().layerManager().lock(); if (!win.isIconic()) { win.screen().updateNetizenWindowRaise(win.clientWindow()); win.layerItem().raise(); } // for each transient do raise WinClient::TransientList::const_iterator it = win.winClient().transientList().begin(); WinClient::TransientList::const_iterator it_end = win.winClient().transientList().end(); for (; it != it_end; ++it) { if ((*it)->fbwindow() && !(*it)->fbwindow()->isIconic()) // TODO: should we also check if it is the active client? raiseFluxboxWindow(*(*it)->fbwindow()); } win.oplock = false; if (!win.winClient().transientList().empty()) win.screen().layerManager().unlock(); } /// lower window and do the same for each transient it holds void lowerFluxboxWindow(FluxboxWindow &win) { if (win.oplock) return; win.oplock = true; // we need to lock actual restacking so that raising above active transient // won't do anything nasty if (!win.winClient().transientList().empty()) win.screen().layerManager().lock(); if (!win.isIconic()) { win.screen().updateNetizenWindowLower(win.clientWindow()); win.layerItem().lower(); } WinClient::TransientList::const_iterator it = win.winClient().transientList().begin(); WinClient::TransientList::const_iterator it_end = win.winClient().transientList().end(); for (; it != it_end; ++it) { if ((*it)->fbwindow() && !(*it)->fbwindow()->isIconic()) // TODO: should we also check if it is the active client? lowerFluxboxWindow(*(*it)->fbwindow()); } win.oplock = false; if (!win.winClient().transientList().empty()) win.screen().layerManager().unlock(); } /// raise window and do the same for each transient it holds void tempRaiseFluxboxWindow(FluxboxWindow &win) { if (win.oplock) return; win.oplock = true; if (!win.winClient().transientList().empty()) win.screen().layerManager().lock(); if (!win.isIconic()) { // don't update netizen, as it is only temporary win.layerItem().tempRaise(); } // for each transient do raise WinClient::TransientList::const_iterator it = win.winClient().transientList().begin(); WinClient::TransientList::const_iterator it_end = win.winClient().transientList().end(); for (; it != it_end; ++it) { if ((*it)->fbwindow() && !(*it)->fbwindow()->isIconic()) // TODO: should we also check if it is the active client? tempRaiseFluxboxWindow(*(*it)->fbwindow()); } win.oplock = false; if (!win.winClient().transientList().empty()) win.screen().layerManager().unlock(); } class SetClientCmd:public FbTk::Command { public: explicit SetClientCmd(WinClient &client):m_client(client) { } void execute() { if (m_client.m_win != 0) m_client.m_win->setCurrentClient(m_client); } private: WinClient &m_client; }; }; template <> void LayerMenuItem<FluxboxWindow>::click(int button, int time) { m_object->moveToLayer(m_layernum); } FluxboxWindow::FluxboxWindow(WinClient &client, FbWinFrameTheme &tm, FbTk::XLayer &layer): oplock(false), m_hintsig(*this), m_statesig(*this), m_layersig(*this), m_workspacesig(*this), m_diesig(*this), m_focussig(*this), m_titlesig(*this), moving(false), resizing(false), shaded(false), iconic(false), focused(false), stuck(false), m_managed(false), maximized(MAX_NONE), m_screen(client.screen()), display(FbTk::App::instance()->display()), m_windowmenu(client.screen().menuTheme(), client.screen().imageControl(), *client.screen().layerManager().getLayer(Fluxbox::instance()->getMenuLayer())), m_old_decoration(DECOR_NORMAL), m_client(&client), m_frame(tm, client.screen().imageControl(), 0, 0, 100, 100), m_layeritem(m_frame.window(), layer), m_layernum(layer.getLayerNum()), m_parent(client.screen().rootWindow()), m_resize_corner(RIGHTBOTTOM) { init(); } FluxboxWindow::~FluxboxWindow() { #ifdef DEBUG cerr<<__FILE__<<"("<<__LINE__<<"): starting ~FluxboxWindow("<<this<<")"<<endl; cerr<<__FILE__<<"("<<__LINE__<<"): num clients = "<<numClients()<<endl; cerr<<__FILE__<<"("<<__LINE__<<"): curr client = "<<m_client<<endl; cerr<<__FILE__<<"("<<__LINE__<<"): m_labelbuttons.size = "<<m_labelbuttons.size()<<endl; #endif // DEBUG if (moving || resizing || m_attaching_tab) { screen().hideGeometry(); screen().hidePosition(); XUngrabPointer(display, CurrentTime); } // no longer a valid window to do stuff with Fluxbox::instance()->removeWindowSearchGroup(frame().window().window()); Client2ButtonMap::iterator it = m_labelbuttons.begin(); Client2ButtonMap::iterator it_end = m_labelbuttons.end(); for (; it != it_end; ++it) { frame().removeLabelButton(*(*it).second); delete (*it).second; } m_labelbuttons.clear(); m_timer.stop(); // notify die m_diesig.notify(); if (m_client != 0) delete m_client; // this also removes client from our list m_client = 0; if (m_clientlist.size() > 1) { cerr<<__FILE__<<"("<<__FUNCTION__<<") WARNING! clientlist > 1"<<endl; while (!m_clientlist.empty()) { detachClient(*m_clientlist.back()); } } // deal with extra menus ExtraMenus::iterator mit = m_extramenus.begin(); ExtraMenus::iterator mit_end = m_extramenus.end(); for (; mit != mit_end; ++mit) { // we set them to NOT internal so that they will be deleted when the // menu is cleaned up. We can't delete them here because they are // still in the menu // (They need to be internal for most of the time so that if we // rebuild the menu, then they won't be removed. mit->second->setInternalMenu(false); } #ifdef DEBUG cerr<<__FILE__<<"("<<__LINE__<<"): ~FluxboxWindow("<<this<<")"<<endl; #endif // DEBUG } void FluxboxWindow::init() { m_attaching_tab = 0; assert(m_client); m_client->m_win = this; m_client->setGroupLeftWindow(None); // nothing to the left. // check for shape extension and whether the window is shaped m_shaped = false; if (Fluxbox::instance()->haveShape()) { Shape::setShapeNotify(winClient()); m_shaped = Shape::isShaped(winClient()); } frame().setUseShape(!m_shaped); //!! TODO init of client should be better // we don't want to duplicate code here and in attachClient m_clientlist.push_back(m_client); #ifdef DEBUG cerr<<__FILE__<<": FluxboxWindow::init(this="<<this<<", client="<<hex<< m_client->window()<<", frame = "<<frame().window().window()<<dec<<")"<<endl; #endif // DEBUG Fluxbox &fluxbox = *Fluxbox::instance(); // setup cursors for resize grips frame().gripLeft().setCursor(frame().theme().lowerLeftAngleCursor()); frame().gripRight().setCursor(frame().theme().lowerRightAngleCursor()); FbTk::TextButton *btn = new FbTk::TextButton(frame().label(), frame().theme().font(), m_client->title()); btn->setJustify(frame().theme().justify()); m_labelbuttons[m_client] = btn; frame().addLabelButton(*btn); frame().setLabelButtonFocus(*btn); btn->show(); FbTk::EventManager &evm = *FbTk::EventManager::instance(); // we need motion notify so we mask it btn->setEventMask(ExposureMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | EnterWindowMask); FbTk::RefCount<FbTk::Command> set_client_cmd(new SetClientCmd(*m_client)); btn->setOnClick(set_client_cmd); evm.add(*this, btn->window()); // we take care of button events for this evm.add(*this, m_client->window()); // redirect events from frame to us frame().setEventHandler(*this); frame().resize(m_client->width(), m_client->height()); m_last_focus_time.tv_sec = m_last_focus_time.tv_usec = 0; m_blackbox_attrib.workspace = m_workspace_number = ~0; m_blackbox_attrib.flags = m_blackbox_attrib.attrib = m_blackbox_attrib.stack = 0; m_blackbox_attrib.premax_x = m_blackbox_attrib.premax_y = 0; m_blackbox_attrib.premax_w = m_blackbox_attrib.premax_h = 0; //use tab as default decorations.tab = true; // enable decorations decorations.enabled = true; // set default values for decoration decorations.menu = true; //override menu option // all decorations on by default decorations.titlebar = decorations.border = decorations.handle = true; decorations.maximize = decorations.close = decorations.sticky = decorations.shade = decorations.tab = true; functions.resize = functions.move = functions.iconify = functions.maximize = true; decorations.close = false; if (m_client->getBlackboxHint() != 0) updateBlackboxHintsFromClient(*m_client); else updateMWMHintsFromClient(*m_client); //!! // fetch client size and placement XWindowAttributes wattrib; if (! m_client->getAttrib(wattrib) || !wattrib.screen // no screen? ?? || wattrib.override_redirect) { // override redirect return; } // save old border width so we can restore it later m_client->old_bw = wattrib.border_width; m_client->x = wattrib.x; m_client->y = wattrib.y; m_timer.setTimeout(fluxbox.getAutoRaiseDelay()); FbTk::RefCount<FbTk::Command> raise_cmd(new FbTk::SimpleCommand<FluxboxWindow>(*this, &FluxboxWindow::raise)); m_timer.setCommand(raise_cmd); m_timer.fireOnce(true); if (m_client->initial_state == WithdrawnState) { return; } m_managed = true; //this window is managed Fluxbox::instance()->saveWindowSearchGroup(frame().window().window(), this); // update transient infomation m_client->updateTransientInfo(); // adjust the window decorations based on transience and window sizes if (m_client->isTransient()) { decorations.maximize = functions.maximize = false; decorations.handle = false; } if ((m_client->normal_hint_flags & PMinSize) && (m_client->normal_hint_flags & PMaxSize) && m_client->max_width != 0 && m_client->max_width <= m_client->min_width && m_client->max_height != 0 && m_client->max_height <= m_client->min_height) { decorations.maximize = decorations.handle = functions.resize = functions.maximize = false; decorations.tab = false; //no tab for this window } upsize(); applyDecorations(true); associateClientWindow(true, wattrib.x, wattrib.y, wattrib.width, wattrib.height); grabButtons(); if (m_workspace_number < 0 || m_workspace_number >= screen().getCount()) m_workspace_number = screen().currentWorkspaceID(); restoreAttributes(); bool place_window = true; if (fluxbox.isStartup() || m_client->isTransient() || m_client->normal_hint_flags & (PPosition|USPosition)) { frame().gravityTranslate(wattrib.x, wattrib.y, m_client->gravity(), false); if (! fluxbox.isStartup()) { int real_x = frame().x(); int real_y = frame().y(); if (real_x >= 0 && real_y + frame().y() >= 0 && real_x <= (signed) screen().width() && real_y <= (signed) screen().height()) place_window = false; } else place_window = false; } if (wattrib.width <= 0) wattrib.width = 1; if (wattrib.height <= 0) wattrib.height = 1; // if we're a transient then we should be on the same layer as our parent if (m_client->isTransient() && m_client->transientFor()->fbwindow() && m_client->transientFor()->fbwindow() != this) layerItem().setLayer(m_client->transientFor()->fbwindow()->layerItem().getLayer()); else // if no parent then set default layer moveToLayer(m_layernum); if (!place_window) moveResize(frame().x(), frame().y(), frame().width(), frame().height()); screen().getWorkspace(m_workspace_number)->addWindow(*this, place_window); if (shaded) { // start shaded shaded = false; shade(); } if (maximized && functions.maximize) { // start maximized // This will set it to the appropriate style of maximisation int req_maximized = maximized; // NOTE: don't manually change maximized ANYWHERE else, it isn't safe maximized = MAX_NONE; // it is not maximized now maximize(req_maximized); } if (stuck) { stuck = false; stick(); deiconify(); //we're omnipresent and visible } setState(m_current_state); // add extra menus addExtraMenu("Send To...", new SendToMenu(*this)); addExtraMenu("Layer...", new LayerMenu<FluxboxWindow>(screen().menuTheme(), screen().imageControl(), *screen().layerManager().getLayer(Fluxbox::instance()->getMenuLayer()), this, false)); // the layermenu will get deleted as an extra menu // don't call setupWindow here as the addExtraMenu call should sendConfigureNotify(); // no focus default setFocusFlag(false); if (m_shaped) shape(); FbTk::App::instance()->sync(false); } /// apply shape to this window void FluxboxWindow::shape() { #ifdef SHAPE if (m_shaped) { XShapeCombineShape(display, frame().window().window(), ShapeBounding, 0, frame().clientArea().y(), // xOff, yOff m_client->window(), ShapeBounding, ShapeSet); XFlush(display); } #endif // SHAPE } /// attach a client to this window and destroy old window void FluxboxWindow::attachClient(WinClient &client) { //!! TODO: check for isGroupable in client if (client.m_win == this) return; // reparent client win to this frame frame().setClientWindow(client); FbTk::EventManager &evm = *FbTk::EventManager::instance(); // get the current window on the end of our client list Window leftwin = None; if (!clientList().empty()) leftwin = clientList().back()->window(); client.setGroupLeftWindow(leftwin); if (client.fbwindow() != 0) { FluxboxWindow *old_win = client.fbwindow(); // store old window // make sure we set new window search for each client ClientList::iterator client_it = old_win->clientList().begin(); ClientList::iterator client_it_end = old_win->clientList().end(); for (; client_it != client_it_end; ++client_it) { // setup eventhandlers for client evm.add(*this, (*client_it)->window()); // reparent window to this frame().setClientWindow(**client_it); moveResizeClient(**client_it, frame().clientArea().x(), frame().clientArea().y(), frame().clientArea().width(), frame().clientArea().height()); (*client_it)->m_win = this; // create a labelbutton for this client and // associate it with the pointer FbTk::TextButton *btn = new FbTk::TextButton(frame().label(), frame().theme().font(), (*client_it)->title()); btn->setJustify(frame().theme().justify()); m_labelbuttons[(*client_it)] = btn; frame().addLabelButton(*btn); btn->show(); // we need motion notify so we mask it btn->setEventMask(ExposureMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | EnterWindowMask); FbTk::RefCount<FbTk::Command> set_client_cmd(new SetClientCmd(*(*client_it))); btn->setOnClick(set_client_cmd); evm.add(*this, btn->window()); // we take care of button events for this (*client_it)->saveBlackboxAttribs(m_blackbox_attrib); } // add client and move over all attached clients // from the old window to this list // all the "left window"s will remain the same, except for the first. m_clientlist.splice(m_clientlist.end(), old_win->m_clientlist); old_win->m_client = 0; delete old_win; } else { // client.fbwindow() == 0 // create a labelbutton for this client and associate it with the pointer FbTk::TextButton *btn = new FbTk::TextButton(frame().label(), frame().theme().font(), client.title()); m_labelbuttons[&client] = btn; frame().addLabelButton(*btn); btn->show(); FbTk::EventManager &evm = *FbTk::EventManager::instance(); // we need motion notify so we mask it btn->setEventMask(ExposureMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | EnterWindowMask); FbTk::RefCount<FbTk::Command> set_client_cmd(new SetClientCmd(client)); btn->setOnClick(set_client_cmd); evm.add(*this, btn->window()); // we take care of button events for this client.m_win = this; client.saveBlackboxAttribs(m_blackbox_attrib); m_clientlist.push_back(&client); } // make sure that the state etc etc is updated for the new client // TODO: one day these should probably be neatened to only act on the // affected clients if possible m_statesig.notify(); m_workspacesig.notify(); m_layersig.notify(); frame().reconfigure(); // keep the current window on top m_client->raise(); } /// detach client from window and create a new window for it bool FluxboxWindow::detachClient(WinClient &client) { if (client.m_win != this || numClients() <= 1) return false; // I'm not sure how to do this bit better // we need to find the window we've got, and update the // window to its right to have a left window set to the // window which is to the left of the current. // Think in terms of: // window1 <- my_window <- window2 // we need to take out my_window, so update window2 leftwin to be window1 Window leftwin = None; ClientList::iterator client_it_end = clientList().end(); ClientList::iterator client_it = clientList().begin(); ClientList::iterator client_it_before = client_it_end; ClientList::iterator client_it_after = clientList().begin(); if (!clientList().empty()) { ++client_it_after; if (clientList().front() == &client) { leftwin = None; } else { ++client_it; client_it_before = clientList().begin(); ++client_it_after; while (client_it != client_it_end) { if (*client_it == &client) { break; } ++client_it_before; ++client_it; ++client_it_after; } } } // update the leftwin of the window to the right if (client_it_before != client_it_end) leftwin = (*client_it_before)->window(); if (client_it_after != client_it_end) (*client_it_after)->setGroupLeftWindow(leftwin); removeClient(client); // m_client must be valid as there should be at least one other window // otherwise this wouldn't be here (refer numClients() <= 1 return) client.m_win = screen().createWindow(client); m_client->raise(); setInputFocus(); return true; } void FluxboxWindow::detachCurrentClient() { // should only operate if we had more than one client if (numClients() <= 1) return; detachClient(*m_client); } /// removes client from client list, does not create new fluxboxwindow for it bool FluxboxWindow::removeClient(WinClient &client) { if (client.m_win != this || numClients() == 0) return false; #ifdef DEBUG cerr<<__FILE__<<"("<<__FUNCTION__<<")["<<this<<"]"<<endl; #endif // DEBUG // if it is our active client, deal with it... if (m_client == &client) { // set next client to be focused // if the client we're about to remove is the last client then set prev client if (&client == m_clientlist.back()) prevClient(); else nextClient(); } client.m_win = 0; m_clientlist.remove(&client); if (m_client == &client) { if (m_clientlist.empty()) m_client = 0; else // this really shouldn't happen m_client = m_clientlist.back(); } FbTk::EventManager &evm = *FbTk::EventManager::instance(); evm.remove(client.window()); FbTk::TextButton *label_btn = m_labelbuttons[&client]; if (label_btn != 0) { frame().removeLabelButton(*label_btn); evm.remove(label_btn->window()); delete label_btn; label_btn = 0; } m_labelbuttons.erase(&client); frame().reconfigure(); #ifdef DEBUG cerr<<__FILE__<<"("<<__FUNCTION__<<")["<<this<<"] numClients = "<<numClients()<<endl; #endif // DEBUG return true; } /// returns WinClient of window we're searching for WinClient *FluxboxWindow::findClient(Window win) { ClientList::iterator it = find_if(clientList().begin(), clientList().end(), FbTk::Compose(bind2nd(equal_to<Window>(), win), mem_fun(&WinClient::window))); return (it == clientList().end() ? 0 : *it); } /// raise and focus next client void FluxboxWindow::nextClient() { if (numClients() <= 1) return; ClientList::iterator it = find(m_clientlist.begin(), m_clientlist.end(), m_client); WinClient *client = 0; if (it == m_clientlist.end()) { client = m_clientlist.front(); } else { it++; if (it == m_clientlist.end()) client = m_clientlist.front(); else client = *it; } setCurrentClient(*client, true); } void FluxboxWindow::prevClient() { if (numClients() <= 1) return; ClientList::iterator it = find(m_clientlist.begin(), m_clientlist.end(), m_client); WinClient *client = 0; if (it == m_clientlist.end()) { client = m_clientlist.front(); } else { if (it == m_clientlist.begin()) client = m_clientlist.back(); else client = *(--it); } setCurrentClient(*client, true); } void FluxboxWindow::moveClientLeft() { if (m_clientlist.size() == 1 || *m_clientlist.begin() == &winClient()) return; // move label button to the left frame().moveLabelButtonLeft(*m_labelbuttons[&winClient()]); // move client in clientlist to the left ClientList::iterator it = find(m_clientlist.begin(), m_clientlist.end(), &winClient()); ClientList::iterator new_pos = it; new_pos--; m_clientlist.erase(it); m_clientlist.insert(new_pos, &winClient()); updateClientLeftWindow(); } void FluxboxWindow::moveClientRight() { if (m_clientlist.size() == 1) return; // move label button to the right frame().moveLabelButtonRight(*m_labelbuttons[&winClient()]); // move client in clientlist to the right ClientList::iterator it = find(m_clientlist.begin(), m_clientlist.end(), &winClient()); ClientList::iterator new_pos = m_clientlist.erase(it); new_pos++; m_clientlist.insert(new_pos, &winClient()); updateClientLeftWindow(); } /// Update LEFT window atom on all clients. void FluxboxWindow::updateClientLeftWindow() { // It should just update the affected clients but that // would require more complex code and we're assuming // the user dont have alot of windows grouped so this // wouldn't be too time consuming and it's easier to // implement. ClientList::iterator it = clientList().begin(); ClientList::iterator it_end = clientList().end(); // set no left window on first tab (*it)->setGroupLeftWindow(0); WinClient *last_client = *it; ++it; for (; it != it_end; ++it) { (*it)->setGroupLeftWindow(last_client->window()); last_client = *it; } } bool FluxboxWindow::setCurrentClient(WinClient &client, bool setinput) { // make sure it's in our list if (client.m_win != this) return false; m_client = &client; m_client->raise(); if (setinput && setInputFocus()) { frame().setLabelButtonFocus(*m_labelbuttons[m_client]); return true; } return false; } bool FluxboxWindow::isGroupable() const { if (isResizable() && isMaximizable() && !winClient().isTransient()) return true; return false; } void FluxboxWindow::associateClientWindow(bool use_attrs, int x, int y, unsigned int width, unsigned int height) { m_client->setBorderWidth(0); updateTitleFromClient(*m_client); updateIconNameFromClient(*m_client); if (use_attrs) frame().moveResizeForClient(x, y, width, height); else frame().resizeForClient(m_client->width(), m_client->height()); frame().setClientWindow(*m_client); } void FluxboxWindow::grabButtons() { XGrabButton(display, Button1, AnyModifier, frame().window().window(), True, ButtonPressMask, GrabModeSync, GrabModeSync, None, None); XUngrabButton(display, Button1, Mod1Mask|Mod2Mask|Mod3Mask, frame().window().window()); if (Fluxbox::instance()->useMod1()) { XGrabButton(display, Button1, Mod1Mask, frame().window().window(), True, ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, frame().theme().moveCursor()); //----grab with "all" modifiers grabButton(display, Button1, frame().window().window(), frame().theme().moveCursor()); XGrabButton(display, Button2, Mod1Mask, frame().window().window(), True, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None); XGrabButton(display, Button3, Mod1Mask, frame().window().window(), True, ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, None); //---grab with "all" modifiers grabButton(display, Button3, frame().window().window(), None); } } void FluxboxWindow::reconfigure() { upsize(); applyDecorations(); setFocusFlag(focused); moveResize(frame().x(), frame().y(), frame().width(), frame().height()); grabButtons(); frame().setDoubleClickTime(Fluxbox::instance()->getDoubleClickInterval()); frame().setUpdateDelayTime(Fluxbox::instance()->getUpdateDelayTime()); frame().reconfigure(); m_windowmenu.reconfigure(); } /// update current client title and title in our frame void FluxboxWindow::updateTitleFromClient(WinClient &client) { client.updateTitle(); // compare old title with new and see if we need to update // graphics if (m_labelbuttons[&client]->text() != client.title()) { m_labelbuttons[&client]->setText(client.title()); m_labelbuttons[&client]->clear(); // redraw text m_labelbuttons[&client]->updateTransparent(); } } /// update icon title from client void FluxboxWindow::updateIconNameFromClient(WinClient &client) { client.updateIconTitle(); } void FluxboxWindow::updateMWMHintsFromClient(WinClient &client) { const WinClient::MwmHints *hint = client.getMwmHint(); if (!hint) return; if (hint->flags & MwmHintsDecorations) { if (hint->decorations & MwmDecorAll) { decorations.titlebar = decorations.handle = decorations.border = decorations.iconify = decorations.maximize = decorations.close = decorations.menu = true; } else { decorations.titlebar = decorations.handle = decorations.border = decorations.iconify = decorations.maximize = decorations.close = decorations.tab = false; decorations.menu = true; if (hint->decorations & MwmDecorBorder) decorations.border = true; if (hint->decorations & MwmDecorHandle) decorations.handle = true; if (hint->decorations & MwmDecorTitle) { //only tab on windows with titlebar decorations.titlebar = decorations.tab = true; } if (hint->decorations & MwmDecorMenu) decorations.menu = true; if (hint->decorations & MwmDecorIconify) decorations.iconify = true; if (hint->decorations & MwmDecorMaximize) decorations.maximize = true; } } if (hint->flags & MwmHintsFunctions) { if (hint->functions & MwmFuncAll) { functions.resize = functions.move = functions.iconify = functions.maximize = functions.close = true; } else { functions.resize = functions.move = functions.iconify = functions.maximize = functions.close = false; if (hint->functions & MwmFuncResize) functions.resize = true; if (hint->functions & MwmFuncMove) functions.move = true; if (hint->functions & MwmFuncIconify) functions.iconify = true; if (hint->functions & MwmFuncMaximize) functions.maximize = true; if (hint->functions & MwmFuncClose) functions.close = true; } } } void FluxboxWindow::updateFunctions() { if (!m_client) return; bool changed = false; if (m_client->isClosable() != functions.close) { functions.close = m_client->isClosable(); changed = true; } if (changed) setupWindow(); } void FluxboxWindow::updateBlackboxHintsFromClient(const WinClient &client) { const FluxboxWindow::BlackboxHints *hint = client.getBlackboxHint(); if (!hint) return; if (hint->flags & ATTRIB_SHADED) shaded = (hint->attrib & ATTRIB_SHADED); if ((hint->flags & ATTRIB_MAXHORIZ) && (hint->flags & ATTRIB_MAXVERT)) maximized = ((hint->attrib & (ATTRIB_MAXHORIZ | ATTRIB_MAXVERT)) ? MAX_FULL : MAX_NONE); else if (hint->flags & ATTRIB_MAXVERT) maximized = ((hint->attrib & ATTRIB_MAXVERT) ? MAX_VERT : MAX_NONE); else if (hint->flags & ATTRIB_MAXHORIZ) maximized = ((hint->attrib & ATTRIB_MAXHORIZ) ? MAX_HORZ : MAX_NONE); if (hint->flags & ATTRIB_OMNIPRESENT) stuck = (hint->attrib & ATTRIB_OMNIPRESENT); if (hint->flags & ATTRIB_WORKSPACE) m_workspace_number = hint->workspace; if (hint->flags & ATTRIB_STACK) m_workspace_number = hint->stack; if (hint->flags & ATTRIB_DECORATION) { m_old_decoration = static_cast<Decoration>(hint->decoration); setDecoration(m_old_decoration); } } void FluxboxWindow::move(int x, int y, int gravity) { moveResize(x, y, frame().width(), frame().height(), gravity); } void FluxboxWindow::resize(unsigned int width, unsigned int height) { moveResize(frame().x(), frame().y(), width, height); } void FluxboxWindow::moveResize(int new_x, int new_y, unsigned int new_width, unsigned int new_height, int gravity) { if (gravity != ForgetGravity) { frame().gravityTranslate(new_x, new_y, gravity, false); } bool send_event = (frame().x() != new_x || frame().y() != new_y); if (new_width != frame().width() || new_height != frame().height()) { if ((((signed) frame().width()) + new_x) < 0) new_x = 0; if ((((signed) frame().height()) + new_y) < 0) new_y = 0; downsize(); if (!isResizable()) { new_width = width(); new_height = height(); } frame().moveResize(new_x, new_y, new_width, new_height); setFocusFlag(focused); shaded = false; send_event = true; } else if (send_event) frame().move(new_x, new_y); if (send_event && ! moving) { sendConfigureNotify(); } shape(); if (!moving) { m_last_resize_x = new_x; m_last_resize_y = new_y; } } // returns whether the focus was "set" to this window // it doesn't guarantee that it has focus, but says that we have // tried. A FocusqIn event should eventually arrive for that // window if it actually got the focus, then setFocusedFlag is called, // which updates all the graphics etc bool FluxboxWindow::setInputFocus() { if (((signed) (frame().x() + frame().width())) < 0) { if (((signed) (frame().y() + frame().height())) < 0) { moveResize(frame().window().borderWidth(), frame().window().borderWidth(), frame().width(), frame().height()); } else if (frame().y() > (signed) screen().height()) { moveResize(frame().window().borderWidth(), screen().height() - frame().height(), frame().width(), frame().height()); } else { moveResize(frame().window().borderWidth(), frame().y() + frame().window().borderWidth(), frame().width(), frame().height()); } } else if (frame().x() > (signed) screen().width()) { if (((signed) (frame().y() + frame().height())) < 0) { moveResize(screen().width() - frame().width(), frame().window().borderWidth(), frame().width(), frame().height()); } else if (frame().y() > (signed) screen().height()) { moveResize(screen().width() - frame().width(), screen().height() - frame().height(), frame().width(), frame().height()); } else { moveResize(screen().width() - frame().width(), frame().y() + frame().window().borderWidth(), frame().width(), frame().height()); } } if (! m_client->validateClient()) return false; if (!m_client->transients.empty() && m_client->isModal()) { WinClient::TransientList::iterator it = m_client->transients.begin(); WinClient::TransientList::iterator it_end = m_client->transients.end(); for (; it != it_end; ++it) { if ((*it)->isModal()) return (*it)->fbwindow()->setCurrentClient(**it, true); } } bool ret = false; if (m_client->getFocusMode() == WinClient::F_LOCALLYACTIVE || m_client->getFocusMode() == WinClient::F_PASSIVE) { m_client->setInputFocus(RevertToPointerRoot, CurrentTime); FbTk::App *app = FbTk::App::instance(); XFlush(app->display()); m_client->sendFocus(); app->sync(false); app->sync(false); ret = true; } else { ret = m_client->sendFocus(); } return ret; } void FluxboxWindow::hide() { #ifdef DEBUG cerr<<__FILE__<<"("<<__FUNCTION__<<")["<<this<<"]"<<endl; #endif // DEBUG m_windowmenu.hide(); frame().hide(); } void FluxboxWindow::show() { frame().show(); } /** Unmaps the window and removes it from workspace list */ void FluxboxWindow::iconify() { if (isIconic()) // no need to iconify if we're already return; menu().hide(); iconic = true; setState(IconicState); frame().hide(); ClientList::iterator client_it = m_clientlist.begin(); const ClientList::iterator client_it_end = m_clientlist.end(); for (; client_it != client_it_end; ++client_it) { WinClient &client = *(*client_it); client.setEventMask(NoEventMask); client.hide(); client.setEventMask(PropertyChangeMask | StructureNotifyMask | FocusChangeMask); if (client.transientFor() && client.transientFor()->fbwindow()) { if (!client.transientFor()->fbwindow()->isIconic()) { client.transientFor()->fbwindow()->iconify(); } } if (!client.transientList().empty()) { WinClient::TransientList::iterator it = client.transientList().begin(); WinClient::TransientList::iterator it_end = client.transientList().end(); for (; it != it_end; it++) if ((*it)->fbwindow()) (*it)->fbwindow()->iconify(); } } WinClient *focused_client = Fluxbox::instance()->getFocusedWindow(); if (focused_client && focused_client->fbwindow() == this) Fluxbox::instance()->revertFocus(screen()); } void FluxboxWindow::deiconify(bool reassoc, bool do_raise) { if (numClients() == 0) return; if (oplock) return; oplock = true; if (iconic || reassoc) { screen().reassociateWindow(this, screen().currentWorkspace()->workspaceID(), false); } else if (moving || m_workspace_number != screen().currentWorkspace()->workspaceID()) { oplock = false; return; } bool was_iconic = iconic; iconic = false; setState(NormalState); ClientList::iterator client_it = clientList().begin(); ClientList::iterator client_it_end = clientList().end(); for (; client_it != client_it_end; ++client_it) { (*client_it)->setEventMask(NoEventMask); (*client_it)->show(); (*client_it)->setEventMask(PropertyChangeMask | StructureNotifyMask | FocusChangeMask); } show(); if (was_iconic && screen().doFocusNew()) setInputFocus(); if (reassoc && !m_client->transients.empty()) { // deiconify all transients client_it = clientList().begin(); for (; client_it != client_it_end; ++client_it) { //TODO: Can this get stuck in a loop? WinClient::TransientList::iterator trans_it = (*client_it)->transientList().begin(); WinClient::TransientList::iterator trans_it_end = (*client_it)->transientList().end(); for (; trans_it != trans_it_end; ++trans_it) { if ((*trans_it)->fbwindow()) (*trans_it)->fbwindow()->deiconify(true, false); } } } oplock = false; if (do_raise) raise(); } /** Set window in withdrawn state */ void FluxboxWindow::withdraw() { iconic = false; if (isResizing()) stopResizing(); frame().hide(); m_windowmenu.hide(); } /** Maximize window both horizontal and vertical */ void FluxboxWindow::maximize(int type) { if (isIconic()) deiconify(); int head = screen().getHead(frame().window()); int new_x = frame().x(), new_y = frame().y(), new_w = frame().width(), new_h = frame().height(); int orig_max = maximized; // These evaluate whether we need to TOGGLE the value for that field // Why? If maximize is only set to zero outside this, // and we only EVER toggle them, then: // 1) We will never loose the old_ values // 2) It shouldn't get confused // Worst case being that some action will toggle the wrong way, but // we still won't lose the state in that case. // NOTE: There is one option to the way this works - what it does when // fully maximised and maximise(vert, horz) is selected. // There are 2 options here - either: // 1) maximiseVertical results in a vertically (not horz) maximised window, or // 2) " toggles vertical maximisation, thus resulting in a horizontally // maximised window. // // The current implementation uses style 1, to change this, removed the // item corresponding to the [[ ]] comment // toggle maximize vertically? // when _don't_ we want to toggle? // - type is horizontal maximise, [[and we aren't fully maximised]] or // - [[ type is vertical maximise and we are fully maximised ]] // - type is none and we are not vertically maximised, or // - type is full and we are not horizontally maximised, but already vertically if (!(type == MAX_HORZ && orig_max != MAX_FULL || type == MAX_VERT && orig_max == MAX_FULL || type == MAX_NONE && !(orig_max & MAX_VERT) || type == MAX_FULL && orig_max == MAX_VERT)) { // already maximized in that direction? if (orig_max & MAX_VERT) { new_y = m_old_pos_y; new_h = m_old_height; } else { m_old_pos_y = new_y; m_old_height = new_h; new_y = screen().maxTop(head); new_h = screen().maxBottom(head) - new_y - 2*frame().window().borderWidth(); } maximized ^= MAX_VERT; } // maximize horizontally? if (!(type == MAX_VERT && orig_max != MAX_FULL || type == MAX_HORZ && orig_max == MAX_FULL || type == MAX_NONE && !(orig_max & MAX_HORZ) || type == MAX_FULL && orig_max == MAX_HORZ)) { // already maximized in that direction? if (orig_max & MAX_HORZ) { new_x = m_old_pos_x; new_w = m_old_width; } else { // only save if we weren't already maximized m_old_pos_x = new_x; m_old_width = new_w; new_x = screen().maxLeft(head); new_w = screen().maxRight(head) - new_x - 2*frame().window().borderWidth(); } maximized ^= MAX_HORZ; } moveResize(new_x, new_y, new_w, new_h); } /** * Maximize window horizontal */ void FluxboxWindow::maximizeHorizontal() { maximize(MAX_HORZ); } /** * Maximize window vertical */ void FluxboxWindow::maximizeVertical() { maximize(MAX_VERT); } /** * Maximize window fully */ void FluxboxWindow::maximizeFull() { maximize(MAX_FULL); } void FluxboxWindow::setWorkspace(int n) { unsigned int old_wkspc = m_workspace_number; m_workspace_number = n; m_blackbox_attrib.flags |= ATTRIB_WORKSPACE; m_blackbox_attrib.workspace = m_workspace_number; // notify workspace change if (!stuck && old_wkspc != m_workspace_number) { #ifdef DEBUG cerr<<this<<" notify workspace signal"<<endl; #endif // DEBUG m_workspacesig.notify(); } } void FluxboxWindow::setLayerNum(int layernum) { m_layernum = layernum; m_blackbox_attrib.flags |= ATTRIB_STACK; m_blackbox_attrib.stack = layernum; saveBlackboxAttribs(); #ifdef DEBUG cerr<<this<<" notify layer signal"<<endl; #endif // DEBUG m_layersig.notify(); } void FluxboxWindow::shade() { // we can only shade if we have a titlebar if (!decorations.titlebar) return; frame().shade(); if (shaded) { shaded = false; m_blackbox_attrib.flags ^= ATTRIB_SHADED; m_blackbox_attrib.attrib ^= ATTRIB_SHADED; setState(NormalState); } else { shaded = true; m_blackbox_attrib.flags |= ATTRIB_SHADED; m_blackbox_attrib.attrib |= ATTRIB_SHADED; // shading is the same as iconic setState(IconicState); } } void FluxboxWindow::stick() { if (stuck) { m_blackbox_attrib.flags ^= ATTRIB_OMNIPRESENT; m_blackbox_attrib.attrib ^= ATTRIB_OMNIPRESENT; stuck = false; } else { stuck = true; m_blackbox_attrib.flags |= ATTRIB_OMNIPRESENT; m_blackbox_attrib.attrib |= ATTRIB_OMNIPRESENT; } setState(m_current_state); // notify since some things consider "stuck" to be a pseudo-workspace m_workspacesig.notify(); } void FluxboxWindow::raise() { if (isIconic()) deiconify(); // get root window WinClient *client = getRootTransientFor(m_client); // if we don't have any root window use this as root if (client == 0) client = m_client; // raise this window and every transient in it if (client->fbwindow()) raiseFluxboxWindow(*client->fbwindow()); } void FluxboxWindow::lower() { if (isIconic()) deiconify(); // get root window WinClient *client = getRootTransientFor(m_client); // if we don't have any root window use this as root if (client == 0) client = m_client; if (client->fbwindow()) lowerFluxboxWindow(*client->fbwindow()); } void FluxboxWindow::tempRaise() { if (isIconic()) deiconify(); // get root window WinClient *client = getRootTransientFor(m_client); // if we don't have any root window use this as root if (client == 0) client = m_client; if (client->fbwindow()) tempRaiseFluxboxWindow(*client->fbwindow()); } void FluxboxWindow::raiseLayer() { // don't let it up to menu layer if (layerNum() == (Fluxbox::instance()->getMenuLayer()+1)) return; // get root window WinClient *client = getRootTransientFor(m_client); // if we don't have any root window use this as root if (client == 0) client = m_client; FluxboxWindow *win = client->fbwindow(); if (!win) return; if (!win->isIconic()) screen().updateNetizenWindowRaise(client->window()); win->layerItem().raiseLayer(); // remember number just in case a transient happens to revisit this window int layer_num = win->layerItem().getLayerNum(); win->setLayerNum(layer_num); WinClient::TransientList::const_iterator it = client->transientList().begin(); WinClient::TransientList::const_iterator it_end = client->transientList().end(); for (; it != it_end; ++it) { win = (*it)->fbwindow(); if (win && !win->isIconic()) { screen().updateNetizenWindowRaise((*it)->window()); win->layerItem().moveToLayer(layer_num); win->setLayerNum(layer_num); } } } void FluxboxWindow::lowerLayer() { // get root window WinClient *client = getRootTransientFor(m_client); // if we don't have any root window use this as root if (client == 0) client = m_client; FluxboxWindow *win = client->fbwindow(); if (!win) return; if (!win->isIconic()) { screen().updateNetizenWindowLower(client->window()); } win->layerItem().lowerLayer(); // remember number just in case a transient happens to revisit this window int layer_num = win->layerItem().getLayerNum(); win->setLayerNum(layer_num); WinClient::TransientList::const_iterator it = client->transientList().begin(); WinClient::TransientList::const_iterator it_end = client->transientList().end(); for (; it != it_end; ++it) { win = (*it)->fbwindow(); if (win && !win->isIconic()) { screen().updateNetizenWindowLower((*it)->window()); win->layerItem().moveToLayer(layer_num); win->setLayerNum(layer_num); } } } void FluxboxWindow::moveToLayer(int layernum) { Fluxbox * fluxbox = Fluxbox::instance(); // don't let it set its layer into menu area if (layernum <= fluxbox->getMenuLayer()) { layernum = fluxbox->getMenuLayer() + 1; } // get root window WinClient *client = getRootTransientFor(m_client); // if we don't have any root window use this as root if (client == 0) client = m_client; FluxboxWindow *win = client->fbwindow(); if (!win) return; if (!win->isIconic()) { screen().updateNetizenWindowRaise(client->window()); } win->layerItem().moveToLayer(layernum); // remember number just in case a transient happens to revisit this window layernum = win->layerItem().getLayerNum(); win->setLayerNum(layernum); WinClient::TransientList::const_iterator it = client->transientList().begin(); WinClient::TransientList::const_iterator it_end = client->transientList().end(); for (; it != it_end; ++it) { win = (*it)->fbwindow(); if (win && !win->isIconic()) { screen().updateNetizenWindowRaise((*it)->window()); win->layerItem().moveToLayer(layernum); win->setLayerNum(layernum); } } } void FluxboxWindow::setHidden(bool value) { if(value) m_blackbox_attrib.flags |= ATTRIB_HIDDEN; else m_blackbox_attrib.flags ^= ATTRIB_HIDDEN; } // window has actually RECEIVED focus (got a FocusIn event) // so now we make it a focused frame etc void FluxboxWindow::setFocusFlag(bool focus) { bool was_focused = isFocused(); focused = focus; // Record focus timestamp for window cycling enhancements if (focused) { gettimeofday(&m_last_focus_time, 0); screen().setFocusedWindow(*m_client); } installColormap(focus); if (focus != frame().focused()) frame().setFocus(focus); if ((screen().isSloppyFocus() || screen().isSemiSloppyFocus()) && screen().doAutoRaise()) { if (focused) m_timer.start(); else m_timer.stop(); } // did focus change? notify listeners if (was_focused != focus) m_focussig.notify(); } void FluxboxWindow::installColormap(bool install) { if (m_client == 0) return; Fluxbox *fluxbox = Fluxbox::instance(); fluxbox->grab(); if (! m_client->validateClient()) return; int i = 0, ncmap = 0; Colormap *cmaps = XListInstalledColormaps(display, m_client->window(), &ncmap); XWindowAttributes wattrib; if (cmaps) { //!! if (m_client->getAttrib(wattrib)) { if (install) { // install the window's colormap for (i = 0; i < ncmap; i++) { if (*(cmaps + i) == wattrib.colormap) { // this window is using an installed color map... do not install install = false; break; //end for-loop (we dont need to check more) } } // otherwise, install the window's colormap if (install) XInstallColormap(display, wattrib.colormap); } else { for (i = 0; i < ncmap; i++) { // uninstall the window's colormap if (*(cmaps + i) == wattrib.colormap) XUninstallColormap(display, wattrib.colormap); } } } XFree(cmaps); } fluxbox->ungrab(); } /** Saves blackbox attributes for every client in our list */ void FluxboxWindow::saveBlackboxAttribs() { for_each(m_clientlist.begin(), m_clientlist.end(), FbTk::ChangeProperty( display, FbAtoms::instance()->getFluxboxAttributesAtom(), PropModeReplace, (unsigned char *)&m_blackbox_attrib, PropBlackboxAttributesElements )); } /** Sets state on each client in our list */ void FluxboxWindow::setState(unsigned long new_state) { if (numClients() == 0) return; m_current_state = new_state; unsigned long state[2]; state[0] = (unsigned long) m_current_state; state[1] = (unsigned long) None; for_each(m_clientlist.begin(), m_clientlist.end(), FbTk::ChangeProperty(display, FbAtoms::instance()->getWMStateAtom(), PropModeReplace, (unsigned char *)state, 2)); saveBlackboxAttribs(); //notify state changed m_statesig.notify(); } bool FluxboxWindow::getState() { m_current_state = 0; Atom atom_return; bool ret = false; int foo; unsigned long *state, ulfoo, nitems; if ((XGetWindowProperty(display, m_client->window(), FbAtoms::instance()->getWMStateAtom(), 0l, 2l, false, FbAtoms::instance()->getWMStateAtom(), &atom_return, &foo, &nitems, &ulfoo, (unsigned char **) &state) != Success) || (! state)) { return false; } if (nitems >= 1) { m_current_state = static_cast<unsigned long>(state[0]); ret = true; } XFree(static_cast<void *>(state)); return ret; } /** * Sets the attributes to what they should be * but doesn't change the actual state * (so the caller can set defaults etc as well) */ void FluxboxWindow::restoreAttributes() { if (!getState()) m_current_state = NormalState; Atom atom_return; int foo; unsigned long ulfoo, nitems; FbAtoms *fbatoms = FbAtoms::instance(); BlackboxAttributes *net; if (m_client->property(fbatoms->getFluxboxAttributesAtom(), 0l, PropBlackboxAttributesElements, false, fbatoms->getFluxboxAttributesAtom(), &atom_return, &foo, &nitems, &ulfoo, (unsigned char **) &net) && net) { if (nitems != (unsigned)PropBlackboxAttributesElements) { XFree(net); return; } m_blackbox_attrib.flags = net->flags; m_blackbox_attrib.attrib = net->attrib; m_blackbox_attrib.workspace = net->workspace; m_blackbox_attrib.stack = net->stack; m_blackbox_attrib.premax_x = net->premax_x; m_blackbox_attrib.premax_y = net->premax_y; m_blackbox_attrib.premax_w = net->premax_w; m_blackbox_attrib.premax_h = net->premax_h; XFree(static_cast<void *>(net)); } else return; if (m_blackbox_attrib.flags & ATTRIB_SHADED && m_blackbox_attrib.attrib & ATTRIB_SHADED) { int save_state = ((m_current_state == IconicState) ? NormalState : m_current_state); shaded = true; m_current_state = save_state; } if (( m_blackbox_attrib.workspace != screen().currentWorkspaceID()) && ( m_blackbox_attrib.workspace < screen().getCount())) { m_workspace_number = m_blackbox_attrib.workspace; if (m_current_state == NormalState) m_current_state = WithdrawnState; } else if (m_current_state == WithdrawnState) m_current_state = NormalState; if (m_blackbox_attrib.flags & ATTRIB_OMNIPRESENT && m_blackbox_attrib.attrib & ATTRIB_OMNIPRESENT) { stuck = true; m_current_state = NormalState; } if (m_blackbox_attrib.flags & ATTRIB_STACK) { //!! TODO check value? m_layernum = m_blackbox_attrib.stack; } if ((m_blackbox_attrib.flags & ATTRIB_MAXHORIZ) || (m_blackbox_attrib.flags & ATTRIB_MAXVERT)) { int x = m_blackbox_attrib.premax_x, y = m_blackbox_attrib.premax_y; unsigned int w = m_blackbox_attrib.premax_w, h = m_blackbox_attrib.premax_h; maximized = MAX_NONE; if ((m_blackbox_attrib.flags & ATTRIB_MAXHORIZ) && (m_blackbox_attrib.flags & ATTRIB_MAXVERT)) maximized = MAX_FULL; else if (m_blackbox_attrib.flags & ATTRIB_MAXVERT) maximized = MAX_VERT; else if (m_blackbox_attrib.flags & ATTRIB_MAXHORIZ) maximized = MAX_HORZ; m_blackbox_attrib.premax_x = x; m_blackbox_attrib.premax_y = y; m_blackbox_attrib.premax_w = w; m_blackbox_attrib.premax_h = h; } setState(m_current_state); } /** Show the window menu at pos mx, my */ void FluxboxWindow::showMenu(int menu_x, int menu_y) { // move menu directly under titlebar int head = screen().getHead(menu_x, menu_y); // but not under screen if (menu_y + m_windowmenu.height() >= screen().maxBottom(head)) menu_y = screen().maxBottom(head) - m_windowmenu.height() - 1 - m_windowmenu.fbwindow().borderWidth(); if (menu_x < static_cast<signed>(screen().maxLeft(head))) menu_x = screen().maxLeft(head); else if (menu_x + static_cast<signed>(m_windowmenu.width()) >= static_cast<signed>(screen().maxRight(head))) menu_x = screen().maxRight(head) - m_windowmenu.width() - 1; m_windowmenu.move(menu_x, menu_y); m_windowmenu.show(); m_windowmenu.raise(); m_windowmenu.grabInputFocus(); } /** Moves the menu to last button press position and shows it, if it's already visible it'll be hidden */ void FluxboxWindow::popupMenu() { if (m_windowmenu.isVisible()) { m_windowmenu.hide(); return; } int menu_y = frame().titlebar().height() + frame().titlebar().borderWidth(); if (!decorations.titlebar) // if we don't have any titlebar menu_y = 0; if (m_last_button_x < x() || m_last_button_x > x() + width()) m_last_button_x = x(); showMenu(m_last_button_x, menu_y + frame().y()); } /** Determine if this is the lowest tab of them all */ bool FluxboxWindow::isLowerTab() const { cerr<<__FILE__<<"("<<__FUNCTION__<<") TODO!"<<endl; return true; } /** Redirect any unhandled event to our handlers */ void FluxboxWindow::handleEvent(XEvent &event) { switch (event.type) { case ConfigureRequest: configureRequestEvent(event.xconfigurerequest); break; case MapNotify: mapNotifyEvent(event.xmap); break; // This is already handled in Fluxbox::handleEvent // case MapRequest: // mapRequestEvent(event.xmaprequest); //break; case PropertyNotify: { WinClient *client = findClient(event.xproperty.window); if (client) { propertyNotifyEvent(*client, event.xproperty.atom); } } break; default: #ifdef SHAPE if (Fluxbox::instance()->haveShape() && event.type == Fluxbox::instance()->shapeEventbase() + ShapeNotify) { XShapeEvent *shape_event = (XShapeEvent *)&event; if (shape_event->kind != ShapeBounding) break; if (shape_event->shaped) { m_shaped = true; shape(); } else { m_shaped = false; // set no shape XShapeCombineMask(display, frame().window().window(), ShapeBounding, 0, 0, None, ShapeSet); } FbTk::App::instance()->sync(false); break; } #endif // SHAPE break; } } void FluxboxWindow::mapRequestEvent(XMapRequestEvent &re) { // we're only concerned about client window event WinClient *client = findClient(re.window); if (client == 0) { #ifdef DEBUG cerr<<__FILE__<<"("<<__FUNCTION__<<"): Can't find client!"<<endl; #endif // DEBUG return; } Fluxbox *fluxbox = Fluxbox::instance(); bool get_state_ret = getState(); if (! (get_state_ret && fluxbox->isStartup())) { if ((m_client->wm_hint_flags & StateHint) && (! (m_current_state == NormalState || m_current_state == IconicState))) { m_current_state = m_client->initial_state; } else m_current_state = NormalState; } else if (iconic) m_current_state = NormalState; switch (m_current_state) { case IconicState: iconify(); break; case WithdrawnState: withdraw(); break; case NormalState: { // if this window was destroyed while autogrouping bool destroyed = false; // check WM_CLASS only when we changed state to NormalState from // WithdrawnState (ICCC 4.1.2.5) client->updateWMClassHint(); Workspace *wsp = screen().getWorkspace(m_workspace_number); if (wsp != 0 && isGroupable()) destroyed = wsp->checkGrouping(*this); // if we wasn't grouped with another window we deiconify ourself if (!destroyed) deiconify(false); } break; case InactiveState: case ZoomState: default: deiconify(false); break; } } void FluxboxWindow::mapNotifyEvent(XMapEvent &ne) { WinClient *client = findClient(ne.window); if (client == 0) return; if (!ne.override_redirect && isVisible()) { Fluxbox *fluxbox = Fluxbox::instance(); fluxbox->grab(); if (! client->validateClient()) return; setState(NormalState); if (client->isTransient() || screen().doFocusNew()) setCurrentClient(*client, true); else setFocusFlag(false); iconic = false; // Auto-group from tab? if (!client->isTransient()) { #ifdef DEBUG cerr<<__FILE__<<"("<<__FUNCTION__<<") TODO check grouping here"<<endl; #endif // DEBUG } fluxbox->ungrab(); } } /** Unmaps frame window and client window if event.window == m_client->window Returns true if *this should die else false */ void FluxboxWindow::unmapNotifyEvent(XUnmapEvent &ue) { WinClient *client = findClient(ue.window); if (client == 0) return; #ifdef DEBUG cerr<<__FILE__<<"("<<__FUNCTION__<<"): 0x"<<hex<<client->window()<<dec<<endl; cerr<<__FILE__<<"("<<__FUNCTION__<<"): title="<<client->title()<<endl; #endif // DEBUG restore(client, false); } /** Checks if event is for m_client->window. If it isn't, we leave it until the window is unmapped, if it is, we just hide it for now. */ void FluxboxWindow::destroyNotifyEvent(XDestroyWindowEvent &de) { if (de.window == m_client->window()) { #ifdef DEBUG cerr<<__FILE__<<"("<<__LINE__<<"): DestroyNotifyEvent this="<<this<<endl; #endif // DEBUG if (numClients() == 1) frame().hide(); } } void FluxboxWindow::propertyNotifyEvent(WinClient &client, Atom atom) { switch(atom) { case XA_WM_CLASS: case XA_WM_CLIENT_MACHINE: case XA_WM_COMMAND: break; case XA_WM_TRANSIENT_FOR: { bool was_transient = client.isTransient(); client.updateTransientInfo(); // update our layer to be the same layer as our transient for if (client.isTransient() && !was_transient && client.transientFor()->fbwindow()) layerItem().setLayer(client.transientFor()->fbwindow()->layerItem().getLayer()); } break; case XA_WM_HINTS: client.updateWMHints(); hintSig().notify(); // notify listeners break; case XA_WM_ICON_NAME: // update icon title and then do normal XA_WM_NAME stuff client.updateIconTitle(); case XA_WM_NAME: updateTitleFromClient(client); m_titlesig.notify(); break; case XA_WM_NORMAL_HINTS: { client.updateWMNormalHints(); if ((client.normal_hint_flags & PMinSize) && (client.normal_hint_flags & PMaxSize)) { if (client.max_width != 0 && client.max_width <= client.min_width && client.max_height != 0 && client.max_height <= client.min_height) { decorations.maximize = false; decorations.handle = false; functions.resize=false; functions.maximize=false; } else { // TODO: is broken while handled by FbW, needs to be in WinClient if (! client.isTransient()) { decorations.maximize = true; decorations.handle = true; functions.maximize = true; } functions.resize = true; } setupWindow(); } // save old values int x = frame().x(), y = frame().y(); unsigned int w = frame().width(), h = frame().height(); upsize(); // reconfigure if the old values changed if (x != frame().x() || y != frame().y() || w != frame().width() || h != frame().height()) { moveResize(x, y, w, h); } break; } default: FbAtoms *fbatoms = FbAtoms::instance(); if (atom == fbatoms->getWMProtocolsAtom()) { client.updateWMProtocols(); } else if (atom == fbatoms->getMWMHintsAtom()) { client.updateMWMHints(); updateMWMHintsFromClient(client); } else if (atom == fbatoms->getFluxboxHintsAtom()) { client.updateBlackboxHints(); updateBlackboxHintsFromClient(client); } break; } } void FluxboxWindow::exposeEvent(XExposeEvent &ee) { frame().exposeEvent(ee); } void FluxboxWindow::configureRequestEvent(XConfigureRequestEvent &cr) { WinClient *client = findClient(cr.window); if (client == 0) return; int cx = frame().x(), cy = frame().y(), ignore = 0; unsigned int cw = frame().width(), ch = frame().height(); if (cr.value_mask & CWBorderWidth) client->old_bw = cr.border_width; if (cr.value_mask & CWX) { cx = cr.x; frame().gravityTranslate(cx, ignore, client->gravity(), false); } if (cr.value_mask & CWY) { cy = cr.y; frame().gravityTranslate(ignore, cy, client->gravity(), false); } if (cr.value_mask & CWWidth) cw = cr.width; if (cr.value_mask & CWHeight) ch = cr.height; // whether we should send ConfigureNotify to netizens bool send_notify = false; // the request is for client window so we resize the frame to it first if (frame().width() != cw || frame().height() != ch) { if (frame().x() != cx || frame().y() != cy) frame().moveResizeForClient(cx, cy, cw, ch); else frame().resizeForClient(cw, ch); send_notify = true; } else if (frame().x() != cx || frame().y() != cy) { move(cx, cy); // since we already send a notify in move we don't need to do that again send_notify = false; } if (cr.value_mask & CWStackMode) { switch (cr.detail) { case Above: case TopIf: default: raise(); break; case Below: case BottomIf: lower(); break; } } sendConfigureNotify(send_notify); } void FluxboxWindow::buttonPressEvent(XButtonEvent &be) { m_last_button_x = be.x_root; m_last_button_y = be.y_root; // check frame events first frame().buttonPressEvent(be); if (be.button == 1 || (be.button == 3 && be.state == Mod1Mask)) { if ((! focused) && (! screen().isSloppyFocus())) { //check focus setInputFocus(); } if (frame().window().window() == be.window) { if (screen().clickRaises()) raise(); XAllowEvents(display, ReplayPointer, be.time); m_button_grab_x = be.x_root - frame().x() - frame().window().borderWidth(); m_button_grab_y = be.y_root - frame().y() - frame().window().borderWidth(); } if (m_windowmenu.isVisible()) m_windowmenu.hide(); } } void FluxboxWindow::buttonReleaseEvent(XButtonEvent &re) { if (isMoving()) stopMoving(); else if (isResizing()) stopResizing(); else if (m_attaching_tab) attachTo(re.x_root, re.y_root); else if (re.window == frame().window()) { if (re.button == 2 && re.state == Mod1Mask) XUngrabPointer(display, CurrentTime); else frame().buttonReleaseEvent(re); } else { frame().buttonReleaseEvent(re); } } void FluxboxWindow::motionNotifyEvent(XMotionEvent &me) { if (isMoving() && me.window == parent()) { me.window = frame().window().window(); } bool inside_titlebar = (frame().titlebar() == me.window || frame().label() == me.window || frame().handle() == me.window || frame().window() == me.window); if (Fluxbox::instance()->getIgnoreBorder() && !(me.state & Mod1Mask) // really should check for exact matches && !(isMoving() || isResizing())) { int borderw = frame().window().borderWidth(); if (me.x_root < (frame().x() + borderw) || me.y_root < (frame().y() + borderw) || me.x_root > (frame().x() + (int)frame().width() + borderw) || me.y_root > (frame().y() + (int)frame().height() + borderw)) return; } WinClient *client = 0; if (!inside_titlebar) { // determine if we're in titlebar Client2ButtonMap::iterator it = m_labelbuttons.begin(); Client2ButtonMap::iterator it_end = m_labelbuttons.end(); for (; it != it_end; ++it) { if ((*it).second->window() == me.window) { inside_titlebar = true; client = (*it).first; break; } } } if ((me.state & Button1Mask) && functions.move && inside_titlebar && !isResizing()) { if (! isMoving()) { startMoving(me.window); } else { int dx = me.x_root - m_button_grab_x, dy = me.y_root - m_button_grab_y; dx -= frame().window().borderWidth(); dy -= frame().window().borderWidth(); // Warp to next or previous workspace?, must have moved sideways some int moved_x = me.x_root - m_last_resize_x; // save last event point m_last_resize_x = me.x_root; m_last_resize_y = me.y_root; if (moved_x && screen().isWorkspaceWarping()) { unsigned int cur_id = screen().currentWorkspaceID(); unsigned int new_id = cur_id; const int warpPad = screen().getEdgeSnapThreshold(); // 1) if we're inside the border threshold // 2) if we moved in the right direction if (me.x_root >= int(screen().width()) - warpPad - 1 && moved_x > 0) { //warp right new_id = (cur_id + 1) % screen().getCount(); dx = - me.x_root; // move mouse back to x=0 } else if (me.x_root <= warpPad && moved_x < 0) { //warp left new_id = (cur_id + screen().getCount() - 1) % screen().getCount(); dx = screen().width() - me.x_root-1; // move mouse to screen width - 1 } if (new_id != cur_id) { XWarpPointer(display, None, None, 0, 0, 0, 0, dx, 0); screen().changeWorkspaceID(new_id); m_last_resize_x = me.x_root + dx; // dx is the difference, so our new x is what it would have been // without the warp, plus the difference. dx += me.x_root - m_button_grab_x; } } // dx = current left side, dy = current top doSnapping(dx, dy); if (! screen().doOpaqueMove()) { parent().drawRectangle(screen().rootTheme().opGC(), m_last_move_x, m_last_move_y, frame().width() + 2*frame().window().borderWidth()-1, frame().height() + 2*frame().window().borderWidth()-1); parent().drawRectangle(screen().rootTheme().opGC(), dx, dy, frame().width() + 2*frame().window().borderWidth()-1, frame().height() + 2*frame().window().borderWidth()-1); m_last_move_x = dx; m_last_move_y = dy; } else { moveResize(dx, dy, frame().width(), frame().height()); } screen().showPosition(dx, dy); } // end if moving } else if (functions.resize && (((me.state & Button1Mask) && (me.window == frame().gripRight() || me.window == frame().gripLeft())) || me.window == frame().window())) { if (! resizing) { int cx = frame().width() / 2; int cy = frame().height() / 2; if (me.window == frame().gripRight()) m_resize_corner = RIGHTBOTTOM; else if (me.window == frame().gripLeft()) m_resize_corner = LEFTBOTTOM; else if (screen().getResizeMode() != "quadrant") m_resize_corner = RIGHTBOTTOM; else if (me.x < cx) m_resize_corner = (me.y < cy) ? LEFTTOP : LEFTBOTTOM; else m_resize_corner = (me.y < cy) ? RIGHTTOP : RIGHTBOTTOM; startResizing(me.window, me.x, me.y); } else if (resizing) { // draw over old rect parent().drawRectangle(screen().rootTheme().opGC(), m_last_resize_x, m_last_resize_y, m_last_resize_w - 1 + 2 * frame().window().borderWidth(), m_last_resize_h - 1 + 2 * frame().window().borderWidth()); // move rectangle int gx = 0, gy = 0; int dx = me.x - m_button_grab_x; int dy = me.y - m_button_grab_y; if (m_resize_corner == LEFTTOP || m_resize_corner == RIGHTTOP) { m_last_resize_h = frame().height() - dy; m_last_resize_y = frame().y() + dy; } else { m_last_resize_h = frame().height() + dy; } if (m_resize_corner == LEFTTOP || m_resize_corner == LEFTBOTTOM) { m_last_resize_w = frame().width() - dx; m_last_resize_x = frame().x() + dx; } else { m_last_resize_w = frame().width() + dx; } fixsize(&gx, &gy); // draw resize rectangle parent().drawRectangle(screen().rootTheme().opGC(), m_last_resize_x, m_last_resize_y, m_last_resize_w - 1 + 2 * frame().window().borderWidth(), m_last_resize_h - 1 + 2 * frame().window().borderWidth()); screen().showGeometry(gx, gy); } } else if ((me.state & Button2Mask) && inside_titlebar && client != 0) { // // drag'n'drop code for tabs // if (m_attaching_tab == 0) { // start drag'n'drop for tab m_attaching_tab = client; XGrabPointer(display, me.window, False, Button2MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, frame().theme().moveCursor(), CurrentTime); m_last_move_x = me.x_root - 1; m_last_move_y = me.y_root - 1; parent().drawRectangle(screen().rootTheme().opGC(), m_last_move_x, m_last_move_y, m_labelbuttons[client]->width(), m_labelbuttons[client]->height()); } else { // we already grabed and started to drag'n'drop tab // so we update drag'n'drop-rectangle int dx = me.x_root - 1, dy = me.y_root - 1; dx -= frame().window().borderWidth(); dy -= frame().window().borderWidth(); if (screen().getEdgeSnapThreshold()) { int drx = screen().width() - (dx + 1); if (dx > 0 && dx < drx && dx < screen().getEdgeSnapThreshold()) dx = 0; else if (drx > 0 && drx < screen().getEdgeSnapThreshold()) dx = screen().width() - 1; int dty, dby; dty = dy; dby = -dy - 1; if (dy > 0 && dty < screen().getEdgeSnapThreshold()) dy = 0; else if (dby > 0 && dby < screen().getEdgeSnapThreshold()) dy = - 1; } //erase rectangle parent().drawRectangle(screen().rootTheme().opGC(), m_last_move_x, m_last_move_y, m_labelbuttons[client]->width(), m_labelbuttons[client]->height()); // redraw rectangle at new pos m_last_move_x = dx; m_last_move_y = dy; parent().drawRectangle(screen().rootTheme().opGC(), m_last_move_x, m_last_move_y, m_labelbuttons[client]->width(), m_labelbuttons[client]->height()); } } } void FluxboxWindow::enterNotifyEvent(XCrossingEvent &ev) { // ignore grab activates, or if we're not visible if (ev.mode == NotifyGrab || !isVisible()) { return; } WinClient *client = 0; // don't waste our time scanning if we aren't real sloppy focus if (screen().isSloppyFocus()) { // determine if we're in a label button (tab) Client2ButtonMap::iterator it = m_labelbuttons.begin(); Client2ButtonMap::iterator it_end = m_labelbuttons.end(); for (; it != it_end; ++it) { if ((*it).second->window() == ev.window) { client = (*it).first; break; } } } if (ev.window == frame().window() || ev.window == m_client->window() || client) { if ((screen().isSloppyFocus() || screen().isSemiSloppyFocus()) && !isFocused() || // or, we are focused, but it isn't the one we want client && screen().isSloppyFocus() && (m_client != client)) { // check that there aren't any subsequent leave notify events in the // X event queue XEvent dummy; scanargs sa; sa.w = ev.window; sa.enter = sa.leave = False; XCheckIfEvent(display, &dummy, queueScanner, (char *) &sa); // if client is set, use setCurrent client, otherwise just setInputFocus if ((!sa.leave || sa.inferior)) { if (client) setCurrentClient(*client, true); else setInputFocus(); } } } } void FluxboxWindow::leaveNotifyEvent(XCrossingEvent &ev) { // I hope commenting this out is right - simon 21jul2003 //if (ev.window == frame().window()) //installColormap(false); } // TODO: functions should not be affected by decoration void FluxboxWindow::setDecoration(Decoration decoration) { switch (decoration) { case DECOR_NONE: decorations.titlebar = decorations.border = decorations.handle = decorations.iconify = decorations.maximize = decorations.tab = false; //tab is also a decor decorations.menu = true; // menu is present // functions.iconify = functions.maximize = true; // functions.move = true; // We need to move even without decor // functions.resize = true; // We need to resize even without decor break; default: case DECOR_NORMAL: decorations.titlebar = decorations.border = decorations.handle = decorations.iconify = decorations.maximize = decorations.menu = true; functions.resize = functions.move = functions.iconify = functions.maximize = true; break; case DECOR_TINY: decorations.titlebar = decorations.iconify = decorations.menu = functions.move = functions.iconify = true; decorations.border = decorations.handle = decorations.maximize = functions.resize = functions.maximize = false; break; case DECOR_TOOL: decorations.titlebar = decorations.menu = functions.move = true; decorations.iconify = decorations.border = decorations.handle = decorations.maximize = functions.resize = functions.maximize = functions.iconify = false; break; } applyDecorations(); // is this reconfigure necessary??? reconfigure(); } // commit current decoration values to actual displayed things void FluxboxWindow::applyDecorations(bool initial) { frame().clientArea().setBorderWidth(0); // client area bordered by other things int grav_x=0, grav_y=0; // negate gravity frame().gravityTranslate(grav_x, grav_y, -m_client->gravity(), false); unsigned int border_width = 0; if (decorations.border) border_width = frame().theme().border().width(); bool client_move = false; if (initial || frame().window().borderWidth() != border_width) { client_move = true; frame().setBorderWidth(border_width); } // we rely on frame not doing anything if it is already shown/hidden if (decorations.titlebar) client_move |= frame().showTitlebar(); else client_move |= frame().hideTitlebar(); if (decorations.handle) { client_move |= frame().showHandle(); } else client_move |= frame().hideHandle(); // apply gravity once more frame().gravityTranslate(grav_x, grav_y, m_client->gravity(), false); // if the location changes, shift it if (grav_x != 0 || grav_y != 0) { move(grav_x + frame().x(), grav_y + frame().y()); client_move = true; } frame().reconfigure(); if (!initial && client_move) sendConfigureNotify(); } void FluxboxWindow::toggleDecoration() { //don't toggle decor if the window is shaded if (isShaded()) return; if (decorations.enabled) { //remove decorations decorations.enabled = false; setDecoration(DECOR_NONE); } else { //revert back to old decoration decorations.enabled = true; if (m_old_decoration == DECOR_NONE) { // make sure something happens setDecoration(DECOR_NORMAL); } else { setDecoration(m_old_decoration); } } } unsigned int FluxboxWindow::decorationMask() const { unsigned int ret = 0; if (decorations.titlebar) ret |= DECORM_TITLEBAR; if (decorations.handle) ret |= DECORM_HANDLE; if (decorations.border) ret |= DECORM_BORDER; if (decorations.iconify) ret |= DECORM_ICONIFY; if (decorations.maximize) ret |= DECORM_MAXIMIZE; if (decorations.close) ret |= DECORM_CLOSE; if (decorations.menu) ret |= DECORM_MENU; if (decorations.sticky) ret |= DECORM_STICKY; if (decorations.shade) ret |= DECORM_SHADE; if (decorations.tab) ret |= DECORM_TAB; if (decorations.enabled) ret |= DECORM_ENABLED; return ret; } void FluxboxWindow::setDecorationMask(unsigned int mask) { decorations.titlebar = mask & DECORM_TITLEBAR; decorations.handle = mask & DECORM_HANDLE; decorations.border = mask & DECORM_BORDER; decorations.iconify = mask & DECORM_ICONIFY; decorations.maximize = mask & DECORM_MAXIMIZE; decorations.close = mask & DECORM_CLOSE; decorations.menu = mask & DECORM_MENU; decorations.sticky = mask & DECORM_STICKY; decorations.shade = mask & DECORM_SHADE; decorations.tab = mask & DECORM_TAB; decorations.enabled = mask & DECORM_ENABLED; applyDecorations(); } void FluxboxWindow::startMoving(Window win) { moving = true; Fluxbox *fluxbox = Fluxbox::instance(); // grabbing (and masking) on the root window allows us to // freely map and unmap the window we're moving. XGrabPointer(display, screen().rootWindow().window(), False, Button1MotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, screen().rootWindow().window(), frame().theme().moveCursor(), CurrentTime); if (m_windowmenu.isVisible()) m_windowmenu.hide(); fluxbox->maskWindowEvents(screen().rootWindow().window(), this); m_last_move_x = frame().x(); m_last_move_y = frame().y(); if (! screen().doOpaqueMove()) { fluxbox->grab(); parent().drawRectangle(screen().rootTheme().opGC(), frame().x(), frame().y(), frame().width() + 2*frame().window().borderWidth()-1, frame().height() + 2*frame().window().borderWidth()-1); screen().showPosition(frame().x(), frame().y()); } } void FluxboxWindow::stopMoving() { moving = false; Fluxbox *fluxbox = Fluxbox::instance(); fluxbox->maskWindowEvents(0, 0); if (! screen().doOpaqueMove()) { parent().drawRectangle(screen().rootTheme().opGC(), m_last_move_x, m_last_move_y, frame().width() + 2*frame().window().borderWidth()-1, frame().height() + 2*frame().window().borderWidth()-1); moveResize(m_last_move_x, m_last_move_y, frame().width(), frame().height()); if (m_workspace_number != screen().currentWorkspaceID()) { screen().reassociateWindow(this, screen().currentWorkspaceID(), true); frame().show(); } fluxbox->ungrab(); } else { moveResize(frame().x(), frame().y(), frame().width(), frame().height()); sendConfigureNotify(); } screen().hidePosition(); XUngrabPointer(display, CurrentTime); FbTk::App::instance()->sync(false); //make sure the redraw is made before we continue } void FluxboxWindow::pauseMoving() { if (screen().doOpaqueMove()) { return; } parent().drawRectangle(screen().rootTheme().opGC(), m_last_move_x, m_last_move_y, frame().width() + 2*frame().window().borderWidth()-1, frame().height() + 2*frame().window().borderWidth()-1); } void FluxboxWindow::resumeMoving() { if (screen().doOpaqueMove()) { return; } if (m_workspace_number == screen().currentWorkspaceID()) { frame().show(); } FbTk::App::instance()->sync(false); parent().drawRectangle(screen().rootTheme().opGC(), m_last_move_x, m_last_move_y, frame().width() + 2*frame().window().borderWidth()-1, frame().height() + 2*frame().window().borderWidth()-1); } /** * Helper function that snaps a window to another window * We snap if we're closer than the x/ylimits. */ inline void snapToWindow(int &xlimit, int &ylimit, int left, int right, int top, int bottom, int oleft, int oright, int otop, int obottom) { // Only snap if we're adjacent to the edge we're looking at // for left + right, need to be in the right y range if (top <= obottom && bottom >= otop) { // left if (abs(left-oleft) < abs(xlimit)) xlimit = -(left-oleft); if (abs(right-oleft) < abs(xlimit)) xlimit = -(right-oleft); // right if (abs(left-oright) < abs(xlimit)) xlimit = -(left-oright); if (abs(right-oright) < abs(xlimit)) xlimit = -(right-oright); } // for top + bottom, need to be in the right x range if (left <= oright && right >= oleft) { // top if (abs(top-otop) < abs(ylimit)) ylimit = -(top-otop); if (abs(bottom-otop) < abs(ylimit)) ylimit = -(bottom-otop); // bottom if (abs(top-obottom) < abs(ylimit)) ylimit = -(top-obottom); if (abs(bottom-obottom) < abs(ylimit)) ylimit = -(bottom-obottom); } } /* * Do Whatever snapping magic is necessary, and return using the orig_left * and orig_top variables to indicate the new x,y position */ void FluxboxWindow::doSnapping(int &orig_left, int &orig_top) { /* * Snap to screen/head edges * Snap to windows */ if (screen().getEdgeSnapThreshold() == 0) return; // Keep track of our best offsets so far // We need to find things less than or equal to the threshold int dx = screen().getEdgeSnapThreshold() + 1; int dy = screen().getEdgeSnapThreshold() + 1; // we only care about the left/top etc that includes borders int borderW = frame().window().borderWidth(); int top = orig_top; // orig include the borders int left = orig_left; int right = orig_left + width() + 2*borderW; int bottom = orig_top + height() + 2*borderW; ///////////////////////////////////// // begin by checking the screen (or Xinerama head) edges // head "0" == whole screen width + height, which we skip since the // sum of all the heads covers those edges for (int h = 1; h <= screen().numHeads(); h++) { snapToWindow(dx, dy, left, right, top, bottom, screen().maxLeft(h), screen().maxRight(h), screen().maxTop(h), screen().maxBottom(h)); } ///////////////////////////////////// // now check window edges Workspace::Windows &wins = screen().currentWorkspace()->windowList(); Workspace::Windows::iterator it = wins.begin(); Workspace::Windows::iterator it_end = wins.end(); for (; it != it_end; it++) { if ((*it) == this) continue; // skip myself snapToWindow(dx, dy, left, right, top, bottom, (*it)->x(), (*it)->x() + (*it)->width() + 2*borderW, (*it)->y(), (*it)->y() + (*it)->height() + 2*borderW); } // commit if (dx <= screen().getEdgeSnapThreshold()) orig_left += dx; if (dy <= screen().getEdgeSnapThreshold()) orig_top += dy; } void FluxboxWindow::startResizing(Window win, int x, int y) { resizing = true; const Cursor& cursor = (m_resize_corner == LEFTTOP) ? frame().theme().upperLeftAngleCursor() : (m_resize_corner == RIGHTTOP) ? frame().theme().upperRightAngleCursor() : (m_resize_corner == RIGHTBOTTOM) ? frame().theme().lowerRightAngleCursor() : frame().theme().lowerLeftAngleCursor(); XGrabPointer(display, win, false, ButtonMotionMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, cursor, CurrentTime); int gx = 0, gy = 0; m_button_grab_x = x; m_button_grab_y = y; m_last_resize_x = frame().x(); m_last_resize_y = frame().y(); m_last_resize_w = frame().width(); m_last_resize_h = frame().height(); fixsize(&gx, &gy); screen().showGeometry(gx, gy); parent().drawRectangle(screen().rootTheme().opGC(), m_last_resize_x, m_last_resize_y, m_last_resize_w - 1 + 2 * frame().window().borderWidth(), m_last_resize_h - 1 + 2 * frame().window().borderWidth()); } void FluxboxWindow::stopResizing(Window win) { resizing = false; parent().drawRectangle(screen().rootTheme().opGC(), m_last_resize_x, m_last_resize_y, m_last_resize_w - 1 + 2 * frame().window().borderWidth(), m_last_resize_h - 1 + 2 * frame().window().borderWidth()); screen().hideGeometry(); fixsize(); moveResize(m_last_resize_x, m_last_resize_y, m_last_resize_w, m_last_resize_h); XUngrabPointer(display, CurrentTime); } void FluxboxWindow::attachTo(int x, int y) { if (m_attaching_tab == 0) return; XUngrabPointer(display, CurrentTime); parent().drawRectangle(screen().rootTheme().opGC(), m_last_move_x, m_last_move_y, m_labelbuttons[m_attaching_tab]->width(), m_labelbuttons[m_attaching_tab]->height()); int dest_x = 0, dest_y = 0; Window child = 0; if (XTranslateCoordinates(display, parent().window(), parent().window(), x, y, &dest_x, &dest_y, &child)) { // search for a fluxboxwindow WinClient *client = Fluxbox::instance()->searchWindow(child); FluxboxWindow *attach_to_win = 0; if (client) attach_to_win = client->fbwindow(); cerr<<"client = "<<client<<", child = "<<hex<<child<<dec<<", fbwin = "<<attach_to_win<<endl; cerr<<"client = "<<client<<", child = "<<hex<<child<<dec<<", fbwin = "<<attach_to_win<<endl; if (attach_to_win != this && attach_to_win != 0) { attach_to_win->attachClient(*m_attaching_tab); } else if (attach_to_win != this) { // disconnect client if we didn't drop on a window WinClient &client = *m_attaching_tab; detachClient(*m_attaching_tab); // move to drop zone if (client.m_win != 0) client.m_win->move(x, y); } } m_attaching_tab = 0; } void FluxboxWindow::restore(WinClient *client, bool remap) { if (client->m_win != this) return; XChangeSaveSet(display, client->window(), SetModeDelete); client->setEventMask(NoEventMask); int wx = frame().x(), wy = frame().y(); // not actually used here frame().gravityTranslate(wx, wy, -client->gravity(), true); // negative to invert // Why was this hide done? It broke vncviewer (and mplayer?), // since it would reparent when going fullscreen. // is it needed for anything? Reparent should imply unmap //client->hide(); // restore old border width client->setBorderWidth(client->old_bw); XEvent not_used; if (! XCheckTypedWindowEvent(display, client->window(), ReparentNotify, ¬_used)) { #ifdef DEBUG cerr<<"FluxboxWindow::restore: reparent 0x"<<hex<<client->window()<<dec<<" to root"<<endl; #endif // DEBUG // reparent to root window client->reparent(screen().rootWindow().window(), frame().x(), frame().y()); } if (remap) client->show(); installColormap(false); delete client; #ifdef DEBUG cerr<<__FILE__<<"("<<__FUNCTION__<<"): numClients() = "<<numClients()<<endl; #endif // DEBUG if (numClients() == 0) { frame().hide(); } } void FluxboxWindow::restore(bool remap) { if (numClients() == 0) return; while (!clientList().empty()) { restore(clientList().back(), remap); // deleting winClient removes it from the clientList } } bool FluxboxWindow::isVisible() const { return frame().isVisible(); } FbTk::FbWindow &FluxboxWindow::fbWindow() { return frame().window(); } const FbTk::FbWindow &FluxboxWindow::fbWindow() const { return frame().window(); } unsigned int FluxboxWindow::titlebarHeight() const { return frame().titlebarHeight(); } Window FluxboxWindow::clientWindow() const { if (m_client == 0) return 0; return m_client->window(); } const std::string &FluxboxWindow::title() const { static string empty_string(""); if (m_client == 0) return empty_string; return m_client->title(); } const std::string &FluxboxWindow::iconTitle() const { static string empty_string(""); if (m_client == 0) return empty_string; return m_client->iconTitle(); } int FluxboxWindow::initialState() const { return m_client->initial_state; } void FluxboxWindow::changeBlackboxHints(const BlackboxHints &net) { if ((net.flags & ATTRIB_SHADED) && ((m_blackbox_attrib.attrib & ATTRIB_SHADED) != (net.attrib & ATTRIB_SHADED))) shade(); if (net.flags & (ATTRIB_MAXVERT | ATTRIB_MAXHORIZ)) { // make maximise look like the net maximise flags int want_max = MAX_NONE; if (net.flags & ATTRIB_MAXVERT) want_max |= MAX_VERT; if (net.flags & ATTRIB_MAXHORIZ) want_max |= MAX_HORZ; if (want_max == MAX_NONE && maximized != MAX_NONE) { maximize(MAX_NONE); } else if (want_max == MAX_FULL && maximized != MAX_FULL) { maximize(MAX_FULL); // horz and vert are a little trickier to morph } // to toggle vert // either we want vert and aren't // or we want horizontal, and are vertically (or full) at present if (want_max == MAX_VERT && !(maximized & MAX_VERT) || want_max == MAX_HORZ && (maximized & MAX_VERT)) { maximize(MAX_VERT); } // note that if we want horz, it WONT be vert any more from above if (want_max == MAX_HORZ && !(maximized & MAX_HORZ) || want_max == MAX_VERT && (maximized & MAX_HORZ)) { maximize(MAX_HORZ); } } if ((net.flags & ATTRIB_OMNIPRESENT) && ((m_blackbox_attrib.attrib & ATTRIB_OMNIPRESENT) != (net.attrib & ATTRIB_OMNIPRESENT))) stick(); if ((net.flags & ATTRIB_WORKSPACE) && (m_workspace_number != net.workspace)) { screen().reassociateWindow(this, net.workspace, true); if (screen().currentWorkspaceID() != net.workspace) withdraw(); else deiconify(); } if (net.flags & ATTRIB_STACK) { if ((unsigned int) m_layernum != net.stack) { moveToLayer(net.stack); } } if (net.flags & ATTRIB_DECORATION) { m_old_decoration = static_cast<Decoration>(net.decoration); setDecoration(m_old_decoration); } } void FluxboxWindow::upsize() { } ///TODO void FluxboxWindow::downsize() { } void FluxboxWindow::fixsize(int *user_w, int *user_h) { int titlebar_height = (decorations.titlebar ? frame().titlebar().height() + frame().titlebar().borderWidth() : 0); int handle_height = (decorations.handle ? frame().handle().height() + frame().handle().borderWidth() : 0); int decoration_height = titlebar_height + handle_height; // dx is new width = current width + difference between new and old x values //int dx = frame().width() + frame().x() - m_last_resize_x; int dw = m_last_resize_w; // dy = new height (w/o decorations), similarly int dh = m_last_resize_h - decoration_height; m_client->applySizeHints(dw, dh, user_w, user_h); // update last resize m_last_resize_w = dw; m_last_resize_h = dh + decoration_height; // move X if necessary if (m_resize_corner == LEFTTOP || m_resize_corner == LEFTBOTTOM) { m_last_resize_x = frame().x() + frame().width() - m_last_resize_w; } if (m_resize_corner == LEFTTOP || m_resize_corner == RIGHTTOP) { m_last_resize_y = frame().y() + frame().height() - m_last_resize_h; } } void FluxboxWindow::moveResizeClient(WinClient &client, int x, int y, unsigned int height, unsigned int width) { client.moveResize(x, y, frame().clientArea().width(), frame().clientArea().height()); client.updateRect(frame().x() + frame().clientArea().x(), frame().y() + frame().clientArea().y(), frame().clientArea().width(), frame().clientArea().height()); } void FluxboxWindow::sendConfigureNotify(bool send_to_netizens) { ClientList::iterator client_it = m_clientlist.begin(); ClientList::iterator client_it_end = m_clientlist.end(); for (; client_it != client_it_end; ++client_it) { WinClient &client = *(*client_it); /* Send event telling where the root position of the client window is. (ie frame pos + client pos inside the frame = send pos) */ //!! client.x = frame().x(); client.y = frame().y(); moveResizeClient(client, frame().clientArea().x(), frame().clientArea().y(), frame().clientArea().width(), frame().clientArea().height()); if (send_to_netizens) { XEvent event; event.type = ConfigureNotify; event.xconfigure.display = display; event.xconfigure.event = client.window(); event.xconfigure.window = client.window(); event.xconfigure.x = frame().x() + frame().clientArea().x(); event.xconfigure.y = frame().y() + frame().clientArea().y(); event.xconfigure.width = client.width(); event.xconfigure.height = client.height(); event.xconfigure.border_width = client.old_bw; event.xconfigure.above = frame().window().window(); event.xconfigure.override_redirect = false; screen().updateNetizenConfigNotify(event); } } // end for } void FluxboxWindow::addExtraMenu(const char *label, FbTk::Menu *menu) { menu->setInternalMenu(); menu->disableTitle(); m_extramenus.push_back(std::make_pair(label, menu)); setupWindow(); } void FluxboxWindow::removeExtraMenu(FbTk::Menu *menu) { ExtraMenus::iterator it = m_extramenus.begin(); ExtraMenus::iterator it_end = m_extramenus.end(); for (; it != it_end; ++it) { if (it->second == menu) { m_extramenus.erase(it); break; } } setupWindow(); } void FluxboxWindow::close() { if (m_client) m_client->sendClose(false); } void FluxboxWindow::setupWindow() { // sets up our window // we allow both to be done at once to share the commands WinButtonTheme &winbutton_theme = screen().winButtonTheme(); using namespace FbTk; typedef RefCount<Command> CommandRef; typedef SimpleCommand<FluxboxWindow> WindowCmd; CommandRef iconify_cmd(new WindowCmd(*this, &FluxboxWindow::iconify)); CommandRef maximize_cmd(new WindowCmd(*this, &FluxboxWindow::maximizeFull)); CommandRef maximize_vert_cmd(new WindowCmd(*this, &FluxboxWindow::maximizeVertical)); CommandRef maximize_horiz_cmd(new WindowCmd(*this, &FluxboxWindow::maximizeHorizontal)); CommandRef close_cmd(new WindowCmd(*this, &FluxboxWindow::close)); CommandRef shade_cmd(new WindowCmd(*this, &FluxboxWindow::shade)); CommandRef raise_cmd(new WindowCmd(*this, &FluxboxWindow::raise)); CommandRef lower_cmd(new WindowCmd(*this, &FluxboxWindow::lower)); CommandRef raise_and_focus_cmd(new WindowCmd(*this, &FluxboxWindow::raiseAndFocus)); CommandRef stick_cmd(new WindowCmd(*this, &FluxboxWindow::stick)); CommandRef show_menu_cmd(new WindowCmd(*this, &FluxboxWindow::popupMenu)); // clear old buttons from frame frame().removeAllButtons(); //!! TODO: fix this ugly hack // get titlebar configuration const vector<Fluxbox::Titlebar> *dir = &Fluxbox::instance()->getTitlebarLeft(); for (char c=0; c<2; c++) { for (size_t i=0; i< dir->size(); ++i) { //create new buttons FbTk::Button *newbutton = 0; if (isIconifiable() && (*dir)[i] == Fluxbox::MINIMIZE) { newbutton = new WinButton(*this, winbutton_theme, WinButton::MINIMIZE, frame().titlebar(), 0, 0, 10, 10); newbutton->setOnClick(iconify_cmd); } else if (isMaximizable() && (*dir)[i] == Fluxbox::MAXIMIZE) { newbutton = new WinButton(*this, winbutton_theme, WinButton::MAXIMIZE, frame().titlebar(), 0, 0, 10, 10); newbutton->setOnClick(maximize_cmd, 1); newbutton->setOnClick(maximize_horiz_cmd, 3); newbutton->setOnClick(maximize_vert_cmd, 2); } else if (m_client->isClosable() && (*dir)[i] == Fluxbox::CLOSE) { newbutton = new WinButton(*this, winbutton_theme, WinButton::CLOSE, frame().titlebar(), 0, 0, 10, 10); newbutton->setOnClick(close_cmd); } else if ((*dir)[i] == Fluxbox::STICK) { WinButton *winbtn = new WinButton(*this, winbutton_theme, WinButton::STICK, frame().titlebar(), 0, 0, 10, 10); stateSig().attach(winbtn); winbtn->setOnClick(stick_cmd); newbutton = winbtn; } else if ((*dir)[i] == Fluxbox::SHADE) { WinButton *winbtn = new WinButton(*this, winbutton_theme, WinButton::SHADE, frame().titlebar(), 0, 0, 10, 10); winbtn->setOnClick(shade_cmd); } if (newbutton != 0) { newbutton->show(); if (c == 0) frame().addLeftButton(newbutton); else frame().addRightButton(newbutton); } } //end for i dir = &Fluxbox::instance()->getTitlebarRight(); } // end for c frame().reconfigure(); // setup titlebar frame().setOnClickTitlebar(raise_and_focus_cmd, 1, false, true); // on press with button 1 frame().setOnClickTitlebar(shade_cmd, 1, true); // doubleclick with button 1 frame().setOnClickTitlebar(show_menu_cmd, 3); // on release with button 3 frame().setOnClickTitlebar(lower_cmd, 2); // on release with button 2 frame().setDoubleClickTime(Fluxbox::instance()->getDoubleClickInterval()); // end setup frame // setup menu menu().removeAll(); // clear old items menu().disableTitle(); // not titlebar // set new menu items menu().insert("Shade", shade_cmd); menu().insert("Stick", stick_cmd); // create maximize item with: // button1: Maximize normal // button2: Maximize Vertical // button3: Maximize Horizontal FbTk::MultiButtonMenuItem *maximize_item = new FbTk::MultiButtonMenuItem(3, "Maximize"); maximize_item->setCommand(1, maximize_cmd); maximize_item->setCommand(2, maximize_vert_cmd); maximize_item->setCommand(3, maximize_horiz_cmd); menu().insert(maximize_item); menu().insert("Iconify", iconify_cmd); menu().insert("Raise", raise_cmd); menu().insert("Lower", lower_cmd); ExtraMenus::iterator it = m_extramenus.begin(); ExtraMenus::iterator it_end = m_extramenus.end(); for (; it != it_end; ++it) { it->second->disableTitle(); // be sure there is no title menu().insert(it->first, it->second); } menu().insert("---"); menu().insert("Close", close_cmd); menu().reconfigure(); // update graphics }