1 #include "decoration.h"
2 
3 #include <X11/Xft/Xft.h>
4 #include <X11/Xlib.h>
5 #include <X11/Xutil.h>
6 #include <limits>
7 
8 #include "client.h"
9 #include "ewmh.h"
10 #include "font.h"
11 #include "fontdata.h"
12 #include "globals.h"
13 #include "settings.h"
14 #include "theme.h"
15 
16 using std::string;
17 using std::vector;
18 
19 std::map<Window,Client*> Decoration::decwin2client;
20 
21 // from openbox/frame.c
check_32bit_client(Client * c)22 static Visual* check_32bit_client(Client* c)
23 {
24     XWindowAttributes wattrib;
25     Status ret;
26 
27     ret = XGetWindowAttributes(g_display, c->window_, &wattrib);
28     HSWeakAssert(ret != BadDrawable);
29     HSWeakAssert(ret != BadWindow);
30 
31     if (wattrib.depth == 32) {
32         return wattrib.visual;
33     }
34     return nullptr;
35 }
36 
Decoration(Client * client,Settings & settings)37 Decoration::Decoration(Client* client, Settings& settings)
38     : client_(client),
39       settings_(settings)
40 {
41 }
42 
createWindow()43 void Decoration::createWindow() {
44     Decoration* dec = this;
45     XSetWindowAttributes at;
46     long mask = 0;
47     // copy attributes from client and not from the root window
48     visual = check_32bit_client(client_);
49     if (visual) {
50         /* client has a 32-bit visual */
51         mask = CWColormap | CWBackPixel | CWBorderPixel;
52         /* create a colormap with the visual */
53         dec->colormap = at.colormap =
54             XCreateColormap(g_display, g_root, visual, AllocNone);
55         at.background_pixel = BlackPixel(g_display, g_screen);
56         at.border_pixel = BlackPixel(g_display, g_screen);
57     } else {
58         dec->colormap = 0;
59     }
60     dec->depth = visual
61                  ? 32
62                  : (DefaultDepth(g_display, DefaultScreen(g_display)));
63     dec->decwin = XCreateWindow(g_display, g_root, 0,0, 30, 30, 0,
64                         dec->depth,
65                         InputOutput,
66                         visual
67                             ? visual
68                             : DefaultVisual(g_display, DefaultScreen(g_display)),
69                         mask, &at);
70     mask = 0;
71     if (visual) {
72         /* client has a 32-bit visual */
73         mask = CWColormap | CWBackPixel | CWBorderPixel;
74         // TODO: why does DefaultColormap work in openbox but crashes hlwm here?
75         // It somehow must be incompatible to the visual and thus causes the
76         // BadMatch on XCreateWindow
77         at.colormap = dec->colormap;
78         at.background_pixel = BlackPixel(g_display, g_screen);
79         at.border_pixel = BlackPixel(g_display, g_screen);
80     }
81     dec->bgwin = 0;
82     dec->bgwin = XCreateWindow(g_display, dec->decwin, 0,0, 30, 30, 0,
83                         dec->depth,
84                         InputOutput,
85                         CopyFromParent,
86                         mask, &at);
87     XMapWindow(g_display, dec->bgwin);
88     // use a clients requested initial floating size as the initial size
89     dec->last_rect_inner = true;
90     dec->last_inner_rect = client_->float_size_;
91     dec->last_outer_rect = client_->float_size_; // TODO: is this correct?
92     dec->last_actual_rect = dec->last_inner_rect;
93     dec->last_actual_rect.x -= dec->last_outer_rect.x;
94     dec->last_actual_rect.y -= dec->last_outer_rect.y;
95     decwin2client[decwin] = client_;
96     // set wm_class for window
97     XClassHint *hint = XAllocClassHint();
98     hint->res_name = (char*)HERBST_DECORATION_CLASS;
99     hint->res_class = (char*)HERBST_DECORATION_CLASS;
100     XSetClassHint(g_display, dec->decwin, hint);
101     XFree(hint);
102 }
103 
~Decoration()104 Decoration::~Decoration() {
105     decwin2client.erase(decwin);
106     if (colormap) {
107         XFreeColormap(g_display, colormap);
108     }
109     if (pixmap) {
110         XFreePixmap(g_display, pixmap);
111     }
112     if (bgwin) {
113         XDestroyWindow(g_display, bgwin);
114     }
115     if (decwin) {
116         XDestroyWindow(g_display, decwin);
117     }
118 }
119 
toClient(Window decoration_window)120 Client* Decoration::toClient(Window decoration_window)
121 {
122     auto cl = decwin2client.find(decoration_window);
123     if (cl == decwin2client.end()) {
124         return nullptr;
125     } else {
126         return cl->second;
127     }
128 }
129 
outline_to_inner_rect(Rectangle rect) const130 Rectangle DecorationScheme::outline_to_inner_rect(Rectangle rect) const {
131     return rect.adjusted(-*border_width, -*border_width)
132             .adjusted(-*padding_left, -*padding_top - *title_height,
133                       -*padding_right, -*padding_bottom);
134 }
135 
inner_rect_to_outline(Rectangle rect) const136 Rectangle DecorationScheme::inner_rect_to_outline(Rectangle rect) const {
137     return rect.adjusted(*border_width, *border_width)
138             .adjusted(*padding_left, *padding_top + *title_height,
139                       *padding_right, *padding_bottom);
140 }
141 
resize_inner(Rectangle inner,const DecorationScheme & scheme)142 void Decoration::resize_inner(Rectangle inner, const DecorationScheme& scheme) {
143     resize_outline(scheme.inner_rect_to_outline(inner), scheme);
144     last_rect_inner = true;
145 }
146 
inner_to_outer(Rectangle rect)147 Rectangle Decoration::inner_to_outer(Rectangle rect) {
148     if (!last_scheme) {
149         // if the decoration was never drawn, we can't do anything reasonable
150         return rect;
151     }
152     return last_scheme->inner_rect_to_outline(rect);
153 }
154 
155 /**
156  * @brief Tell whether clicking on the decoration at the specified location
157  * should result in resizing or moving the client
158  * @param the location of the cursor, relative on this window
159  * @return true if this should init resizing the client
160  * false if this should init moving the client
161  */
positionTriggersResize(Point2D p)162 bool Decoration::positionTriggersResize(Point2D p)
163 {
164     if (!last_scheme) {
165         // this should never happen, so we just randomly pick:
166         // always resize if there is no decoration scheme
167         return true;
168     }
169     const
170     auto border_width = static_cast<int>(last_scheme->border_width());
171     vector<Point2D> corners = {
172         {0,0},
173         {last_outer_rect.width - 1, last_outer_rect.height - 1},
174     };
175     for (const auto& c : corners) {
176         if (std::abs(p.x - c.x) < border_width) {
177             return true;
178         }
179         if (std::abs(p.y - c.y) < border_width) {
180             return true;
181         }
182     }
183     return false;
184 }
185 
resize_outline(Rectangle outline,const DecorationScheme & scheme)186 void Decoration::resize_outline(Rectangle outline, const DecorationScheme& scheme)
187 {
188     auto inner = scheme.outline_to_inner_rect(outline);
189     Window win = client_->window_;
190 
191     auto tile = inner;
192     client_->applysizehints(&inner.width, &inner.height);
193 
194     // center the window in the outline tile
195     // but only if it's relative coordinates would not be too close to the
196     // upper left tile border
197     int threshold = settings_.pseudotile_center_threshold;
198     int dx = tile.width/2 - inner.width/2;
199     int dy = tile.height/2 - inner.height/2;
200     inner.x = tile.x + ((dx < threshold) ? 0 : dx);
201     inner.y = tile.y + ((dy < threshold) ? 0 : dy);
202 
203     //if (RECTANGLE_EQUALS(client->last_size, rect)
204     //    && client->last_border_width == border_width) {
205     //    return;
206     //}
207 
208     if (scheme.tight_decoration()) {
209         // updating the outline only has an affect for tiled clients
210         // because for floating clients, this has been done already
211         // right when the window size changed.
212         outline = scheme.inner_rect_to_outline(inner);
213     }
214     last_inner_rect = inner;
215     inner.x -= outline.x;
216     inner.y -= outline.y;
217     XWindowChanges changes;
218     changes.x = inner.x;
219     changes.y = inner.y;
220     changes.width = inner.width;
221     changes.height = inner.height;
222     changes.border_width = 0;
223 
224     int mask = CWX | CWY | CWWidth | CWHeight | CWBorderWidth;
225     //if (*g_window_border_inner_width > 0
226     //    && *g_window_border_inner_width < *g_window_border_width) {
227     //    unsigned long current_border_color = get_window_border_color(client);
228     //    HSDebug("client_resize %s\n",
229     //            current_border_color == g_window_border_active_color
230     //            ? "ACTIVE" : "NORMAL");
231     //    set_window_double_border(g_display, win, *g_window_border_inner_width,
232     //                             g_window_border_inner_color,
233     //                             current_border_color);
234     //}
235     // send new size to client
236     // update structs
237     bool size_changed = outline.width != last_outer_rect.width
238                      || outline.height != last_outer_rect.height;
239     last_outer_rect = outline;
240     last_rect_inner = false;
241     client_->last_size_ = inner;
242     last_scheme = &scheme;
243     // redraw
244     // TODO: reduce flickering
245     if (!client_->dragged_ || settings_.update_dragged_clients()) {
246         last_actual_rect.x = changes.x;
247         last_actual_rect.y = changes.y;
248         last_actual_rect.width = changes.width;
249         last_actual_rect.height = changes.height;
250     }
251     redrawPixmap();
252     XSetWindowBackgroundPixmap(g_display, decwin, pixmap);
253     if (!size_changed) {
254         // if size changes, then the window is cleared automatically
255         XClearWindow(g_display, decwin);
256     }
257     if (!client_->dragged_ || settings_.update_dragged_clients()) {
258         XConfigureWindow(g_display, win, mask, &changes);
259         XMoveResizeWindow(g_display, bgwin,
260                           changes.x, changes.y,
261                           changes.width, changes.height);
262     }
263     XMoveResizeWindow(g_display, decwin,
264                       outline.x, outline.y, outline.width, outline.height);
265     updateFrameExtends();
266     if (!client_->dragged_ || settings_.update_dragged_clients()) {
267         client_->send_configure();
268     }
269     XSync(g_display, False);
270 }
271 
updateFrameExtends()272 void Decoration::updateFrameExtends() {
273     int left = last_inner_rect.x - last_outer_rect.x;
274     int top  = last_inner_rect.y - last_outer_rect.y;
275     int right = last_outer_rect.width - last_inner_rect.width - left;
276     int bottom = last_outer_rect.height - last_inner_rect.height - top;
277     client_->ewmh.updateFrameExtents(client_->window_, left,right, top,bottom);
278 }
279 
change_scheme(const DecorationScheme & scheme)280 void Decoration::change_scheme(const DecorationScheme& scheme) {
281     if (last_inner_rect.width < 0) {
282         // TODO: do something useful here
283         return;
284     }
285     if (last_rect_inner) {
286         resize_inner(last_inner_rect, scheme);
287     } else {
288         resize_outline(last_outer_rect, scheme);
289     }
290 }
291 
redraw()292 void Decoration::redraw()
293 {
294     if (last_scheme) {
295         change_scheme(*last_scheme);
296     }
297 }
298 
get_client_color(Color color)299 unsigned int Decoration::get_client_color(Color color) {
300     XColor xcol = color.toXColor();
301     if (colormap) {
302         /* get pixel value back appropriate for client */
303         XAllocColor(g_display, colormap, &xcol);
304         return xcol.pixel;
305     } else {
306         /* get pixel value back appropriate for main color map*/
307         XAllocColor(g_display, DefaultColormap(g_display, g_screen), &xcol);
308         return xcol.pixel;
309     }
310 }
311 
312 // draw a decoration to the client->dec.pixmap
redrawPixmap()313 void Decoration::redrawPixmap() {
314     if (!last_scheme) {
315         // do nothing if we don't have a scheme.
316         return;
317     }
318     const DecorationScheme& s = *last_scheme;
319     auto dec = this;
320     auto outer = last_outer_rect;
321     // TODO: maybe do something like pixmap recreate threshhold?
322     bool recreate_pixmap = (dec->pixmap == 0) || (dec->pixmap_width != outer.width)
323                                               || (dec->pixmap_height != outer.height);
324     if (recreate_pixmap) {
325         if (dec->pixmap) {
326             XFreePixmap(g_display, dec->pixmap);
327         }
328         dec->pixmap = XCreatePixmap(g_display, decwin, outer.width, outer.height, depth);
329     }
330     Pixmap pix = dec->pixmap;
331     GC gc = XCreateGC(g_display, pix, 0, nullptr);
332 
333     // draw background
334     XSetForeground(g_display, gc, get_client_color(s.border_color()));
335     XFillRectangle(g_display, pix, gc, 0, 0, outer.width, outer.height);
336 
337     // Draw inner border
338     unsigned short iw = s.inner_width();
339     auto inner = last_inner_rect;
340     inner.x -= last_outer_rect.x;
341     inner.y -= last_outer_rect.y;
342     if (iw > 0) {
343         /* fill rectangles because drawing does not work */
344         vector<XRectangle> rects{
345             { (short)(inner.x - iw), (short)(inner.y - iw), (unsigned short)(inner.width + 2*iw), iw }, /* top */
346             { (short)(inner.x - iw), (short)(inner.y), iw, (unsigned short)(inner.height) },  /* left */
347             { (short)(inner.x + inner.width), (short)(inner.y), iw, (unsigned short)(inner.height) }, /* right */
348             { (short)(inner.x - iw), (short)(inner.y + inner.height), (unsigned short)(inner.width + 2*iw), iw }, /* bottom */
349         };
350         XSetForeground(g_display, gc, get_client_color(s.inner_color()));
351         XFillRectangles(g_display, pix, gc, &rects.front(), rects.size());
352     }
353 
354     // Draw outer border
355     unsigned short ow = s.outer_width;
356     outer.x -= last_outer_rect.x;
357     outer.y -= last_outer_rect.y;
358     if (ow > 0) {
359         ow = std::min((int)ow, (outer.height+1) / 2);
360         vector<XRectangle> rects{
361             { 0, 0, (unsigned short)(outer.width), ow }, /* top */
362             { 0, (short)ow, ow, (unsigned short)(outer.height - 2*ow) }, /* left */
363             { (short)(outer.width - ow), (short)ow, ow, (unsigned short)(outer.height - 2*ow) }, /* right */
364             { 0, (short)(outer.height - ow), (unsigned short)(outer.width), ow }, /* bottom */
365         };
366         XSetForeground(g_display, gc, get_client_color(s.outer_color));
367         XFillRectangles(g_display, pix, gc, &rects.front(), rects.size());
368     }
369     // fill inner rect that is not covered by the client
370     XSetForeground(g_display, gc, get_client_color(s.background_color));
371     if (dec->last_actual_rect.width < inner.width) {
372         XFillRectangle(g_display, pix, gc,
373                        dec->last_actual_rect.x + dec->last_actual_rect.width,
374                        dec->last_actual_rect.y,
375                        inner.width - dec->last_actual_rect.width,
376                        dec->last_actual_rect.height);
377     }
378     if (dec->last_actual_rect.height < inner.height) {
379         XFillRectangle(g_display, pix, gc,
380                        dec->last_actual_rect.x,
381                        dec->last_actual_rect.y + dec->last_actual_rect.height,
382                        inner.width,
383                        inner.height - dec->last_actual_rect.height);
384     }
385     if (s.title_height() > 0) {
386         FontData& fontData = s.title_font->data();
387         string title = client_->title_();
388         Point2D titlepos = {
389             static_cast<int>(s.padding_left() + s.border_width()),
390             static_cast<int>(s.title_height())
391         };
392         if (fontData.xftFont_) {
393             Visual* xftvisual = visual ? visual : DefaultVisual(g_display, g_screen);
394             Colormap xftcmap = colormap ? colormap : DefaultColormap(g_display, g_screen);
395             XftDraw* xftd = XftDrawCreate(g_display, pix, xftvisual, xftcmap);
396             XRenderColor xrendercol = {
397                     s.title_color->red_,
398                     s.title_color->green_,
399                     s.title_color->blue_,
400                     0xffff, // alpha as set by XftColorAllocName()
401             };
402             XftColor xftcol = { };
403             XftColorAllocValue(g_display, xftvisual, xftcmap, &xrendercol, &xftcol);
404             XftDrawStringUtf8(xftd, &xftcol, fontData.xftFont_,
405                            titlepos.x, titlepos.y,
406                            (const XftChar8*)title.c_str(), title.size());
407             XftDrawDestroy(xftd);
408             XftColorFree(g_display, xftvisual, xftcmap, &xftcol);
409         } else if (fontData.xFontSet_) {
410             XSetForeground(g_display, gc, get_client_color(s.title_color));
411             XmbDrawString(g_display, pix, fontData.xFontSet_, gc, titlepos.x, titlepos.y,
412                     title.c_str(), title.size());
413         } else if (fontData.xFontStruct_) {
414             XSetForeground(g_display, gc, get_client_color(s.title_color));
415             XFontStruct* font = s.title_font->data().xFontStruct_;
416             XSetFont(g_display, gc, font->fid);
417             XDrawString(g_display, pix, gc, titlepos.x, titlepos.y,
418                     title.c_str(), title.size());
419         }
420     }
421     // clean up
422     XFreeGC(g_display, gc);
423 }
424 
425