From a0f44b9e9a7c2e401e2cf1ef80fed98acf1d7e44 Mon Sep 17 00:00:00 2001 From: simonb Date: Thu, 9 Aug 2007 03:45:31 +0000 Subject: Fix handling of Shape, stage 2 (more involved/complete handling) --- ChangeLog | 6 + src/FbWinFrame.cc | 39 +++---- src/FbWinFrame.hh | 7 +- src/Shape.cc | 334 +++++++++++++++++++++++++++++++++++------------------- src/Shape.hh | 26 ++++- src/Window.cc | 64 ++--------- src/Window.hh | 3 - 7 files changed, 279 insertions(+), 200 deletions(-) diff --git a/ChangeLog b/ChangeLog index fed2ab2..7dd6ee5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,12 @@ (Format: Year/Month/Day) Changes for 1.0.0: *07/08/09: + * Fix shaping handling, stage 2, (Simon) + - rewrite the core of the Shape handling so that it properly + merges client and frame shapes. Fixes all sorts of odd shaping + behaviour, and incidentally xeyes now gets a visible frame + (not having the frame was actually a bug). + Shape.hh/cc FbWinFrame.hh/cc Window.hh/cc * Fix shaping handling, stage 1, (Simon) - do borders properly with rounded corners - propagate client clip mask as well as bounding mask diff --git a/src/FbWinFrame.cc b/src/FbWinFrame.cc index d3b3d2f..fbf1102 100644 --- a/src/FbWinFrame.cc +++ b/src/FbWinFrame.cc @@ -96,8 +96,8 @@ FbWinFrame::FbWinFrame(BScreen &screen, FbWinFrameTheme &theme, FbTk::ImageContr m_unfocused_alpha(0), m_double_click_time(0), m_themelistener(*this), - m_shape(new Shape(m_window, theme.shapePlace())), - m_disable_shape(false) { + m_shape(m_window, theme.shapePlace()), + m_disable_themeshape(false) { m_theme.reconfigSig().attach(&m_themelistener); init(); } @@ -210,7 +210,7 @@ void FbWinFrame::show() { if (m_need_render) { renderAll(); - applyAll(); + applyAll(); clearAll(); } @@ -235,8 +235,7 @@ void FbWinFrame::shade() { m_window.resize(m_window.width(), m_titlebar.height()); alignTabs(); // need to update our shape - if ( m_shape.get() ) - m_shape->update(); + m_shape.update(); } else { // should be unshaded m_window.resize(m_window.width(), m_height_before_shade); reconfigure(); @@ -1122,26 +1121,28 @@ void FbWinFrame::reconfigure() { m_need_render = true; } - if (m_shape.get() && theme().shapePlace() == Shape::NONE || m_disable_shape) - m_shape.reset(0); - else if (m_shape.get() == 0 && theme().shapePlace() != Shape::NONE) - m_shape.reset(new Shape(window(), theme().shapePlace())); - else if (m_shape.get()) - m_shape->setPlaces(theme().shapePlace()); + if (m_disable_themeshape) + m_shape.setPlaces(Shape::NONE); + else + m_shape.setPlaces(theme().shapePlace()); - if (m_shape.get()) - m_shape->update(); + m_shape.setShapeOffsets(0, titlebarHeight()); // titlebar stuff rendered already by reconftitlebar } void FbWinFrame::setUseShape(bool value) { - m_disable_shape = !value; + m_disable_themeshape = !value; + + if (m_disable_themeshape) + m_shape.setPlaces(Shape::NONE); + else + m_shape.setPlaces(theme().shapePlace()); + +} - if (m_shape.get() && m_disable_shape) - m_shape.reset(0); - else if (m_shape.get() == 0 && !m_disable_shape) - m_shape.reset(new Shape(window(), theme().shapePlace())); +void FbWinFrame::setShapingClient(FbTk::FbWindow *win, bool always_update) { + m_shape.setShapeSource(win, 0, titlebarHeight(), always_update); } unsigned int FbWinFrame::buttonHeight() const { @@ -1452,7 +1453,7 @@ void FbWinFrame::init() { if (theme().handleWidth() == 0) m_use_handle = false; - m_disable_shape = false; + m_disable_themeshape = false; m_current_label = 0; // no focused button at first diff --git a/src/FbWinFrame.hh b/src/FbWinFrame.hh index 87c290c..1eefe42 100644 --- a/src/FbWinFrame.hh +++ b/src/FbWinFrame.hh @@ -33,6 +33,7 @@ #include "FbTk/XLayerItem.hh" #include "FbTk/TextButton.hh" #include "Container.hh" +#include "Shape.hh" #include #include @@ -197,6 +198,8 @@ public: void reconfigure(); void setUseShape(bool value); + void setShapingClient(FbTk::FbWindow *win, bool always_update); + void updateShape() { m_shape.update(); } /** @name accessors @@ -403,9 +406,9 @@ private: FbWinFrame &m_frame; }; ThemeListener m_themelistener; - std::auto_ptr m_shape; - bool m_disable_shape; + Shape m_shape; + bool m_disable_themeshape; }; #endif // FBWINFRAME_HH diff --git a/src/Shape.cc b/src/Shape.cc index a073b9f..7198d00 100644 --- a/src/Shape.cc +++ b/src/Shape.cc @@ -48,25 +48,13 @@ using std::min; -// ignore_border basically means do clip shape instead of bounding shape -FbTk::FbPixmap *Shape::createShape(bool ignore_border) { - if (m_win->window() == 0 || m_shapeplaces == 0 || - m_win->width() < 3 || m_win->height() < 3) - return 0; - - static char left_bits[] = { 0xc0, 0xf8, 0xfc, 0xfe, 0xfe, 0xfe, 0xff, 0xff }; - static char right_bits[] = { 0x03, 0x1f, 0x3f, 0x7f, 0x7f, 0x7f, 0xff, 0xff}; - static char bottom_left_bits[] = { 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfc, 0xf8, 0xc0 }; - static char bottom_right_bits[] = { 0xff, 0xff, 0x7f, 0x7f, 0x7f, 0x3f, 0x1f, 0x03 }; - - const int borderw = m_win->borderWidth(); - const int win_width = m_win->width() + (ignore_border?0:2*borderw); - const int win_height = m_win->height() + (ignore_border?0:2*borderw); - const int pixmap_width = min(8, win_width); - const int pixmap_height = min(8, win_height); +namespace { +/* rows is an array of 8 bytes, i.e. 8x8 bits */ +Pixmap makePixmap(FbTk::FbWindow &drawable, const unsigned char rows[]) { Display *disp = FbTk::App::instance()->display(); - const size_t data_size = win_width * win_height; + + const size_t data_size = 8 * 8; // we use calloc here so we get consistent C alloc/free with XDestroyImage // and no warnings in valgrind :) char *data = (char *)calloc(data_size, sizeof (char)); @@ -76,74 +64,50 @@ FbTk::FbPixmap *Shape::createShape(bool ignore_border) { memset(data, 0xFF, data_size); XImage *ximage = XCreateImage(disp, - DefaultVisual(disp, m_win->screenNumber()), + DefaultVisual(disp, drawable.screenNumber()), 1, XYPixmap, 0, data, - win_width, win_height, + 8, 8, 32, 0); if (ximage == 0) return 0; XInitImage(ximage); - // shape corners - - if (m_shapeplaces & Shape::TOPLEFT) { - for (int y=0; ydrawable(), gc.gc(), ximage, 0, 0, 0, 0, - win_width, win_height); + XPutImage(disp, pm.drawable(), gc.gc(), ximage, 0, 0, 0, 0, + 8, 8); XDestroyImage(ximage); - return pm; - + return pm.release(); } +}; + +std::vector Shape::s_corners; + Shape::Shape(FbTk::FbWindow &win, int shapeplaces): m_win(&win), + m_shapesource(0), + m_shapesource_xoff(0), + m_shapesource_yoff(0), m_shapeplaces(shapeplaces) { - m_clipshape.reset(createShape(true)); - m_boundingshape.reset(createShape(false)); +#ifdef SHAPE + initCorners(win.screenNumber()); +#endif + + update(); } Shape::~Shape() { @@ -157,19 +121,32 @@ Shape::~Shape() { 0, 0, 0, ShapeSet); - // Reset shape of window - if (m_boundingshape.get()) { - XShapeCombineMask(FbTk::App::instance()->display(), - m_win->window(), - ShapeBounding, - 0, 0, - 0, - ShapeSet); - } - } + XShapeCombineMask(FbTk::App::instance()->display(), + m_win->window(), + ShapeBounding, + 0, 0, + 0, + ShapeSet); + } #endif // SHAPE } +void Shape::initCorners(int screen_num) { + if (s_corners.size() == 0) + s_corners.resize(ScreenCount(FbTk::App::instance()->display())); + + static const unsigned char left_bits[] = { 0xc0, 0xf8, 0xfc, 0xfe, 0xfe, 0xfe, 0xff, 0xff }; + static const unsigned char right_bits[] = { 0x03, 0x1f, 0x3f, 0x7f, 0x7f, 0x7f, 0xff, 0xff}; + static const unsigned char bottom_left_bits[] = { 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfc, 0xf8, 0xc0 }; + static const unsigned char bottom_right_bits[] = { 0xff, 0xff, 0x7f, 0x7f, 0x7f, 0x3f, 0x1f, 0x03 }; + + s_corners[screen_num].topleft = ::makePixmap(*m_win, left_bits); + s_corners[screen_num].topright = ::makePixmap(*m_win, right_bits); + s_corners[screen_num].botleft = ::makePixmap(*m_win, bottom_left_bits); + s_corners[screen_num].botright = ::makePixmap(*m_win, bottom_right_bits); + +} + void Shape::setPlaces(int shapeplaces) { m_shapeplaces = shapeplaces; } @@ -177,41 +154,144 @@ void Shape::setPlaces(int shapeplaces) { void Shape::update() { if (m_win == 0 || m_win->window() == 0) return; + #ifdef SHAPE - if (m_clipshape.get() == 0 || - m_win->width() != clipWidth() || - m_win->height() != clipHeight()) { - m_clipshape.reset(createShape(true)); + /** + * Set the client's shape in position, + * or wipe the shape and return. + */ + Display *display = FbTk::App::instance()->display(); + int bw = m_win->borderWidth(); + int width = m_win->width(); + int height = m_win->height(); + + if (m_shapesource == 0 && m_shapeplaces == 0) { + /* clear the shape and return */ + XShapeCombineMask(display, + m_win->window(), ShapeClip, + 0, 0, + None, ShapeSet); + XShapeCombineMask(display, + m_win->window(), ShapeBounding, + 0, 0, + None, ShapeSet); + return; } - if (m_boundingshape.get() == 0 || - (m_win->width()+m_win->borderWidth()*2) != width() || - (m_win->height()+m_win->borderWidth()*2) != height()) { - if (m_win->borderWidth() != 0) - m_boundingshape.reset(createShape(false)); - else - m_boundingshape.reset(0); + XRectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = width; + rect.height = height; + + XShapeCombineRectangles(display, + m_win->window(), ShapeClip, + 0, 0, /* offsets */ + &rect, + 1, /* number of rectangles */ + ShapeSet, /* op */ + 2 /* ordering: YXSorted... only 1: doesn't matter */ ); + + rect.x = -bw; + rect.y = -bw; + rect.width = width+2*bw; + rect.height = height+2*bw; + + XShapeCombineRectangles(display, + m_win->window(), ShapeBounding, + 0, 0, /* offsets */ + &rect, + 1, /* number of rectangles */ + ShapeSet, /* op */ + 2 /* ordering: YXSorted... only 1: doesn't matter */ ); + + if (m_shapesource != 0) { + + /* + Copy the shape from the source. + We achieve this by subtracting the client-area size from the shape, and then + unioning in the client's mask. + */ + rect.x = m_shapesource_xoff; + rect.y = m_shapesource_yoff; + rect.width = m_shapesource->width(); + rect.height = m_shapesource->height(); + + XShapeCombineRectangles(display, + m_win->window(), ShapeClip, + 0, 0, /* offsets */ + &rect, + 1, /* number of rectangles */ + ShapeSubtract, /* op */ + 2 /* ordering: YXSorted... only 1: doesn't matter */ ); + + XShapeCombineShape(display, + m_win->window(), ShapeClip, + rect.x, rect.y, // xOff, yOff + m_shapesource->window(), + ShapeClip, ShapeUnion); + + /* + Now the bounding rectangle. Note that the frame has a shared border with the region above the + client (i.e. titlebar), so we don't want to wipe the shared border, hence the adjustments. + */ + rect.x = m_shapesource_xoff; // note that the full bounding region is already offset by a -borderwidth! + rect.y = m_shapesource_yoff; + rect.width = m_shapesource->width(); // we don't wipe the outer bounding region [i think] + rect.height = m_shapesource->height(); + + // we want to delete the client area, dont care about borders really + XShapeCombineRectangles(display, + m_win->window(), ShapeBounding, + 0, 0, /* offsets */ + &rect, + 1, /* number of rectangles */ + ShapeSubtract, /* op */ + 2 /* ordering: YXSorted... only 1: doesn't matter */ ); + + XShapeCombineShape(display, + m_win->window(), ShapeBounding, + rect.x , rect.y, // xOff, yOff + m_shapesource->window(), + ShapeBounding, ShapeUnion); } - // the m_shape can be = 0 which will just raeset the shape mask - // and make the window normal - XShapeCombineMask(FbTk::App::instance()->display(), - m_win->window(), - ShapeClip, - 0, 0, - (m_clipshape.get() != 0)? m_clipshape->drawable() : 0, - ShapeSet); - - // the m_shape can be = 0 which will just raeset the shape mask - // and make the window normal - XShapeCombineMask(FbTk::App::instance()->display(), - m_win->window(), - ShapeBounding, - -m_win->borderWidth(), -m_win->borderWidth(), - (m_boundingshape.get() != 0)? m_boundingshape->drawable() : - ((m_clipshape.get() != 0)? m_clipshape->drawable(): 0), - ShapeSet); + CornerPixmaps &corners = s_corners[m_win->screenNumber()]; +#define SHAPECORNER(corner, x, y, shapekind) \ + XShapeCombineMask(FbTk::App::instance()->display(), \ + m_win->window(), \ + shapekind, \ + x, y, \ + corners.corner.drawable(), \ + ShapeSubtract); + + /** + * Set the top corners if the y offset is nonzero. + */ + if (m_shapesource == 0 || m_shapesource_yoff != 0) { + if (m_shapeplaces & TOPLEFT) { + SHAPECORNER(topleft, 0, 0, ShapeClip); + SHAPECORNER(topleft, -bw, -bw, ShapeBounding); + } + if (m_shapeplaces & TOPRIGHT) { + SHAPECORNER(topright, width-8, 0, ShapeClip); + SHAPECORNER(topright, width+bw-8, -bw, ShapeBounding); + } + } + + // note that the bottom corners y-vals are offset by 8 (the height of the corner pixmaps) + if (m_shapesource == 0 || (m_shapesource_yoff+(signed) m_shapesource->height()) < height + || m_shapesource_yoff >= height /* shaded */) { + if (m_shapeplaces & BOTTOMLEFT) { + SHAPECORNER(botleft, 0, height-8, ShapeClip); + SHAPECORNER(botleft, -bw, height+bw-8, ShapeBounding); + } + if (m_shapeplaces & BOTTOMRIGHT) { + SHAPECORNER(botright, width-8, height-8, ShapeClip); + SHAPECORNER(botright, width+bw-8, height+bw-8, ShapeBounding); + } + } #endif // SHAPE @@ -222,6 +302,41 @@ void Shape::setWindow(FbTk::FbWindow &win) { update(); } +/** + * set the shape source to the given window. + * This is purely for client windows at the moment, where the offsets and height/width of the + * target window and the source window are used to determine whether to shape a given corner. + * + * (note: xoffset will always be zero, and widths always match, so we ignore those) + * + * i.e. if the yoffset is not zero, then the top corners are shaped. + * if the target height is bigger than the source plus yoffset, then the bottom corners are + * shaped. + * + * If *either* the top or bottom corners are not shaped due to this, but a shape source window + * is given, then the bounding shape has the borders alongside the source window deleted, otherwise + * they are left hanging outside the client's shape. + */ +void Shape::setShapeSource(FbTk::FbWindow *win, int xoff, int yoff, bool always_update) { + if (win != 0 && !isShaped(*win)) { + win = 0; + if (m_shapesource == 0 && !always_update) + return; + } + + // even if source is same, want to update the shape on it + m_shapesource = win; + m_shapesource_xoff = xoff; + m_shapesource_yoff = yoff; + update(); +} + +void Shape::setShapeOffsets(int xoff, int yoff) { + m_shapesource_xoff = xoff; + m_shapesource_yoff = yoff; + update(); +} + void Shape::setShapeNotify(const FbTk::FbWindow &win) { #ifdef SHAPE XShapeSelectInput(FbTk::App::instance()->display(), @@ -248,18 +363,3 @@ bool Shape::isShaped(const FbTk::FbWindow &win) { return (shaped != 0 ? true : false); } -unsigned int Shape::width() const { - return m_boundingshape.get() ? m_boundingshape->width() : 0; -} - -unsigned int Shape::height() const { - return m_boundingshape.get() ? m_boundingshape->height() : 0; -} - -unsigned int Shape::clipWidth() const { - return m_clipshape.get() ? m_clipshape->width() : 0; -} - -unsigned int Shape::clipHeight() const { - return m_clipshape.get() ? m_clipshape->height() : 0; -} diff --git a/src/Shape.hh b/src/Shape.hh index c8db79a..5f26d05 100644 --- a/src/Shape.hh +++ b/src/Shape.hh @@ -24,12 +24,14 @@ #ifndef SHAPE_HH #define SHAPE_HH +#include "FbTk/FbPixmap.hh" + #include #include +#include namespace FbTk { class FbWindow; -class FbPixmap; } /// creates round corners on windows @@ -51,6 +53,10 @@ public: void update(); /// assign a new window void setWindow(FbTk::FbWindow &win); + /// Assign a window to merge our shape with. + /// (note that this is currently specific to frames) + void setShapeSource(FbTk::FbWindow *win, int xoff, int yoff, bool always_update); + void setShapeOffsets(int xoff, int yoff); unsigned int width() const; unsigned int height() const; unsigned int clipWidth() const; @@ -60,11 +66,21 @@ public: /// @return true if window has shape static bool isShaped(const FbTk::FbWindow &win); private: - FbTk::FbPixmap *createShape(bool clipshape); // true for clip, false for bounding - FbTk::FbWindow *m_win; ///< window to be shaped - std::auto_ptr m_clipshape; ///< our shape pixmap - std::auto_ptr m_boundingshape; ///< our shape pixmap + FbTk::FbWindow *m_shapesource; ///< window to pull shape from + int m_shapesource_xoff, m_shapesource_yoff; + + void initCorners(int screen_num); + + struct CornerPixmaps { + FbTk::FbPixmap topleft; + FbTk::FbPixmap topright; + FbTk::FbPixmap botleft; + FbTk::FbPixmap botright; + }; + + // unfortunately, we need a separate pixmap per screen + static std::vector s_corners; int m_shapeplaces; ///< places to shape }; diff --git a/src/Window.cc b/src/Window.cc index ab917da..9ee1c5e 100644 --- a/src/Window.cc +++ b/src/Window.cc @@ -268,7 +268,6 @@ FluxboxWindow::FluxboxWindow(WinClient &client, FbWinFrameTheme &tm, m_old_decoration_mask(0), m_client(&client), m_toggled_decos(false), - m_shaped(false), m_icon_hidden(false), m_focus_hidden(false), m_old_pos_x(0), m_old_pos_y(0), @@ -350,16 +349,10 @@ void FluxboxWindow::init() { m_client->setFluxboxWindow(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); @@ -548,36 +541,12 @@ void FluxboxWindow::init() { // no focus default setFocusFlag(false); - if (m_shaped) - shape(); - setupWindow(); FbTk::App::instance()->sync(false); } -/// apply shape to this window -void FluxboxWindow::shape() { -#ifdef SHAPE - if (m_shaped) { - XShapeCombineShape(display, - frame().window().window(), ShapeClip, - 0, frame().clientArea().y(), // xOff, yOff - m_client->window(), - ShapeClip, ShapeSet); - 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, int x, int y) { //!! TODO: check for isGroupable in client @@ -995,8 +964,10 @@ bool FluxboxWindow::setCurrentClient(WinClient &client, bool setinput) { if (!button) return false; - if (&client != m_client) + if (&client != m_client) { m_screen.focusControl().setScreenFocusedWindow(client); + frame().setShapingClient(&client, false); + } m_client = &client; m_client->raise(); m_client->focusSig().notify(); @@ -1042,6 +1013,8 @@ void FluxboxWindow::associateClientWindow(bool use_attrs, updateTitleFromClient(*m_client); updateIconNameFromClient(*m_client); + frame().setShapingClient(m_client, false); + if (use_attrs) frame().moveResizeForClient(x, y, width, height, gravity, client_bw); @@ -1299,7 +1272,6 @@ void FluxboxWindow::moveResize(int new_x, int new_y, sendConfigureNotify(); } - shape(); if (!moving) { m_last_resize_x = new_x; @@ -1319,8 +1291,6 @@ void FluxboxWindow::moveResizeForClient(int new_x, int new_y, shaded = false; sendConfigureNotify(); - shape(); - if (!moving) { m_last_resize_x = new_x; m_last_resize_y = new_y; @@ -1617,7 +1587,7 @@ void FluxboxWindow::setFullscreen(bool flag) { fullscreen = false; - frame().setUseShape(!m_shaped); + frame().setUseShape(true); if (m_toggled_decos) { if (m_old_decoration_mask & DECORM_TITLEBAR) setDecoration(DECOR_NONE); @@ -2342,24 +2312,10 @@ void FluxboxWindow::handleEvent(XEvent &event) { #endif // DEBUG 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(), ShapeClip, - 0, 0, - None, ShapeSet); - XShapeCombineMask(display, - frame().window().window(), ShapeBounding, - 0, 0, - None, ShapeSet); - } + if (shape_event->shaped) + frame().setShapingClient(m_client, true); + else + frame().setShapingClient(0, true); FbTk::App::instance()->sync(false); break; diff --git a/src/Window.hh b/src/Window.hh index 15de2bb..92431ab 100644 --- a/src/Window.hh +++ b/src/Window.hh @@ -454,8 +454,6 @@ private: void updateButtons(); void init(); - /// applies a shape mask to the window if it has one - void shape(); void updateClientLeftWindow(); void grabButtons(); @@ -560,7 +558,6 @@ private: bool resize, move, iconify, maximize, close, tabable; } functions; - bool m_shaped; ///< if the window is shaped with a mask bool m_icon_hidden; ///< if the window is in the iconbar bool m_focus_hidden; ///< if the window is in the NextWindow list int m_old_pos_x, m_old_pos_y; ///< old position so we can restore from maximized -- cgit v0.11.2