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("Window requested desktop: %ld\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
495 printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
496 (long)_window
, _urgent
? "ON" : "OFF");
498 // fire the urgent callback if we're mapped, otherwise, wait until after
500 if (_urgent
&& frame
)
506 void Client::updateTitle()
511 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_name
,
512 otk::Property::utf8
, &_title
)) {
514 otk::Property::get(_window
, otk::Property::atoms
.wm_name
,
515 otk::Property::ascii
, &_title
);
519 _title
= _("Unnamed Window");
522 frame
->setTitle(_title
);
526 void Client::updateIconTitle()
531 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_icon_name
,
532 otk::Property::utf8
, &_icon_title
)) {
534 otk::Property::get(_window
, otk::Property::atoms
.wm_icon_name
,
535 otk::Property::ascii
, &_icon_title
);
539 _icon_title
= _("Unnamed Window");
543 void Client::updateClass()
546 _app_name
= _app_class
= _role
= "";
548 otk::Property::StringVect v
;
549 unsigned long num
= 2;
551 if (otk::Property::get(_window
, otk::Property::atoms
.wm_class
,
552 otk::Property::ascii
, &num
, &v
)) {
553 if (num
> 0) _app_name
= v
[0].c_str();
554 if (num
> 1) _app_class
= v
[1].c_str();
559 if (otk::Property::get(_window
, otk::Property::atoms
.wm_window_role
,
560 otk::Property::ascii
, &num
, &v
)) {
561 if (num
> 0) _role
= v
[0].c_str();
566 void Client::updateStrut()
568 unsigned long num
= 4;
570 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_strut
,
571 otk::Property::atoms
.cardinal
, &num
, &data
))
575 _strut
.left
= data
[0];
576 _strut
.right
= data
[1];
577 _strut
.top
= data
[2];
578 _strut
.bottom
= data
[3];
580 openbox
->screen(_screen
)->updateStrut();
587 void Client::updateTransientFor()
592 if (XGetTransientForHint(**otk::display
, _window
, &t
) &&
593 t
!= _window
) { // cant be transient to itself!
594 c
= openbox
->findClient(t
);
595 assert(c
!= this); // if this happens then we need to check for it
597 if (!c
/*XXX: && _group*/) {
598 // not transient to a client, see if it is transient for a group
599 if (//t == _group->leader() ||
601 t
== otk::display
->screenInfo(_screen
)->rootWindow()) {
602 // window is a transient for its group!
603 // XXX: for now this is treated as non-transient.
604 // this needs to be fixed!
609 // if anything has changed...
610 if (c
!= _transient_for
) {
612 _transient_for
->_transients
.remove(this); // remove from old parent
615 _transient_for
->_transients
.push_back(this); // add to new parent
617 // XXX: change decor status?
622 void Client::propertyHandler(const XPropertyEvent
&e
)
624 otk::EventHandler::propertyHandler(e
);
626 // compress changes to a single property into a single change
628 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
629 // XXX: it would be nice to compress ALL changes to a property, not just
630 // changes in a row without other props between.
631 if (ce
.xproperty
.atom
!= e
.atom
) {
632 XPutBackEvent(**otk::display
, &ce
);
637 if (e
.atom
== XA_WM_NORMAL_HINTS
)
639 else if (e
.atom
== XA_WM_HINTS
)
641 else if (e
.atom
== XA_WM_TRANSIENT_FOR
) {
642 updateTransientFor();
644 calcLayer(); // type may have changed, so update the layer
645 setupDecorAndFunctions();
646 frame
->adjustSize(); // this updates the frame for any new decor settings
648 else if (e
.atom
== otk::Property::atoms
.net_wm_name
||
649 e
.atom
== otk::Property::atoms
.wm_name
)
651 else if (e
.atom
== otk::Property::atoms
.net_wm_icon_name
||
652 e
.atom
== otk::Property::atoms
.wm_icon_name
)
654 else if (e
.atom
== otk::Property::atoms
.wm_class
)
656 else if (e
.atom
== otk::Property::atoms
.wm_protocols
) {
658 setupDecorAndFunctions();
659 frame
->adjustSize(); // update the decorations
661 else if (e
.atom
== otk::Property::atoms
.net_wm_strut
)
666 void Client::setWMState(long state
)
668 if (state
== _wmstate
) return; // no change
672 setDesktop(ICONIC_DESKTOP
);
675 setDesktop(openbox
->screen(_screen
)->desktop());
681 void Client::setDesktop(long target
)
683 if (target
== _desktop
) return;
685 printf("Setting desktop %ld\n", target
);
687 if (!(target
>= 0 || target
== (signed)0xffffffff ||
688 target
== ICONIC_DESKTOP
))
693 // set the desktop hint, but not if we're iconifying
694 if (_desktop
!= ICONIC_DESKTOP
)
695 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
696 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
698 // 'move' the window to the new desktop
699 if (_desktop
== openbox
->screen(_screen
)->desktop() ||
700 _desktop
== (signed)0xffffffff)
705 // Handle Iconic state. Iconic state is maintained by the client being a
706 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
707 // uniconifying happen.
708 bool i
= _desktop
== ICONIC_DESKTOP
;
709 if (i
!= _iconic
) { // has the state changed?
712 _wmstate
= IconicState
;
714 // we unmap the client itself so that we can get MapRequest events, and
715 // because the ICCCM tells us to!
716 XUnmapWindow(**otk::display
, _window
);
718 _wmstate
= NormalState
;
719 XMapWindow(**otk::display
, _window
);
724 frame
->adjustState();
728 void Client::setState(StateAction action
, long data1
, long data2
)
730 bool shadestate
= _shaded
;
731 bool fsstate
= _fullscreen
;
733 if (!(action
== State_Add
|| action
== State_Remove
||
734 action
== State_Toggle
))
735 return; // an invalid action was passed to the client message, ignore it
737 for (int i
= 0; i
< 2; ++i
) {
738 Atom state
= i
== 0 ? data1
: data2
;
740 if (! state
) continue;
742 // if toggling, then pick whether we're adding or removing
743 if (action
== State_Toggle
) {
744 if (state
== otk::Property::atoms
.net_wm_state_modal
)
745 action
= _modal
? State_Remove
: State_Add
;
746 else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
)
747 action
= _max_vert
? State_Remove
: State_Add
;
748 else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
)
749 action
= _max_horz
? State_Remove
: State_Add
;
750 else if (state
== otk::Property::atoms
.net_wm_state_shaded
)
751 action
= _shaded
? State_Remove
: State_Add
;
752 else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
)
753 action
= _skip_taskbar
? State_Remove
: State_Add
;
754 else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
)
755 action
= _skip_pager
? State_Remove
: State_Add
;
756 else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
)
757 action
= _fullscreen
? State_Remove
: State_Add
;
758 else if (state
== otk::Property::atoms
.net_wm_state_above
)
759 action
= _above
? State_Remove
: State_Add
;
760 else if (state
== otk::Property::atoms
.net_wm_state_below
)
761 action
= _below
? State_Remove
: State_Add
;
764 if (action
== State_Add
) {
765 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
766 if (_modal
) continue;
768 // XXX: give it focus if another window has focus that shouldnt now
769 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
770 if (_max_vert
) continue;
772 // XXX: resize the window etc
773 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
774 if (_max_horz
) continue;
776 // XXX: resize the window etc
777 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
779 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
780 _skip_taskbar
= true;
781 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
783 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
785 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
786 if (_above
) continue;
788 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
789 if (_below
) continue;
793 } else { // action == State_Remove
794 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
795 if (!_modal
) continue;
797 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
798 if (!_max_vert
) continue;
800 // XXX: resize the window etc
801 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
802 if (!_max_horz
) continue;
804 // XXX: resize the window etc
805 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
807 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
808 _skip_taskbar
= false;
809 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
811 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
813 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
814 if (!_above
) continue;
816 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
817 if (!_below
) continue;
822 // change fullscreen state before shading, as it will affect if the window
824 if (fsstate
!= _fullscreen
)
826 if (shadestate
!= _shaded
)
832 void Client::toggleClientBorder(bool addborder
)
834 // adjust our idea of where the client is, based on its border. When the
835 // border is removed, the client should now be considered to be in a
836 // different position.
837 // when re-adding the border to the client, the same operation needs to be
839 int x
= _area
.x(), y
= _area
.y();
842 case NorthWestGravity
:
844 case SouthWestGravity
:
846 case NorthEastGravity
:
848 case SouthEastGravity
:
849 if (addborder
) x
-= _border_width
* 2;
850 else x
+= _border_width
* 2;
857 if (addborder
) x
-= _border_width
;
858 else x
+= _border_width
;
863 case NorthWestGravity
:
865 case NorthEastGravity
:
867 case SouthWestGravity
:
869 case SouthEastGravity
:
870 if (addborder
) y
-= _border_width
* 2;
871 else y
+= _border_width
* 2;
878 if (addborder
) y
-= _border_width
;
879 else y
+= _border_width
;
885 XSetWindowBorderWidth(**otk::display
, _window
, _border_width
);
887 // move the client so it is back it the right spot _with_ its border!
888 XMoveWindow(**otk::display
, _window
, x
, y
);
890 XSetWindowBorderWidth(**otk::display
, _window
, 0);
894 void Client::clientMessageHandler(const XClientMessageEvent
&e
)
896 otk::EventHandler::clientMessageHandler(e
);
898 if (e
.format
!= 32) return;
900 if (e
.message_type
== otk::Property::atoms
.wm_change_state
) {
901 // compress changes into a single change
902 bool compress
= false;
904 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
905 // XXX: it would be nice to compress ALL messages of a type, not just
906 // messages in a row without other message types between.
907 if (ce
.xclient
.message_type
!= e
.message_type
) {
908 XPutBackEvent(**otk::display
, &ce
);
914 setWMState(ce
.xclient
.data
.l
[0]); // use the found event
916 setWMState(e
.data
.l
[0]); // use the original event
917 } else if (e
.message_type
== otk::Property::atoms
.net_wm_desktop
) {
918 // compress changes into a single change
919 bool compress
= false;
921 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
922 // XXX: it would be nice to compress ALL messages of a type, not just
923 // messages in a row without other message types between.
924 if (ce
.xclient
.message_type
!= e
.message_type
) {
925 XPutBackEvent(**otk::display
, &ce
);
931 setDesktop(e
.data
.l
[0]); // use the found event
933 setDesktop(e
.data
.l
[0]); // use the original event
934 } else if (e
.message_type
== otk::Property::atoms
.net_wm_state
) {
935 // can't compress these
937 printf("net_wm_state %s %ld %ld for 0x%lx\n",
938 (e
.data
.l
[0] == 0 ? "Remove" : e
.data
.l
[0] == 1 ? "Add" :
939 e
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
940 e
.data
.l
[1], e
.data
.l
[2], _window
);
942 setState((StateAction
)e
.data
.l
[0], e
.data
.l
[1], e
.data
.l
[2]);
943 } else if (e
.message_type
== otk::Property::atoms
.net_close_window
) {
945 printf("net_close_window for 0x%lx\n", _window
);
948 } else if (e
.message_type
== otk::Property::atoms
.net_active_window
) {
950 printf("net_active_window for 0x%lx\n", _window
);
953 setDesktop(openbox
->screen(_screen
)->desktop());
958 openbox
->screen(_screen
)->raiseWindow(this);
964 void Client::shapeHandler(const XShapeEvent
&e
)
966 otk::EventHandler::shapeHandler(e
);
968 if (e
.kind
== ShapeBounding
) {
970 frame
->adjustShape();
976 void Client::resize(Corner anchor
, int w
, int h
)
978 if (!(_functions
& Func_Resize
)) return;
979 internal_resize(anchor
, w
, h
);
983 void Client::internal_resize(Corner anchor
, int w
, int h
, int x
, int y
)
988 // for interactive resizing. have to move half an increment in each
990 w
+= _size_inc
.x() / 2;
991 h
+= _size_inc
.y() / 2;
993 // is the window resizable? if it is not, then don't check its sizes, the
994 // client can do what it wants and the user can't change it anyhow
995 if (_min_size
.x() <= _max_size
.x() && _min_size
.y() <= _max_size
.y()) {
996 // smaller than min size or bigger than max size?
997 if (w
< _min_size
.x()) w
= _min_size
.x();
998 else if (w
> _max_size
.x()) w
= _max_size
.x();
999 if (h
< _min_size
.y()) h
= _min_size
.y();
1000 else if (h
> _max_size
.y()) h
= _max_size
.y();
1003 // keep to the increments
1007 // you cannot resize to nothing
1011 // store the logical size
1012 _logical_size
.setPoint(w
, h
);
1017 w
+= _base_size
.x();
1018 h
+= _base_size
.y();
1020 if (x
== INT_MIN
|| y
== INT_MIN
) {
1027 x
-= w
- _area
.width();
1030 y
-= h
- _area
.height();
1033 x
-= w
- _area
.width();
1034 y
-= h
- _area
.height();
1039 _area
.setSize(w
, h
);
1041 XResizeWindow(**otk::display
, _window
, w
, h
);
1043 // resize the frame to match the request
1044 frame
->adjustSize();
1045 internal_move(x
, y
);
1049 void Client::move(int x
, int y
)
1051 if (!(_functions
& Func_Move
)) return;
1052 internal_move(x
, y
);
1056 void Client::internal_move(int x
, int y
)
1060 // move the frame to be in the requested position
1061 if (frame
) { // this can be called while mapping, before frame exists
1062 frame
->adjustPosition();
1064 // send synthetic configure notify (we don't need to if we aren't mapped
1067 event
.type
= ConfigureNotify
;
1068 event
.xconfigure
.display
= **otk::display
;
1069 event
.xconfigure
.event
= _window
;
1070 event
.xconfigure
.window
= _window
;
1071 event
.xconfigure
.x
= x
;
1072 event
.xconfigure
.y
= y
;
1073 event
.xconfigure
.width
= _area
.width();
1074 event
.xconfigure
.height
= _area
.height();
1075 event
.xconfigure
.border_width
= _border_width
;
1076 event
.xconfigure
.above
= frame
->window();
1077 event
.xconfigure
.override_redirect
= False
;
1078 XSendEvent(event
.xconfigure
.display
, event
.xconfigure
.window
, False
,
1079 StructureNotifyMask
, &event
);
1084 void Client::close()
1088 if (!(_functions
& Func_Close
)) return;
1090 // XXX: itd be cool to do timeouts and shit here for killing the client's
1092 // like... if the window is around after 5 seconds, then the close button
1093 // turns a nice red, and if this function is called again, the client is
1094 // explicitly killed.
1096 ce
.xclient
.type
= ClientMessage
;
1097 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1098 ce
.xclient
.display
= **otk::display
;
1099 ce
.xclient
.window
= _window
;
1100 ce
.xclient
.format
= 32;
1101 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_delete_window
;
1102 ce
.xclient
.data
.l
[1] = CurrentTime
;
1103 ce
.xclient
.data
.l
[2] = 0l;
1104 ce
.xclient
.data
.l
[3] = 0l;
1105 ce
.xclient
.data
.l
[4] = 0l;
1106 XSendEvent(**otk::display
, _window
, false, NoEventMask
, &ce
);
1110 void Client::changeState()
1112 unsigned long state
[2];
1113 state
[0] = _wmstate
;
1115 otk::Property::set(_window
, otk::Property::atoms
.wm_state
,
1116 otk::Property::atoms
.wm_state
, state
, 2);
1121 netstate
[num
++] = otk::Property::atoms
.net_wm_state_modal
;
1123 netstate
[num
++] = otk::Property::atoms
.net_wm_state_shaded
;
1125 netstate
[num
++] = otk::Property::atoms
.net_wm_state_hidden
;
1127 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_taskbar
;
1129 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_pager
;
1131 netstate
[num
++] = otk::Property::atoms
.net_wm_state_fullscreen
;
1133 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_vert
;
1135 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_horz
;
1137 netstate
[num
++] = otk::Property::atoms
.net_wm_state_above
;
1139 netstate
[num
++] = otk::Property::atoms
.net_wm_state_below
;
1140 otk::Property::set(_window
, otk::Property::atoms
.net_wm_state
,
1141 otk::Property::atoms
.atom
, netstate
, num
);
1146 frame
->adjustState();
1150 void Client::changeAllowedActions(void)
1155 actions
[num
++] = otk::Property::atoms
.net_wm_action_change_desktop
;
1157 if (_functions
& Func_Shade
)
1158 actions
[num
++] = otk::Property::atoms
.net_wm_action_shade
;
1159 if (_functions
& Func_Close
)
1160 actions
[num
++] = otk::Property::atoms
.net_wm_action_close
;
1161 if (_functions
& Func_Move
)
1162 actions
[num
++] = otk::Property::atoms
.net_wm_action_move
;
1163 if (_functions
& Func_Iconify
)
1164 actions
[num
++] = otk::Property::atoms
.net_wm_action_minimize
;
1165 if (_functions
& Func_Resize
)
1166 actions
[num
++] = otk::Property::atoms
.net_wm_action_resize
;
1167 if (_functions
& Func_Fullscreen
)
1168 actions
[num
++] = otk::Property::atoms
.net_wm_action_fullscreen
;
1169 if (_functions
& Func_Maximize
) {
1170 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_horz
;
1171 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_vert
;
1174 otk::Property::set(_window
, otk::Property::atoms
.net_wm_allowed_actions
,
1175 otk::Property::atoms
.atom
, actions
, num
);
1179 void Client::applyStartupState()
1181 // these are in a carefully crafted order..
1185 setDesktop(ICONIC_DESKTOP
);
1188 _fullscreen
= false;
1198 if (_max_vert
); // XXX: incomplete
1199 if (_max_horz
); // XXX: incomplete
1201 if (_skip_taskbar
); // nothing to do for this
1202 if (_skip_pager
); // nothing to do for this
1203 if (_modal
); // nothing to do for this
1204 if (_above
); // nothing to do for this
1205 if (_below
); // nothing to do for this
1209 void Client::fireUrgent()
1211 // call the python UrgentWindow callbacks
1212 EventData
data(_screen
, this, EventAction::UrgentWindow
, 0);
1213 openbox
->bindings()->fireEvent(&data
);
1217 void Client::shade(bool shade
)
1219 if (!(_functions
& Func_Shade
) || // can't
1220 _shaded
== shade
) return; // already done
1222 // when we're iconic, don't change the wmstate
1224 _wmstate
= shade
? IconicState
: NormalState
;
1227 frame
->adjustSize();
1231 void Client::fullscreen(bool fs
)
1233 static FunctionFlags saved_func
;
1234 static DecorationFlags saved_decor
;
1235 static otk::Rect saved_area
;
1236 static otk::Point saved_logical_size
;
1238 if (!(_functions
& Func_Fullscreen
) || // can't
1239 _fullscreen
== fs
) return; // already done
1242 changeState(); // change the state hints on the client
1245 // save the functions and remove them
1246 saved_func
= _functions
;
1247 _functions
= _functions
& (Func_Close
| Func_Fullscreen
| Func_Iconify
);
1248 // save the decorations and remove them
1249 saved_decor
= _decorations
;
1251 // save the area and adjust it (we don't call internal resize here for
1252 // constraints on the size, etc, we just make it fullscreen).
1254 const otk::ScreenInfo
*info
= otk::display
->screenInfo(_screen
);
1255 _area
.setRect(0, 0, info
->width(), info
->height());
1256 saved_logical_size
= _logical_size
;
1257 _logical_size
.setPoint((info
->width() - _base_size
.x()) / _size_inc
.x(),
1258 (info
->height() - _base_size
.y()) / _size_inc
.y());
1260 _functions
= saved_func
;
1261 _decorations
= saved_decor
;
1263 _logical_size
= saved_logical_size
;
1266 changeAllowedActions(); // based on the new _functions
1268 frame
->adjustSize(); // drop/replace the decor's and resize
1269 frame
->adjustPosition(); // get (back) in position!
1271 // raise (back) into our stacking layer
1272 openbox
->screen(_screen
)->raiseWindow(this);
1274 // try focus us when we go into fullscreen mode
1279 bool Client::focus()
1281 // won't try focus if the client doesn't want it, or if the window isn't
1282 // visible on the screen
1283 if (!(frame
->isVisible() && (_can_focus
|| _focus_notify
))) return false;
1285 if (_focused
) return true;
1287 // do a check to see if the window has already been unmapped or destroyed
1288 // do this intelligently while watching out for unmaps we've generated
1289 // (ignore_unmaps > 0)
1291 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &ev
)) {
1292 XPutBackEvent(**otk::display
, &ev
);
1295 while (XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &ev
)) {
1296 if (ignore_unmaps
) {
1297 unmapHandler(ev
.xunmap
);
1299 XPutBackEvent(**otk::display
, &ev
);
1305 XSetInputFocus(**otk::display
, _window
,
1306 RevertToNone
, CurrentTime
);
1308 if (_focus_notify
) {
1310 ce
.xclient
.type
= ClientMessage
;
1311 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1312 ce
.xclient
.display
= **otk::display
;
1313 ce
.xclient
.window
= _window
;
1314 ce
.xclient
.format
= 32;
1315 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_take_focus
;
1316 ce
.xclient
.data
.l
[1] = openbox
->lastTime();
1317 ce
.xclient
.data
.l
[2] = 0l;
1318 ce
.xclient
.data
.l
[3] = 0l;
1319 ce
.xclient
.data
.l
[4] = 0l;
1320 XSendEvent(**otk::display
, _window
, False
, NoEventMask
, &ce
);
1327 void Client::unfocus() const
1329 if (!_focused
) return;
1331 assert(openbox
->focusedClient() == this);
1332 openbox
->setFocusedClient(0);
1336 void Client::focusHandler(const XFocusChangeEvent
&e
)
1339 // printf("FocusIn for 0x%lx\n", e.window);
1342 otk::EventHandler::focusHandler(e
);
1347 openbox
->setFocusedClient(this);
1351 void Client::unfocusHandler(const XFocusChangeEvent
&e
)
1354 // printf("FocusOut for 0x%lx\n", e.window);
1357 otk::EventHandler::unfocusHandler(e
);
1362 if (openbox
->focusedClient() == this)
1363 openbox
->setFocusedClient(0);
1367 void Client::configureRequestHandler(const XConfigureRequestEvent
&e
)
1370 printf("ConfigureRequest for 0x%lx\n", e
.window
);
1373 otk::EventHandler::configureRequestHandler(e
);
1375 // if we are iconic (or shaded (fvwm does this)) ignore the event
1376 if (_iconic
|| _shaded
) return;
1378 if (e
.value_mask
& CWBorderWidth
)
1379 _border_width
= e
.border_width
;
1381 // resize, then move, as specified in the EWMH section 7.7
1382 if (e
.value_mask
& (CWWidth
| CWHeight
)) {
1383 int w
= (e
.value_mask
& CWWidth
) ? e
.width
: _area
.width();
1384 int h
= (e
.value_mask
& CWHeight
) ? e
.height
: _area
.height();
1388 case NorthEastGravity
:
1392 case SouthWestGravity
:
1394 corner
= BottomLeft
;
1396 case SouthEastGravity
:
1397 corner
= BottomRight
;
1399 default: // NorthWest, Static, etc
1403 // if moving AND resizing ...
1404 if (e
.value_mask
& (CWX
| CWY
)) {
1405 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1406 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1407 internal_resize(corner
, w
, h
, x
, y
);
1408 } else // if JUST resizing...
1409 internal_resize(corner
, w
, h
);
1410 } else if (e
.value_mask
& (CWX
| CWY
)) { // if JUST moving...
1411 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1412 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1413 internal_move(x
, y
);
1416 if (e
.value_mask
& CWStackMode
) {
1420 openbox
->screen(_screen
)->lowerWindow(this);
1426 openbox
->screen(_screen
)->raiseWindow(this);
1433 void Client::unmapHandler(const XUnmapEvent
&e
)
1435 if (ignore_unmaps
) {
1437 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1444 printf("UnmapNotify for 0x%lx\n", e
.window
);
1447 otk::EventHandler::unmapHandler(e
);
1449 // this deletes us etc
1450 openbox
->screen(_screen
)->unmanageWindow(this);
1454 void Client::destroyHandler(const XDestroyWindowEvent
&e
)
1457 printf("DestroyNotify for 0x%lx\n", e
.window
);
1460 otk::EventHandler::destroyHandler(e
);
1462 // this deletes us etc
1463 openbox
->screen(_screen
)->unmanageWindow(this);
1467 void Client::reparentHandler(const XReparentEvent
&e
)
1469 // this is when the client is first taken captive in the frame
1470 if (e
.parent
== frame
->plate()) return;
1473 printf("ReparentNotify for 0x%lx\n", e
.window
);
1476 otk::EventHandler::reparentHandler(e
);
1479 This event is quite rare and is usually handled in unmapHandler.
1480 However, if the window is unmapped when the reparent event occurs,
1481 the window manager never sees it because an unmap event is not sent
1482 to an already unmapped window.
1485 // we don't want the reparent event, put it back on the stack for the X
1486 // server to deal with after we unmanage the window
1489 XPutBackEvent(**otk::display
, &ev
);
1491 // this deletes us etc
1492 openbox
->screen(_screen
)->unmanageWindow(this);
1495 void Client::mapRequestHandler(const XMapRequestEvent
&e
)
1498 printf("MapRequest for already managed 0x%lx\n", e
.window
);
1501 assert(_iconic
); // we shouldn't be able to get this unless we're iconic
1503 // move to the current desktop (uniconify)
1504 setDesktop(openbox
->screen(_screen
)->desktop());
1505 // XXX: should we focus/raise the window? (basically a net_wm_active_window)