1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Slit.cc for Blackbox - an X11 Window Manager
3 // Copyright (c) 2001 - 2005 Sean 'Shaleh' Perry <shaleh@debian.org>
4 // Copyright (c) 1997 - 2000, 2002 - 2005
5 // Bradley T Hughes <bhughes at trolltech.com>
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a
8 // copy of this software and associated documentation files (the "Software"),
9 // to deal in the Software without restriction, including without limitation
10 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 // and/or sell copies of the Software, and to permit persons to whom the
12 // Software is furnished to do so, subject to the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 // DEALINGS IN THE SOFTWARE.
24
25 #include "Slit.hh"
26 #include "Screen.hh"
27 #include "Slitmenu.hh"
28 #include "Toolbar.hh"
29
30 #include <PixmapCache.hh>
31
32 #include <algorithm>
33
34 #include <X11/Xutil.h>
35 #include <assert.h>
36
37
Slit(BScreen * scr)38 Slit::Slit(BScreen *scr) {
39 screen = scr;
40 blackbox = screen->blackbox();
41
42 const SlitOptions &options = screen->resource().slitOptions();
43
44 setLayer(options.always_on_top
45 ? StackingList::LayerAbove
46 : StackingList::LayerNormal);
47
48 hidden = options.auto_hide;
49
50 display = screen->screenInfo().display().XDisplay();
51 frame.window = frame.pixmap = None;
52
53 timer = new bt::Timer(blackbox, this);
54 timer->setTimeout(blackbox->resource().autoRaiseDelay());
55
56 XSetWindowAttributes attrib;
57 unsigned long create_mask = CWColormap | CWEventMask;
58 attrib.colormap = screen->screenInfo().colormap();
59 attrib.event_mask = SubstructureRedirectMask | ButtonPressMask |
60 EnterWindowMask | LeaveWindowMask | ExposureMask;
61
62 frame.rect.setSize(1, 1);
63
64 frame.window =
65 XCreateWindow(display, screen->screenInfo().rootWindow(),
66 frame.rect.x(), frame.rect.y(),
67 frame.rect.width(), frame.rect.height(),
68 0, screen->screenInfo().depth(),
69 InputOutput, screen->screenInfo().visual(),
70 create_mask, &attrib);
71 blackbox->insertEventHandler(frame.window, this);
72
73 screen->addStrut(&strut);
74 }
75
76
~Slit(void)77 Slit::~Slit(void) {
78 screen->removeStrut(&strut);
79
80 bt::PixmapCache::release(frame.pixmap);
81
82 blackbox->removeEventHandler(frame.window);
83
84 XDestroyWindow(display, frame.window);
85
86 delete timer;
87 }
88
89
exposedWidth(void) const90 unsigned int Slit::exposedWidth(void) const {
91 const SlitOptions &options = screen->resource().slitOptions();
92 if (options.direction == Vertical && options.auto_hide)
93 return screen->resource().slitStyle().margin;
94 return frame.rect.width();
95 }
96
97
exposedHeight(void) const98 unsigned int Slit::exposedHeight(void) const {
99 const SlitOptions &options = screen->resource().slitOptions();
100 if (options.direction == Horizontal && options.auto_hide)
101 return screen->resource().slitStyle().margin;
102 return frame.rect.height();
103 }
104
105
addClient(Window w)106 void Slit::addClient(Window w) {
107 SlitClient *client = new SlitClient;
108 client->client_window = w;
109
110 XWMHints *wmhints = XGetWMHints(display, w);
111
112 if (wmhints) {
113 if ((wmhints->flags & IconWindowHint) &&
114 (wmhints->icon_window != None)) {
115 // some dock apps use separate windows, we need to hide these
116 XMoveWindow(display, client->client_window,
117 screen->screenInfo().width() + 10,
118 screen->screenInfo().height() + 10);
119 XMapWindow(display, client->client_window);
120
121 client->icon_window = wmhints->icon_window;
122 client->window = client->icon_window;
123 } else {
124 client->icon_window = None;
125 client->window = client->client_window;
126 }
127
128 XFree(wmhints);
129 } else {
130 client->icon_window = None;
131 client->window = client->client_window;
132 }
133
134 XWindowAttributes attrib;
135 if (XGetWindowAttributes(display, client->window, &attrib)) {
136 client->rect.setSize(attrib.width, attrib.height);
137 } else {
138 client->rect.setSize(64, 64);
139 }
140
141 XSetWindowBorderWidth(display, client->window, 0);
142
143 blackbox->XGrabServer();
144 XSelectInput(display, client->window, NoEventMask);
145 XReparentWindow(display, client->window, frame.window, 0, 0);
146 XMapWindow(display, client->window);
147 XChangeSaveSet(display, client->window, SetModeInsert);
148 XSelectInput(display, client->window, StructureNotifyMask |
149 SubstructureNotifyMask | EnterWindowMask);
150 blackbox->XUngrabServer();
151
152 clientList.push_back(client);
153
154 blackbox->insertEventHandler(client->client_window, this);
155 blackbox->insertEventHandler(client->icon_window, this);
156 reconfigure();
157 }
158
159
removeClient(SlitClient * client,bool remap)160 void Slit::removeClient(SlitClient *client, bool remap) {
161 blackbox->removeEventHandler(client->client_window);
162 blackbox->removeEventHandler(client->icon_window);
163 clientList.remove(client);
164
165 if (remap) {
166 blackbox->XGrabServer();
167 XSelectInput(display, client->window, NoEventMask);
168 XReparentWindow(display, client->window, screen->screenInfo().rootWindow(),
169 frame.rect.x() + client->rect.x(),
170 frame.rect.y() + client->rect.y());
171 XChangeSaveSet(display, client->window, SetModeDelete);
172 blackbox->XUngrabServer();
173 }
174
175 delete client;
176 }
177
178
179 struct SlitClientMatch {
180 Window window;
SlitClientMatchSlitClientMatch181 SlitClientMatch(Window w): window(w) {}
operator ()SlitClientMatch182 inline bool operator()(const Slit::SlitClient* client) const {
183 return (client->window == window);
184 }
185 };
186
187
removeClient(Window w,bool remap)188 void Slit::removeClient(Window w, bool remap) {
189 SlitClientList::iterator it = clientList.begin();
190 const SlitClientList::iterator end = clientList.end();
191
192 it = std::find_if(it, end, SlitClientMatch(w));
193 if (it != end)
194 removeClient(*it, remap);
195
196 if (clientList.empty())
197 screen->destroySlit();
198 else
199 reconfigure();
200 }
201
202
reconfigure(void)203 void Slit::reconfigure(void) {
204 assert(!clientList.empty());
205
206 SlitClientList::iterator it = clientList.begin();
207 const SlitClientList::iterator end = clientList.end();
208 SlitClient *client;
209
210 unsigned int width = 0, height = 0;
211
212 const SlitOptions &options = screen->resource().slitOptions();
213 const SlitStyle &style = screen->resource().slitStyle();
214
215 switch (options.direction) {
216 case Vertical:
217 for (; it != end; ++it) {
218 client = *it;
219 height += client->rect.height();
220 width = std::max(width, client->rect.width());
221 }
222
223 width += (style.slit.borderWidth() + style.margin) * 2;
224 height += (style.margin * (clientList.size() + 1))
225 + (style.slit.borderWidth() * 2);
226 break;
227
228 case Horizontal:
229 for (; it != end; ++it) {
230 client = *it;
231 width += client->rect.width();
232 height = std::max(height, client->rect.height());
233 }
234
235 width += (style.margin * (clientList.size() + 1))
236 + (style.slit.borderWidth() * 2);
237 height += (style.slit.borderWidth() + style.margin) * 2;
238 break;
239 }
240 frame.rect.setSize(width, height);
241
242 reposition();
243
244 XMapWindow(display, frame.window);
245
246 const bt::Texture &texture = style.slit;
247 frame.pixmap =
248 bt::PixmapCache::find(screen->screenNumber(), texture,
249 frame.rect.width(), frame.rect.height(),
250 frame.pixmap);
251
252 if ((texture.texture() & bt::Texture::Gradient) && frame.pixmap)
253 XSetWindowBackgroundPixmap(display, frame.window, frame.pixmap);
254 else if ((texture.texture() & bt::Texture::Solid))
255 XSetWindowBackground(display, frame.window,
256 texture.color1().pixel(screen->screenNumber()));
257
258 XClearArea(display, frame.window, 0, 0,
259 frame.rect.width(), frame.rect.height(), True);
260
261 it = clientList.begin();
262
263 int x, y;
264 x = y = style.slit.borderWidth() + style.margin;
265
266 switch (options.direction) {
267 case Vertical:
268 for (; it != end; ++it) {
269 client = *it;
270 x = (frame.rect.width() - client->rect.width()) / 2;
271
272 XMoveResizeWindow(display, client->window, x, y,
273 client->rect.width(), client->rect.height());
274 XMapWindow(display, client->window);
275
276 // for ICCCM compliance
277 client->rect.setPos(x, y);
278
279 XEvent event;
280 event.type = ConfigureNotify;
281
282 event.xconfigure.display = display;
283 event.xconfigure.event = client->window;
284 event.xconfigure.window = client->window;
285 event.xconfigure.x = x;
286 event.xconfigure.y = y;
287 event.xconfigure.width = client->rect.width();
288 event.xconfigure.height = client->rect.height();
289 event.xconfigure.border_width = 0;
290 event.xconfigure.above = frame.window;
291 event.xconfigure.override_redirect = False;
292
293 XSendEvent(display, client->window, False, StructureNotifyMask, &event);
294
295 y += client->rect.height() + style.margin;
296 }
297
298 break;
299
300 case Horizontal:
301 for (; it != end; ++it) {
302 client = *it;
303 y = (frame.rect.height() - client->rect.height()) / 2;
304
305 XMoveResizeWindow(display, client->window, x, y,
306 client->rect.width(), client->rect.height());
307 XMapWindow(display, client->window);
308
309 // for ICCCM compliance
310 client->rect.setPos(x, y);
311
312 XEvent event;
313 event.type = ConfigureNotify;
314
315 event.xconfigure.display = display;
316 event.xconfigure.event = client->window;
317 event.xconfigure.window = client->window;
318 event.xconfigure.x = x;
319 event.xconfigure.y = y;
320 event.xconfigure.width = client->rect.width();
321 event.xconfigure.height = client->rect.height();
322 event.xconfigure.border_width = 0;
323 event.xconfigure.above = frame.window;
324 event.xconfigure.override_redirect = False;
325
326 XSendEvent(display, client->window, False, StructureNotifyMask, &event);
327
328 x += client->rect.width() + style.margin;
329 }
330 break;
331 }
332 }
333
334
updateStrut(void)335 void Slit::updateStrut(void) {
336 strut.top = strut.bottom = strut.left = strut.right = 0;
337
338 const SlitOptions &options = screen->resource().slitOptions();
339 switch (options.direction) {
340 case Vertical:
341 switch (options.placement) {
342 case TopCenter:
343 strut.top = exposedHeight();
344 break;
345 case BottomCenter:
346 strut.bottom = exposedHeight();
347 break;
348 case TopLeft:
349 case CenterLeft:
350 case BottomLeft:
351 strut.left = exposedWidth();
352 break;
353 case TopRight:
354 case CenterRight:
355 case BottomRight:
356 strut.right = exposedWidth();
357 break;
358 }
359 break;
360 case Horizontal:
361 switch (options.placement) {
362 case TopCenter:
363 case TopLeft:
364 case TopRight:
365 strut.top = frame.rect.top() + exposedHeight();
366 break;
367 case BottomCenter:
368 case BottomLeft:
369 case BottomRight:
370 strut.bottom = (screen->screenInfo().rect().bottom()
371 - (options.auto_hide
372 ? frame.y_hidden
373 : frame.rect.y()));
374 break;
375 case CenterLeft:
376 strut.left = exposedWidth();
377 break;
378 case CenterRight:
379 strut.right = exposedWidth();
380 break;
381 }
382 break;
383 }
384
385 screen->updateStrut();
386 }
387
388
reposition(void)389 void Slit::reposition(void) {
390 int x = 0, y = 0;
391
392 const SlitOptions &options = screen->resource().slitOptions();
393 const SlitStyle &style = screen->resource().slitStyle();
394
395 switch (options.placement) {
396 case TopLeft:
397 case CenterLeft:
398 case BottomLeft:
399 x = 0;
400 frame.x_hidden = style.margin - frame.rect.width();
401
402 if (options.placement == TopLeft)
403 y = 0;
404 else if (options.placement == CenterLeft)
405 y = (screen->screenInfo().height() - frame.rect.height()) / 2;
406 else
407 y = screen->screenInfo().height() - frame.rect.height();
408
409 break;
410
411 case TopCenter:
412 case BottomCenter:
413 x = (screen->screenInfo().width() - frame.rect.width()) / 2;
414 frame.x_hidden = x;
415
416 if (options.placement == TopCenter)
417 y = 0;
418 else
419 y = screen->screenInfo().height() - frame.rect.height();
420
421 break;
422
423 case TopRight:
424 case CenterRight:
425 case BottomRight:
426 x = screen->screenInfo().width() - frame.rect.width();
427 frame.x_hidden =
428 screen->screenInfo().width() - style.margin;
429
430 if (options.placement == TopRight)
431 y = 0;
432 else if (options.placement == CenterRight)
433 y = (screen->screenInfo().height() - frame.rect.height()) / 2;
434 else
435 y = screen->screenInfo().height() - frame.rect.height();
436
437 break;
438 }
439
440 frame.rect.setPos(x, y);
441
442 if (screen->toolbar()) {
443 bt::Rect tbar_rect = screen->toolbar()->rect();
444 bt::Rect slit_rect = frame.rect;
445
446 if (slit_rect.intersects(tbar_rect)) {
447 int delta = screen->toolbar()->exposedHeight();
448 if (frame.rect.bottom() <= tbar_rect.bottom())
449 delta = -delta;
450
451 frame.rect.setY(frame.rect.y() + delta);
452 }
453 }
454
455 if (options.placement == TopCenter)
456 frame.y_hidden = 0 - frame.rect.height() + style.margin;
457 else if (options.placement == BottomCenter)
458 frame.y_hidden =
459 screen->screenInfo().height() - style.margin;
460 else
461 frame.y_hidden = frame.rect.y();
462
463 updateStrut();
464
465 if (hidden)
466 XMoveResizeWindow(display, frame.window,
467 frame.x_hidden, frame.y_hidden,
468 frame.rect.width(), frame.rect.height());
469 else
470 XMoveResizeWindow(display, frame.window,
471 frame.rect.x(), frame.rect.y(),
472 frame.rect.width(), frame.rect.height());
473 }
474
475
shutdown(void)476 void Slit::shutdown(void) {
477 for (;;) {
478 bool done = clientList.size() == 1;
479 removeClient(clientList.front());
480 if (done) break;
481 }
482 }
483
484
buttonPressEvent(const XButtonEvent * const event)485 void Slit::buttonPressEvent(const XButtonEvent * const event) {
486 if (event->window != frame.window) return;
487
488 switch (event->button) {
489 case Button1:
490 screen->raiseWindow(this);
491 break;
492 case Button2:
493 screen->lowerWindow(this);
494 break;
495 case Button3:
496 screen->slitmenu()->popup(event->x_root, event->y_root,
497 screen->availableArea());
498 break;
499 }
500 }
501
502
enterNotifyEvent(const XCrossingEvent * const)503 void Slit::enterNotifyEvent(const XCrossingEvent * const /*unused*/) {
504 if (!screen->resource().slitOptions().auto_hide)
505 return;
506
507 if (hidden) {
508 if (! timer->isTiming()) timer->start();
509 } else {
510 if (timer->isTiming()) timer->stop();
511 }
512 }
513
514
leaveNotifyEvent(const XCrossingEvent * const)515 void Slit::leaveNotifyEvent(const XCrossingEvent * const /*unused*/) {
516 if (!screen->resource().slitOptions().auto_hide)
517 return;
518
519 if (hidden) {
520 if (timer->isTiming())
521 timer->stop();
522 } else if (! screen->slitmenu()->isVisible()) {
523 if (! timer->isTiming())
524 timer->start();
525 }
526 }
527
528
configureRequestEvent(const XConfigureRequestEvent * const event)529 void Slit::configureRequestEvent(const XConfigureRequestEvent * const event) {
530 XWindowChanges xwc;
531
532 xwc.x = event->x;
533 xwc.y = event->y;
534 xwc.width = event->width;
535 xwc.height = event->height;
536 xwc.border_width = 0;
537 xwc.sibling = event->above;
538 xwc.stack_mode = event->detail;
539
540 XConfigureWindow(display, event->window, event->value_mask, &xwc);
541
542 SlitClientList::iterator it = clientList.begin();
543 const SlitClientList::iterator end = clientList.end();
544 for (; it != end; ++it) {
545 SlitClient *client = *it;
546 if (client->window == event->window &&
547 (static_cast<signed>(client->rect.width()) != event->width ||
548 static_cast<signed>(client->rect.height()) != event->height)) {
549 client->rect.setSize(event->width, event->height);
550
551 reconfigure();
552 return;
553 }
554 }
555 }
556
557
timeout(bt::Timer *)558 void Slit::timeout(bt::Timer *) {
559 hidden = !hidden;
560 if (hidden)
561 XMoveWindow(display, frame.window, frame.x_hidden, frame.y_hidden);
562 else
563 XMoveWindow(display, frame.window, frame.rect.x(), frame.rect.y());
564 }
565
566
toggleAutoHide(void)567 void Slit::toggleAutoHide(void) {
568 updateStrut();
569
570 if (!screen->resource().slitOptions().auto_hide && hidden) {
571 // force the slit to be visible
572 if (timer->isTiming())
573 timer->stop();
574 timer->fireTimeout();
575 }
576 }
577
578
unmapNotifyEvent(const XUnmapEvent * const event)579 void Slit::unmapNotifyEvent(const XUnmapEvent * const event) {
580 removeClient(event->window);
581 }
582
583
reparentNotifyEvent(const XReparentEvent * const event)584 void Slit::reparentNotifyEvent(const XReparentEvent * const event) {
585 if (event->parent != frame.window)
586 removeClient(event->window, False);
587 }
588
589
exposeEvent(const XExposeEvent * const event)590 void Slit::exposeEvent(const XExposeEvent * const event) {
591 bt::drawTexture(screen->screenNumber(),
592 screen->resource().slitStyle().slit,
593 frame.window,
594 bt::Rect(0, 0, frame.rect.width(), frame.rect.height()),
595 bt::Rect(event->x, event->y, event->width, event->height),
596 frame.pixmap);
597 }
598
599
direction(void) const600 Slit::Direction Slit::direction(void) const {
601 const SlitOptions &options = screen->resource().slitOptions();
602 return static_cast<Slit::Direction>(options.direction);
603 }
604
605
placement(void) const606 Slit::Placement Slit::placement(void) const {
607 const SlitOptions &options = screen->resource().slitOptions();
608 return static_cast<Slit::Placement>(options.placement);
609 }
610