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