Fix handling of Shape, stage 2 (more involved/complete handling)

This commit is contained in:
simonb 2007-08-09 03:45:31 +00:00
parent a04eed16c5
commit a0f44b9e9a
7 changed files with 279 additions and 200 deletions

View file

@ -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

View file

@ -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_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 @@ 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

View file

@ -33,6 +33,7 @@
#include "FbTk/XLayerItem.hh"
#include "FbTk/TextButton.hh"
#include "Container.hh"
#include "Shape.hh"
#include <vector>
#include <list>
@ -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<Shape> m_shape;
bool m_disable_shape;
Shape m_shape;
bool m_disable_themeshape;
};
#endif // FBWINFRAME_HH

View file

@ -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; 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);
}
}
}
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);
XPutImage(disp, pm.drawable(), gc.gc(), ximage, 0, 0, 0, 0,
8, 8);
XDestroyImage(ximage);
return pm;
return pm.release();
}
};
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 @@ 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));
}
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);
/**
* 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;
}
// 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);
XRectangle rect;
rect.x = 0;
rect.y = 0;
rect.width = width;
rect.height = height;
// 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);
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);
}
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;
}

View file

@ -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 @@ 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<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
};

View file

@ -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;

View file

@ -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