Fix handling of Shape, stage 2 (more involved/complete handling)
@@ -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
@@ -96,8 +96,8 @@ m_focused_alpha(0),
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 @@ m_visible = true;
if (m_need_render) { renderAll(); - applyAll(); + applyAll(); clearAll(); }@@ -235,8 +235,7 @@ m_height_before_shade = m_window.height();
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 @@ } else {
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_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())); + if (m_disable_themeshape) + m_shape.setPlaces(Shape::NONE); + else + m_shape.setPlaces(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 @@
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
@@ -33,6 +33,7 @@ #include "FbTk/FbPixmap.hh"
#include "FbTk/XLayerItem.hh" #include "FbTk/TextButton.hh" #include "Container.hh" +#include "Shape.hh" #include <vector> #include <list>@@ -197,6 +198,8 @@ //@}
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<Shape> m_shape; - bool m_disable_shape; + Shape m_shape; + bool m_disable_themeshape; }; #endif // FBWINFRAME_HH
@@ -48,25 +48,13 @@ #include <algorithm>
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; +namespace { +/* rows is an array of 8 bytes, i.e. 8x8 bits */ +Pixmap makePixmap(FbTk::FbWindow &drawable, const unsigned char rows[]) { - 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 }; + Display *disp = FbTk::App::instance()->display(); - 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); - - 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 @@
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; y<pixmap_height; y++) { - for (int x=0; x<pixmap_width; x++) { - XPutPixel(ximage, x, y, (left_bits[y] & (0x01 << x)) ? 1 : 0); - } + for (int y=0; y<8; y++) { + for (int x=0; x<8; x++) { + XPutPixel(ximage, x, y, (rows[y] & (0x01 << x)) ? 0 : 1); // inverted, it is subtracted } } - if (m_shapeplaces & Shape::TOPRIGHT) { - for (int y=0; y<pixmap_height; y++) { - for (int x=0; x<pixmap_width; x++) { - XPutPixel(ximage, x + win_width - pixmap_width, y, - (right_bits[y] & (0x01 << x)) ? 1 : 0); - } - } - } + FbTk::FbPixmap pm(drawable, 8, 8, 1); + FbTk::GContext gc(pm); - if (m_shapeplaces & Shape::BOTTOMLEFT) { - for (int y=0; y<pixmap_height; y++) { - for (int x=0; x<pixmap_width; x++) { - XPutPixel(ximage, x, y + win_height - pixmap_height, - (bottom_left_bits[y] & (0x01 << x)) ? 1 : 0); - } - } - } + XPutImage(disp, pm.drawable(), gc.gc(), ximage, 0, 0, 0, 0, + 8, 8); - if (m_shapeplaces & Shape::BOTTOMRIGHT) { - for (int y=0; y<pixmap_height; y++) { - for (int x=0; x<pixmap_width; x++) { - XPutPixel(ximage, x + win_width - pixmap_width, y + win_height - pixmap_height, - (bottom_right_bits[y] & (0x01 << x)) ? 1 : 0); - } - } - } - - FbTk::FbPixmap *pm = new FbTk::FbPixmap(*m_win, win_width, win_height, 1); - - - FbTk::GContext gc(*pm); - - XPutImage(disp, pm->drawable(), gc.gc(), ximage, 0, 0, 0, 0, - win_width, win_height); + XDestroyImage(ximage); - XDestroyImage(ximage); + return pm.release(); +} - return pm; +}; -} +std::vector<Shape::CornerPixmaps> 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 @@ ShapeClip,
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::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 @@ m_win = &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 @@
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; -}
@@ -24,12 +24,14 @@
#ifndef SHAPE_HH #define SHAPE_HH +#include "FbTk/FbPixmap.hh" + #include <X11/Xlib.h> #include <memory> +#include <vector> namespace FbTk { class FbWindow; -class FbPixmap; } /// creates round corners on windows@@ -51,6 +53,10 @@ /// update our shape
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 @@ static void setShapeNotify(const FbTk::FbWindow &win);
/// @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<FbTk::FbPixmap> m_clipshape; ///< our shape pixmap - std::auto_ptr<FbTk::FbPixmap> 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<CornerPixmaps> s_corners; int m_shapeplaces; ///< places to shape };
@@ -268,7 +268,6 @@ m_current_state(0),
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,15 +349,9 @@ assert(m_client);
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@@ -548,35 +541,11 @@ sendConfigureNotify();
// 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) {@@ -995,8 +964,10 @@ // in case the window is being destroyed, but this should never happen
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();@@ -1041,6 +1012,8 @@ unsigned int width, unsigned int height,
int gravity, unsigned int client_bw) { updateTitleFromClient(*m_client); updateIconNameFromClient(*m_client); + + frame().setShapingClient(m_client, false); if (use_attrs) frame().moveResizeForClient(x, y,@@ -1299,7 +1272,6 @@ if (send_event && ! moving) {
sendConfigureNotify(); } - shape(); if (!moving) { m_last_resize_x = new_x;@@ -1318,8 +1290,6 @@ frame().moveResizeForClient(new_x, new_y, new_width, new_height, gravity, client_bw);
setFocusFlag(focused); shaded = false; sendConfigureNotify(); - - shape(); if (!moving) { m_last_resize_x = new_x;@@ -1617,7 +1587,7 @@ } else if (!flag && isFullscreen()) {
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 @@ cerr<<"ShapeNotify("<<title()<<")"<<endl;
#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;
@@ -454,8 +454,6 @@ void setupWindow();
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 @@ struct _functions {
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