1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
4 # include "../config.h"
11 #include "bindings.hh"
12 #include "otk/display.hh"
13 #include "otk/property.hh"
17 #include <X11/Xutil.h>
18 #include <X11/Xatom.h>
23 #define _(str) gettext(str)
28 Client::Client(int screen
, Window window
)
29 : otk::EventHandler(),
30 WidgetBase(WidgetBase::Type_Client
),
31 frame(0), _screen(screen
), _window(window
)
38 // update EVERYTHING the first time!!
40 // we default to NormalState, visible
41 _wmstate
= NormalState
;
44 // not a transient by default of course
46 // pick a layer to start from
47 _layer
= Layer_Normal
;
48 // default to not urgent
63 // got the type, the mwmhints, and the protocols, so we're ready to set up
64 // the decorations/functions
65 setupDecorAndFunctions();
67 getGravity(); // get the attribute gravity
68 updateNormalHints(); // this may override the attribute gravity
69 // also get the initial_state and set _iconic if we aren't "starting"
70 // when we're "starting" that means we should use whatever state was already
71 // on the window over the initial map state, because it was already mapped
72 updateWMHints(openbox
->state() != Openbox::State_Starting
);
78 // this makes sure that these windows appear on all desktops
79 if (_type
== Type_Dock
|| _type
== Type_Desktop
)
80 _desktop
= 0xffffffff;
82 // set the desktop hint, to make sure that it always exists, and to reflect
83 // any changes we've made here
84 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
85 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
93 // clean up childrens' references
94 while (!_transients
.empty()) {
95 _transients
.front()->_transient_for
= 0;
96 _transients
.pop_front();
99 // clean up parents reference to this
101 _transient_for
->_transients
.remove(this); // remove from old parent
103 if (openbox
->state() != Openbox::State_Exiting
) {
104 // these values should not be persisted across a window unmapping/mapping
105 otk::Property::erase(_window
, otk::Property::atoms
.net_wm_desktop
);
106 otk::Property::erase(_window
, otk::Property::atoms
.net_wm_state
);
108 // if we're left in an iconic state, the client wont be mapped. this is
109 // bad, since we will no longer be managing the window on restart
111 XMapWindow(**otk::display
, _window
);
116 void Client::getGravity()
118 XWindowAttributes wattrib
;
121 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
122 assert(ret
!= BadWindow
);
123 _gravity
= wattrib
.win_gravity
;
127 void Client::getDesktop()
129 // defaults to the current desktop
130 _desktop
= openbox
->screen(_screen
)->desktop();
132 otk::Property::get(_window
, otk::Property::atoms
.net_wm_desktop
,
133 otk::Property::atoms
.cardinal
,
134 (long unsigned*)&_desktop
);
138 void Client::getType()
140 _type
= (WindowType
) -1;
143 unsigned long num
= (unsigned) -1;
144 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_window_type
,
145 otk::Property::atoms
.atom
, &num
, &val
)) {
146 // use the first value that we know about in the array
147 for (unsigned long i
= 0; i
< num
; ++i
) {
148 if (val
[i
] == otk::Property::atoms
.net_wm_window_type_desktop
)
149 _type
= Type_Desktop
;
150 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dock
)
152 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_toolbar
)
153 _type
= Type_Toolbar
;
154 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_menu
)
156 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_utility
)
157 _type
= Type_Utility
;
158 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_splash
)
160 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dialog
)
162 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_normal
)
164 // XXX: make this work again
165 // else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override)
166 // mwm_decorations = 0; // prevent this window from getting any decor
167 if (_type
!= (WindowType
) -1)
168 break; // grab the first known type
173 if (_type
== (WindowType
) -1) {
175 * the window type hint was not set, which means we either classify ourself
176 * as a normal window or a dialog, depending on if we are a transient.
186 void Client::setupDecorAndFunctions()
188 // start with everything (cept fullscreen)
189 _decorations
= Decor_Titlebar
| Decor_Handle
| Decor_Border
|
190 Decor_AllDesktops
| Decor_Iconify
| Decor_Maximize
;
191 _functions
= Func_Resize
| Func_Move
| Func_Iconify
| Func_Maximize
|
193 if (_delete_window
) {
194 _decorations
|= Decor_Close
;
195 _functions
|= Func_Close
;
200 // normal windows retain all of the possible decorations and
201 // functionality, and are the only windows that you can fullscreen
202 _functions
|= Func_Fullscreen
;
205 // dialogs cannot be maximized
206 _decorations
&= ~Decor_Maximize
;
207 _functions
&= ~Func_Maximize
;
213 // these windows get less functionality
214 _decorations
&= ~(Decor_Iconify
| Decor_Handle
);
215 _functions
&= ~(Func_Iconify
| Func_Resize
);
221 // none of these windows are manipulated by the window manager
227 // Mwm Hints are applied subtractively to what has already been chosen for
228 // decor and functionality
229 if (_mwmhints
.flags
& MwmFlag_Decorations
) {
230 if (! (_mwmhints
.decorations
& MwmDecor_All
)) {
231 if (! (_mwmhints
.decorations
& MwmDecor_Border
))
232 _decorations
&= ~Decor_Border
;
233 if (! (_mwmhints
.decorations
& MwmDecor_Handle
))
234 _decorations
&= ~Decor_Handle
;
235 if (! (_mwmhints
.decorations
& MwmDecor_Title
)) {
236 _decorations
&= ~Decor_Titlebar
;
237 // if we don't have a titlebar, then we cannot shade!
238 _functions
&= ~Func_Shade
;
240 if (! (_mwmhints
.decorations
& MwmDecor_Iconify
))
241 _decorations
&= ~Decor_Iconify
;
242 if (! (_mwmhints
.decorations
& MwmDecor_Maximize
))
243 _decorations
&= ~Decor_Maximize
;
247 if (_mwmhints
.flags
& MwmFlag_Functions
) {
248 if (! (_mwmhints
.functions
& MwmFunc_All
)) {
249 if (! (_mwmhints
.functions
& MwmFunc_Resize
))
250 _functions
&= ~Func_Resize
;
251 if (! (_mwmhints
.functions
& MwmFunc_Move
))
252 _functions
&= ~Func_Move
;
253 if (! (_mwmhints
.functions
& MwmFunc_Iconify
))
254 _functions
&= ~Func_Iconify
;
255 if (! (_mwmhints
.functions
& MwmFunc_Maximize
))
256 _functions
&= ~Func_Maximize
;
257 // dont let mwm hints kill the close button
258 //if (! (_mwmhints.functions & MwmFunc_Close))
259 // _functions &= ~Func_Close;
263 changeAllowedActions();
267 void Client::getMwmHints()
269 unsigned long num
= MwmHints::elements
;
270 unsigned long *hints
;
272 _mwmhints
.flags
= 0; // default to none
274 if (!otk::Property::get(_window
, otk::Property::atoms
.motif_wm_hints
,
275 otk::Property::atoms
.motif_wm_hints
, &num
,
276 (unsigned long **)&hints
))
279 if (num
>= MwmHints::elements
) {
280 // retrieved the hints
281 _mwmhints
.flags
= hints
[0];
282 _mwmhints
.functions
= hints
[1];
283 _mwmhints
.decorations
= hints
[2];
290 void Client::getArea()
292 XWindowAttributes wattrib
;
295 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
296 assert(ret
!= BadWindow
);
298 _area
.setRect(wattrib
.x
, wattrib
.y
, wattrib
.width
, wattrib
.height
);
299 _border_width
= wattrib
.border_width
;
303 void Client::getState()
305 _modal
= _shaded
= _max_horz
= _max_vert
= _fullscreen
= _above
= _below
=
306 _iconic
= _skip_taskbar
= _skip_pager
= false;
308 unsigned long *state
;
309 unsigned long num
= (unsigned) -1;
311 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_state
,
312 otk::Property::atoms
.atom
, &num
, &state
)) {
313 for (unsigned long i
= 0; i
< num
; ++i
) {
314 if (state
[i
] == otk::Property::atoms
.net_wm_state_modal
)
316 else if (state
[i
] == otk::Property::atoms
.net_wm_state_shaded
)
318 else if (state
[i
] == otk::Property::atoms
.net_wm_state_hidden
)
320 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_taskbar
)
321 _skip_taskbar
= true;
322 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_pager
)
324 else if (state
[i
] == otk::Property::atoms
.net_wm_state_fullscreen
)
326 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_vert
)
328 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_horz
)
330 else if (state
[i
] == otk::Property::atoms
.net_wm_state_above
)
332 else if (state
[i
] == otk::Property::atoms
.net_wm_state_below
)
341 void Client::getShaped()
345 if (otk::display
->shape()) {
350 XShapeSelectInput(**otk::display
, _window
, ShapeNotifyMask
);
352 XShapeQueryExtents(**otk::display
, _window
, &s
, &foo
,
353 &foo
, &ufoo
, &ufoo
, &foo
, &foo
, &foo
, &ufoo
, &ufoo
);
360 void Client::calcLayer() {
363 if (_iconic
) l
= Layer_Icon
;
364 else if (_fullscreen
) l
= Layer_Fullscreen
;
365 else if (_type
== Type_Desktop
) l
= Layer_Desktop
;
366 else if (_type
== Type_Dock
) {
367 if (!_below
) l
= Layer_Top
;
368 else l
= Layer_Normal
;
370 else if (_above
) l
= Layer_Above
;
371 else if (_below
) l
= Layer_Below
;
372 else l
= Layer_Normal
;
378 if we don't have a frame, then we aren't mapped yet (and this would
381 openbox
->screen(_screen
)->raiseWindow(this);
387 void Client::updateProtocols()
392 _focus_notify
= false;
393 _delete_window
= false;
395 if (XGetWMProtocols(**otk::display
, _window
, &proto
, &num_return
)) {
396 for (int i
= 0; i
< num_return
; ++i
) {
397 if (proto
[i
] == otk::Property::atoms
.wm_delete_window
) {
398 // this means we can request the window to close
399 _delete_window
= true;
400 } else if (proto
[i
] == otk::Property::atoms
.wm_take_focus
)
401 // if this protocol is requested, then the window will be notified
402 // by the window manager whenever it receives focus
403 _focus_notify
= true;
410 void Client::updateNormalHints()
414 int oldgravity
= _gravity
;
417 _size_inc
.setPoint(1, 1);
418 _base_size
.setPoint(0, 0);
419 _min_size
.setPoint(0, 0);
420 _max_size
.setPoint(INT_MAX
, INT_MAX
);
422 // XXX: might want to cancel any interactive resizing of the window at this
425 // get the hints from the window
426 if (XGetWMNormalHints(**otk::display
, _window
, &size
, &ret
)) {
427 _positioned
= (size
.flags
& (PPosition
|USPosition
));
429 if (size
.flags
& PWinGravity
) {
430 _gravity
= size
.win_gravity
;
432 // if the client has a frame, i.e. has already been mapped and is
433 // changing its gravity
434 if (frame
&& _gravity
!= oldgravity
) {
435 // move our idea of the client's position based on its new gravity
437 frame
->frameGravity(x
, y
);
442 if (size
.flags
& PMinSize
)
443 _min_size
.setPoint(size
.min_width
, size
.min_height
);
445 if (size
.flags
& PMaxSize
)
446 _max_size
.setPoint(size
.max_width
, size
.max_height
);
448 if (size
.flags
& PBaseSize
)
449 _base_size
.setPoint(size
.base_width
, size
.base_height
);
451 if (size
.flags
& PResizeInc
)
452 _size_inc
.setPoint(size
.width_inc
, size
.height_inc
);
457 void Client::updateWMHints(bool initstate
)
461 // assume a window takes input if it doesnt specify
465 if ((hints
= XGetWMHints(**otk::display
, _window
)) != NULL
) {
466 if (hints
->flags
& InputHint
)
467 _can_focus
= hints
->input
;
469 // only do this when initstate is true!
470 if (initstate
&& (hints
->flags
& StateHint
))
471 _iconic
= hints
->initial_state
== IconicState
;
473 if (hints
->flags
& XUrgencyHint
)
476 if (hints
->flags
& WindowGroupHint
) {
477 if (hints
->window_group
!= _group
) {
478 // XXX: remove from the old group if there was one
479 _group
= hints
->window_group
;
480 // XXX: do stuff with the group
490 // fire the urgent callback if we're mapped, otherwise, wait until after
492 if (_urgent
&& frame
)
498 void Client::updateTitle()
503 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_name
,
504 otk::Property::utf8
, &_title
)) {
506 otk::Property::get(_window
, otk::Property::atoms
.wm_name
,
507 otk::Property::ascii
, &_title
);
511 _title
= _("Unnamed Window");
514 frame
->setTitle(_title
);
518 void Client::updateIconTitle()
523 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_icon_name
,
524 otk::Property::utf8
, &_icon_title
)) {
526 otk::Property::get(_window
, otk::Property::atoms
.wm_icon_name
,
527 otk::Property::ascii
, &_icon_title
);
531 _icon_title
= _("Unnamed Window");
535 void Client::updateClass()
538 _app_name
= _app_class
= _role
= "";
540 otk::Property::StringVect v
;
541 unsigned long num
= 2;
543 if (otk::Property::get(_window
, otk::Property::atoms
.wm_class
,
544 otk::Property::ascii
, &num
, &v
)) {
545 if (num
> 0) _app_name
= v
[0].c_str();
546 if (num
> 1) _app_class
= v
[1].c_str();
551 if (otk::Property::get(_window
, otk::Property::atoms
.wm_window_role
,
552 otk::Property::ascii
, &num
, &v
)) {
553 if (num
> 0) _role
= v
[0].c_str();
558 void Client::updateStrut()
560 unsigned long num
= 4;
562 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_strut
,
563 otk::Property::atoms
.cardinal
, &num
, &data
))
567 _strut
.left
= data
[0];
568 _strut
.right
= data
[1];
569 _strut
.top
= data
[2];
570 _strut
.bottom
= data
[3];
572 openbox
->screen(_screen
)->updateStrut();
579 void Client::updateTransientFor()
584 if (XGetTransientForHint(**otk::display
, _window
, &t
) &&
585 t
!= _window
) { // cant be transient to itself!
586 c
= openbox
->findClient(t
);
587 assert(c
!= this); // if this happens then we need to check for it
589 if (!c
/*XXX: && _group*/) {
590 // not transient to a client, see if it is transient for a group
591 if (//t == _group->leader() ||
593 t
== otk::display
->screenInfo(_screen
)->rootWindow()) {
594 // window is a transient for its group!
595 // XXX: for now this is treated as non-transient.
596 // this needs to be fixed!
601 // if anything has changed...
602 if (c
!= _transient_for
) {
604 _transient_for
->_transients
.remove(this); // remove from old parent
607 _transient_for
->_transients
.push_back(this); // add to new parent
609 // XXX: change decor status?
614 void Client::propertyHandler(const XPropertyEvent
&e
)
616 otk::EventHandler::propertyHandler(e
);
618 // compress changes to a single property into a single change
620 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
621 // XXX: it would be nice to compress ALL changes to a property, not just
622 // changes in a row without other props between.
623 if (ce
.xproperty
.atom
!= e
.atom
) {
624 XPutBackEvent(**otk::display
, &ce
);
629 if (e
.atom
== XA_WM_NORMAL_HINTS
)
631 else if (e
.atom
== XA_WM_HINTS
)
633 else if (e
.atom
== XA_WM_TRANSIENT_FOR
) {
634 updateTransientFor();
636 calcLayer(); // type may have changed, so update the layer
637 setupDecorAndFunctions();
638 frame
->adjustSize(); // this updates the frame for any new decor settings
640 else if (e
.atom
== otk::Property::atoms
.net_wm_name
||
641 e
.atom
== otk::Property::atoms
.wm_name
)
643 else if (e
.atom
== otk::Property::atoms
.net_wm_icon_name
||
644 e
.atom
== otk::Property::atoms
.wm_icon_name
)
646 else if (e
.atom
== otk::Property::atoms
.wm_class
)
648 else if (e
.atom
== otk::Property::atoms
.wm_protocols
) {
650 setupDecorAndFunctions();
651 frame
->adjustSize(); // update the decorations
653 else if (e
.atom
== otk::Property::atoms
.net_wm_strut
)
658 void Client::setWMState(long state
)
660 if (state
== _wmstate
) return; // no change
664 setDesktop(ICONIC_DESKTOP
);
667 setDesktop(openbox
->screen(_screen
)->desktop());
673 void Client::setDesktop(long target
)
675 if (target
== _desktop
) return;
677 printf("Setting desktop %ld\n", target
);
679 if (!(target
>= 0 || target
== (signed)0xffffffff ||
680 target
== ICONIC_DESKTOP
))
685 // set the desktop hint, but not if we're iconifying
686 if (_desktop
!= ICONIC_DESKTOP
)
687 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
688 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
690 // 'move' the window to the new desktop
691 if (_desktop
== openbox
->screen(_screen
)->desktop() ||
692 _desktop
== (signed)0xffffffff)
697 // Handle Iconic state. Iconic state is maintained by the client being a
698 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
699 // uniconifying happen.
700 bool i
= _desktop
== ICONIC_DESKTOP
;
701 if (i
!= _iconic
) { // has the state changed?
704 _wmstate
= IconicState
;
706 // we unmap the client itself so that we can get MapRequest events, and
707 // because the ICCCM tells us to!
708 XUnmapWindow(**otk::display
, _window
);
710 _wmstate
= NormalState
;
711 XMapWindow(**otk::display
, _window
);
716 frame
->adjustState();
720 void Client::setState(StateAction action
, long data1
, long data2
)
722 bool shadestate
= _shaded
;
723 bool fsstate
= _fullscreen
;
725 if (!(action
== State_Add
|| action
== State_Remove
||
726 action
== State_Toggle
))
727 return; // an invalid action was passed to the client message, ignore it
729 for (int i
= 0; i
< 2; ++i
) {
730 Atom state
= i
== 0 ? data1
: data2
;
732 if (! state
) continue;
734 // if toggling, then pick whether we're adding or removing
735 if (action
== State_Toggle
) {
736 if (state
== otk::Property::atoms
.net_wm_state_modal
)
737 action
= _modal
? State_Remove
: State_Add
;
738 else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
)
739 action
= _max_vert
? State_Remove
: State_Add
;
740 else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
)
741 action
= _max_horz
? State_Remove
: State_Add
;
742 else if (state
== otk::Property::atoms
.net_wm_state_shaded
)
743 action
= _shaded
? State_Remove
: State_Add
;
744 else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
)
745 action
= _skip_taskbar
? State_Remove
: State_Add
;
746 else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
)
747 action
= _skip_pager
? State_Remove
: State_Add
;
748 else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
)
749 action
= _fullscreen
? State_Remove
: State_Add
;
750 else if (state
== otk::Property::atoms
.net_wm_state_above
)
751 action
= _above
? State_Remove
: State_Add
;
752 else if (state
== otk::Property::atoms
.net_wm_state_below
)
753 action
= _below
? State_Remove
: State_Add
;
756 if (action
== State_Add
) {
757 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
758 if (_modal
) continue;
760 // XXX: give it focus if another window has focus that shouldnt now
761 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
762 if (_max_vert
) continue;
764 // XXX: resize the window etc
765 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
766 if (_max_horz
) continue;
768 // XXX: resize the window etc
769 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
771 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
772 _skip_taskbar
= true;
773 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
775 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
777 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
778 if (_above
) continue;
780 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
781 if (_below
) continue;
785 } else { // action == State_Remove
786 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
787 if (!_modal
) continue;
789 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
790 if (!_max_vert
) continue;
792 // XXX: resize the window etc
793 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
794 if (!_max_horz
) continue;
796 // XXX: resize the window etc
797 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
799 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
800 _skip_taskbar
= false;
801 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
803 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
805 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
806 if (!_above
) continue;
808 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
809 if (!_below
) continue;
814 // change fullscreen state before shading, as it will affect if the window
816 if (fsstate
!= _fullscreen
)
818 if (shadestate
!= _shaded
)
824 void Client::toggleClientBorder(bool addborder
)
826 // adjust our idea of where the client is, based on its border. When the
827 // border is removed, the client should now be considered to be in a
828 // different position.
829 // when re-adding the border to the client, the same operation needs to be
831 int x
= _area
.x(), y
= _area
.y();
834 case NorthWestGravity
:
836 case SouthWestGravity
:
838 case NorthEastGravity
:
840 case SouthEastGravity
:
841 if (addborder
) x
-= _border_width
* 2;
842 else x
+= _border_width
* 2;
849 if (addborder
) x
-= _border_width
;
850 else x
+= _border_width
;
855 case NorthWestGravity
:
857 case NorthEastGravity
:
859 case SouthWestGravity
:
861 case SouthEastGravity
:
862 if (addborder
) y
-= _border_width
* 2;
863 else y
+= _border_width
* 2;
870 if (addborder
) y
-= _border_width
;
871 else y
+= _border_width
;
877 XSetWindowBorderWidth(**otk::display
, _window
, _border_width
);
879 // move the client so it is back it the right spot _with_ its border!
880 XMoveWindow(**otk::display
, _window
, x
, y
);
882 XSetWindowBorderWidth(**otk::display
, _window
, 0);
886 void Client::clientMessageHandler(const XClientMessageEvent
&e
)
888 otk::EventHandler::clientMessageHandler(e
);
890 if (e
.format
!= 32) return;
892 if (e
.message_type
== otk::Property::atoms
.wm_change_state
) {
893 // compress changes into a single change
894 bool compress
= false;
896 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
897 // XXX: it would be nice to compress ALL messages of a type, not just
898 // messages in a row without other message types between.
899 if (ce
.xclient
.message_type
!= e
.message_type
) {
900 XPutBackEvent(**otk::display
, &ce
);
906 setWMState(ce
.xclient
.data
.l
[0]); // use the found event
908 setWMState(e
.data
.l
[0]); // use the original event
909 } else if (e
.message_type
== otk::Property::atoms
.net_wm_desktop
) {
910 // compress changes into a single change
911 bool compress
= false;
913 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
914 // XXX: it would be nice to compress ALL messages of a type, not just
915 // messages in a row without other message types between.
916 if (ce
.xclient
.message_type
!= e
.message_type
) {
917 XPutBackEvent(**otk::display
, &ce
);
923 setDesktop(e
.data
.l
[0]); // use the found event
925 setDesktop(e
.data
.l
[0]); // use the original event
926 } else if (e
.message_type
== otk::Property::atoms
.net_wm_state
) {
927 // can't compress these
929 printf("net_wm_state %s %ld %ld for 0x%lx\n",
930 (e
.data
.l
[0] == 0 ? "Remove" : e
.data
.l
[0] == 1 ? "Add" :
931 e
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
932 e
.data
.l
[1], e
.data
.l
[2], _window
);
934 setState((StateAction
)e
.data
.l
[0], e
.data
.l
[1], e
.data
.l
[2]);
935 } else if (e
.message_type
== otk::Property::atoms
.net_close_window
) {
937 printf("net_close_window for 0x%lx\n", _window
);
940 } else if (e
.message_type
== otk::Property::atoms
.net_active_window
) {
942 printf("net_active_window for 0x%lx\n", _window
);
945 setDesktop(openbox
->screen(_screen
)->desktop());
950 openbox
->screen(_screen
)->raiseWindow(this);
956 void Client::shapeHandler(const XShapeEvent
&e
)
958 otk::EventHandler::shapeHandler(e
);
960 if (e
.kind
== ShapeBounding
) {
962 frame
->adjustShape();
968 void Client::resize(Corner anchor
, int w
, int h
)
970 if (!(_functions
& Func_Resize
)) return;
971 internal_resize(anchor
, w
, h
);
975 void Client::internal_resize(Corner anchor
, int w
, int h
, int x
, int y
)
980 // for interactive resizing. have to move half an increment in each
982 w
+= _size_inc
.x() / 2;
983 h
+= _size_inc
.y() / 2;
985 // is the window resizable? if it is not, then don't check its sizes, the
986 // client can do what it wants and the user can't change it anyhow
987 if (_min_size
.x() <= _max_size
.x() && _min_size
.y() <= _max_size
.y()) {
988 // smaller than min size or bigger than max size?
989 if (w
< _min_size
.x()) w
= _min_size
.x();
990 else if (w
> _max_size
.x()) w
= _max_size
.x();
991 if (h
< _min_size
.y()) h
= _min_size
.y();
992 else if (h
> _max_size
.y()) h
= _max_size
.y();
995 // keep to the increments
999 // you cannot resize to nothing
1003 // store the logical size
1004 _logical_size
.setPoint(w
, h
);
1009 w
+= _base_size
.x();
1010 h
+= _base_size
.y();
1012 if (x
== INT_MIN
|| y
== INT_MIN
) {
1019 x
-= w
- _area
.width();
1022 y
-= h
- _area
.height();
1025 x
-= w
- _area
.width();
1026 y
-= h
- _area
.height();
1031 _area
.setSize(w
, h
);
1033 XResizeWindow(**otk::display
, _window
, w
, h
);
1035 // resize the frame to match the request
1036 frame
->adjustSize();
1037 internal_move(x
, y
);
1041 void Client::move(int x
, int y
)
1043 if (!(_functions
& Func_Move
)) return;
1044 internal_move(x
, y
);
1048 void Client::internal_move(int x
, int y
)
1052 // move the frame to be in the requested position
1053 if (frame
) { // this can be called while mapping, before frame exists
1054 frame
->adjustPosition();
1056 // send synthetic configure notify (we don't need to if we aren't mapped
1059 event
.type
= ConfigureNotify
;
1060 event
.xconfigure
.display
= **otk::display
;
1061 event
.xconfigure
.event
= _window
;
1062 event
.xconfigure
.window
= _window
;
1063 event
.xconfigure
.x
= x
;
1064 event
.xconfigure
.y
= y
;
1065 event
.xconfigure
.width
= _area
.width();
1066 event
.xconfigure
.height
= _area
.height();
1067 event
.xconfigure
.border_width
= _border_width
;
1068 event
.xconfigure
.above
= frame
->window();
1069 event
.xconfigure
.override_redirect
= False
;
1070 XSendEvent(event
.xconfigure
.display
, event
.xconfigure
.window
, False
,
1071 StructureNotifyMask
, &event
);
1076 void Client::close()
1080 if (!(_functions
& Func_Close
)) return;
1082 // XXX: itd be cool to do timeouts and shit here for killing the client's
1084 // like... if the window is around after 5 seconds, then the close button
1085 // turns a nice red, and if this function is called again, the client is
1086 // explicitly killed.
1088 ce
.xclient
.type
= ClientMessage
;
1089 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1090 ce
.xclient
.display
= **otk::display
;
1091 ce
.xclient
.window
= _window
;
1092 ce
.xclient
.format
= 32;
1093 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_delete_window
;
1094 ce
.xclient
.data
.l
[1] = CurrentTime
;
1095 ce
.xclient
.data
.l
[2] = 0l;
1096 ce
.xclient
.data
.l
[3] = 0l;
1097 ce
.xclient
.data
.l
[4] = 0l;
1098 XSendEvent(**otk::display
, _window
, false, NoEventMask
, &ce
);
1102 void Client::changeState()
1104 unsigned long state
[2];
1105 state
[0] = _wmstate
;
1107 otk::Property::set(_window
, otk::Property::atoms
.wm_state
,
1108 otk::Property::atoms
.wm_state
, state
, 2);
1113 netstate
[num
++] = otk::Property::atoms
.net_wm_state_modal
;
1115 netstate
[num
++] = otk::Property::atoms
.net_wm_state_shaded
;
1117 netstate
[num
++] = otk::Property::atoms
.net_wm_state_hidden
;
1119 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_taskbar
;
1121 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_pager
;
1123 netstate
[num
++] = otk::Property::atoms
.net_wm_state_fullscreen
;
1125 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_vert
;
1127 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_horz
;
1129 netstate
[num
++] = otk::Property::atoms
.net_wm_state_above
;
1131 netstate
[num
++] = otk::Property::atoms
.net_wm_state_below
;
1132 otk::Property::set(_window
, otk::Property::atoms
.net_wm_state
,
1133 otk::Property::atoms
.atom
, netstate
, num
);
1138 frame
->adjustState();
1142 void Client::changeAllowedActions(void)
1147 actions
[num
++] = otk::Property::atoms
.net_wm_action_change_desktop
;
1149 if (_functions
& Func_Shade
)
1150 actions
[num
++] = otk::Property::atoms
.net_wm_action_shade
;
1151 if (_functions
& Func_Close
)
1152 actions
[num
++] = otk::Property::atoms
.net_wm_action_close
;
1153 if (_functions
& Func_Move
)
1154 actions
[num
++] = otk::Property::atoms
.net_wm_action_move
;
1155 if (_functions
& Func_Iconify
)
1156 actions
[num
++] = otk::Property::atoms
.net_wm_action_minimize
;
1157 if (_functions
& Func_Resize
)
1158 actions
[num
++] = otk::Property::atoms
.net_wm_action_resize
;
1159 if (_functions
& Func_Fullscreen
)
1160 actions
[num
++] = otk::Property::atoms
.net_wm_action_fullscreen
;
1161 if (_functions
& Func_Maximize
) {
1162 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_horz
;
1163 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_vert
;
1166 otk::Property::set(_window
, otk::Property::atoms
.net_wm_allowed_actions
,
1167 otk::Property::atoms
.atom
, actions
, num
);
1171 void Client::applyStartupState()
1173 // these are in a carefully crafted order..
1176 printf("MAP ICONIC\n");
1178 setDesktop(ICONIC_DESKTOP
);
1181 _fullscreen
= false;
1191 if (_max_vert
); // XXX: incomplete
1192 if (_max_horz
); // XXX: incomplete
1194 if (_skip_taskbar
); // nothing to do for this
1195 if (_skip_pager
); // nothing to do for this
1196 if (_modal
); // nothing to do for this
1197 if (_above
); // nothing to do for this
1198 if (_below
); // nothing to do for this
1202 void Client::fireUrgent()
1204 // call the python UrgentNotify callbacks
1205 EventData
data(_screen
, this, EventUrgentNotify
, 0);
1206 openbox
->bindings()->fireEvent(&data
);
1210 void Client::shade(bool shade
)
1212 if (!(_functions
& Func_Shade
) || // can't
1213 _shaded
== shade
) return; // already done
1215 // when we're iconic, don't change the wmstate
1217 _wmstate
= shade
? IconicState
: NormalState
;
1220 frame
->adjustSize();
1224 void Client::fullscreen(bool fs
)
1226 static FunctionFlags saved_func
;
1227 static DecorationFlags saved_decor
;
1228 static otk::Rect saved_area
;
1229 static otk::Point saved_logical_size
;
1231 if (!(_functions
& Func_Fullscreen
) || // can't
1232 _fullscreen
== fs
) return; // already done
1235 changeState(); // change the state hints on the client
1238 // save the functions and remove them
1239 saved_func
= _functions
;
1240 _functions
= _functions
& (Func_Close
| Func_Fullscreen
| Func_Iconify
);
1241 // save the decorations and remove them
1242 saved_decor
= _decorations
;
1244 // save the area and adjust it (we don't call internal resize here for
1245 // constraints on the size, etc, we just make it fullscreen).
1247 const otk::ScreenInfo
*info
= otk::display
->screenInfo(_screen
);
1248 _area
.setRect(0, 0, info
->width(), info
->height());
1249 saved_logical_size
= _logical_size
;
1250 _logical_size
.setPoint((info
->width() - _base_size
.x()) / _size_inc
.x(),
1251 (info
->height() - _base_size
.y()) / _size_inc
.y());
1253 _functions
= saved_func
;
1254 _decorations
= saved_decor
;
1256 _logical_size
= saved_logical_size
;
1259 changeAllowedActions(); // based on the new _functions
1261 frame
->adjustSize(); // drop/replace the decor's and resize
1262 frame
->adjustPosition(); // get (back) in position!
1264 // raise (back) into our stacking layer
1265 openbox
->screen(_screen
)->raiseWindow(this);
1267 // try focus us when we go into fullscreen mode
1272 bool Client::focus()
1274 // won't try focus if the client doesn't want it, or if the window isn't
1275 // visible on the screen
1276 if (!(frame
->isVisible() && (_can_focus
|| _focus_notify
))) return false;
1278 if (_focused
) return true;
1280 // do a check to see if the window has already been unmapped or destroyed
1281 // do this intelligently while watching out for unmaps we've generated
1282 // (ignore_unmaps > 0)
1284 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &ev
)) {
1285 XPutBackEvent(**otk::display
, &ev
);
1288 while (XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &ev
)) {
1289 if (ignore_unmaps
) {
1290 unmapHandler(ev
.xunmap
);
1292 XPutBackEvent(**otk::display
, &ev
);
1298 XSetInputFocus(**otk::display
, _window
,
1299 RevertToNone
, CurrentTime
);
1301 if (_focus_notify
) {
1303 ce
.xclient
.type
= ClientMessage
;
1304 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1305 ce
.xclient
.display
= **otk::display
;
1306 ce
.xclient
.window
= _window
;
1307 ce
.xclient
.format
= 32;
1308 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_take_focus
;
1309 ce
.xclient
.data
.l
[1] = openbox
->lastTime();
1310 ce
.xclient
.data
.l
[2] = 0l;
1311 ce
.xclient
.data
.l
[3] = 0l;
1312 ce
.xclient
.data
.l
[4] = 0l;
1313 XSendEvent(**otk::display
, _window
, False
, NoEventMask
, &ce
);
1320 void Client::unfocus() const
1322 if (!_focused
) return;
1324 assert(openbox
->focusedClient() == this);
1325 openbox
->setFocusedClient(0);
1329 void Client::focusHandler(const XFocusChangeEvent
&e
)
1332 // printf("FocusIn for 0x%lx\n", e.window);
1335 otk::EventHandler::focusHandler(e
);
1340 openbox
->setFocusedClient(this);
1344 void Client::unfocusHandler(const XFocusChangeEvent
&e
)
1347 // printf("FocusOut for 0x%lx\n", e.window);
1350 otk::EventHandler::unfocusHandler(e
);
1355 if (openbox
->focusedClient() == this)
1356 openbox
->setFocusedClient(0);
1360 void Client::configureRequestHandler(const XConfigureRequestEvent
&e
)
1363 printf("ConfigureRequest for 0x%lx\n", e
.window
);
1366 otk::EventHandler::configureRequestHandler(e
);
1368 // if we are iconic (or shaded (fvwm does this)) ignore the event
1369 if (_iconic
|| _shaded
) return;
1371 if (e
.value_mask
& CWBorderWidth
)
1372 _border_width
= e
.border_width
;
1374 // resize, then move, as specified in the EWMH section 7.7
1375 if (e
.value_mask
& (CWWidth
| CWHeight
)) {
1376 int w
= (e
.value_mask
& CWWidth
) ? e
.width
: _area
.width();
1377 int h
= (e
.value_mask
& CWHeight
) ? e
.height
: _area
.height();
1381 case NorthEastGravity
:
1385 case SouthWestGravity
:
1387 corner
= BottomLeft
;
1389 case SouthEastGravity
:
1390 corner
= BottomRight
;
1392 default: // NorthWest, Static, etc
1396 // if moving AND resizing ...
1397 if (e
.value_mask
& (CWX
| CWY
)) {
1398 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1399 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1400 internal_resize(corner
, w
, h
, x
, y
);
1401 } else // if JUST resizing...
1402 internal_resize(corner
, w
, h
);
1403 } else if (e
.value_mask
& (CWX
| CWY
)) { // if JUST moving...
1404 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1405 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1406 internal_move(x
, y
);
1409 if (e
.value_mask
& CWStackMode
) {
1413 openbox
->screen(_screen
)->lowerWindow(this);
1419 openbox
->screen(_screen
)->raiseWindow(this);
1426 void Client::unmapHandler(const XUnmapEvent
&e
)
1428 if (ignore_unmaps
) {
1430 printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e
.window
, e
.event
);
1437 printf("UnmapNotify for 0x%lx\n", e
.window
);
1440 otk::EventHandler::unmapHandler(e
);
1442 // this deletes us etc
1443 openbox
->screen(_screen
)->unmanageWindow(this);
1447 void Client::destroyHandler(const XDestroyWindowEvent
&e
)
1450 printf("DestroyNotify for 0x%lx\n", e
.window
);
1453 otk::EventHandler::destroyHandler(e
);
1455 // this deletes us etc
1456 openbox
->screen(_screen
)->unmanageWindow(this);
1460 void Client::reparentHandler(const XReparentEvent
&e
)
1462 // this is when the client is first taken captive in the frame
1463 if (e
.parent
== frame
->plate()) return;
1466 printf("ReparentNotify for 0x%lx\n", e
.window
);
1469 otk::EventHandler::reparentHandler(e
);
1472 This event is quite rare and is usually handled in unmapHandler.
1473 However, if the window is unmapped when the reparent event occurs,
1474 the window manager never sees it because an unmap event is not sent
1475 to an already unmapped window.
1478 // we don't want the reparent event, put it back on the stack for the X
1479 // server to deal with after we unmanage the window
1482 XPutBackEvent(**otk::display
, &ev
);
1484 // this deletes us etc
1485 openbox
->screen(_screen
)->unmanageWindow(this);
1488 void Client::mapRequestHandler(const XMapRequestEvent
&e
)
1491 printf("MapRequest for already managed 0x%lx\n", e
.window
);
1494 assert(_iconic
); // we shouldn't be able to get this unless we're iconic
1496 // move to the current desktop (uniconify)
1497 setDesktop(openbox
->screen(_screen
)->desktop());
1498 // XXX: should we focus/raise the window? (basically a net_wm_active_window)