1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
4 # include "../config.h"
11 #include "otk/display.hh"
12 #include "otk/property.hh"
16 #include <X11/Xutil.h>
17 #include <X11/Xatom.h>
22 #define _(str) gettext(str)
27 Client::Client(int screen
, Window window
)
28 : otk::EventHandler(),
29 WidgetBase(WidgetBase::Type_Client
),
30 frame(0), _screen(screen
), _window(window
)
37 // update EVERYTHING the first time!!
39 // we default to NormalState, visible
40 _wmstate
= NormalState
;
43 // not a transient by default of course
45 // pick a layer to start from
46 _layer
= Layer_Normal
;
60 // got the type, the mwmhints, and the protocols, so we're ready to set up
61 // the decorations/functions
62 setupDecorAndFunctions();
64 getGravity(); // get the attribute gravity
65 updateNormalHints(); // this may override the attribute gravity
66 // also get the initial_state and set _iconic if we aren't "starting"
67 // when we're "starting" that means we should use whatever state was already
68 // on the window over the initial map state, because it was already mapped
69 updateWMHints(openbox
->state() != Openbox::State_Starting
);
75 // this makes sure that these windows appear on all desktops
76 if (_type
== Type_Dock
|| _type
== Type_Desktop
)
77 _desktop
= 0xffffffff;
79 // set the desktop hint, to make sure that it always exists, and to reflect
80 // any changes we've made here
81 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
82 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
90 // clean up childrens' references
91 while (!_transients
.empty()) {
92 _transients
.front()->_transient_for
= 0;
93 _transients
.pop_front();
96 // clean up parents reference to this
98 _transient_for
->_transients
.remove(this); // remove from old parent
100 if (openbox
->state() != Openbox::State_Exiting
) {
101 // these values should not be persisted across a window unmapping/mapping
102 otk::Property::erase(_window
, otk::Property::atoms
.net_wm_desktop
);
103 otk::Property::erase(_window
, otk::Property::atoms
.net_wm_state
);
105 // if we're left in an iconic state, the client wont be mapped. this is
106 // bad, since we will no longer be managing the window on restart
108 XMapWindow(**otk::display
, _window
);
113 void Client::getGravity()
115 XWindowAttributes wattrib
;
118 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
119 assert(ret
!= BadWindow
);
120 _gravity
= wattrib
.win_gravity
;
124 void Client::getDesktop()
126 // defaults to the current desktop
127 _desktop
= openbox
->screen(_screen
)->desktop();
129 otk::Property::get(_window
, otk::Property::atoms
.net_wm_desktop
,
130 otk::Property::atoms
.cardinal
,
131 (long unsigned*)&_desktop
);
135 void Client::getType()
137 _type
= (WindowType
) -1;
140 unsigned long num
= (unsigned) -1;
141 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_window_type
,
142 otk::Property::atoms
.atom
, &num
, &val
)) {
143 // use the first value that we know about in the array
144 for (unsigned long i
= 0; i
< num
; ++i
) {
145 if (val
[i
] == otk::Property::atoms
.net_wm_window_type_desktop
)
146 _type
= Type_Desktop
;
147 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dock
)
149 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_toolbar
)
150 _type
= Type_Toolbar
;
151 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_menu
)
153 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_utility
)
154 _type
= Type_Utility
;
155 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_splash
)
157 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dialog
)
159 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_normal
)
161 // XXX: make this work again
162 // else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override)
163 // mwm_decorations = 0; // prevent this window from getting any decor
164 if (_type
!= (WindowType
) -1)
165 break; // grab the first known type
170 if (_type
== (WindowType
) -1) {
172 * the window type hint was not set, which means we either classify ourself
173 * as a normal window or a dialog, depending on if we are a transient.
183 void Client::setupDecorAndFunctions()
185 // start with everything (cept fullscreen)
186 _decorations
= Decor_Titlebar
| Decor_Handle
| Decor_Border
|
187 Decor_AllDesktops
| Decor_Iconify
| Decor_Maximize
;
188 _functions
= Func_Resize
| Func_Move
| Func_Iconify
| Func_Maximize
|
190 if (_delete_window
) {
191 _decorations
|= Decor_Close
;
192 _functions
|= Func_Close
;
197 // normal windows retain all of the possible decorations and
198 // functionality, and are the only windows that you can fullscreen
199 _functions
|= Func_Fullscreen
;
202 // dialogs cannot be maximized
203 _decorations
&= ~Decor_Maximize
;
204 _functions
&= ~Func_Maximize
;
210 // these windows get less functionality
211 _decorations
&= ~(Decor_Iconify
| Decor_Handle
);
212 _functions
&= ~(Func_Iconify
| Func_Resize
);
218 // none of these windows are manipulated by the window manager
224 // Mwm Hints are applied subtractively to what has already been chosen for
225 // decor and functionality
226 if (_mwmhints
.flags
& MwmFlag_Decorations
) {
227 if (! (_mwmhints
.decorations
& MwmDecor_All
)) {
228 if (! (_mwmhints
.decorations
& MwmDecor_Border
))
229 _decorations
&= ~Decor_Border
;
230 if (! (_mwmhints
.decorations
& MwmDecor_Handle
))
231 _decorations
&= ~Decor_Handle
;
232 if (! (_mwmhints
.decorations
& MwmDecor_Title
)) {
233 _decorations
&= ~Decor_Titlebar
;
234 // if we don't have a titlebar, then we cannot shade!
235 _functions
&= ~Func_Shade
;
237 if (! (_mwmhints
.decorations
& MwmDecor_Iconify
))
238 _decorations
&= ~Decor_Iconify
;
239 if (! (_mwmhints
.decorations
& MwmDecor_Maximize
))
240 _decorations
&= ~Decor_Maximize
;
244 if (_mwmhints
.flags
& MwmFlag_Functions
) {
245 if (! (_mwmhints
.functions
& MwmFunc_All
)) {
246 if (! (_mwmhints
.functions
& MwmFunc_Resize
))
247 _functions
&= ~Func_Resize
;
248 if (! (_mwmhints
.functions
& MwmFunc_Move
))
249 _functions
&= ~Func_Move
;
250 if (! (_mwmhints
.functions
& MwmFunc_Iconify
))
251 _functions
&= ~Func_Iconify
;
252 if (! (_mwmhints
.functions
& MwmFunc_Maximize
))
253 _functions
&= ~Func_Maximize
;
254 // dont let mwm hints kill the close button
255 //if (! (_mwmhints.functions & MwmFunc_Close))
256 // _functions &= ~Func_Close;
260 changeAllowedActions();
264 void Client::getMwmHints()
266 unsigned long num
= MwmHints::elements
;
267 unsigned long *hints
;
269 _mwmhints
.flags
= 0; // default to none
271 if (!otk::Property::get(_window
, otk::Property::atoms
.motif_wm_hints
,
272 otk::Property::atoms
.motif_wm_hints
, &num
,
273 (unsigned long **)&hints
))
276 if (num
>= MwmHints::elements
) {
277 // retrieved the hints
278 _mwmhints
.flags
= hints
[0];
279 _mwmhints
.functions
= hints
[1];
280 _mwmhints
.decorations
= hints
[2];
287 void Client::getArea()
289 XWindowAttributes wattrib
;
292 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
293 assert(ret
!= BadWindow
);
295 _area
.setRect(wattrib
.x
, wattrib
.y
, wattrib
.width
, wattrib
.height
);
296 _border_width
= wattrib
.border_width
;
300 void Client::getState()
302 _modal
= _shaded
= _max_horz
= _max_vert
= _fullscreen
= _above
= _below
=
303 _iconic
= _skip_taskbar
= _skip_pager
= false;
305 unsigned long *state
;
306 unsigned long num
= (unsigned) -1;
308 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_state
,
309 otk::Property::atoms
.atom
, &num
, &state
)) {
310 for (unsigned long i
= 0; i
< num
; ++i
) {
311 if (state
[i
] == otk::Property::atoms
.net_wm_state_modal
)
313 else if (state
[i
] == otk::Property::atoms
.net_wm_state_shaded
)
315 else if (state
[i
] == otk::Property::atoms
.net_wm_state_hidden
)
317 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_taskbar
)
318 _skip_taskbar
= true;
319 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_pager
)
321 else if (state
[i
] == otk::Property::atoms
.net_wm_state_fullscreen
)
323 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_vert
)
325 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_horz
)
327 else if (state
[i
] == otk::Property::atoms
.net_wm_state_above
)
329 else if (state
[i
] == otk::Property::atoms
.net_wm_state_below
)
338 void Client::getShaped()
342 if (otk::display
->shape()) {
347 XShapeSelectInput(**otk::display
, _window
, ShapeNotifyMask
);
349 XShapeQueryExtents(**otk::display
, _window
, &s
, &foo
,
350 &foo
, &ufoo
, &ufoo
, &foo
, &foo
, &foo
, &ufoo
, &ufoo
);
357 void Client::calcLayer() {
360 if (_iconic
) l
= Layer_Icon
;
361 else if (_fullscreen
) l
= Layer_Fullscreen
;
362 else if (_type
== Type_Desktop
) l
= Layer_Desktop
;
363 else if (_type
== Type_Dock
) {
364 if (!_below
) l
= Layer_Top
;
365 else l
= Layer_Normal
;
367 else if (_above
) l
= Layer_Above
;
368 else if (_below
) l
= Layer_Below
;
369 else l
= Layer_Normal
;
375 if we don't have a frame, then we aren't mapped yet (and this would
378 openbox
->screen(_screen
)->raiseWindow(this);
384 void Client::updateProtocols()
389 _focus_notify
= false;
390 _delete_window
= false;
392 if (XGetWMProtocols(**otk::display
, _window
, &proto
, &num_return
)) {
393 for (int i
= 0; i
< num_return
; ++i
) {
394 if (proto
[i
] == otk::Property::atoms
.wm_delete_window
) {
395 // this means we can request the window to close
396 _delete_window
= true;
397 } else if (proto
[i
] == otk::Property::atoms
.wm_take_focus
)
398 // if this protocol is requested, then the window will be notified
399 // by the window manager whenever it receives focus
400 _focus_notify
= true;
407 void Client::updateNormalHints()
411 int oldgravity
= _gravity
;
414 _size_inc
.setPoint(1, 1);
415 _base_size
.setPoint(0, 0);
416 _min_size
.setPoint(0, 0);
417 _max_size
.setPoint(INT_MAX
, INT_MAX
);
419 // XXX: might want to cancel any interactive resizing of the window at this
422 // get the hints from the window
423 if (XGetWMNormalHints(**otk::display
, _window
, &size
, &ret
)) {
424 _positioned
= (size
.flags
& (PPosition
|USPosition
));
426 if (size
.flags
& PWinGravity
) {
427 _gravity
= size
.win_gravity
;
429 // if the client has a frame, i.e. has already been mapped and is
430 // changing its gravity
431 if (frame
&& _gravity
!= oldgravity
) {
432 // move our idea of the client's position based on its new gravity
434 frame
->frameGravity(x
, y
);
439 if (size
.flags
& PMinSize
)
440 _min_size
.setPoint(size
.min_width
, size
.min_height
);
442 if (size
.flags
& PMaxSize
)
443 _max_size
.setPoint(size
.max_width
, size
.max_height
);
445 if (size
.flags
& PBaseSize
)
446 _base_size
.setPoint(size
.base_width
, size
.base_height
);
448 if (size
.flags
& PResizeInc
)
449 _size_inc
.setPoint(size
.width_inc
, size
.height_inc
);
454 void Client::updateWMHints(bool initstate
)
458 // assume a window takes input if it doesnt specify
462 if ((hints
= XGetWMHints(**otk::display
, _window
)) != NULL
) {
463 if (hints
->flags
& InputHint
)
464 _can_focus
= hints
->input
;
466 // only do this when initstate is true!
467 if (initstate
&& (hints
->flags
& StateHint
))
468 _iconic
= hints
->initial_state
== IconicState
;
470 if (hints
->flags
& XUrgencyHint
)
473 if (hints
->flags
& WindowGroupHint
) {
474 if (hints
->window_group
!= _group
) {
475 // XXX: remove from the old group if there was one
476 _group
= hints
->window_group
;
477 // XXX: do stuff with the group
487 void Client::updateTitle()
492 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_name
,
493 otk::Property::utf8
, &_title
)) {
495 otk::Property::get(_window
, otk::Property::atoms
.wm_name
,
496 otk::Property::ascii
, &_title
);
500 _title
= _("Unnamed Window");
503 frame
->setTitle(_title
);
507 void Client::updateIconTitle()
512 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_icon_name
,
513 otk::Property::utf8
, &_icon_title
)) {
515 otk::Property::get(_window
, otk::Property::atoms
.wm_icon_name
,
516 otk::Property::ascii
, &_icon_title
);
520 _icon_title
= _("Unnamed Window");
524 void Client::updateClass()
527 _app_name
= _app_class
= _role
= "";
529 otk::Property::StringVect v
;
530 unsigned long num
= 2;
532 if (otk::Property::get(_window
, otk::Property::atoms
.wm_class
,
533 otk::Property::ascii
, &num
, &v
)) {
534 if (num
> 0) _app_name
= v
[0].c_str();
535 if (num
> 1) _app_class
= v
[1].c_str();
540 if (otk::Property::get(_window
, otk::Property::atoms
.wm_window_role
,
541 otk::Property::ascii
, &num
, &v
)) {
542 if (num
> 0) _role
= v
[0].c_str();
547 void Client::updateStrut()
549 unsigned long num
= 4;
551 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_strut
,
552 otk::Property::atoms
.cardinal
, &num
, &data
))
556 _strut
.left
= data
[0];
557 _strut
.right
= data
[1];
558 _strut
.top
= data
[2];
559 _strut
.bottom
= data
[3];
561 openbox
->screen(_screen
)->updateStrut();
568 void Client::updateTransientFor()
573 if (XGetTransientForHint(**otk::display
, _window
, &t
) &&
574 t
!= _window
) { // cant be transient to itself!
575 c
= openbox
->findClient(t
);
576 assert(c
!= this); // if this happens then we need to check for it
578 if (!c
/*XXX: && _group*/) {
579 // not transient to a client, see if it is transient for a group
580 if (//t == _group->leader() ||
582 t
== otk::display
->screenInfo(_screen
)->rootWindow()) {
583 // window is a transient for its group!
584 // XXX: for now this is treated as non-transient.
585 // this needs to be fixed!
590 // if anything has changed...
591 if (c
!= _transient_for
) {
593 _transient_for
->_transients
.remove(this); // remove from old parent
596 _transient_for
->_transients
.push_back(this); // add to new parent
598 // XXX: change decor status?
603 void Client::propertyHandler(const XPropertyEvent
&e
)
605 otk::EventHandler::propertyHandler(e
);
607 // compress changes to a single property into a single change
609 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
610 // XXX: it would be nice to compress ALL changes to a property, not just
611 // changes in a row without other props between.
612 if (ce
.xproperty
.atom
!= e
.atom
) {
613 XPutBackEvent(**otk::display
, &ce
);
618 if (e
.atom
== XA_WM_NORMAL_HINTS
)
620 else if (e
.atom
== XA_WM_HINTS
)
622 else if (e
.atom
== XA_WM_TRANSIENT_FOR
) {
623 updateTransientFor();
625 calcLayer(); // type may have changed, so update the layer
626 setupDecorAndFunctions();
627 frame
->adjustSize(); // this updates the frame for any new decor settings
629 else if (e
.atom
== otk::Property::atoms
.net_wm_name
||
630 e
.atom
== otk::Property::atoms
.wm_name
)
632 else if (e
.atom
== otk::Property::atoms
.net_wm_icon_name
||
633 e
.atom
== otk::Property::atoms
.wm_icon_name
)
635 else if (e
.atom
== otk::Property::atoms
.wm_class
)
637 else if (e
.atom
== otk::Property::atoms
.wm_protocols
) {
639 setupDecorAndFunctions();
640 frame
->adjustSize(); // update the decorations
642 else if (e
.atom
== otk::Property::atoms
.net_wm_strut
)
647 void Client::setWMState(long state
)
649 if (state
== _wmstate
) return; // no change
653 setDesktop(ICONIC_DESKTOP
);
656 setDesktop(openbox
->screen(_screen
)->desktop());
662 void Client::setDesktop(long target
)
664 if (target
== _desktop
) return;
666 printf("Setting desktop %ld\n", target
);
668 if (!(target
>= 0 || target
== (signed)0xffffffff ||
669 target
== ICONIC_DESKTOP
))
674 // set the desktop hint, but not if we're iconifying
675 if (_desktop
!= ICONIC_DESKTOP
)
676 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
677 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
679 // 'move' the window to the new desktop
680 if (_desktop
== openbox
->screen(_screen
)->desktop() ||
681 _desktop
== (signed)0xffffffff)
686 // Handle Iconic state. Iconic state is maintained by the client being a
687 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
688 // uniconifying happen.
689 bool i
= _desktop
== ICONIC_DESKTOP
;
690 if (i
!= _iconic
) { // has the state changed?
693 _wmstate
= IconicState
;
695 // we unmap the client itself so that we can get MapRequest events, and
696 // because the ICCCM tells us to!
697 XUnmapWindow(**otk::display
, _window
);
699 _wmstate
= NormalState
;
700 XMapWindow(**otk::display
, _window
);
705 frame
->adjustState();
709 void Client::setState(StateAction action
, long data1
, long data2
)
711 bool shadestate
= _shaded
;
712 bool fsstate
= _fullscreen
;
714 if (!(action
== State_Add
|| action
== State_Remove
||
715 action
== State_Toggle
))
716 return; // an invalid action was passed to the client message, ignore it
718 for (int i
= 0; i
< 2; ++i
) {
719 Atom state
= i
== 0 ? data1
: data2
;
721 if (! state
) continue;
723 // if toggling, then pick whether we're adding or removing
724 if (action
== State_Toggle
) {
725 if (state
== otk::Property::atoms
.net_wm_state_modal
)
726 action
= _modal
? State_Remove
: State_Add
;
727 else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
)
728 action
= _max_vert
? State_Remove
: State_Add
;
729 else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
)
730 action
= _max_horz
? State_Remove
: State_Add
;
731 else if (state
== otk::Property::atoms
.net_wm_state_shaded
)
732 action
= _shaded
? State_Remove
: State_Add
;
733 else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
)
734 action
= _skip_taskbar
? State_Remove
: State_Add
;
735 else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
)
736 action
= _skip_pager
? State_Remove
: State_Add
;
737 else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
)
738 action
= _fullscreen
? State_Remove
: State_Add
;
739 else if (state
== otk::Property::atoms
.net_wm_state_above
)
740 action
= _above
? State_Remove
: State_Add
;
741 else if (state
== otk::Property::atoms
.net_wm_state_below
)
742 action
= _below
? State_Remove
: State_Add
;
745 if (action
== State_Add
) {
746 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
747 if (_modal
) continue;
749 // XXX: give it focus if another window has focus that shouldnt now
750 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
751 if (_max_vert
) continue;
753 // XXX: resize the window etc
754 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
755 if (_max_horz
) continue;
757 // XXX: resize the window etc
758 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
760 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
761 _skip_taskbar
= true;
762 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
764 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
766 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
767 if (_above
) continue;
769 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
770 if (_below
) continue;
774 } else { // action == State_Remove
775 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
776 if (!_modal
) continue;
778 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
779 if (!_max_vert
) continue;
781 // XXX: resize the window etc
782 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
783 if (!_max_horz
) continue;
785 // XXX: resize the window etc
786 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
788 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
789 _skip_taskbar
= false;
790 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
792 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
794 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
795 if (!_above
) continue;
797 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
798 if (!_below
) continue;
803 // change fullscreen state before shading, as it will affect if the window
805 if (fsstate
!= _fullscreen
)
807 if (shadestate
!= _shaded
)
813 void Client::toggleClientBorder(bool addborder
)
815 // adjust our idea of where the client is, based on its border. When the
816 // border is removed, the client should now be considered to be in a
817 // different position.
818 // when re-adding the border to the client, the same operation needs to be
820 int x
= _area
.x(), y
= _area
.y();
823 case NorthWestGravity
:
825 case SouthWestGravity
:
827 case NorthEastGravity
:
829 case SouthEastGravity
:
830 if (addborder
) x
-= _border_width
* 2;
831 else x
+= _border_width
* 2;
838 if (addborder
) x
-= _border_width
;
839 else x
+= _border_width
;
844 case NorthWestGravity
:
846 case NorthEastGravity
:
848 case SouthWestGravity
:
850 case SouthEastGravity
:
851 if (addborder
) y
-= _border_width
* 2;
852 else y
+= _border_width
* 2;
859 if (addborder
) y
-= _border_width
;
860 else y
+= _border_width
;
866 XSetWindowBorderWidth(**otk::display
, _window
, _border_width
);
868 // move the client so it is back it the right spot _with_ its border!
869 XMoveWindow(**otk::display
, _window
, x
, y
);
871 XSetWindowBorderWidth(**otk::display
, _window
, 0);
875 void Client::clientMessageHandler(const XClientMessageEvent
&e
)
877 otk::EventHandler::clientMessageHandler(e
);
879 if (e
.format
!= 32) return;
881 if (e
.message_type
== otk::Property::atoms
.wm_change_state
) {
882 // compress changes into a single change
883 bool compress
= false;
885 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
886 // XXX: it would be nice to compress ALL messages of a type, not just
887 // messages in a row without other message types between.
888 if (ce
.xclient
.message_type
!= e
.message_type
) {
889 XPutBackEvent(**otk::display
, &ce
);
895 setWMState(ce
.xclient
.data
.l
[0]); // use the found event
897 setWMState(e
.data
.l
[0]); // use the original event
898 } else if (e
.message_type
== otk::Property::atoms
.net_wm_desktop
) {
899 // compress changes into a single change
900 bool compress
= false;
902 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
903 // XXX: it would be nice to compress ALL messages of a type, not just
904 // messages in a row without other message types between.
905 if (ce
.xclient
.message_type
!= e
.message_type
) {
906 XPutBackEvent(**otk::display
, &ce
);
912 setDesktop(e
.data
.l
[0]); // use the found event
914 setDesktop(e
.data
.l
[0]); // use the original event
915 } else if (e
.message_type
== otk::Property::atoms
.net_wm_state
) {
916 // can't compress these
918 printf("net_wm_state %s %ld %ld for 0x%lx\n",
919 (e
.data
.l
[0] == 0 ? "Remove" : e
.data
.l
[0] == 1 ? "Add" :
920 e
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
921 e
.data
.l
[1], e
.data
.l
[2], _window
);
923 setState((StateAction
)e
.data
.l
[0], e
.data
.l
[1], e
.data
.l
[2]);
924 } else if (e
.message_type
== otk::Property::atoms
.net_close_window
) {
926 printf("net_close_window for 0x%lx\n", _window
);
929 } else if (e
.message_type
== otk::Property::atoms
.net_active_window
) {
931 printf("net_active_window for 0x%lx\n", _window
);
934 setDesktop(openbox
->screen(_screen
)->desktop());
939 openbox
->screen(_screen
)->raiseWindow(this);
945 void Client::shapeHandler(const XShapeEvent
&e
)
947 otk::EventHandler::shapeHandler(e
);
949 if (e
.kind
== ShapeBounding
) {
951 frame
->adjustShape();
957 void Client::resize(Corner anchor
, int w
, int h
)
959 if (!(_functions
& Func_Resize
)) return;
960 internal_resize(anchor
, w
, h
);
964 void Client::internal_resize(Corner anchor
, int w
, int h
, int x
, int y
)
969 // for interactive resizing. have to move half an increment in each
971 w
+= _size_inc
.x() / 2;
972 h
+= _size_inc
.y() / 2;
974 // is the window resizable? if it is not, then don't check its sizes, the
975 // client can do what it wants and the user can't change it anyhow
976 if (_min_size
.x() <= _max_size
.x() && _min_size
.y() <= _max_size
.y()) {
977 // smaller than min size or bigger than max size?
978 if (w
< _min_size
.x()) w
= _min_size
.x();
979 else if (w
> _max_size
.x()) w
= _max_size
.x();
980 if (h
< _min_size
.y()) h
= _min_size
.y();
981 else if (h
> _max_size
.y()) h
= _max_size
.y();
984 // keep to the increments
988 // you cannot resize to nothing
992 // store the logical size
993 _logical_size
.setPoint(w
, h
);
1001 if (x
== INT_MIN
|| y
== INT_MIN
) {
1008 x
-= w
- _area
.width();
1011 y
-= h
- _area
.height();
1014 x
-= w
- _area
.width();
1015 y
-= h
- _area
.height();
1020 _area
.setSize(w
, h
);
1022 XResizeWindow(**otk::display
, _window
, w
, h
);
1024 // resize the frame to match the request
1025 frame
->adjustSize();
1026 internal_move(x
, y
);
1030 void Client::move(int x
, int y
)
1032 if (!(_functions
& Func_Move
)) return;
1033 internal_move(x
, y
);
1037 void Client::internal_move(int x
, int y
)
1041 // move the frame to be in the requested position
1042 if (frame
) { // this can be called while mapping, before frame exists
1043 frame
->adjustPosition();
1045 // send synthetic configure notify (we don't need to if we aren't mapped
1048 event
.type
= ConfigureNotify
;
1049 event
.xconfigure
.display
= **otk::display
;
1050 event
.xconfigure
.event
= _window
;
1051 event
.xconfigure
.window
= _window
;
1052 event
.xconfigure
.x
= x
;
1053 event
.xconfigure
.y
= y
;
1054 event
.xconfigure
.width
= _area
.width();
1055 event
.xconfigure
.height
= _area
.height();
1056 event
.xconfigure
.border_width
= _border_width
;
1057 event
.xconfigure
.above
= frame
->window();
1058 event
.xconfigure
.override_redirect
= False
;
1059 XSendEvent(event
.xconfigure
.display
, event
.xconfigure
.window
, False
,
1060 StructureNotifyMask
, &event
);
1065 void Client::close()
1069 if (!(_functions
& Func_Close
)) return;
1071 // XXX: itd be cool to do timeouts and shit here for killing the client's
1073 // like... if the window is around after 5 seconds, then the close button
1074 // turns a nice red, and if this function is called again, the client is
1075 // explicitly killed.
1077 ce
.xclient
.type
= ClientMessage
;
1078 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1079 ce
.xclient
.display
= **otk::display
;
1080 ce
.xclient
.window
= _window
;
1081 ce
.xclient
.format
= 32;
1082 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_delete_window
;
1083 ce
.xclient
.data
.l
[1] = CurrentTime
;
1084 ce
.xclient
.data
.l
[2] = 0l;
1085 ce
.xclient
.data
.l
[3] = 0l;
1086 ce
.xclient
.data
.l
[4] = 0l;
1087 XSendEvent(**otk::display
, _window
, false, NoEventMask
, &ce
);
1091 void Client::changeState()
1093 unsigned long state
[2];
1094 state
[0] = _wmstate
;
1096 otk::Property::set(_window
, otk::Property::atoms
.wm_state
,
1097 otk::Property::atoms
.wm_state
, state
, 2);
1102 netstate
[num
++] = otk::Property::atoms
.net_wm_state_modal
;
1104 netstate
[num
++] = otk::Property::atoms
.net_wm_state_shaded
;
1106 netstate
[num
++] = otk::Property::atoms
.net_wm_state_hidden
;
1108 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_taskbar
;
1110 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_pager
;
1112 netstate
[num
++] = otk::Property::atoms
.net_wm_state_fullscreen
;
1114 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_vert
;
1116 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_horz
;
1118 netstate
[num
++] = otk::Property::atoms
.net_wm_state_above
;
1120 netstate
[num
++] = otk::Property::atoms
.net_wm_state_below
;
1121 otk::Property::set(_window
, otk::Property::atoms
.net_wm_state
,
1122 otk::Property::atoms
.atom
, netstate
, num
);
1127 frame
->adjustState();
1131 void Client::changeAllowedActions(void)
1136 actions
[num
++] = otk::Property::atoms
.net_wm_action_change_desktop
;
1138 if (_functions
& Func_Shade
)
1139 actions
[num
++] = otk::Property::atoms
.net_wm_action_shade
;
1140 if (_functions
& Func_Close
)
1141 actions
[num
++] = otk::Property::atoms
.net_wm_action_close
;
1142 if (_functions
& Func_Move
)
1143 actions
[num
++] = otk::Property::atoms
.net_wm_action_move
;
1144 if (_functions
& Func_Iconify
)
1145 actions
[num
++] = otk::Property::atoms
.net_wm_action_minimize
;
1146 if (_functions
& Func_Resize
)
1147 actions
[num
++] = otk::Property::atoms
.net_wm_action_resize
;
1148 if (_functions
& Func_Fullscreen
)
1149 actions
[num
++] = otk::Property::atoms
.net_wm_action_fullscreen
;
1150 if (_functions
& Func_Maximize
) {
1151 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_horz
;
1152 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_vert
;
1155 otk::Property::set(_window
, otk::Property::atoms
.net_wm_allowed_actions
,
1156 otk::Property::atoms
.atom
, actions
, num
);
1160 void Client::applyStartupState()
1162 // these are in a carefully crafted order..
1165 printf("MAP ICONIC\n");
1167 setDesktop(ICONIC_DESKTOP
);
1170 _fullscreen
= false;
1178 if (_max_vert
); // XXX: incomplete
1179 if (_max_horz
); // XXX: incomplete
1181 if (_skip_taskbar
); // nothing to do for this
1182 if (_skip_pager
); // nothing to do for this
1183 if (_modal
); // nothing to do for this
1184 if (_above
); // nothing to do for this
1185 if (_below
); // nothing to do for this
1189 void Client::shade(bool shade
)
1191 if (!(_functions
& Func_Shade
) || // can't
1192 _shaded
== shade
) return; // already done
1194 // when we're iconic, don't change the wmstate
1196 _wmstate
= shade
? IconicState
: NormalState
;
1199 frame
->adjustSize();
1203 void Client::fullscreen(bool fs
)
1205 static FunctionFlags saved_func
;
1206 static DecorationFlags saved_decor
;
1207 static otk::Rect saved_area
;
1208 static otk::Point saved_logical_size
;
1210 if (!(_functions
& Func_Fullscreen
) || // can't
1211 _fullscreen
== fs
) return; // already done
1214 changeState(); // change the state hints on the client
1217 // save the functions and remove them
1218 saved_func
= _functions
;
1219 _functions
= _functions
& (Func_Close
| Func_Fullscreen
| Func_Iconify
);
1220 // save the decorations and remove them
1221 saved_decor
= _decorations
;
1223 // save the area and adjust it (we don't call internal resize here for
1224 // constraints on the size, etc, we just make it fullscreen).
1226 const otk::ScreenInfo
*info
= otk::display
->screenInfo(_screen
);
1227 _area
.setRect(0, 0, info
->width(), info
->height());
1228 saved_logical_size
= _logical_size
;
1229 _logical_size
.setPoint((info
->width() - _base_size
.x()) / _size_inc
.x(),
1230 (info
->height() - _base_size
.y()) / _size_inc
.y());
1232 _functions
= saved_func
;
1233 _decorations
= saved_decor
;
1235 _logical_size
= saved_logical_size
;
1238 changeAllowedActions(); // based on the new _functions
1240 frame
->adjustSize(); // drop/replace the decor's and resize
1241 frame
->adjustPosition(); // get (back) in position!
1243 // raise (back) into our stacking layer
1244 openbox
->screen(_screen
)->raiseWindow(this);
1246 // try focus us when we go into fullscreen mode
1251 bool Client::focus()
1253 // won't try focus if the client doesn't want it, or if the window isn't
1254 // visible on the screen
1255 if (!(frame
->isVisible() && (_can_focus
|| _focus_notify
))) return false;
1257 if (_focused
) return true;
1259 // do a check to see if the window has already been unmapped or destroyed
1260 // do this intelligently while watching out for unmaps we've generated
1261 // (ignore_unmaps > 0)
1263 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &ev
)) {
1264 XPutBackEvent(**otk::display
, &ev
);
1267 while (XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &ev
)) {
1268 if (ignore_unmaps
) {
1271 XPutBackEvent(**otk::display
, &ev
);
1277 XSetInputFocus(**otk::display
, _window
,
1278 RevertToNone
, CurrentTime
);
1280 if (_focus_notify
) {
1282 ce
.xclient
.type
= ClientMessage
;
1283 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1284 ce
.xclient
.display
= **otk::display
;
1285 ce
.xclient
.window
= _window
;
1286 ce
.xclient
.format
= 32;
1287 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_take_focus
;
1288 ce
.xclient
.data
.l
[1] = openbox
->lastTime();
1289 ce
.xclient
.data
.l
[2] = 0l;
1290 ce
.xclient
.data
.l
[3] = 0l;
1291 ce
.xclient
.data
.l
[4] = 0l;
1292 XSendEvent(**otk::display
, _window
, False
, NoEventMask
, &ce
);
1299 void Client::unfocus() const
1301 if (!_focused
) return;
1303 assert(openbox
->focusedClient() == this);
1304 openbox
->setFocusedClient(0);
1308 void Client::focusHandler(const XFocusChangeEvent
&e
)
1311 // printf("FocusIn for 0x%lx\n", e.window);
1314 otk::EventHandler::focusHandler(e
);
1319 openbox
->setFocusedClient(this);
1323 void Client::unfocusHandler(const XFocusChangeEvent
&e
)
1326 // printf("FocusOut for 0x%lx\n", e.window);
1329 otk::EventHandler::unfocusHandler(e
);
1334 if (openbox
->focusedClient() == this)
1335 openbox
->setFocusedClient(0);
1339 void Client::configureRequestHandler(const XConfigureRequestEvent
&e
)
1342 printf("ConfigureRequest for 0x%lx\n", e
.window
);
1345 otk::EventHandler::configureRequestHandler(e
);
1347 // if we are iconic (or shaded (fvwm does this)) ignore the event
1348 if (_iconic
|| _shaded
) return;
1350 if (e
.value_mask
& CWBorderWidth
)
1351 _border_width
= e
.border_width
;
1353 // resize, then move, as specified in the EWMH section 7.7
1354 if (e
.value_mask
& (CWWidth
| CWHeight
)) {
1355 int w
= (e
.value_mask
& CWWidth
) ? e
.width
: _area
.width();
1356 int h
= (e
.value_mask
& CWHeight
) ? e
.height
: _area
.height();
1360 case NorthEastGravity
:
1364 case SouthWestGravity
:
1366 corner
= BottomLeft
;
1368 case SouthEastGravity
:
1369 corner
= BottomRight
;
1371 default: // NorthWest, Static, etc
1375 // if moving AND resizing ...
1376 if (e
.value_mask
& (CWX
| CWY
)) {
1377 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1378 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1379 internal_resize(corner
, w
, h
, x
, y
);
1380 } else // if JUST resizing...
1381 internal_resize(corner
, w
, h
);
1382 } else if (e
.value_mask
& (CWX
| CWY
)) { // if JUST moving...
1383 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1384 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1385 internal_move(x
, y
);
1388 if (e
.value_mask
& CWStackMode
) {
1392 openbox
->screen(_screen
)->lowerWindow(this);
1398 openbox
->screen(_screen
)->raiseWindow(this);
1405 void Client::unmapHandler(const XUnmapEvent
&e
)
1407 if (ignore_unmaps
) {
1409 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1416 printf("UnmapNotify for 0x%lx\n", e
.window
);
1419 otk::EventHandler::unmapHandler(e
);
1421 // this deletes us etc
1422 openbox
->screen(_screen
)->unmanageWindow(this);
1426 void Client::destroyHandler(const XDestroyWindowEvent
&e
)
1429 printf("DestroyNotify for 0x%lx\n", e
.window
);
1432 otk::EventHandler::destroyHandler(e
);
1434 // this deletes us etc
1435 openbox
->screen(_screen
)->unmanageWindow(this);
1439 void Client::reparentHandler(const XReparentEvent
&e
)
1441 // this is when the client is first taken captive in the frame
1442 if (e
.parent
== frame
->plate()) return;
1445 printf("ReparentNotify for 0x%lx\n", e
.window
);
1448 otk::EventHandler::reparentHandler(e
);
1451 This event is quite rare and is usually handled in unmapHandler.
1452 However, if the window is unmapped when the reparent event occurs,
1453 the window manager never sees it because an unmap event is not sent
1454 to an already unmapped window.
1457 // we don't want the reparent event, put it back on the stack for the X
1458 // server to deal with after we unmanage the window
1461 XPutBackEvent(**otk::display
, &ev
);
1463 // this deletes us etc
1464 openbox
->screen(_screen
)->unmanageWindow(this);
1467 void Client::mapRequestHandler(const XMapRequestEvent
&e
)
1470 printf("MapRequest for already managed 0x%lx\n", e
.window
);
1473 assert(_iconic
); // we shouldn't be able to get this unless we're iconic
1475 // move to the current desktop (uniconify)
1476 setDesktop(openbox
->screen(_screen
)->desktop());
1477 // XXX: should we focus/raise the window? (basically a net_wm_active_window)