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 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_desktop
,
133 otk::Property::atoms
.cardinal
,
134 (long unsigned*)&_desktop
)) {
136 printf("DEBUG: Window requested desktop: %d\n", _desktop
);
142 void Client::getType()
144 _type
= (WindowType
) -1;
147 unsigned long num
= (unsigned) -1;
148 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_window_type
,
149 otk::Property::atoms
.atom
, &num
, &val
)) {
150 // use the first value that we know about in the array
151 for (unsigned long i
= 0; i
< num
; ++i
) {
152 if (val
[i
] == otk::Property::atoms
.net_wm_window_type_desktop
)
153 _type
= Type_Desktop
;
154 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dock
)
156 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_toolbar
)
157 _type
= Type_Toolbar
;
158 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_menu
)
160 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_utility
)
161 _type
= Type_Utility
;
162 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_splash
)
164 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dialog
)
166 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_normal
)
168 // XXX: make this work again
169 // else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override)
170 // mwm_decorations = 0; // prevent this window from getting any decor
171 if (_type
!= (WindowType
) -1)
172 break; // grab the first known type
177 if (_type
== (WindowType
) -1) {
179 * the window type hint was not set, which means we either classify ourself
180 * as a normal window or a dialog, depending on if we are a transient.
190 void Client::setupDecorAndFunctions()
192 // start with everything (cept fullscreen)
193 _decorations
= Decor_Titlebar
| Decor_Handle
| Decor_Border
|
194 Decor_AllDesktops
| Decor_Iconify
| Decor_Maximize
;
195 _functions
= Func_Resize
| Func_Move
| Func_Iconify
| Func_Maximize
|
197 if (_delete_window
) {
198 _decorations
|= Decor_Close
;
199 _functions
|= Func_Close
;
204 // normal windows retain all of the possible decorations and
205 // functionality, and are the only windows that you can fullscreen
206 _functions
|= Func_Fullscreen
;
209 // dialogs cannot be maximized
210 _decorations
&= ~Decor_Maximize
;
211 _functions
&= ~Func_Maximize
;
217 // these windows get less functionality
218 _decorations
&= ~(Decor_Iconify
| Decor_Handle
);
219 _functions
&= ~(Func_Iconify
| Func_Resize
);
225 // none of these windows are manipulated by the window manager
231 // Mwm Hints are applied subtractively to what has already been chosen for
232 // decor and functionality
233 if (_mwmhints
.flags
& MwmFlag_Decorations
) {
234 if (! (_mwmhints
.decorations
& MwmDecor_All
)) {
235 if (! (_mwmhints
.decorations
& MwmDecor_Border
))
236 _decorations
&= ~Decor_Border
;
237 if (! (_mwmhints
.decorations
& MwmDecor_Handle
))
238 _decorations
&= ~Decor_Handle
;
239 if (! (_mwmhints
.decorations
& MwmDecor_Title
)) {
240 _decorations
&= ~Decor_Titlebar
;
241 // if we don't have a titlebar, then we cannot shade!
242 _functions
&= ~Func_Shade
;
244 if (! (_mwmhints
.decorations
& MwmDecor_Iconify
))
245 _decorations
&= ~Decor_Iconify
;
246 if (! (_mwmhints
.decorations
& MwmDecor_Maximize
))
247 _decorations
&= ~Decor_Maximize
;
251 if (_mwmhints
.flags
& MwmFlag_Functions
) {
252 if (! (_mwmhints
.functions
& MwmFunc_All
)) {
253 if (! (_mwmhints
.functions
& MwmFunc_Resize
))
254 _functions
&= ~Func_Resize
;
255 if (! (_mwmhints
.functions
& MwmFunc_Move
))
256 _functions
&= ~Func_Move
;
257 if (! (_mwmhints
.functions
& MwmFunc_Iconify
))
258 _functions
&= ~Func_Iconify
;
259 if (! (_mwmhints
.functions
& MwmFunc_Maximize
))
260 _functions
&= ~Func_Maximize
;
261 // dont let mwm hints kill the close button
262 //if (! (_mwmhints.functions & MwmFunc_Close))
263 // _functions &= ~Func_Close;
267 changeAllowedActions();
271 void Client::getMwmHints()
273 unsigned long num
= MwmHints::elements
;
274 unsigned long *hints
;
276 _mwmhints
.flags
= 0; // default to none
278 if (!otk::Property::get(_window
, otk::Property::atoms
.motif_wm_hints
,
279 otk::Property::atoms
.motif_wm_hints
, &num
,
280 (unsigned long **)&hints
))
283 if (num
>= MwmHints::elements
) {
284 // retrieved the hints
285 _mwmhints
.flags
= hints
[0];
286 _mwmhints
.functions
= hints
[1];
287 _mwmhints
.decorations
= hints
[2];
294 void Client::getArea()
296 XWindowAttributes wattrib
;
299 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
300 assert(ret
!= BadWindow
);
302 _area
.setRect(wattrib
.x
, wattrib
.y
, wattrib
.width
, wattrib
.height
);
303 _border_width
= wattrib
.border_width
;
307 void Client::getState()
309 _modal
= _shaded
= _max_horz
= _max_vert
= _fullscreen
= _above
= _below
=
310 _iconic
= _skip_taskbar
= _skip_pager
= false;
312 unsigned long *state
;
313 unsigned long num
= (unsigned) -1;
315 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_state
,
316 otk::Property::atoms
.atom
, &num
, &state
)) {
317 for (unsigned long i
= 0; i
< num
; ++i
) {
318 if (state
[i
] == otk::Property::atoms
.net_wm_state_modal
)
320 else if (state
[i
] == otk::Property::atoms
.net_wm_state_shaded
)
322 else if (state
[i
] == otk::Property::atoms
.net_wm_state_hidden
)
324 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_taskbar
)
325 _skip_taskbar
= true;
326 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_pager
)
328 else if (state
[i
] == otk::Property::atoms
.net_wm_state_fullscreen
)
330 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_vert
)
332 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_horz
)
334 else if (state
[i
] == otk::Property::atoms
.net_wm_state_above
)
336 else if (state
[i
] == otk::Property::atoms
.net_wm_state_below
)
345 void Client::getShaped()
349 if (otk::display
->shape()) {
354 XShapeSelectInput(**otk::display
, _window
, ShapeNotifyMask
);
356 XShapeQueryExtents(**otk::display
, _window
, &s
, &foo
,
357 &foo
, &ufoo
, &ufoo
, &foo
, &foo
, &foo
, &ufoo
, &ufoo
);
364 void Client::calcLayer() {
367 if (_iconic
) l
= Layer_Icon
;
368 else if (_fullscreen
) l
= Layer_Fullscreen
;
369 else if (_type
== Type_Desktop
) l
= Layer_Desktop
;
370 else if (_type
== Type_Dock
) {
371 if (!_below
) l
= Layer_Top
;
372 else l
= Layer_Normal
;
374 else if (_above
) l
= Layer_Above
;
375 else if (_below
) l
= Layer_Below
;
376 else l
= Layer_Normal
;
382 if we don't have a frame, then we aren't mapped yet (and this would
385 openbox
->screen(_screen
)->raiseWindow(this);
391 void Client::updateProtocols()
396 _focus_notify
= false;
397 _delete_window
= false;
399 if (XGetWMProtocols(**otk::display
, _window
, &proto
, &num_return
)) {
400 for (int i
= 0; i
< num_return
; ++i
) {
401 if (proto
[i
] == otk::Property::atoms
.wm_delete_window
) {
402 // this means we can request the window to close
403 _delete_window
= true;
404 } else if (proto
[i
] == otk::Property::atoms
.wm_take_focus
)
405 // if this protocol is requested, then the window will be notified
406 // by the window manager whenever it receives focus
407 _focus_notify
= true;
414 void Client::updateNormalHints()
418 int oldgravity
= _gravity
;
421 _size_inc
.setPoint(1, 1);
422 _base_size
.setPoint(0, 0);
423 _min_size
.setPoint(0, 0);
424 _max_size
.setPoint(INT_MAX
, INT_MAX
);
426 // XXX: might want to cancel any interactive resizing of the window at this
429 // get the hints from the window
430 if (XGetWMNormalHints(**otk::display
, _window
, &size
, &ret
)) {
431 _positioned
= (size
.flags
& (PPosition
|USPosition
));
433 if (size
.flags
& PWinGravity
) {
434 _gravity
= size
.win_gravity
;
436 // if the client has a frame, i.e. has already been mapped and is
437 // changing its gravity
438 if (frame
&& _gravity
!= oldgravity
) {
439 // move our idea of the client's position based on its new gravity
441 frame
->frameGravity(x
, y
);
446 if (size
.flags
& PMinSize
)
447 _min_size
.setPoint(size
.min_width
, size
.min_height
);
449 if (size
.flags
& PMaxSize
)
450 _max_size
.setPoint(size
.max_width
, size
.max_height
);
452 if (size
.flags
& PBaseSize
)
453 _base_size
.setPoint(size
.base_width
, size
.base_height
);
455 if (size
.flags
& PResizeInc
)
456 _size_inc
.setPoint(size
.width_inc
, size
.height_inc
);
461 void Client::updateWMHints(bool initstate
)
465 // assume a window takes input if it doesnt specify
469 if ((hints
= XGetWMHints(**otk::display
, _window
)) != NULL
) {
470 if (hints
->flags
& InputHint
)
471 _can_focus
= hints
->input
;
473 // only do this when initstate is true!
474 if (initstate
&& (hints
->flags
& StateHint
))
475 _iconic
= hints
->initial_state
== IconicState
;
477 if (hints
->flags
& XUrgencyHint
)
480 if (hints
->flags
& WindowGroupHint
) {
481 if (hints
->window_group
!= _group
) {
482 // XXX: remove from the old group if there was one
483 _group
= hints
->window_group
;
484 // XXX: do stuff with the group
494 // fire the urgent callback if we're mapped, otherwise, wait until after
496 if (_urgent
&& frame
)
502 void Client::updateTitle()
507 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_name
,
508 otk::Property::utf8
, &_title
)) {
510 otk::Property::get(_window
, otk::Property::atoms
.wm_name
,
511 otk::Property::ascii
, &_title
);
515 _title
= _("Unnamed Window");
518 frame
->setTitle(_title
);
522 void Client::updateIconTitle()
527 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_icon_name
,
528 otk::Property::utf8
, &_icon_title
)) {
530 otk::Property::get(_window
, otk::Property::atoms
.wm_icon_name
,
531 otk::Property::ascii
, &_icon_title
);
535 _icon_title
= _("Unnamed Window");
539 void Client::updateClass()
542 _app_name
= _app_class
= _role
= "";
544 otk::Property::StringVect v
;
545 unsigned long num
= 2;
547 if (otk::Property::get(_window
, otk::Property::atoms
.wm_class
,
548 otk::Property::ascii
, &num
, &v
)) {
549 if (num
> 0) _app_name
= v
[0].c_str();
550 if (num
> 1) _app_class
= v
[1].c_str();
555 if (otk::Property::get(_window
, otk::Property::atoms
.wm_window_role
,
556 otk::Property::ascii
, &num
, &v
)) {
557 if (num
> 0) _role
= v
[0].c_str();
562 void Client::updateStrut()
564 unsigned long num
= 4;
566 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_strut
,
567 otk::Property::atoms
.cardinal
, &num
, &data
))
571 _strut
.left
= data
[0];
572 _strut
.right
= data
[1];
573 _strut
.top
= data
[2];
574 _strut
.bottom
= data
[3];
576 openbox
->screen(_screen
)->updateStrut();
583 void Client::updateTransientFor()
588 if (XGetTransientForHint(**otk::display
, _window
, &t
) &&
589 t
!= _window
) { // cant be transient to itself!
590 c
= openbox
->findClient(t
);
591 assert(c
!= this); // if this happens then we need to check for it
593 if (!c
/*XXX: && _group*/) {
594 // not transient to a client, see if it is transient for a group
595 if (//t == _group->leader() ||
597 t
== otk::display
->screenInfo(_screen
)->rootWindow()) {
598 // window is a transient for its group!
599 // XXX: for now this is treated as non-transient.
600 // this needs to be fixed!
605 // if anything has changed...
606 if (c
!= _transient_for
) {
608 _transient_for
->_transients
.remove(this); // remove from old parent
611 _transient_for
->_transients
.push_back(this); // add to new parent
613 // XXX: change decor status?
618 void Client::propertyHandler(const XPropertyEvent
&e
)
620 otk::EventHandler::propertyHandler(e
);
622 // compress changes to a single property into a single change
624 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
625 // XXX: it would be nice to compress ALL changes to a property, not just
626 // changes in a row without other props between.
627 if (ce
.xproperty
.atom
!= e
.atom
) {
628 XPutBackEvent(**otk::display
, &ce
);
633 if (e
.atom
== XA_WM_NORMAL_HINTS
)
635 else if (e
.atom
== XA_WM_HINTS
)
637 else if (e
.atom
== XA_WM_TRANSIENT_FOR
) {
638 updateTransientFor();
640 calcLayer(); // type may have changed, so update the layer
641 setupDecorAndFunctions();
642 frame
->adjustSize(); // this updates the frame for any new decor settings
644 else if (e
.atom
== otk::Property::atoms
.net_wm_name
||
645 e
.atom
== otk::Property::atoms
.wm_name
)
647 else if (e
.atom
== otk::Property::atoms
.net_wm_icon_name
||
648 e
.atom
== otk::Property::atoms
.wm_icon_name
)
650 else if (e
.atom
== otk::Property::atoms
.wm_class
)
652 else if (e
.atom
== otk::Property::atoms
.wm_protocols
) {
654 setupDecorAndFunctions();
655 frame
->adjustSize(); // update the decorations
657 else if (e
.atom
== otk::Property::atoms
.net_wm_strut
)
662 void Client::setWMState(long state
)
664 if (state
== _wmstate
) return; // no change
668 setDesktop(ICONIC_DESKTOP
);
671 setDesktop(openbox
->screen(_screen
)->desktop());
677 void Client::setDesktop(long target
)
679 if (target
== _desktop
) return;
681 printf("Setting desktop %ld\n", target
);
683 if (!(target
>= 0 || target
== (signed)0xffffffff ||
684 target
== ICONIC_DESKTOP
))
689 // set the desktop hint, but not if we're iconifying
690 if (_desktop
!= ICONIC_DESKTOP
)
691 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
692 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
694 // 'move' the window to the new desktop
695 if (_desktop
== openbox
->screen(_screen
)->desktop() ||
696 _desktop
== (signed)0xffffffff)
701 // Handle Iconic state. Iconic state is maintained by the client being a
702 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
703 // uniconifying happen.
704 bool i
= _desktop
== ICONIC_DESKTOP
;
705 if (i
!= _iconic
) { // has the state changed?
708 _wmstate
= IconicState
;
710 // we unmap the client itself so that we can get MapRequest events, and
711 // because the ICCCM tells us to!
712 XUnmapWindow(**otk::display
, _window
);
714 _wmstate
= NormalState
;
715 XMapWindow(**otk::display
, _window
);
720 frame
->adjustState();
724 void Client::setState(StateAction action
, long data1
, long data2
)
726 bool shadestate
= _shaded
;
727 bool fsstate
= _fullscreen
;
729 if (!(action
== State_Add
|| action
== State_Remove
||
730 action
== State_Toggle
))
731 return; // an invalid action was passed to the client message, ignore it
733 for (int i
= 0; i
< 2; ++i
) {
734 Atom state
= i
== 0 ? data1
: data2
;
736 if (! state
) continue;
738 // if toggling, then pick whether we're adding or removing
739 if (action
== State_Toggle
) {
740 if (state
== otk::Property::atoms
.net_wm_state_modal
)
741 action
= _modal
? State_Remove
: State_Add
;
742 else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
)
743 action
= _max_vert
? State_Remove
: State_Add
;
744 else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
)
745 action
= _max_horz
? State_Remove
: State_Add
;
746 else if (state
== otk::Property::atoms
.net_wm_state_shaded
)
747 action
= _shaded
? State_Remove
: State_Add
;
748 else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
)
749 action
= _skip_taskbar
? State_Remove
: State_Add
;
750 else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
)
751 action
= _skip_pager
? State_Remove
: State_Add
;
752 else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
)
753 action
= _fullscreen
? State_Remove
: State_Add
;
754 else if (state
== otk::Property::atoms
.net_wm_state_above
)
755 action
= _above
? State_Remove
: State_Add
;
756 else if (state
== otk::Property::atoms
.net_wm_state_below
)
757 action
= _below
? State_Remove
: State_Add
;
760 if (action
== State_Add
) {
761 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
762 if (_modal
) continue;
764 // XXX: give it focus if another window has focus that shouldnt now
765 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
766 if (_max_vert
) continue;
768 // XXX: resize the window etc
769 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
770 if (_max_horz
) continue;
772 // XXX: resize the window etc
773 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
775 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
776 _skip_taskbar
= true;
777 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
779 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
781 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
782 if (_above
) continue;
784 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
785 if (_below
) continue;
789 } else { // action == State_Remove
790 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
791 if (!_modal
) continue;
793 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
794 if (!_max_vert
) continue;
796 // XXX: resize the window etc
797 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
798 if (!_max_horz
) continue;
800 // XXX: resize the window etc
801 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
803 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
804 _skip_taskbar
= false;
805 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
807 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
809 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
810 if (!_above
) continue;
812 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
813 if (!_below
) continue;
818 // change fullscreen state before shading, as it will affect if the window
820 if (fsstate
!= _fullscreen
)
822 if (shadestate
!= _shaded
)
828 void Client::toggleClientBorder(bool addborder
)
830 // adjust our idea of where the client is, based on its border. When the
831 // border is removed, the client should now be considered to be in a
832 // different position.
833 // when re-adding the border to the client, the same operation needs to be
835 int x
= _area
.x(), y
= _area
.y();
838 case NorthWestGravity
:
840 case SouthWestGravity
:
842 case NorthEastGravity
:
844 case SouthEastGravity
:
845 if (addborder
) x
-= _border_width
* 2;
846 else x
+= _border_width
* 2;
853 if (addborder
) x
-= _border_width
;
854 else x
+= _border_width
;
859 case NorthWestGravity
:
861 case NorthEastGravity
:
863 case SouthWestGravity
:
865 case SouthEastGravity
:
866 if (addborder
) y
-= _border_width
* 2;
867 else y
+= _border_width
* 2;
874 if (addborder
) y
-= _border_width
;
875 else y
+= _border_width
;
881 XSetWindowBorderWidth(**otk::display
, _window
, _border_width
);
883 // move the client so it is back it the right spot _with_ its border!
884 XMoveWindow(**otk::display
, _window
, x
, y
);
886 XSetWindowBorderWidth(**otk::display
, _window
, 0);
890 void Client::clientMessageHandler(const XClientMessageEvent
&e
)
892 otk::EventHandler::clientMessageHandler(e
);
894 if (e
.format
!= 32) return;
896 if (e
.message_type
== otk::Property::atoms
.wm_change_state
) {
897 // compress changes into a single change
898 bool compress
= false;
900 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
901 // XXX: it would be nice to compress ALL messages of a type, not just
902 // messages in a row without other message types between.
903 if (ce
.xclient
.message_type
!= e
.message_type
) {
904 XPutBackEvent(**otk::display
, &ce
);
910 setWMState(ce
.xclient
.data
.l
[0]); // use the found event
912 setWMState(e
.data
.l
[0]); // use the original event
913 } else if (e
.message_type
== otk::Property::atoms
.net_wm_desktop
) {
914 // compress changes into a single change
915 bool compress
= false;
917 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
918 // XXX: it would be nice to compress ALL messages of a type, not just
919 // messages in a row without other message types between.
920 if (ce
.xclient
.message_type
!= e
.message_type
) {
921 XPutBackEvent(**otk::display
, &ce
);
927 setDesktop(e
.data
.l
[0]); // use the found event
929 setDesktop(e
.data
.l
[0]); // use the original event
930 } else if (e
.message_type
== otk::Property::atoms
.net_wm_state
) {
931 // can't compress these
933 printf("net_wm_state %s %ld %ld for 0x%lx\n",
934 (e
.data
.l
[0] == 0 ? "Remove" : e
.data
.l
[0] == 1 ? "Add" :
935 e
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
936 e
.data
.l
[1], e
.data
.l
[2], _window
);
938 setState((StateAction
)e
.data
.l
[0], e
.data
.l
[1], e
.data
.l
[2]);
939 } else if (e
.message_type
== otk::Property::atoms
.net_close_window
) {
941 printf("net_close_window for 0x%lx\n", _window
);
944 } else if (e
.message_type
== otk::Property::atoms
.net_active_window
) {
946 printf("net_active_window for 0x%lx\n", _window
);
949 setDesktop(openbox
->screen(_screen
)->desktop());
954 openbox
->screen(_screen
)->raiseWindow(this);
960 void Client::shapeHandler(const XShapeEvent
&e
)
962 otk::EventHandler::shapeHandler(e
);
964 if (e
.kind
== ShapeBounding
) {
966 frame
->adjustShape();
972 void Client::resize(Corner anchor
, int w
, int h
)
974 if (!(_functions
& Func_Resize
)) return;
975 internal_resize(anchor
, w
, h
);
979 void Client::internal_resize(Corner anchor
, int w
, int h
, int x
, int y
)
984 // for interactive resizing. have to move half an increment in each
986 w
+= _size_inc
.x() / 2;
987 h
+= _size_inc
.y() / 2;
989 // is the window resizable? if it is not, then don't check its sizes, the
990 // client can do what it wants and the user can't change it anyhow
991 if (_min_size
.x() <= _max_size
.x() && _min_size
.y() <= _max_size
.y()) {
992 // smaller than min size or bigger than max size?
993 if (w
< _min_size
.x()) w
= _min_size
.x();
994 else if (w
> _max_size
.x()) w
= _max_size
.x();
995 if (h
< _min_size
.y()) h
= _min_size
.y();
996 else if (h
> _max_size
.y()) h
= _max_size
.y();
999 // keep to the increments
1003 // you cannot resize to nothing
1007 // store the logical size
1008 _logical_size
.setPoint(w
, h
);
1013 w
+= _base_size
.x();
1014 h
+= _base_size
.y();
1016 if (x
== INT_MIN
|| y
== INT_MIN
) {
1023 x
-= w
- _area
.width();
1026 y
-= h
- _area
.height();
1029 x
-= w
- _area
.width();
1030 y
-= h
- _area
.height();
1035 _area
.setSize(w
, h
);
1037 XResizeWindow(**otk::display
, _window
, w
, h
);
1039 // resize the frame to match the request
1040 frame
->adjustSize();
1041 internal_move(x
, y
);
1045 void Client::move(int x
, int y
)
1047 if (!(_functions
& Func_Move
)) return;
1048 internal_move(x
, y
);
1052 void Client::internal_move(int x
, int y
)
1056 // move the frame to be in the requested position
1057 if (frame
) { // this can be called while mapping, before frame exists
1058 frame
->adjustPosition();
1060 // send synthetic configure notify (we don't need to if we aren't mapped
1063 event
.type
= ConfigureNotify
;
1064 event
.xconfigure
.display
= **otk::display
;
1065 event
.xconfigure
.event
= _window
;
1066 event
.xconfigure
.window
= _window
;
1067 event
.xconfigure
.x
= x
;
1068 event
.xconfigure
.y
= y
;
1069 event
.xconfigure
.width
= _area
.width();
1070 event
.xconfigure
.height
= _area
.height();
1071 event
.xconfigure
.border_width
= _border_width
;
1072 event
.xconfigure
.above
= frame
->window();
1073 event
.xconfigure
.override_redirect
= False
;
1074 XSendEvent(event
.xconfigure
.display
, event
.xconfigure
.window
, False
,
1075 StructureNotifyMask
, &event
);
1080 void Client::close()
1084 if (!(_functions
& Func_Close
)) return;
1086 // XXX: itd be cool to do timeouts and shit here for killing the client's
1088 // like... if the window is around after 5 seconds, then the close button
1089 // turns a nice red, and if this function is called again, the client is
1090 // explicitly killed.
1092 ce
.xclient
.type
= ClientMessage
;
1093 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1094 ce
.xclient
.display
= **otk::display
;
1095 ce
.xclient
.window
= _window
;
1096 ce
.xclient
.format
= 32;
1097 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_delete_window
;
1098 ce
.xclient
.data
.l
[1] = CurrentTime
;
1099 ce
.xclient
.data
.l
[2] = 0l;
1100 ce
.xclient
.data
.l
[3] = 0l;
1101 ce
.xclient
.data
.l
[4] = 0l;
1102 XSendEvent(**otk::display
, _window
, false, NoEventMask
, &ce
);
1106 void Client::changeState()
1108 unsigned long state
[2];
1109 state
[0] = _wmstate
;
1111 otk::Property::set(_window
, otk::Property::atoms
.wm_state
,
1112 otk::Property::atoms
.wm_state
, state
, 2);
1117 netstate
[num
++] = otk::Property::atoms
.net_wm_state_modal
;
1119 netstate
[num
++] = otk::Property::atoms
.net_wm_state_shaded
;
1121 netstate
[num
++] = otk::Property::atoms
.net_wm_state_hidden
;
1123 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_taskbar
;
1125 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_pager
;
1127 netstate
[num
++] = otk::Property::atoms
.net_wm_state_fullscreen
;
1129 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_vert
;
1131 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_horz
;
1133 netstate
[num
++] = otk::Property::atoms
.net_wm_state_above
;
1135 netstate
[num
++] = otk::Property::atoms
.net_wm_state_below
;
1136 otk::Property::set(_window
, otk::Property::atoms
.net_wm_state
,
1137 otk::Property::atoms
.atom
, netstate
, num
);
1142 frame
->adjustState();
1146 void Client::changeAllowedActions(void)
1151 actions
[num
++] = otk::Property::atoms
.net_wm_action_change_desktop
;
1153 if (_functions
& Func_Shade
)
1154 actions
[num
++] = otk::Property::atoms
.net_wm_action_shade
;
1155 if (_functions
& Func_Close
)
1156 actions
[num
++] = otk::Property::atoms
.net_wm_action_close
;
1157 if (_functions
& Func_Move
)
1158 actions
[num
++] = otk::Property::atoms
.net_wm_action_move
;
1159 if (_functions
& Func_Iconify
)
1160 actions
[num
++] = otk::Property::atoms
.net_wm_action_minimize
;
1161 if (_functions
& Func_Resize
)
1162 actions
[num
++] = otk::Property::atoms
.net_wm_action_resize
;
1163 if (_functions
& Func_Fullscreen
)
1164 actions
[num
++] = otk::Property::atoms
.net_wm_action_fullscreen
;
1165 if (_functions
& Func_Maximize
) {
1166 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_horz
;
1167 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_vert
;
1170 otk::Property::set(_window
, otk::Property::atoms
.net_wm_allowed_actions
,
1171 otk::Property::atoms
.atom
, actions
, num
);
1175 void Client::applyStartupState()
1177 // these are in a carefully crafted order..
1180 printf("MAP ICONIC\n");
1182 setDesktop(ICONIC_DESKTOP
);
1185 _fullscreen
= false;
1195 if (_max_vert
); // XXX: incomplete
1196 if (_max_horz
); // XXX: incomplete
1198 if (_skip_taskbar
); // nothing to do for this
1199 if (_skip_pager
); // nothing to do for this
1200 if (_modal
); // nothing to do for this
1201 if (_above
); // nothing to do for this
1202 if (_below
); // nothing to do for this
1206 void Client::fireUrgent()
1208 // call the python UrgentNotify callbacks
1209 EventData
data(_screen
, this, EventUrgentNotify
, 0);
1210 openbox
->bindings()->fireEvent(&data
);
1214 void Client::shade(bool shade
)
1216 if (!(_functions
& Func_Shade
) || // can't
1217 _shaded
== shade
) return; // already done
1219 // when we're iconic, don't change the wmstate
1221 _wmstate
= shade
? IconicState
: NormalState
;
1224 frame
->adjustSize();
1228 void Client::fullscreen(bool fs
)
1230 static FunctionFlags saved_func
;
1231 static DecorationFlags saved_decor
;
1232 static otk::Rect saved_area
;
1233 static otk::Point saved_logical_size
;
1235 if (!(_functions
& Func_Fullscreen
) || // can't
1236 _fullscreen
== fs
) return; // already done
1239 changeState(); // change the state hints on the client
1242 // save the functions and remove them
1243 saved_func
= _functions
;
1244 _functions
= _functions
& (Func_Close
| Func_Fullscreen
| Func_Iconify
);
1245 // save the decorations and remove them
1246 saved_decor
= _decorations
;
1248 // save the area and adjust it (we don't call internal resize here for
1249 // constraints on the size, etc, we just make it fullscreen).
1251 const otk::ScreenInfo
*info
= otk::display
->screenInfo(_screen
);
1252 _area
.setRect(0, 0, info
->width(), info
->height());
1253 saved_logical_size
= _logical_size
;
1254 _logical_size
.setPoint((info
->width() - _base_size
.x()) / _size_inc
.x(),
1255 (info
->height() - _base_size
.y()) / _size_inc
.y());
1257 _functions
= saved_func
;
1258 _decorations
= saved_decor
;
1260 _logical_size
= saved_logical_size
;
1263 changeAllowedActions(); // based on the new _functions
1265 frame
->adjustSize(); // drop/replace the decor's and resize
1266 frame
->adjustPosition(); // get (back) in position!
1268 // raise (back) into our stacking layer
1269 openbox
->screen(_screen
)->raiseWindow(this);
1271 // try focus us when we go into fullscreen mode
1276 bool Client::focus()
1278 // won't try focus if the client doesn't want it, or if the window isn't
1279 // visible on the screen
1280 if (!(frame
->isVisible() && (_can_focus
|| _focus_notify
))) return false;
1282 if (_focused
) return true;
1284 // do a check to see if the window has already been unmapped or destroyed
1285 // do this intelligently while watching out for unmaps we've generated
1286 // (ignore_unmaps > 0)
1288 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &ev
)) {
1289 XPutBackEvent(**otk::display
, &ev
);
1292 while (XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &ev
)) {
1293 if (ignore_unmaps
) {
1294 unmapHandler(ev
.xunmap
);
1296 XPutBackEvent(**otk::display
, &ev
);
1302 XSetInputFocus(**otk::display
, _window
,
1303 RevertToNone
, CurrentTime
);
1305 if (_focus_notify
) {
1307 ce
.xclient
.type
= ClientMessage
;
1308 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1309 ce
.xclient
.display
= **otk::display
;
1310 ce
.xclient
.window
= _window
;
1311 ce
.xclient
.format
= 32;
1312 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_take_focus
;
1313 ce
.xclient
.data
.l
[1] = openbox
->lastTime();
1314 ce
.xclient
.data
.l
[2] = 0l;
1315 ce
.xclient
.data
.l
[3] = 0l;
1316 ce
.xclient
.data
.l
[4] = 0l;
1317 XSendEvent(**otk::display
, _window
, False
, NoEventMask
, &ce
);
1324 void Client::unfocus() const
1326 if (!_focused
) return;
1328 assert(openbox
->focusedClient() == this);
1329 openbox
->setFocusedClient(0);
1333 void Client::focusHandler(const XFocusChangeEvent
&e
)
1336 // printf("FocusIn for 0x%lx\n", e.window);
1339 otk::EventHandler::focusHandler(e
);
1344 openbox
->setFocusedClient(this);
1348 void Client::unfocusHandler(const XFocusChangeEvent
&e
)
1351 // printf("FocusOut for 0x%lx\n", e.window);
1354 otk::EventHandler::unfocusHandler(e
);
1359 if (openbox
->focusedClient() == this)
1360 openbox
->setFocusedClient(0);
1364 void Client::configureRequestHandler(const XConfigureRequestEvent
&e
)
1367 printf("ConfigureRequest for 0x%lx\n", e
.window
);
1370 otk::EventHandler::configureRequestHandler(e
);
1372 // if we are iconic (or shaded (fvwm does this)) ignore the event
1373 if (_iconic
|| _shaded
) return;
1375 if (e
.value_mask
& CWBorderWidth
)
1376 _border_width
= e
.border_width
;
1378 // resize, then move, as specified in the EWMH section 7.7
1379 if (e
.value_mask
& (CWWidth
| CWHeight
)) {
1380 int w
= (e
.value_mask
& CWWidth
) ? e
.width
: _area
.width();
1381 int h
= (e
.value_mask
& CWHeight
) ? e
.height
: _area
.height();
1385 case NorthEastGravity
:
1389 case SouthWestGravity
:
1391 corner
= BottomLeft
;
1393 case SouthEastGravity
:
1394 corner
= BottomRight
;
1396 default: // NorthWest, Static, etc
1400 // if moving AND resizing ...
1401 if (e
.value_mask
& (CWX
| CWY
)) {
1402 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1403 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1404 internal_resize(corner
, w
, h
, x
, y
);
1405 } else // if JUST resizing...
1406 internal_resize(corner
, w
, h
);
1407 } else if (e
.value_mask
& (CWX
| CWY
)) { // if JUST moving...
1408 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1409 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1410 internal_move(x
, y
);
1413 if (e
.value_mask
& CWStackMode
) {
1417 openbox
->screen(_screen
)->lowerWindow(this);
1423 openbox
->screen(_screen
)->raiseWindow(this);
1430 void Client::unmapHandler(const XUnmapEvent
&e
)
1432 if (ignore_unmaps
) {
1434 printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e
.window
, e
.event
);
1441 printf("UnmapNotify for 0x%lx\n", e
.window
);
1444 otk::EventHandler::unmapHandler(e
);
1446 // this deletes us etc
1447 openbox
->screen(_screen
)->unmanageWindow(this);
1451 void Client::destroyHandler(const XDestroyWindowEvent
&e
)
1454 printf("DestroyNotify for 0x%lx\n", e
.window
);
1457 otk::EventHandler::destroyHandler(e
);
1459 // this deletes us etc
1460 openbox
->screen(_screen
)->unmanageWindow(this);
1464 void Client::reparentHandler(const XReparentEvent
&e
)
1466 // this is when the client is first taken captive in the frame
1467 if (e
.parent
== frame
->plate()) return;
1470 printf("ReparentNotify for 0x%lx\n", e
.window
);
1473 otk::EventHandler::reparentHandler(e
);
1476 This event is quite rare and is usually handled in unmapHandler.
1477 However, if the window is unmapped when the reparent event occurs,
1478 the window manager never sees it because an unmap event is not sent
1479 to an already unmapped window.
1482 // we don't want the reparent event, put it back on the stack for the X
1483 // server to deal with after we unmanage the window
1486 XPutBackEvent(**otk::display
, &ev
);
1488 // this deletes us etc
1489 openbox
->screen(_screen
)->unmanageWindow(this);
1492 void Client::mapRequestHandler(const XMapRequestEvent
&e
)
1495 printf("MapRequest for already managed 0x%lx\n", e
.window
);
1498 assert(_iconic
); // we shouldn't be able to get this unless we're iconic
1500 // move to the current desktop (uniconify)
1501 setDesktop(openbox
->screen(_screen
)->desktop());
1502 // XXX: should we focus/raise the window? (basically a net_wm_active_window)