// -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*- // Slit.cc for Blackbox - an X11 Window Manager // Copyright (c) 2001 - 2005 Sean 'Shaleh' Perry // Copyright (c) 1997 - 2000, 2002 - 2005 // Bradley T Hughes // // 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. #include "Slit.hh" #include "Screen.hh" #include "Slitmenu.hh" #include "Toolbar.hh" #include #include #include #include Slit::Slit(BScreen *scr) { screen = scr; blackbox = screen->blackbox(); const SlitOptions &options = screen->resource().slitOptions(); setLayer(options.always_on_top ? StackingList::LayerAbove : StackingList::LayerNormal); hidden = options.auto_hide; display = screen->screenInfo().display().XDisplay(); frame.window = frame.pixmap = None; timer = new bt::Timer(blackbox, this); timer->setTimeout(blackbox->resource().autoRaiseDelay()); XSetWindowAttributes attrib; unsigned long create_mask = CWColormap | CWEventMask; attrib.colormap = screen->screenInfo().colormap(); attrib.event_mask = SubstructureRedirectMask | ButtonPressMask | EnterWindowMask | LeaveWindowMask | ExposureMask; frame.rect.setSize(1, 1); frame.window = XCreateWindow(display, screen->screenInfo().rootWindow(), frame.rect.x(), frame.rect.y(), frame.rect.width(), frame.rect.height(), 0, screen->screenInfo().depth(), InputOutput, screen->screenInfo().visual(), create_mask, &attrib); blackbox->insertEventHandler(frame.window, this); screen->addStrut(&strut); } Slit::~Slit(void) { screen->removeStrut(&strut); bt::PixmapCache::release(frame.pixmap); blackbox->removeEventHandler(frame.window); XDestroyWindow(display, frame.window); delete timer; } unsigned int Slit::exposedWidth(void) const { const SlitOptions &options = screen->resource().slitOptions(); if (options.direction == Vertical && options.auto_hide) return screen->resource().slitStyle().margin; return frame.rect.width(); } unsigned int Slit::exposedHeight(void) const { const SlitOptions &options = screen->resource().slitOptions(); if (options.direction == Horizontal && options.auto_hide) return screen->resource().slitStyle().margin; return frame.rect.height(); } void Slit::addClient(Window w) { SlitClient *client = new SlitClient; client->client_window = w; XWMHints *wmhints = XGetWMHints(display, w); if (wmhints) { if ((wmhints->flags & IconWindowHint) && (wmhints->icon_window != None)) { // some dock apps use separate windows, we need to hide these XMoveWindow(display, client->client_window, screen->screenInfo().width() + 10, screen->screenInfo().height() + 10); XMapWindow(display, client->client_window); client->icon_window = wmhints->icon_window; client->window = client->icon_window; } else { client->icon_window = None; client->window = client->client_window; } XFree(wmhints); } else { client->icon_window = None; client->window = client->client_window; } XWindowAttributes attrib; if (XGetWindowAttributes(display, client->window, &attrib)) { client->rect.setSize(attrib.width, attrib.height); } else { client->rect.setSize(64, 64); } XSetWindowBorderWidth(display, client->window, 0); blackbox->XGrabServer(); XSelectInput(display, client->window, NoEventMask); XReparentWindow(display, client->window, frame.window, 0, 0); XMapWindow(display, client->window); XChangeSaveSet(display, client->window, SetModeInsert); XSelectInput(display, client->window, StructureNotifyMask | SubstructureNotifyMask | EnterWindowMask); blackbox->XUngrabServer(); clientList.push_back(client); blackbox->insertEventHandler(client->client_window, this); blackbox->insertEventHandler(client->icon_window, this); reconfigure(); } void Slit::removeClient(SlitClient *client, bool remap) { blackbox->removeEventHandler(client->client_window); blackbox->removeEventHandler(client->icon_window); clientList.remove(client); if (remap) { blackbox->XGrabServer(); XSelectInput(display, client->window, NoEventMask); XReparentWindow(display, client->window, screen->screenInfo().rootWindow(), frame.rect.x() + client->rect.x(), frame.rect.y() + client->rect.y()); XChangeSaveSet(display, client->window, SetModeDelete); blackbox->XUngrabServer(); } delete client; } struct SlitClientMatch { Window window; SlitClientMatch(Window w): window(w) {} inline bool operator()(const Slit::SlitClient* client) const { return (client->window == window); } }; void Slit::removeClient(Window w, bool remap) { SlitClientList::iterator it = clientList.begin(); const SlitClientList::iterator end = clientList.end(); it = std::find_if(it, end, SlitClientMatch(w)); if (it != end) removeClient(*it, remap); if (clientList.empty()) screen->destroySlit(); else reconfigure(); } void Slit::reconfigure(void) { assert(!clientList.empty()); SlitClientList::iterator it = clientList.begin(); const SlitClientList::iterator end = clientList.end(); SlitClient *client; unsigned int width = 0, height = 0; const SlitOptions &options = screen->resource().slitOptions(); const SlitStyle &style = screen->resource().slitStyle(); switch (options.direction) { case Vertical: for (; it != end; ++it) { client = *it; height += client->rect.height(); width = std::max(width, client->rect.width()); } width += (style.slit.borderWidth() + style.margin) * 2; height += (style.margin * (clientList.size() + 1)) + (style.slit.borderWidth() * 2); break; case Horizontal: for (; it != end; ++it) { client = *it; width += client->rect.width(); height = std::max(height, client->rect.height()); } width += (style.margin * (clientList.size() + 1)) + (style.slit.borderWidth() * 2); height += (style.slit.borderWidth() + style.margin) * 2; break; } frame.rect.setSize(width, height); reposition(); XMapWindow(display, frame.window); const bt::Texture &texture = style.slit; frame.pixmap = bt::PixmapCache::find(screen->screenNumber(), texture, frame.rect.width(), frame.rect.height(), frame.pixmap); /*** START: BBDOCK PATCH FOR DOCK APPS THAT USE ParentRelative **************/ if ((texture.texture() & bt::Texture::Gradient) && frame.pixmap) XSetWindowBackgroundPixmap(display, frame.window, frame.pixmap); else if ((texture.texture() & bt::Texture::Solid)) XSetWindowBackground(display, frame.window, texture.color1().pixel(screen->screenNumber())); /*** STOP: BBDOCK PATCH FOR DOCK APPS THAT USE ParentRelative ***************/ XClearArea(display, frame.window, 0, 0, frame.rect.width(), frame.rect.height(), True); it = clientList.begin(); int x, y; x = y = style.slit.borderWidth() + style.margin; switch (options.direction) { case Vertical: for (; it != end; ++it) { client = *it; x = (frame.rect.width() - client->rect.width()) / 2; XMoveResizeWindow(display, client->window, x, y, client->rect.width(), client->rect.height()); XMapWindow(display, client->window); // for ICCCM compliance client->rect.setPos(x, y); XEvent event; event.type = ConfigureNotify; event.xconfigure.display = display; event.xconfigure.event = client->window; event.xconfigure.window = client->window; event.xconfigure.x = x; event.xconfigure.y = y; event.xconfigure.width = client->rect.width(); event.xconfigure.height = client->rect.height(); event.xconfigure.border_width = 0; event.xconfigure.above = frame.window; event.xconfigure.override_redirect = False; XSendEvent(display, client->window, False, StructureNotifyMask, &event); y += client->rect.height() + style.margin; } break; case Horizontal: for (; it != end; ++it) { client = *it; y = (frame.rect.height() - client->rect.height()) / 2; XMoveResizeWindow(display, client->window, x, y, client->rect.width(), client->rect.height()); XMapWindow(display, client->window); // for ICCCM compliance client->rect.setPos(x, y); XEvent event; event.type = ConfigureNotify; event.xconfigure.display = display; event.xconfigure.event = client->window; event.xconfigure.window = client->window; event.xconfigure.x = x; event.xconfigure.y = y; event.xconfigure.width = client->rect.width(); event.xconfigure.height = client->rect.height(); event.xconfigure.border_width = 0; event.xconfigure.above = frame.window; event.xconfigure.override_redirect = False; XSendEvent(display, client->window, False, StructureNotifyMask, &event); x += client->rect.width() + style.margin; } break; } } void Slit::updateStrut(void) { strut.top = strut.bottom = strut.left = strut.right = 0; const SlitOptions &options = screen->resource().slitOptions(); switch (options.direction) { case Vertical: switch (options.placement) { case TopCenter: strut.top = exposedHeight(); break; case BottomCenter: strut.bottom = exposedHeight(); break; case TopLeft: case CenterLeft: case BottomLeft: strut.left = exposedWidth(); break; case TopRight: case CenterRight: case BottomRight: strut.right = exposedWidth(); break; } break; case Horizontal: switch (options.placement) { case TopCenter: case TopLeft: case TopRight: strut.top = frame.rect.top() + exposedHeight(); break; case BottomCenter: case BottomLeft: case BottomRight: strut.bottom = (screen->screenInfo().rect().bottom() - (options.auto_hide ? frame.y_hidden : frame.rect.y())); break; case CenterLeft: strut.left = exposedWidth(); break; case CenterRight: strut.right = exposedWidth(); break; } break; } screen->updateStrut(); } void Slit::reposition(void) { int x = 0, y = 0; const SlitOptions &options = screen->resource().slitOptions(); const SlitStyle &style = screen->resource().slitStyle(); switch (options.placement) { case TopLeft: case CenterLeft: case BottomLeft: x = 0; frame.x_hidden = style.margin - frame.rect.width(); if (options.placement == TopLeft) y = 0; else if (options.placement == CenterLeft) y = (screen->screenInfo().height() - frame.rect.height()) / 2; else y = screen->screenInfo().height() - frame.rect.height(); break; case TopCenter: case BottomCenter: x = (screen->screenInfo().width() - frame.rect.width()) / 2; frame.x_hidden = x; if (options.placement == TopCenter) y = 0; else y = screen->screenInfo().height() - frame.rect.height(); break; case TopRight: case CenterRight: case BottomRight: x = screen->screenInfo().width() - frame.rect.width(); frame.x_hidden = screen->screenInfo().width() - style.margin; if (options.placement == TopRight) y = 0; else if (options.placement == CenterRight) y = (screen->screenInfo().height() - frame.rect.height()) / 2; else y = screen->screenInfo().height() - frame.rect.height(); break; } frame.rect.setPos(x, y); if (screen->toolbar()) { bt::Rect tbar_rect = screen->toolbar()->rect(); bt::Rect slit_rect = frame.rect; if (slit_rect.intersects(tbar_rect)) { int delta = screen->toolbar()->exposedHeight(); if (frame.rect.bottom() <= tbar_rect.bottom()) delta = -delta; frame.rect.setY(frame.rect.y() + delta); } } if (options.placement == TopCenter) frame.y_hidden = 0 - frame.rect.height() + style.margin; else if (options.placement == BottomCenter) frame.y_hidden = screen->screenInfo().height() - style.margin; else frame.y_hidden = frame.rect.y(); updateStrut(); if (hidden) XMoveResizeWindow(display, frame.window, frame.x_hidden, frame.y_hidden, frame.rect.width(), frame.rect.height()); else XMoveResizeWindow(display, frame.window, frame.rect.x(), frame.rect.y(), frame.rect.width(), frame.rect.height()); } void Slit::shutdown(void) { for (;;) { bool done = clientList.size() == 1; removeClient(clientList.front()); if (done) break; } } void Slit::buttonPressEvent(const XButtonEvent * const event) { if (event->window != frame.window) return; switch (event->button) { case Button1: screen->raiseWindow(this); break; case Button2: screen->lowerWindow(this); break; case Button3: screen->slitmenu()->popup(event->x_root, event->y_root, screen->availableArea()); break; } } void Slit::enterNotifyEvent(const XCrossingEvent * const /*unused*/) { if (!screen->resource().slitOptions().auto_hide) return; if (hidden) { if (! timer->isTiming()) timer->start(); } else { if (timer->isTiming()) timer->stop(); } } void Slit::leaveNotifyEvent(const XCrossingEvent * const /*unused*/) { if (!screen->resource().slitOptions().auto_hide) return; if (hidden) { if (timer->isTiming()) timer->stop(); } else if (! screen->slitmenu()->isVisible()) { if (! timer->isTiming()) timer->start(); } } void Slit::configureRequestEvent(const XConfigureRequestEvent * const event) { XWindowChanges xwc; xwc.x = event->x; xwc.y = event->y; xwc.width = event->width; xwc.height = event->height; xwc.border_width = 0; xwc.sibling = event->above; xwc.stack_mode = event->detail; XConfigureWindow(display, event->window, event->value_mask, &xwc); SlitClientList::iterator it = clientList.begin(); const SlitClientList::iterator end = clientList.end(); for (; it != end; ++it) { SlitClient *client = *it; if (client->window == event->window && (static_cast(client->rect.width()) != event->width || static_cast(client->rect.height()) != event->height)) { client->rect.setSize(event->width, event->height); reconfigure(); return; } } } void Slit::timeout(bt::Timer *) { hidden = !hidden; if (hidden) XMoveWindow(display, frame.window, frame.x_hidden, frame.y_hidden); else XMoveWindow(display, frame.window, frame.rect.x(), frame.rect.y()); } void Slit::toggleAutoHide(void) { updateStrut(); if (!screen->resource().slitOptions().auto_hide && hidden) { // force the slit to be visible if (timer->isTiming()) timer->stop(); timer->fireTimeout(); } } void Slit::unmapNotifyEvent(const XUnmapEvent * const event) { removeClient(event->window); } void Slit::reparentNotifyEvent(const XReparentEvent * const event) { if (event->parent != frame.window) removeClient(event->window, False); } void Slit::exposeEvent(const XExposeEvent * const event) { bt::drawTexture(screen->screenNumber(), screen->resource().slitStyle().slit, frame.window, bt::Rect(0, 0, frame.rect.width(), frame.rect.height()), bt::Rect(event->x, event->y, event->width, event->height), frame.pixmap); } Slit::Direction Slit::direction(void) const { const SlitOptions &options = screen->resource().slitOptions(); return static_cast(options.direction); } Slit::Placement Slit::placement(void) const { const SlitOptions &options = screen->resource().slitOptions(); return static_cast(options.placement); }