]> Dogcows Code - chaz/openbox/blob - src/client.cc
add iconifying!!!!
[chaz/openbox] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef HAVE_CONFIG_H
4 # include "../config.h"
5 #endif
6
7 #include "client.hh"
8 #include "frame.hh"
9 #include "screen.hh"
10 #include "openbox.hh"
11 #include "otk/display.hh"
12 #include "otk/property.hh"
13
14 extern "C" {
15 #include <X11/Xlib.h>
16 #include <X11/Xutil.h>
17 #include <X11/Xatom.h>
18
19 #include <assert.h>
20
21 #include "gettext.h"
22 #define _(str) gettext(str)
23 }
24
25 namespace ob {
26
27 Client::Client(int screen, Window window)
28 : otk::EventHandler(),
29 WidgetBase(WidgetBase::Type_Client),
30 frame(0), _screen(screen), _window(window)
31 {
32 assert(screen >= 0);
33 assert(window);
34
35 ignore_unmaps = 0;
36
37 // update EVERYTHING the first time!!
38
39 // we default to NormalState, visible
40 _wmstate = NormalState; _iconic = false;
41 // no default decors or functions, each has to be enabled
42 _decorations = _functions = 0;
43 // start unfocused
44 _focused = false;
45 // not a transient by default of course
46 _transient_for = 0;
47 // pick a layer to start from
48 _layer = Layer_Normal;
49
50 getArea();
51 getDesktop();
52
53 updateTransientFor();
54 getType();
55 getMwmHints();
56
57 getState(); // gets all the states except for iconic, which is found from
58 // the desktop == ICONIC_DESKTOP
59 getShaped();
60
61 updateProtocols();
62
63 // got the type, the mwmhints, and the protocols, so we're ready to set up
64 // the decorations/functions
65 setupDecorAndFunctions();
66
67 getGravity(); // get the attribute gravity
68 updateNormalHints(); // this may override the attribute gravity
69 updateWMHints(true); // also get the initial_state and set _iconic
70 updateTitle();
71 updateIconTitle();
72 updateClass();
73 updateStrut();
74
75 // restores iconic state when we restart.
76 // this will override the initial_state if that was set
77 if (_desktop == ICONIC_DESKTOP) _iconic = true;
78
79 changeState();
80 }
81
82
83 Client::~Client()
84 {
85 // clean up childrens' references
86 while (!_transients.empty()) {
87 _transients.front()->_transient_for = 0;
88 _transients.pop_front();
89 }
90
91 // clean up parents reference to this
92 if (_transient_for)
93 _transient_for->_transients.remove(this); // remove from old parent
94
95 if (openbox->state() != Openbox::State_Exiting) {
96 // these values should not be persisted across a window unmapping/mapping
97 otk::Property::erase(_window, otk::Property::atoms.net_wm_desktop);
98 otk::Property::erase(_window, otk::Property::atoms.net_wm_state);
99 } else {
100 // if we're left in an iconic state, the client wont be mapped. this is
101 // bad, since we will no longer be managing the window on restart
102 if (_iconic)
103 XMapWindow(**otk::display, _window);
104 }
105 }
106
107
108 void Client::getGravity()
109 {
110 XWindowAttributes wattrib;
111 Status ret;
112
113 ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
114 assert(ret != BadWindow);
115 _gravity = wattrib.win_gravity;
116 }
117
118
119 void Client::getDesktop()
120 {
121 // defaults to the current desktop
122 _desktop = openbox->screen(_screen)->desktop();
123
124 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_desktop,
125 otk::Property::atoms.cardinal,
126 (long unsigned*)&_desktop)) {
127 // make sure the hint exists
128 otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
129 otk::Property::atoms.cardinal, (unsigned)_desktop);
130 }
131 }
132
133
134 void Client::getType()
135 {
136 _type = (WindowType) -1;
137
138 unsigned long *val;
139 unsigned long num = (unsigned) -1;
140 if (otk::Property::get(_window, otk::Property::atoms.net_wm_window_type,
141 otk::Property::atoms.atom, &num, &val)) {
142 // use the first value that we know about in the array
143 for (unsigned long i = 0; i < num; ++i) {
144 if (val[i] == otk::Property::atoms.net_wm_window_type_desktop)
145 _type = Type_Desktop;
146 else if (val[i] == otk::Property::atoms.net_wm_window_type_dock)
147 _type = Type_Dock;
148 else if (val[i] == otk::Property::atoms.net_wm_window_type_toolbar)
149 _type = Type_Toolbar;
150 else if (val[i] == otk::Property::atoms.net_wm_window_type_menu)
151 _type = Type_Menu;
152 else if (val[i] == otk::Property::atoms.net_wm_window_type_utility)
153 _type = Type_Utility;
154 else if (val[i] == otk::Property::atoms.net_wm_window_type_splash)
155 _type = Type_Splash;
156 else if (val[i] == otk::Property::atoms.net_wm_window_type_dialog)
157 _type = Type_Dialog;
158 else if (val[i] == otk::Property::atoms.net_wm_window_type_normal)
159 _type = Type_Normal;
160 // XXX: make this work again
161 // else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override)
162 // mwm_decorations = 0; // prevent this window from getting any decor
163 if (_type != (WindowType) -1)
164 break; // grab the first known type
165 }
166 delete val;
167 }
168
169 if (_type == (WindowType) -1) {
170 /*
171 * the window type hint was not set, which means we either classify ourself
172 * as a normal window or a dialog, depending on if we are a transient.
173 */
174 if (_transient_for)
175 _type = Type_Dialog;
176 else
177 _type = Type_Normal;
178 }
179 }
180
181
182 void Client::setupDecorAndFunctions()
183 {
184 // start with everything (cept fullscreen)
185 _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
186 Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
187 _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
188 Func_Shade;
189 if (_delete_window) {
190 _decorations |= Decor_Close;
191 _functions |= Func_Close;
192 }
193
194 switch (_type) {
195 case Type_Normal:
196 // normal windows retain all of the possible decorations and
197 // functionality, and are the only windows that you can fullscreen
198 _functions |= Func_Fullscreen;
199
200 case Type_Dialog:
201 // dialogs cannot be maximized
202 _decorations &= ~Decor_Maximize;
203 _functions &= ~Func_Maximize;
204 break;
205
206 case Type_Menu:
207 case Type_Toolbar:
208 case Type_Utility:
209 // these windows get less functionality
210 _decorations &= ~(Decor_Iconify | Decor_Handle);
211 _functions &= ~(Func_Iconify | Func_Resize);
212 break;
213
214 case Type_Desktop:
215 case Type_Dock:
216 case Type_Splash:
217 // none of these windows are manipulated by the window manager
218 _decorations = 0;
219 _functions = 0;
220 break;
221 }
222
223 // Mwm Hints are applied subtractively to what has already been chosen for
224 // decor and functionality
225 if (_mwmhints.flags & MwmFlag_Decorations) {
226 if (! (_mwmhints.decorations & MwmDecor_All)) {
227 if (! (_mwmhints.decorations & MwmDecor_Border))
228 _decorations &= ~Decor_Border;
229 if (! (_mwmhints.decorations & MwmDecor_Handle))
230 _decorations &= ~Decor_Handle;
231 if (! (_mwmhints.decorations & MwmDecor_Title)) {
232 _decorations &= ~Decor_Titlebar;
233 // if we don't have a titlebar, then we cannot shade!
234 _functions &= ~Func_Shade;
235 }
236 if (! (_mwmhints.decorations & MwmDecor_Iconify))
237 _decorations &= ~Decor_Iconify;
238 if (! (_mwmhints.decorations & MwmDecor_Maximize))
239 _decorations &= ~Decor_Maximize;
240 }
241 }
242
243 if (_mwmhints.flags & MwmFlag_Functions) {
244 if (! (_mwmhints.functions & MwmFunc_All)) {
245 if (! (_mwmhints.functions & MwmFunc_Resize))
246 _functions &= ~Func_Resize;
247 if (! (_mwmhints.functions & MwmFunc_Move))
248 _functions &= ~Func_Move;
249 if (! (_mwmhints.functions & MwmFunc_Iconify))
250 _functions &= ~Func_Iconify;
251 if (! (_mwmhints.functions & MwmFunc_Maximize))
252 _functions &= ~Func_Maximize;
253 // dont let mwm hints kill the close button
254 //if (! (_mwmhints.functions & MwmFunc_Close))
255 // _functions &= ~Func_Close;
256 }
257 }
258
259 changeAllowedActions();
260 }
261
262
263 void Client::getMwmHints()
264 {
265 unsigned long num = MwmHints::elements;
266 unsigned long *hints;
267
268 _mwmhints.flags = 0; // default to none
269
270 if (!otk::Property::get(_window, otk::Property::atoms.motif_wm_hints,
271 otk::Property::atoms.motif_wm_hints, &num,
272 (unsigned long **)&hints))
273 return;
274
275 if (num >= MwmHints::elements) {
276 // retrieved the hints
277 _mwmhints.flags = hints[0];
278 _mwmhints.functions = hints[1];
279 _mwmhints.decorations = hints[2];
280 }
281
282 delete [] hints;
283 }
284
285
286 void Client::getArea()
287 {
288 XWindowAttributes wattrib;
289 Status ret;
290
291 ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
292 assert(ret != BadWindow);
293
294 _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
295 _border_width = wattrib.border_width;
296 }
297
298
299 void Client::getState()
300 {
301 _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
302 _skip_taskbar = _skip_pager = false;
303
304 unsigned long *state;
305 unsigned long num = (unsigned) -1;
306
307 if (otk::Property::get(_window, otk::Property::atoms.net_wm_state,
308 otk::Property::atoms.atom, &num, &state)) {
309 for (unsigned long i = 0; i < num; ++i) {
310 if (state[i] == otk::Property::atoms.net_wm_state_modal)
311 _modal = true;
312 else if (state[i] == otk::Property::atoms.net_wm_state_shaded)
313 _shaded = true;
314 else if (state[i] == otk::Property::atoms.net_wm_state_skip_taskbar)
315 _skip_taskbar = true;
316 else if (state[i] == otk::Property::atoms.net_wm_state_skip_pager)
317 _skip_pager = true;
318 else if (state[i] == otk::Property::atoms.net_wm_state_fullscreen)
319 _fullscreen = true;
320 else if (state[i] == otk::Property::atoms.net_wm_state_maximized_vert)
321 _max_vert = true;
322 else if (state[i] == otk::Property::atoms.net_wm_state_maximized_horz)
323 _max_horz = true;
324 else if (state[i] == otk::Property::atoms.net_wm_state_above)
325 _above = true;
326 else if (state[i] == otk::Property::atoms.net_wm_state_below)
327 _below = true;
328 }
329
330 delete [] state;
331 }
332 }
333
334
335 void Client::getShaped()
336 {
337 _shaped = false;
338 #ifdef SHAPE
339 if (otk::display->shape()) {
340 int foo;
341 unsigned int ufoo;
342 int s;
343
344 XShapeSelectInput(**otk::display, _window, ShapeNotifyMask);
345
346 XShapeQueryExtents(**otk::display, _window, &s, &foo,
347 &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
348 _shaped = (s != 0);
349 }
350 #endif // SHAPE
351 }
352
353
354 void Client::calcLayer() {
355 StackLayer l;
356
357 if (_iconic) l = Layer_Icon;
358 else if (_fullscreen) l = Layer_Fullscreen;
359 else if (_type == Type_Desktop) l = Layer_Desktop;
360 else if (_type == Type_Dock) {
361 if (!_below) l = Layer_Top;
362 else l = Layer_Normal;
363 }
364 else if (_above) l = Layer_Above;
365 else if (_below) l = Layer_Below;
366 else l = Layer_Normal;
367
368 if (l != _layer) {
369 _layer = l;
370 if (frame) {
371 /*
372 if we don't have a frame, then we aren't mapped yet (and this would
373 SIGSEGV :)
374 */
375 openbox->screen(_screen)->raiseWindow(this);
376 }
377 }
378 }
379
380
381 void Client::updateProtocols()
382 {
383 Atom *proto;
384 int num_return = 0;
385
386 _focus_notify = false;
387 _delete_window = false;
388
389 if (XGetWMProtocols(**otk::display, _window, &proto, &num_return)) {
390 for (int i = 0; i < num_return; ++i) {
391 if (proto[i] == otk::Property::atoms.wm_delete_window) {
392 // this means we can request the window to close
393 _delete_window = true;
394 } else if (proto[i] == otk::Property::atoms.wm_take_focus)
395 // if this protocol is requested, then the window will be notified
396 // by the window manager whenever it receives focus
397 _focus_notify = true;
398 }
399 XFree(proto);
400 }
401 }
402
403
404 void Client::updateNormalHints()
405 {
406 XSizeHints size;
407 long ret;
408 int oldgravity = _gravity;
409
410 // defaults
411 _size_inc.setPoint(1, 1);
412 _base_size.setPoint(0, 0);
413 _min_size.setPoint(0, 0);
414 _max_size.setPoint(INT_MAX, INT_MAX);
415
416 // XXX: might want to cancel any interactive resizing of the window at this
417 // point..
418
419 // get the hints from the window
420 if (XGetWMNormalHints(**otk::display, _window, &size, &ret)) {
421 _positioned = (size.flags & (PPosition|USPosition));
422
423 if (size.flags & PWinGravity) {
424 _gravity = size.win_gravity;
425
426 // if the client has a frame, i.e. has already been mapped and is
427 // changing its gravity
428 if (frame && _gravity != oldgravity) {
429 // move our idea of the client's position based on its new gravity
430 int x, y;
431 frame->frameGravity(x, y);
432 _area.setPos(x, y);
433 }
434 }
435
436 if (size.flags & PMinSize)
437 _min_size.setPoint(size.min_width, size.min_height);
438
439 if (size.flags & PMaxSize)
440 _max_size.setPoint(size.max_width, size.max_height);
441
442 if (size.flags & PBaseSize)
443 _base_size.setPoint(size.base_width, size.base_height);
444
445 if (size.flags & PResizeInc)
446 _size_inc.setPoint(size.width_inc, size.height_inc);
447 }
448 }
449
450
451 void Client::updateWMHints(bool initstate)
452 {
453 XWMHints *hints;
454
455 // assume a window takes input if it doesnt specify
456 _can_focus = true;
457 _urgent = false;
458
459 if ((hints = XGetWMHints(**otk::display, _window)) != NULL) {
460 if (hints->flags & InputHint)
461 _can_focus = hints->input;
462
463 // only do this when initstate is true!
464 if (initstate && (hints->flags & StateHint))
465 _iconic = hints->initial_state == IconicState;
466
467 if (hints->flags & XUrgencyHint)
468 _urgent = true;
469
470 if (hints->flags & WindowGroupHint) {
471 if (hints->window_group != _group) {
472 // XXX: remove from the old group if there was one
473 _group = hints->window_group;
474 // XXX: do stuff with the group
475 }
476 } else // no group!
477 _group = None;
478
479 XFree(hints);
480 }
481 }
482
483
484 void Client::updateTitle()
485 {
486 _title = "";
487
488 // try netwm
489 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_name,
490 otk::Property::utf8, &_title)) {
491 // try old x stuff
492 otk::Property::get(_window, otk::Property::atoms.wm_name,
493 otk::Property::ascii, &_title);
494 }
495
496 if (_title.empty())
497 _title = _("Unnamed Window");
498
499 if (frame)
500 frame->setTitle(_title);
501 }
502
503
504 void Client::updateIconTitle()
505 {
506 _icon_title = "";
507
508 // try netwm
509 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_icon_name,
510 otk::Property::utf8, &_icon_title)) {
511 // try old x stuff
512 otk::Property::get(_window, otk::Property::atoms.wm_icon_name,
513 otk::Property::ascii, &_icon_title);
514 }
515
516 if (_title.empty())
517 _icon_title = _("Unnamed Window");
518 }
519
520
521 void Client::updateClass()
522 {
523 // set the defaults
524 _app_name = _app_class = _role = "";
525
526 otk::Property::StringVect v;
527 unsigned long num = 2;
528
529 if (otk::Property::get(_window, otk::Property::atoms.wm_class,
530 otk::Property::ascii, &num, &v)) {
531 if (num > 0) _app_name = v[0].c_str();
532 if (num > 1) _app_class = v[1].c_str();
533 }
534
535 v.clear();
536 num = 1;
537 if (otk::Property::get(_window, otk::Property::atoms.wm_window_role,
538 otk::Property::ascii, &num, &v)) {
539 if (num > 0) _role = v[0].c_str();
540 }
541 }
542
543
544 void Client::updateStrut()
545 {
546 unsigned long num = 4;
547 unsigned long *data;
548 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_strut,
549 otk::Property::atoms.cardinal, &num, &data))
550 return;
551
552 if (num == 4) {
553 _strut.left = data[0];
554 _strut.right = data[1];
555 _strut.top = data[2];
556 _strut.bottom = data[3];
557
558 openbox->screen(_screen)->updateStrut();
559 }
560
561 delete [] data;
562 }
563
564
565 void Client::updateTransientFor()
566 {
567 Window t = 0;
568 Client *c = 0;
569
570 if (XGetTransientForHint(**otk::display, _window, &t) &&
571 t != _window) { // cant be transient to itself!
572 c = openbox->findClient(t);
573 assert(c != this); // if this happens then we need to check for it
574
575 if (!c /*XXX: && _group*/) {
576 // not transient to a client, see if it is transient for a group
577 if (//t == _group->leader() ||
578 t == None ||
579 t == otk::display->screenInfo(_screen)->rootWindow()) {
580 // window is a transient for its group!
581 // XXX: for now this is treated as non-transient.
582 // this needs to be fixed!
583 }
584 }
585 }
586
587 // if anything has changed...
588 if (c != _transient_for) {
589 if (_transient_for)
590 _transient_for->_transients.remove(this); // remove from old parent
591 _transient_for = c;
592 if (_transient_for)
593 _transient_for->_transients.push_back(this); // add to new parent
594
595 // XXX: change decor status?
596 }
597 }
598
599
600 void Client::propertyHandler(const XPropertyEvent &e)
601 {
602 otk::EventHandler::propertyHandler(e);
603
604 // compress changes to a single property into a single change
605 XEvent ce;
606 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
607 // XXX: it would be nice to compress ALL changes to a property, not just
608 // changes in a row without other props between.
609 if (ce.xproperty.atom != e.atom) {
610 XPutBackEvent(**otk::display, &ce);
611 break;
612 }
613 }
614
615 if (e.atom == XA_WM_NORMAL_HINTS)
616 updateNormalHints();
617 else if (e.atom == XA_WM_HINTS)
618 updateWMHints();
619 else if (e.atom == XA_WM_TRANSIENT_FOR) {
620 updateTransientFor();
621 getType();
622 calcLayer(); // type may have changed, so update the layer
623 setupDecorAndFunctions();
624 frame->adjustSize(); // this updates the frame for any new decor settings
625 }
626 else if (e.atom == otk::Property::atoms.net_wm_name ||
627 e.atom == otk::Property::atoms.wm_name)
628 updateTitle();
629 else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
630 e.atom == otk::Property::atoms.wm_icon_name)
631 updateIconTitle();
632 else if (e.atom == otk::Property::atoms.wm_class)
633 updateClass();
634 else if (e.atom == otk::Property::atoms.wm_protocols) {
635 updateProtocols();
636 setupDecorAndFunctions();
637 frame->adjustSize(); // update the decorations
638 }
639 else if (e.atom == otk::Property::atoms.net_wm_strut)
640 updateStrut();
641 }
642
643
644 void Client::setWMState(long state)
645 {
646 if (state == _wmstate) return; // no change
647
648 switch (state) {
649 case IconicState:
650 setDesktop(ICONIC_DESKTOP);
651 break;
652 case NormalState:
653 setDesktop(openbox->screen(_screen)->desktop());
654 break;
655 }
656 }
657
658
659 void Client::setDesktop(long target)
660 {
661 if (target == _desktop) return;
662
663 printf("Setting desktop %ld\n", target);
664
665 if (!(target >= 0 || target == (signed)0xffffffff ||
666 target == ICONIC_DESKTOP))
667 return;
668
669 _desktop = target;
670
671 // set the desktop hint
672 otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
673 otk::Property::atoms.cardinal, (unsigned)_desktop);
674
675 // 'move' the window to the new desktop
676 if (_desktop == openbox->screen(_screen)->desktop() ||
677 _desktop == (signed)0xffffffff)
678 frame->show();
679 else
680 frame->hide();
681
682 // Handle Iconic state. Iconic state is maintained by the client being a
683 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
684 // uniconifying happen.
685 bool i = _desktop == ICONIC_DESKTOP;
686 if (i != _iconic) { // has the state changed?
687 _iconic = i;
688 if (_iconic) {
689 _wmstate = IconicState;
690 ignore_unmaps++;
691 // we unmap the client itself so that we can get MapRequest events, and
692 // because the ICCCM tells us to!
693 XUnmapWindow(**otk::display, _window);
694 } else {
695 _wmstate = NormalState;
696 XMapWindow(**otk::display, _window);
697 }
698 changeState();
699 }
700
701 frame->adjustState();
702 }
703
704
705 void Client::setState(StateAction action, long data1, long data2)
706 {
707 bool shadestate = _shaded;
708 bool fsstate = _fullscreen;
709
710 if (!(action == State_Add || action == State_Remove ||
711 action == State_Toggle))
712 return; // an invalid action was passed to the client message, ignore it
713
714 for (int i = 0; i < 2; ++i) {
715 Atom state = i == 0 ? data1 : data2;
716
717 if (! state) continue;
718
719 // if toggling, then pick whether we're adding or removing
720 if (action == State_Toggle) {
721 if (state == otk::Property::atoms.net_wm_state_modal)
722 action = _modal ? State_Remove : State_Add;
723 else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
724 action = _max_vert ? State_Remove : State_Add;
725 else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
726 action = _max_horz ? State_Remove : State_Add;
727 else if (state == otk::Property::atoms.net_wm_state_shaded)
728 action = _shaded ? State_Remove : State_Add;
729 else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
730 action = _skip_taskbar ? State_Remove : State_Add;
731 else if (state == otk::Property::atoms.net_wm_state_skip_pager)
732 action = _skip_pager ? State_Remove : State_Add;
733 else if (state == otk::Property::atoms.net_wm_state_fullscreen)
734 action = _fullscreen ? State_Remove : State_Add;
735 else if (state == otk::Property::atoms.net_wm_state_above)
736 action = _above ? State_Remove : State_Add;
737 else if (state == otk::Property::atoms.net_wm_state_below)
738 action = _below ? State_Remove : State_Add;
739 }
740
741 if (action == State_Add) {
742 if (state == otk::Property::atoms.net_wm_state_modal) {
743 if (_modal) continue;
744 _modal = true;
745 // XXX: give it focus if another window has focus that shouldnt now
746 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
747 if (_max_vert) continue;
748 _max_vert = true;
749 // XXX: resize the window etc
750 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
751 if (_max_horz) continue;
752 _max_horz = true;
753 // XXX: resize the window etc
754 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
755 shadestate = true;
756 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
757 _skip_taskbar = true;
758 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
759 _skip_pager = true;
760 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
761 fsstate = true;
762 } else if (state == otk::Property::atoms.net_wm_state_above) {
763 if (_above) continue;
764 _above = true;
765 } else if (state == otk::Property::atoms.net_wm_state_below) {
766 if (_below) continue;
767 _below = true;
768 }
769
770 } else { // action == State_Remove
771 if (state == otk::Property::atoms.net_wm_state_modal) {
772 if (!_modal) continue;
773 _modal = false;
774 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
775 if (!_max_vert) continue;
776 _max_vert = false;
777 // XXX: resize the window etc
778 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
779 if (!_max_horz) continue;
780 _max_horz = false;
781 // XXX: resize the window etc
782 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
783 shadestate = false;
784 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
785 _skip_taskbar = false;
786 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
787 _skip_pager = false;
788 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
789 fsstate = false;
790 } else if (state == otk::Property::atoms.net_wm_state_above) {
791 if (!_above) continue;
792 _above = false;
793 } else if (state == otk::Property::atoms.net_wm_state_below) {
794 if (!_below) continue;
795 _below = false;
796 }
797 }
798 }
799 // change fullscreen state before shading, as it will affect if the window
800 // can shade or not
801 if (fsstate != _fullscreen)
802 fullscreen(fsstate);
803 if (shadestate != _shaded)
804 shade(shadestate);
805 calcLayer();
806 }
807
808
809 void Client::toggleClientBorder(bool addborder)
810 {
811 // adjust our idea of where the client is, based on its border. When the
812 // border is removed, the client should now be considered to be in a
813 // different position.
814 // when re-adding the border to the client, the same operation needs to be
815 // reversed.
816 int x = _area.x(), y = _area.y();
817 switch(_gravity) {
818 default:
819 case NorthWestGravity:
820 case WestGravity:
821 case SouthWestGravity:
822 break;
823 case NorthEastGravity:
824 case EastGravity:
825 case SouthEastGravity:
826 if (addborder) x -= _border_width * 2;
827 else x += _border_width * 2;
828 break;
829 case NorthGravity:
830 case SouthGravity:
831 case CenterGravity:
832 case ForgetGravity:
833 case StaticGravity:
834 if (addborder) x -= _border_width;
835 else x += _border_width;
836 break;
837 }
838 switch(_gravity) {
839 default:
840 case NorthWestGravity:
841 case NorthGravity:
842 case NorthEastGravity:
843 break;
844 case SouthWestGravity:
845 case SouthGravity:
846 case SouthEastGravity:
847 if (addborder) y -= _border_width * 2;
848 else y += _border_width * 2;
849 break;
850 case WestGravity:
851 case EastGravity:
852 case CenterGravity:
853 case ForgetGravity:
854 case StaticGravity:
855 if (addborder) y -= _border_width;
856 else y += _border_width;
857 break;
858 }
859 _area.setPos(x, y);
860
861 if (addborder) {
862 XSetWindowBorderWidth(**otk::display, _window, _border_width);
863
864 // move the client so it is back it the right spot _with_ its border!
865 XMoveWindow(**otk::display, _window, x, y);
866 } else
867 XSetWindowBorderWidth(**otk::display, _window, 0);
868 }
869
870
871 void Client::clientMessageHandler(const XClientMessageEvent &e)
872 {
873 otk::EventHandler::clientMessageHandler(e);
874
875 if (e.format != 32) return;
876
877 if (e.message_type == otk::Property::atoms.wm_change_state) {
878 // compress changes into a single change
879 bool compress = false;
880 XEvent ce;
881 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
882 // XXX: it would be nice to compress ALL messages of a type, not just
883 // messages in a row without other message types between.
884 if (ce.xclient.message_type != e.message_type) {
885 XPutBackEvent(**otk::display, &ce);
886 break;
887 }
888 compress = true;
889 }
890 if (compress)
891 setWMState(ce.xclient.data.l[0]); // use the found event
892 else
893 setWMState(e.data.l[0]); // use the original event
894 } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
895 // compress changes into a single change
896 bool compress = false;
897 XEvent ce;
898 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
899 // XXX: it would be nice to compress ALL messages of a type, not just
900 // messages in a row without other message types between.
901 if (ce.xclient.message_type != e.message_type) {
902 XPutBackEvent(**otk::display, &ce);
903 break;
904 }
905 compress = true;
906 }
907 if (compress)
908 setDesktop(e.data.l[0]); // use the found event
909 else
910 setDesktop(e.data.l[0]); // use the original event
911 } else if (e.message_type == otk::Property::atoms.net_wm_state) {
912 // can't compress these
913 #ifdef DEBUG
914 printf("net_wm_state %s %ld %ld for 0x%lx\n",
915 (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
916 e.data.l[0] == 2 ? "Toggle" : "INVALID"),
917 e.data.l[1], e.data.l[2], _window);
918 #endif
919 setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
920 } else if (e.message_type == otk::Property::atoms.net_close_window) {
921 #ifdef DEBUG
922 printf("net_close_window for 0x%lx\n", _window);
923 #endif
924 close();
925 } else if (e.message_type == otk::Property::atoms.net_active_window) {
926 #ifdef DEBUG
927 printf("net_active_window for 0x%lx\n", _window);
928 #endif
929 if (_iconic)
930 setDesktop(openbox->screen(_screen)->desktop());
931 if (_shaded)
932 shade(false);
933 // XXX: deiconify
934 focus();
935 openbox->screen(_screen)->raiseWindow(this);
936 }
937 }
938
939
940 #if defined(SHAPE)
941 void Client::shapeHandler(const XShapeEvent &e)
942 {
943 otk::EventHandler::shapeHandler(e);
944
945 if (e.kind == ShapeBounding) {
946 _shaped = e.shaped;
947 frame->adjustShape();
948 }
949 }
950 #endif
951
952
953 void Client::resize(Corner anchor, int w, int h)
954 {
955 if (!(_functions & Func_Resize)) return;
956 internal_resize(anchor, w, h);
957 }
958
959
960 void Client::internal_resize(Corner anchor, int w, int h, int x, int y)
961 {
962 w -= _base_size.x();
963 h -= _base_size.y();
964
965 // for interactive resizing. have to move half an increment in each
966 // direction.
967 w += _size_inc.x() / 2;
968 h += _size_inc.y() / 2;
969
970 // is the window resizable? if it is not, then don't check its sizes, the
971 // client can do what it wants and the user can't change it anyhow
972 if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) {
973 // smaller than min size or bigger than max size?
974 if (w < _min_size.x()) w = _min_size.x();
975 else if (w > _max_size.x()) w = _max_size.x();
976 if (h < _min_size.y()) h = _min_size.y();
977 else if (h > _max_size.y()) h = _max_size.y();
978 }
979
980 // keep to the increments
981 w /= _size_inc.x();
982 h /= _size_inc.y();
983
984 // you cannot resize to nothing
985 if (w < 1) w = 1;
986 if (h < 1) h = 1;
987
988 // store the logical size
989 _logical_size.setPoint(w, h);
990
991 w *= _size_inc.x();
992 h *= _size_inc.y();
993
994 w += _base_size.x();
995 h += _base_size.y();
996
997 if (x == INT_MIN || y == INT_MIN) {
998 x = _area.x();
999 y = _area.y();
1000 switch (anchor) {
1001 case TopLeft:
1002 break;
1003 case TopRight:
1004 x -= w - _area.width();
1005 break;
1006 case BottomLeft:
1007 y -= h - _area.height();
1008 break;
1009 case BottomRight:
1010 x -= w - _area.width();
1011 y -= h - _area.height();
1012 break;
1013 }
1014 }
1015
1016 _area.setSize(w, h);
1017
1018 XResizeWindow(**otk::display, _window, w, h);
1019
1020 // resize the frame to match the request
1021 frame->adjustSize();
1022 internal_move(x, y);
1023 }
1024
1025
1026 void Client::move(int x, int y)
1027 {
1028 if (!(_functions & Func_Move)) return;
1029 internal_move(x, y);
1030 }
1031
1032
1033 void Client::internal_move(int x, int y)
1034 {
1035 _area.setPos(x, y);
1036
1037 // move the frame to be in the requested position
1038 if (frame) { // this can be called while mapping, before frame exists
1039 frame->adjustPosition();
1040
1041 // send synthetic configure notify (we don't need to if we aren't mapped
1042 // yet)
1043 XEvent event;
1044 event.type = ConfigureNotify;
1045 event.xconfigure.display = **otk::display;
1046 event.xconfigure.event = _window;
1047 event.xconfigure.window = _window;
1048 event.xconfigure.x = x;
1049 event.xconfigure.y = y;
1050 event.xconfigure.width = _area.width();
1051 event.xconfigure.height = _area.height();
1052 event.xconfigure.border_width = _border_width;
1053 event.xconfigure.above = frame->window();
1054 event.xconfigure.override_redirect = False;
1055 XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1056 StructureNotifyMask, &event);
1057 }
1058 }
1059
1060
1061 void Client::close()
1062 {
1063 XEvent ce;
1064
1065 if (!(_functions & Func_Close)) return;
1066
1067 // XXX: itd be cool to do timeouts and shit here for killing the client's
1068 // process off
1069 // like... if the window is around after 5 seconds, then the close button
1070 // turns a nice red, and if this function is called again, the client is
1071 // explicitly killed.
1072
1073 ce.xclient.type = ClientMessage;
1074 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1075 ce.xclient.display = **otk::display;
1076 ce.xclient.window = _window;
1077 ce.xclient.format = 32;
1078 ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1079 ce.xclient.data.l[1] = CurrentTime;
1080 ce.xclient.data.l[2] = 0l;
1081 ce.xclient.data.l[3] = 0l;
1082 ce.xclient.data.l[4] = 0l;
1083 XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1084 }
1085
1086
1087 void Client::changeState()
1088 {
1089 unsigned long state[2];
1090 state[0] = _wmstate;
1091 state[1] = None;
1092 otk::Property::set(_window, otk::Property::atoms.wm_state,
1093 otk::Property::atoms.wm_state, state, 2);
1094
1095 Atom netstate[10];
1096 int num = 0;
1097 if (_modal)
1098 netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1099 if (_shaded)
1100 netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1101 if (_iconic)
1102 netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1103 if (_skip_taskbar)
1104 netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1105 if (_skip_pager)
1106 netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1107 if (_fullscreen)
1108 netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1109 if (_max_vert)
1110 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1111 if (_max_horz)
1112 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1113 if (_above)
1114 netstate[num++] = otk::Property::atoms.net_wm_state_above;
1115 if (_below)
1116 netstate[num++] = otk::Property::atoms.net_wm_state_below;
1117 otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1118 otk::Property::atoms.atom, netstate, num);
1119
1120 calcLayer();
1121
1122 if (frame)
1123 frame->adjustState();
1124 }
1125
1126
1127 void Client::changeAllowedActions(void)
1128 {
1129 Atom actions[9];
1130 int num = 0;
1131
1132 actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1133
1134 if (_functions & Func_Shade)
1135 actions[num++] = otk::Property::atoms.net_wm_action_shade;
1136 if (_functions & Func_Close)
1137 actions[num++] = otk::Property::atoms.net_wm_action_close;
1138 if (_functions & Func_Move)
1139 actions[num++] = otk::Property::atoms.net_wm_action_move;
1140 if (_functions & Func_Iconify)
1141 actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1142 if (_functions & Func_Resize)
1143 actions[num++] = otk::Property::atoms.net_wm_action_resize;
1144 if (_functions & Func_Fullscreen)
1145 actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1146 if (_functions & Func_Maximize) {
1147 actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1148 actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1149 }
1150
1151 otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1152 otk::Property::atoms.atom, actions, num);
1153 }
1154
1155
1156 void Client::applyStartupState()
1157 {
1158 // these are in a carefully crafted order..
1159
1160 if (_iconic) {
1161 _iconic = false;
1162 _desktop = 0; // set some other source desktop so this goes through
1163 setDesktop(ICONIC_DESKTOP);
1164 }
1165 if (_fullscreen) {
1166 _fullscreen = false;
1167 fullscreen(true);
1168 }
1169 if (_shaded) {
1170 _shaded = false;
1171 shade(true);
1172 }
1173
1174 if (_max_vert); // XXX: incomplete
1175 if (_max_horz); // XXX: incomplete
1176
1177 if (_skip_taskbar); // nothing to do for this
1178 if (_skip_pager); // nothing to do for this
1179 if (_modal); // nothing to do for this
1180 if (_above); // nothing to do for this
1181 if (_below); // nothing to do for this
1182 }
1183
1184
1185 void Client::shade(bool shade)
1186 {
1187 if (!(_functions & Func_Shade) || // can't
1188 _shaded == shade) return; // already done
1189
1190 // when we're iconic, don't change the wmstate
1191 if (!_iconic)
1192 _wmstate = shade ? IconicState : NormalState;
1193 _shaded = shade;
1194 changeState();
1195 frame->adjustSize();
1196 }
1197
1198
1199 void Client::fullscreen(bool fs)
1200 {
1201 static FunctionFlags saved_func;
1202 static DecorationFlags saved_decor;
1203 static otk::Rect saved_area;
1204 static otk::Point saved_logical_size;
1205
1206 if (!(_functions & Func_Fullscreen) || // can't
1207 _fullscreen == fs) return; // already done
1208
1209 _fullscreen = fs;
1210 changeState(); // change the state hints on the client
1211
1212 if (fs) {
1213 // save the functions and remove them
1214 saved_func = _functions;
1215 _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1216 // save the decorations and remove them
1217 saved_decor = _decorations;
1218 _decorations = 0;
1219 // save the area and adjust it (we don't call internal resize here for
1220 // constraints on the size, etc, we just make it fullscreen).
1221 saved_area = _area;
1222 const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1223 _area.setRect(0, 0, info->width(), info->height());
1224 saved_logical_size = _logical_size;
1225 _logical_size.setPoint((info->width() - _base_size.x()) / _size_inc.x(),
1226 (info->height() - _base_size.y()) / _size_inc.y());
1227 } else {
1228 _functions = saved_func;
1229 _decorations = saved_decor;
1230 _area = saved_area;
1231 _logical_size = saved_logical_size;
1232 }
1233
1234 changeAllowedActions(); // based on the new _functions
1235
1236 frame->adjustSize(); // drop/replace the decor's and resize
1237 frame->adjustPosition(); // get (back) in position!
1238
1239 // raise (back) into our stacking layer
1240 openbox->screen(_screen)->raiseWindow(this);
1241
1242 // try focus us when we go into fullscreen mode
1243 if (fs) focus();
1244 }
1245
1246
1247 bool Client::focus() const
1248 {
1249 // won't try focus if the client doesn't want it, or if the window isn't
1250 // visible on the screen
1251 if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1252
1253 if (_focused) return true;
1254
1255 // do a check to see if the window has already been unmapped or destroyed
1256 XEvent ev;
1257 if (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev) ||
1258 XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1259 XPutBackEvent(**otk::display, &ev);
1260 return false;
1261 }
1262
1263 if (_can_focus)
1264 XSetInputFocus(**otk::display, _window,
1265 RevertToNone, CurrentTime);
1266
1267 if (_focus_notify) {
1268 XEvent ce;
1269 ce.xclient.type = ClientMessage;
1270 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1271 ce.xclient.display = **otk::display;
1272 ce.xclient.window = _window;
1273 ce.xclient.format = 32;
1274 ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1275 ce.xclient.data.l[1] = openbox->lastTime();
1276 ce.xclient.data.l[2] = 0l;
1277 ce.xclient.data.l[3] = 0l;
1278 ce.xclient.data.l[4] = 0l;
1279 XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1280 }
1281
1282 return true;
1283 }
1284
1285
1286 void Client::unfocus() const
1287 {
1288 if (!_focused) return;
1289
1290 assert(openbox->focusedClient() == this);
1291 openbox->setFocusedClient(0);
1292 }
1293
1294
1295 void Client::focusHandler(const XFocusChangeEvent &e)
1296 {
1297 #ifdef DEBUG
1298 // printf("FocusIn for 0x%lx\n", e.window);
1299 #endif // DEBUG
1300
1301 otk::EventHandler::focusHandler(e);
1302
1303 frame->focus();
1304 _focused = true;
1305
1306 openbox->setFocusedClient(this);
1307 }
1308
1309
1310 void Client::unfocusHandler(const XFocusChangeEvent &e)
1311 {
1312 #ifdef DEBUG
1313 // printf("FocusOut for 0x%lx\n", e.window);
1314 #endif // DEBUG
1315
1316 otk::EventHandler::unfocusHandler(e);
1317
1318 frame->unfocus();
1319 _focused = false;
1320
1321 if (openbox->focusedClient() == this)
1322 openbox->setFocusedClient(0);
1323 }
1324
1325
1326 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1327 {
1328 #ifdef DEBUG
1329 printf("ConfigureRequest for 0x%lx\n", e.window);
1330 #endif // DEBUG
1331
1332 otk::EventHandler::configureRequestHandler(e);
1333
1334 // if we are iconic (or shaded (fvwm does this)) ignore the event
1335 if (_iconic || _shaded) return;
1336
1337 if (e.value_mask & CWBorderWidth)
1338 _border_width = e.border_width;
1339
1340 // resize, then move, as specified in the EWMH section 7.7
1341 if (e.value_mask & (CWWidth | CWHeight)) {
1342 int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1343 int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1344
1345 Corner corner;
1346 switch (_gravity) {
1347 case NorthEastGravity:
1348 case EastGravity:
1349 corner = TopRight;
1350 break;
1351 case SouthWestGravity:
1352 case SouthGravity:
1353 corner = BottomLeft;
1354 break;
1355 case SouthEastGravity:
1356 corner = BottomRight;
1357 break;
1358 default: // NorthWest, Static, etc
1359 corner = TopLeft;
1360 }
1361
1362 // if moving AND resizing ...
1363 if (e.value_mask & (CWX | CWY)) {
1364 int x = (e.value_mask & CWX) ? e.x : _area.x();
1365 int y = (e.value_mask & CWY) ? e.y : _area.y();
1366 internal_resize(corner, w, h, x, y);
1367 } else // if JUST resizing...
1368 internal_resize(corner, w, h);
1369 } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1370 int x = (e.value_mask & CWX) ? e.x : _area.x();
1371 int y = (e.value_mask & CWY) ? e.y : _area.y();
1372 internal_move(x, y);
1373 }
1374
1375 if (e.value_mask & CWStackMode) {
1376 switch (e.detail) {
1377 case Below:
1378 case BottomIf:
1379 openbox->screen(_screen)->lowerWindow(this);
1380 break;
1381
1382 case Above:
1383 case TopIf:
1384 default:
1385 openbox->screen(_screen)->raiseWindow(this);
1386 break;
1387 }
1388 }
1389 }
1390
1391
1392 void Client::unmapHandler(const XUnmapEvent &e)
1393 {
1394 if (ignore_unmaps) {
1395 #ifdef DEBUG
1396 printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1397 #endif // DEBUG
1398 ignore_unmaps--;
1399 return;
1400 }
1401
1402 #ifdef DEBUG
1403 printf("UnmapNotify for 0x%lx\n", e.window);
1404 #endif // DEBUG
1405
1406 otk::EventHandler::unmapHandler(e);
1407
1408 // this deletes us etc
1409 openbox->screen(_screen)->unmanageWindow(this);
1410 }
1411
1412
1413 void Client::destroyHandler(const XDestroyWindowEvent &e)
1414 {
1415 #ifdef DEBUG
1416 printf("DestroyNotify for 0x%lx\n", e.window);
1417 #endif // DEBUG
1418
1419 otk::EventHandler::destroyHandler(e);
1420
1421 // this deletes us etc
1422 openbox->screen(_screen)->unmanageWindow(this);
1423 }
1424
1425
1426 void Client::reparentHandler(const XReparentEvent &e)
1427 {
1428 // this is when the client is first taken captive in the frame
1429 if (e.parent == frame->plate()) return;
1430
1431 #ifdef DEBUG
1432 printf("ReparentNotify for 0x%lx\n", e.window);
1433 #endif // DEBUG
1434
1435 otk::EventHandler::reparentHandler(e);
1436
1437 /*
1438 This event is quite rare and is usually handled in unmapHandler.
1439 However, if the window is unmapped when the reparent event occurs,
1440 the window manager never sees it because an unmap event is not sent
1441 to an already unmapped window.
1442 */
1443
1444 // we don't want the reparent event, put it back on the stack for the X
1445 // server to deal with after we unmanage the window
1446 XEvent ev;
1447 ev.xreparent = e;
1448 XPutBackEvent(**otk::display, &ev);
1449
1450 // this deletes us etc
1451 openbox->screen(_screen)->unmanageWindow(this);
1452 }
1453
1454 void Client::mapRequestHandler(const XMapRequestEvent &e)
1455 {
1456 #ifdef DEBUG
1457 printf("MapRequest for already managed 0x%lx\n", e.window);
1458 #endif // DEBUG
1459
1460 assert(_iconic); // we shouldn't be able to get this unless we're iconic
1461
1462 // move to the current desktop (uniconify)
1463 setDesktop(openbox->screen(_screen)->desktop());
1464 // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1465 }
1466
1467 }
This page took 0.099932 seconds and 5 git commands to generate.