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