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