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)
30 Client::Client(int screen
, Window window
)
31 : otk::EventHandler(),
32 WidgetBase(WidgetBase::Type_Client
),
33 frame(0), _screen(screen
), _window(window
)
40 // update EVERYTHING the first time!!
42 // we default to NormalState, visible
43 _wmstate
= NormalState
;
46 // not a transient by default of course
48 // pick a layer to start from
49 _layer
= Layer_Normal
;
50 // default to not urgent
52 // not positioned unless specified
54 // nothing is disabled unless specified
55 _disabled_decorations
= 0;
69 // got the type, the mwmhints, and the protocols, so we're ready to set up
70 // the decorations/functions
71 setupDecorAndFunctions();
73 getGravity(); // get the attribute gravity
74 updateNormalHints(); // this may override the attribute gravity
75 // also get the initial_state and set _iconic if we aren't "starting"
76 // when we're "starting" that means we should use whatever state was already
77 // on the window over the initial map state, because it was already mapped
78 updateWMHints(openbox
->state() != Openbox::State_Starting
);
84 // this makes sure that these windows appear on all desktops
85 if (/*_type == Type_Dock ||*/ _type
== Type_Desktop
)
86 _desktop
= 0xffffffff;
88 // set the desktop hint, to make sure that it always exists, and to reflect
89 // any changes we've made here
90 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
91 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
99 // clean up childrens' references
100 while (!_transients
.empty()) {
101 _transients
.front()->_transient_for
= 0;
102 _transients
.pop_front();
105 // clean up parents reference to this
107 _transient_for
->_transients
.remove(this); // remove from old parent
109 if (openbox
->state() != Openbox::State_Exiting
) {
110 // these values should not be persisted across a window unmapping/mapping
111 otk::Property::erase(_window
, otk::Property::atoms
.net_wm_desktop
);
112 otk::Property::erase(_window
, otk::Property::atoms
.net_wm_state
);
114 // if we're left in an iconic state, the client wont be mapped. this is
115 // bad, since we will no longer be managing the window on restart
117 XMapWindow(**otk::display
, _window
);
122 bool Client::validate() const
124 XSync(**otk::display
, false); // get all events on the server
127 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &e
) ||
128 XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &e
)) {
129 XPutBackEvent(**otk::display
, &e
);
137 void Client::getGravity()
139 XWindowAttributes wattrib
;
142 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
143 assert(ret
!= BadWindow
);
144 _gravity
= wattrib
.win_gravity
;
148 void Client::getDesktop()
150 // defaults to the current desktop
151 _desktop
= openbox
->screen(_screen
)->desktop();
153 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_desktop
,
154 otk::Property::atoms
.cardinal
,
155 (long unsigned*)&_desktop
)) {
157 // printf("Window requested desktop: %ld\n", _desktop);
163 void Client::getType()
165 _type
= (WindowType
) -1;
168 unsigned long num
= (unsigned) -1;
169 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_window_type
,
170 otk::Property::atoms
.atom
, &num
, &val
)) {
171 // use the first value that we know about in the array
172 for (unsigned long i
= 0; i
< num
; ++i
) {
173 if (val
[i
] == otk::Property::atoms
.net_wm_window_type_desktop
)
174 _type
= Type_Desktop
;
175 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dock
)
177 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_toolbar
)
178 _type
= Type_Toolbar
;
179 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_menu
)
181 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_utility
)
182 _type
= Type_Utility
;
183 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_splash
)
185 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dialog
)
187 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_normal
)
189 // XXX: make this work again
190 // else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override)
191 // mwm_decorations = 0; // prevent this window from getting any decor
192 if (_type
!= (WindowType
) -1)
193 break; // grab the first known type
198 if (_type
== (WindowType
) -1) {
200 * the window type hint was not set, which means we either classify ourself
201 * as a normal window or a dialog, depending on if we are a transient.
211 void Client::setupDecorAndFunctions()
213 // start with everything (cept fullscreen)
214 _decorations
= Decor_Titlebar
| Decor_Handle
| Decor_Border
|
215 Decor_AllDesktops
| Decor_Iconify
| Decor_Maximize
;
216 _functions
= Func_Resize
| Func_Move
| Func_Iconify
| Func_Maximize
|
218 if (_delete_window
) {
219 _decorations
|= Decor_Close
;
220 _functions
|= Func_Close
;
223 if (!(_min_size
.x() < _max_size
.x() || _min_size
.y() < _max_size
.y())) {
224 _decorations
&= ~(Decor_Maximize
| Decor_Handle
);
225 _functions
&= ~(Func_Resize
| Func_Maximize
);
230 // normal windows retain all of the possible decorations and
231 // functionality, and are the only windows that you can fullscreen
232 _functions
|= Func_Fullscreen
;
235 // dialogs cannot be maximized
236 _decorations
&= ~Decor_Maximize
;
237 _functions
&= ~Func_Maximize
;
243 // these windows get less functionality
244 _decorations
&= ~(Decor_Iconify
| Decor_Handle
);
245 _functions
&= ~(Func_Iconify
| Func_Resize
);
251 // none of these windows are manipulated by the window manager
257 // Mwm Hints are applied subtractively to what has already been chosen for
258 // decor and functionality
259 if (_mwmhints
.flags
& MwmFlag_Decorations
) {
260 if (! (_mwmhints
.decorations
& MwmDecor_All
)) {
261 if (! (_mwmhints
.decorations
& MwmDecor_Border
))
262 _decorations
&= ~Decor_Border
;
263 if (! (_mwmhints
.decorations
& MwmDecor_Handle
))
264 _decorations
&= ~Decor_Handle
;
265 if (! (_mwmhints
.decorations
& MwmDecor_Title
)) {
266 _decorations
&= ~Decor_Titlebar
;
267 // if we don't have a titlebar, then we cannot shade!
268 _functions
&= ~Func_Shade
;
270 if (! (_mwmhints
.decorations
& MwmDecor_Iconify
))
271 _decorations
&= ~Decor_Iconify
;
272 if (! (_mwmhints
.decorations
& MwmDecor_Maximize
))
273 _decorations
&= ~Decor_Maximize
;
277 if (_mwmhints
.flags
& MwmFlag_Functions
) {
278 if (! (_mwmhints
.functions
& MwmFunc_All
)) {
279 if (! (_mwmhints
.functions
& MwmFunc_Resize
))
280 _functions
&= ~Func_Resize
;
281 if (! (_mwmhints
.functions
& MwmFunc_Move
))
282 _functions
&= ~Func_Move
;
283 if (! (_mwmhints
.functions
& MwmFunc_Iconify
))
284 _functions
&= ~Func_Iconify
;
285 if (! (_mwmhints
.functions
& MwmFunc_Maximize
))
286 _functions
&= ~Func_Maximize
;
287 // dont let mwm hints kill the close button
288 //if (! (_mwmhints.functions & MwmFunc_Close))
289 // _functions &= ~Func_Close;
293 // finally, user specified disabled decorations are applied to subtract
295 if (_disabled_decorations
& Decor_Titlebar
)
296 _decorations
&= ~Decor_Titlebar
;
297 if (_disabled_decorations
& Decor_Handle
)
298 _decorations
&= ~Decor_Handle
;
299 if (_disabled_decorations
& Decor_Border
)
300 _decorations
&= ~Decor_Border
;
301 if (_disabled_decorations
& Decor_Iconify
)
302 _decorations
&= ~Decor_Iconify
;
303 if (_disabled_decorations
& Decor_Maximize
)
304 _decorations
&= ~Decor_Maximize
;
305 if (_disabled_decorations
& Decor_AllDesktops
)
306 _decorations
&= ~Decor_AllDesktops
;
307 if (_disabled_decorations
& Decor_Close
)
308 _decorations
&= ~Decor_Close
;
310 changeAllowedActions();
313 frame
->adjustSize(); // change the decors on the frame
314 frame
->adjustPosition(); // with more/less decorations, we may need to be
320 void Client::getMwmHints()
322 unsigned long num
= MwmHints::elements
;
323 unsigned long *hints
;
325 _mwmhints
.flags
= 0; // default to none
327 if (!otk::Property::get(_window
, otk::Property::atoms
.motif_wm_hints
,
328 otk::Property::atoms
.motif_wm_hints
, &num
,
329 (unsigned long **)&hints
))
332 if (num
>= MwmHints::elements
) {
333 // retrieved the hints
334 _mwmhints
.flags
= hints
[0];
335 _mwmhints
.functions
= hints
[1];
336 _mwmhints
.decorations
= hints
[2];
343 void Client::getArea()
345 XWindowAttributes wattrib
;
348 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
349 assert(ret
!= BadWindow
);
351 _area
.setRect(wattrib
.x
, wattrib
.y
, wattrib
.width
, wattrib
.height
);
352 _border_width
= wattrib
.border_width
;
356 void Client::getState()
358 _modal
= _shaded
= _max_horz
= _max_vert
= _fullscreen
= _above
= _below
=
359 _iconic
= _skip_taskbar
= _skip_pager
= false;
361 unsigned long *state
;
362 unsigned long num
= (unsigned) -1;
364 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_state
,
365 otk::Property::atoms
.atom
, &num
, &state
)) {
366 for (unsigned long i
= 0; i
< num
; ++i
) {
367 if (state
[i
] == otk::Property::atoms
.net_wm_state_modal
)
369 else if (state
[i
] == otk::Property::atoms
.net_wm_state_shaded
)
371 else if (state
[i
] == otk::Property::atoms
.net_wm_state_hidden
)
373 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_taskbar
)
374 _skip_taskbar
= true;
375 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_pager
)
377 else if (state
[i
] == otk::Property::atoms
.net_wm_state_fullscreen
)
379 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_vert
)
381 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_horz
)
383 else if (state
[i
] == otk::Property::atoms
.net_wm_state_above
)
385 else if (state
[i
] == otk::Property::atoms
.net_wm_state_below
)
394 void Client::getShaped()
398 if (otk::display
->shape()) {
403 XShapeSelectInput(**otk::display
, _window
, ShapeNotifyMask
);
405 XShapeQueryExtents(**otk::display
, _window
, &s
, &foo
,
406 &foo
, &ufoo
, &ufoo
, &foo
, &foo
, &foo
, &ufoo
, &ufoo
);
413 void Client::calcLayer() {
416 if (_iconic
) l
= Layer_Icon
;
417 else if (_fullscreen
) l
= Layer_Fullscreen
;
418 else if (_type
== Type_Desktop
) l
= Layer_Desktop
;
419 else if (_type
== Type_Dock
) {
420 if (!_below
) l
= Layer_Top
;
421 else l
= Layer_Normal
;
423 else if (_above
) l
= Layer_Above
;
424 else if (_below
) l
= Layer_Below
;
425 else l
= Layer_Normal
;
431 if we don't have a frame, then we aren't mapped yet (and this would
434 openbox
->screen(_screen
)->raiseWindow(this);
440 void Client::updateProtocols()
445 _focus_notify
= false;
446 _delete_window
= false;
448 if (XGetWMProtocols(**otk::display
, _window
, &proto
, &num_return
)) {
449 for (int i
= 0; i
< num_return
; ++i
) {
450 if (proto
[i
] == otk::Property::atoms
.wm_delete_window
) {
451 // this means we can request the window to close
452 _delete_window
= true;
453 } else if (proto
[i
] == otk::Property::atoms
.wm_take_focus
)
454 // if this protocol is requested, then the window will be notified
455 // by the window manager whenever it receives focus
456 _focus_notify
= true;
463 void Client::updateNormalHints()
467 int oldgravity
= _gravity
;
472 _size_inc
.setPoint(1, 1);
473 _base_size
.setPoint(0, 0);
474 _min_size
.setPoint(0, 0);
475 _max_size
.setPoint(INT_MAX
, INT_MAX
);
477 // XXX: might want to cancel any interactive resizing of the window at this
480 // get the hints from the window
481 if (XGetWMNormalHints(**otk::display
, _window
, &size
, &ret
)) {
482 _positioned
= (size
.flags
& (PPosition
|USPosition
));
484 if (size
.flags
& PWinGravity
) {
485 _gravity
= size
.win_gravity
;
487 // if the client has a frame, i.e. has already been mapped and is
488 // changing its gravity
489 if (frame
&& _gravity
!= oldgravity
) {
490 // move our idea of the client's position based on its new gravity
492 frame
->frameGravity(x
, y
);
497 if (size
.flags
& PAspect
) {
498 if (size
.min_aspect
.y
) _min_ratio
= size
.min_aspect
.x
/size
.min_aspect
.y
;
499 if (size
.max_aspect
.y
) _max_ratio
= size
.max_aspect
.x
/size
.max_aspect
.y
;
502 if (size
.flags
& PMinSize
)
503 _min_size
.setPoint(size
.min_width
, size
.min_height
);
505 if (size
.flags
& PMaxSize
)
506 _max_size
.setPoint(size
.max_width
, size
.max_height
);
508 if (size
.flags
& PBaseSize
)
509 _base_size
.setPoint(size
.base_width
, size
.base_height
);
511 if (size
.flags
& PResizeInc
)
512 _size_inc
.setPoint(size
.width_inc
, size
.height_inc
);
517 void Client::updateWMHints(bool initstate
)
521 // assume a window takes input if it doesnt specify
525 if ((hints
= XGetWMHints(**otk::display
, _window
)) != NULL
) {
526 if (hints
->flags
& InputHint
)
527 _can_focus
= hints
->input
;
529 // only do this when initstate is true!
530 if (initstate
&& (hints
->flags
& StateHint
))
531 _iconic
= hints
->initial_state
== IconicState
;
533 if (hints
->flags
& XUrgencyHint
)
536 if (hints
->flags
& WindowGroupHint
) {
537 if (hints
->window_group
!= _group
) {
538 // XXX: remove from the old group if there was one
539 _group
= hints
->window_group
;
540 // XXX: do stuff with the group
551 printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
552 (long)_window
, _urgent
? "ON" : "OFF");
554 // fire the urgent callback if we're mapped, otherwise, wait until after
562 void Client::updateTitle()
567 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_name
,
568 otk::Property::utf8
, &_title
)) {
570 otk::Property::get(_window
, otk::Property::atoms
.wm_name
,
571 otk::Property::ascii
, &_title
);
575 _title
= _("Unnamed Window");
578 frame
->setTitle(_title
);
582 void Client::updateIconTitle()
587 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_icon_name
,
588 otk::Property::utf8
, &_icon_title
)) {
590 otk::Property::get(_window
, otk::Property::atoms
.wm_icon_name
,
591 otk::Property::ascii
, &_icon_title
);
595 _icon_title
= _("Unnamed Window");
599 void Client::updateClass()
602 _app_name
= _app_class
= _role
= "";
604 otk::Property::StringVect v
;
605 unsigned long num
= 2;
607 if (otk::Property::get(_window
, otk::Property::atoms
.wm_class
,
608 otk::Property::ascii
, &num
, &v
)) {
609 if (num
> 0) _app_name
= v
[0].c_str();
610 if (num
> 1) _app_class
= v
[1].c_str();
615 if (otk::Property::get(_window
, otk::Property::atoms
.wm_window_role
,
616 otk::Property::ascii
, &num
, &v
)) {
617 if (num
> 0) _role
= v
[0].c_str();
622 void Client::updateStrut()
624 unsigned long num
= 4;
626 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_strut
,
627 otk::Property::atoms
.cardinal
, &num
, &data
))
631 _strut
.left
= data
[0];
632 _strut
.right
= data
[1];
633 _strut
.top
= data
[2];
634 _strut
.bottom
= data
[3];
636 // updating here is pointless while we're being mapped cuz we're not in
637 // the screen's client list yet
639 openbox
->screen(_screen
)->updateStrut();
646 void Client::updateTransientFor()
651 if (XGetTransientForHint(**otk::display
, _window
, &t
) &&
652 t
!= _window
) { // cant be transient to itself!
653 c
= openbox
->findClient(t
);
654 assert(c
!= this); // if this happens then we need to check for it
656 if (!c
/*XXX: && _group*/) {
657 // not transient to a client, see if it is transient for a group
658 if (//t == _group->leader() ||
660 t
== otk::display
->screenInfo(_screen
)->rootWindow()) {
661 // window is a transient for its group!
662 // XXX: for now this is treated as non-transient.
663 // this needs to be fixed!
668 // if anything has changed...
669 if (c
!= _transient_for
) {
671 _transient_for
->_transients
.remove(this); // remove from old parent
674 _transient_for
->_transients
.push_back(this); // add to new parent
676 // XXX: change decor status?
681 void Client::propertyHandler(const XPropertyEvent
&e
)
683 otk::EventHandler::propertyHandler(e
);
685 // validate cuz we query stuff off the client here
686 if (!validate()) return;
688 // compress changes to a single property into a single change
690 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
691 // XXX: it would be nice to compress ALL changes to a property, not just
692 // changes in a row without other props between.
693 if (ce
.xproperty
.atom
!= e
.atom
) {
694 XPutBackEvent(**otk::display
, &ce
);
699 if (e
.atom
== XA_WM_NORMAL_HINTS
) {
701 setupDecorAndFunctions(); // normal hints can make a window non-resizable
702 } else if (e
.atom
== XA_WM_HINTS
)
704 else if (e
.atom
== XA_WM_TRANSIENT_FOR
) {
705 updateTransientFor();
707 calcLayer(); // type may have changed, so update the layer
708 setupDecorAndFunctions();
710 else if (e
.atom
== otk::Property::atoms
.net_wm_name
||
711 e
.atom
== otk::Property::atoms
.wm_name
)
713 else if (e
.atom
== otk::Property::atoms
.net_wm_icon_name
||
714 e
.atom
== otk::Property::atoms
.wm_icon_name
)
716 else if (e
.atom
== otk::Property::atoms
.wm_class
)
718 else if (e
.atom
== otk::Property::atoms
.wm_protocols
) {
720 setupDecorAndFunctions();
722 else if (e
.atom
== otk::Property::atoms
.net_wm_strut
)
727 void Client::setWMState(long state
)
729 if (state
== _wmstate
) return; // no change
733 setDesktop(ICONIC_DESKTOP
);
736 setDesktop(openbox
->screen(_screen
)->desktop());
742 void Client::setDesktop(long target
)
744 if (target
== _desktop
) return;
746 printf("Setting desktop %ld\n", target
);
748 if (!(target
>= 0 || target
== (signed)0xffffffff ||
749 target
== ICONIC_DESKTOP
))
754 // set the desktop hint, but not if we're iconifying
755 if (_desktop
!= ICONIC_DESKTOP
)
756 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
757 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
759 // 'move' the window to the new desktop
760 if (_desktop
== openbox
->screen(_screen
)->desktop() ||
761 _desktop
== (signed)0xffffffff)
766 // Handle Iconic state. Iconic state is maintained by the client being a
767 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
768 // uniconifying happen.
769 bool i
= _desktop
== ICONIC_DESKTOP
;
770 if (i
!= _iconic
) { // has the state changed?
773 _wmstate
= IconicState
;
775 // we unmap the client itself so that we can get MapRequest events, and
776 // because the ICCCM tells us to!
777 XUnmapWindow(**otk::display
, _window
);
779 _wmstate
= NormalState
;
780 XMapWindow(**otk::display
, _window
);
785 frame
->adjustState();
789 void Client::setState(StateAction action
, long data1
, long data2
)
791 bool shadestate
= _shaded
;
792 bool fsstate
= _fullscreen
;
794 if (!(action
== State_Add
|| action
== State_Remove
||
795 action
== State_Toggle
))
796 return; // an invalid action was passed to the client message, ignore it
798 for (int i
= 0; i
< 2; ++i
) {
799 Atom state
= i
== 0 ? data1
: data2
;
801 if (! state
) continue;
803 // if toggling, then pick whether we're adding or removing
804 if (action
== State_Toggle
) {
805 if (state
== otk::Property::atoms
.net_wm_state_modal
)
806 action
= _modal
? State_Remove
: State_Add
;
807 else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
)
808 action
= _max_vert
? State_Remove
: State_Add
;
809 else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
)
810 action
= _max_horz
? State_Remove
: State_Add
;
811 else if (state
== otk::Property::atoms
.net_wm_state_shaded
)
812 action
= _shaded
? State_Remove
: State_Add
;
813 else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
)
814 action
= _skip_taskbar
? State_Remove
: State_Add
;
815 else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
)
816 action
= _skip_pager
? State_Remove
: State_Add
;
817 else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
)
818 action
= _fullscreen
? State_Remove
: State_Add
;
819 else if (state
== otk::Property::atoms
.net_wm_state_above
)
820 action
= _above
? State_Remove
: State_Add
;
821 else if (state
== otk::Property::atoms
.net_wm_state_below
)
822 action
= _below
? State_Remove
: State_Add
;
825 if (action
== State_Add
) {
826 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
827 if (_modal
) continue;
829 // XXX: give it focus if another window has focus that shouldnt now
830 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
831 if (_max_vert
) continue;
833 // XXX: resize the window etc
834 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
835 if (_max_horz
) continue;
837 // XXX: resize the window etc
838 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
840 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
841 _skip_taskbar
= true;
842 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
844 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
846 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
847 if (_above
) continue;
849 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
850 if (_below
) continue;
854 } else { // action == State_Remove
855 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
856 if (!_modal
) continue;
858 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
859 if (!_max_vert
) continue;
861 // XXX: resize the window etc
862 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
863 if (!_max_horz
) continue;
865 // XXX: resize the window etc
866 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
868 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
869 _skip_taskbar
= false;
870 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
872 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
874 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
875 if (!_above
) continue;
877 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
878 if (!_below
) continue;
883 // change fullscreen state before shading, as it will affect if the window
885 if (fsstate
!= _fullscreen
)
887 if (shadestate
!= _shaded
)
893 void Client::toggleClientBorder(bool addborder
)
895 // adjust our idea of where the client is, based on its border. When the
896 // border is removed, the client should now be considered to be in a
897 // different position.
898 // when re-adding the border to the client, the same operation needs to be
900 int x
= _area
.x(), y
= _area
.y();
903 case NorthWestGravity
:
905 case SouthWestGravity
:
907 case NorthEastGravity
:
909 case SouthEastGravity
:
910 if (addborder
) x
-= _border_width
* 2;
911 else x
+= _border_width
* 2;
918 if (addborder
) x
-= _border_width
;
919 else x
+= _border_width
;
924 case NorthWestGravity
:
926 case NorthEastGravity
:
928 case SouthWestGravity
:
930 case SouthEastGravity
:
931 if (addborder
) y
-= _border_width
* 2;
932 else y
+= _border_width
* 2;
939 if (addborder
) y
-= _border_width
;
940 else y
+= _border_width
;
946 XSetWindowBorderWidth(**otk::display
, _window
, _border_width
);
948 // move the client so it is back it the right spot _with_ its border!
949 XMoveWindow(**otk::display
, _window
, x
, y
);
951 XSetWindowBorderWidth(**otk::display
, _window
, 0);
955 void Client::clientMessageHandler(const XClientMessageEvent
&e
)
957 otk::EventHandler::clientMessageHandler(e
);
959 // validate cuz we query stuff off the client here
960 if (!validate()) return;
962 if (e
.format
!= 32) return;
964 if (e
.message_type
== otk::Property::atoms
.wm_change_state
) {
965 // compress changes into a single change
966 bool compress
= false;
968 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
969 // XXX: it would be nice to compress ALL messages of a type, not just
970 // messages in a row without other message types between.
971 if (ce
.xclient
.message_type
!= e
.message_type
) {
972 XPutBackEvent(**otk::display
, &ce
);
978 setWMState(ce
.xclient
.data
.l
[0]); // use the found event
980 setWMState(e
.data
.l
[0]); // use the original event
981 } else if (e
.message_type
== otk::Property::atoms
.net_wm_desktop
) {
982 // compress changes into a single change
983 bool compress
= false;
985 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
986 // XXX: it would be nice to compress ALL messages of a type, not just
987 // messages in a row without other message types between.
988 if (ce
.xclient
.message_type
!= e
.message_type
) {
989 XPutBackEvent(**otk::display
, &ce
);
995 setDesktop(e
.data
.l
[0]); // use the found event
997 setDesktop(e
.data
.l
[0]); // use the original event
998 } else if (e
.message_type
== otk::Property::atoms
.net_wm_state
) {
999 // can't compress these
1001 printf("net_wm_state %s %ld %ld for 0x%lx\n",
1002 (e
.data
.l
[0] == 0 ? "Remove" : e
.data
.l
[0] == 1 ? "Add" :
1003 e
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
1004 e
.data
.l
[1], e
.data
.l
[2], _window
);
1006 setState((StateAction
)e
.data
.l
[0], e
.data
.l
[1], e
.data
.l
[2]);
1007 } else if (e
.message_type
== otk::Property::atoms
.net_close_window
) {
1009 printf("net_close_window for 0x%lx\n", _window
);
1012 } else if (e
.message_type
== otk::Property::atoms
.net_active_window
) {
1014 printf("net_active_window for 0x%lx\n", _window
);
1017 setDesktop(openbox
->screen(_screen
)->desktop());
1022 openbox
->screen(_screen
)->raiseWindow(this);
1028 void Client::shapeHandler(const XShapeEvent
&e
)
1030 otk::EventHandler::shapeHandler(e
);
1032 if (e
.kind
== ShapeBounding
) {
1034 frame
->adjustShape();
1040 void Client::resize(Corner anchor
, int w
, int h
)
1042 if (!(_functions
& Func_Resize
)) return;
1043 internal_resize(anchor
, w
, h
);
1047 void Client::internal_resize(Corner anchor
, int w
, int h
, bool user
,
1050 w
-= _base_size
.x();
1051 h
-= _base_size
.y();
1053 // for interactive resizing. have to move half an increment in each
1055 w
+= _size_inc
.x() / 2;
1056 h
+= _size_inc
.y() / 2;
1059 // if this is a user-requested resize, then check against min/max sizes
1060 // and aspect ratios
1062 // smaller than min size or bigger than max size?
1063 if (w
< _min_size
.x()) w
= _min_size
.x();
1064 else if (w
> _max_size
.x()) w
= _max_size
.x();
1065 if (h
< _min_size
.y()) h
= _min_size
.y();
1066 else if (h
> _max_size
.y()) h
= _max_size
.y();
1068 // adjust the height ot match the width for the aspect ratios
1070 if (h
* _min_ratio
> w
) h
= static_cast<int>(w
/ _min_ratio
);
1072 if (h
* _max_ratio
< w
) h
= static_cast<int>(w
/ _max_ratio
);
1075 // keep to the increments
1079 // you cannot resize to nothing
1083 // store the logical size
1084 _logical_size
.setPoint(w
, h
);
1089 w
+= _base_size
.x();
1090 h
+= _base_size
.y();
1092 if (x
== INT_MIN
|| y
== INT_MIN
) {
1099 x
-= w
- _area
.width();
1102 y
-= h
- _area
.height();
1105 x
-= w
- _area
.width();
1106 y
-= h
- _area
.height();
1111 _area
.setSize(w
, h
);
1113 XResizeWindow(**otk::display
, _window
, w
, h
);
1115 // resize the frame to match the request
1116 frame
->adjustSize();
1117 internal_move(x
, y
);
1121 void Client::move(int x
, int y
)
1123 if (!(_functions
& Func_Move
)) return;
1124 internal_move(x
, y
);
1128 void Client::internal_move(int x
, int y
)
1132 // move the frame to be in the requested position
1133 if (frame
) { // this can be called while mapping, before frame exists
1134 frame
->adjustPosition();
1136 // send synthetic configure notify (we don't need to if we aren't mapped
1139 event
.type
= ConfigureNotify
;
1140 event
.xconfigure
.display
= **otk::display
;
1141 event
.xconfigure
.event
= _window
;
1142 event
.xconfigure
.window
= _window
;
1143 event
.xconfigure
.x
= x
;
1144 event
.xconfigure
.y
= y
;
1145 event
.xconfigure
.width
= _area
.width();
1146 event
.xconfigure
.height
= _area
.height();
1147 event
.xconfigure
.border_width
= _border_width
;
1148 event
.xconfigure
.above
= frame
->window();
1149 event
.xconfigure
.override_redirect
= False
;
1150 XSendEvent(event
.xconfigure
.display
, event
.xconfigure
.window
, False
,
1151 StructureNotifyMask
, &event
);
1156 void Client::close()
1160 if (!(_functions
& Func_Close
)) return;
1162 // XXX: itd be cool to do timeouts and shit here for killing the client's
1164 // like... if the window is around after 5 seconds, then the close button
1165 // turns a nice red, and if this function is called again, the client is
1166 // explicitly killed.
1168 ce
.xclient
.type
= ClientMessage
;
1169 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1170 ce
.xclient
.display
= **otk::display
;
1171 ce
.xclient
.window
= _window
;
1172 ce
.xclient
.format
= 32;
1173 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_delete_window
;
1174 ce
.xclient
.data
.l
[1] = CurrentTime
;
1175 ce
.xclient
.data
.l
[2] = 0l;
1176 ce
.xclient
.data
.l
[3] = 0l;
1177 ce
.xclient
.data
.l
[4] = 0l;
1178 XSendEvent(**otk::display
, _window
, false, NoEventMask
, &ce
);
1182 void Client::changeState()
1184 unsigned long state
[2];
1185 state
[0] = _wmstate
;
1187 otk::Property::set(_window
, otk::Property::atoms
.wm_state
,
1188 otk::Property::atoms
.wm_state
, state
, 2);
1193 netstate
[num
++] = otk::Property::atoms
.net_wm_state_modal
;
1195 netstate
[num
++] = otk::Property::atoms
.net_wm_state_shaded
;
1197 netstate
[num
++] = otk::Property::atoms
.net_wm_state_hidden
;
1199 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_taskbar
;
1201 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_pager
;
1203 netstate
[num
++] = otk::Property::atoms
.net_wm_state_fullscreen
;
1205 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_vert
;
1207 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_horz
;
1209 netstate
[num
++] = otk::Property::atoms
.net_wm_state_above
;
1211 netstate
[num
++] = otk::Property::atoms
.net_wm_state_below
;
1212 otk::Property::set(_window
, otk::Property::atoms
.net_wm_state
,
1213 otk::Property::atoms
.atom
, netstate
, num
);
1218 frame
->adjustState();
1222 void Client::changeAllowedActions(void)
1227 actions
[num
++] = otk::Property::atoms
.net_wm_action_change_desktop
;
1229 if (_functions
& Func_Shade
)
1230 actions
[num
++] = otk::Property::atoms
.net_wm_action_shade
;
1231 if (_functions
& Func_Close
)
1232 actions
[num
++] = otk::Property::atoms
.net_wm_action_close
;
1233 if (_functions
& Func_Move
)
1234 actions
[num
++] = otk::Property::atoms
.net_wm_action_move
;
1235 if (_functions
& Func_Iconify
)
1236 actions
[num
++] = otk::Property::atoms
.net_wm_action_minimize
;
1237 if (_functions
& Func_Resize
)
1238 actions
[num
++] = otk::Property::atoms
.net_wm_action_resize
;
1239 if (_functions
& Func_Fullscreen
)
1240 actions
[num
++] = otk::Property::atoms
.net_wm_action_fullscreen
;
1241 if (_functions
& Func_Maximize
) {
1242 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_horz
;
1243 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_vert
;
1246 otk::Property::set(_window
, otk::Property::atoms
.net_wm_allowed_actions
,
1247 otk::Property::atoms
.atom
, actions
, num
);
1251 void Client::applyStartupState()
1253 // these are in a carefully crafted order..
1257 setDesktop(ICONIC_DESKTOP
);
1260 _fullscreen
= false;
1270 if (_max_vert
); // XXX: incomplete
1271 if (_max_horz
); // XXX: incomplete
1273 if (_skip_taskbar
); // nothing to do for this
1274 if (_skip_pager
); // nothing to do for this
1275 if (_modal
); // nothing to do for this
1276 if (_above
); // nothing to do for this
1277 if (_below
); // nothing to do for this
1281 void Client::fireUrgent()
1283 // call the python UrgentWindow callbacks
1284 EventData
data(_screen
, this, EventAction::UrgentWindow
, 0);
1285 openbox
->bindings()->fireEvent(&data
);
1289 void Client::shade(bool shade
)
1291 if (!(_functions
& Func_Shade
) || // can't
1292 _shaded
== shade
) return; // already done
1294 // when we're iconic, don't change the wmstate
1296 _wmstate
= shade
? IconicState
: NormalState
;
1299 frame
->adjustSize();
1303 void Client::fullscreen(bool fs
)
1305 static FunctionFlags saved_func
;
1306 static DecorationFlags saved_decor
;
1307 static otk::Rect saved_area
;
1308 static otk::Point saved_logical_size
;
1310 if (!(_functions
& Func_Fullscreen
) || // can't
1311 _fullscreen
== fs
) return; // already done
1314 changeState(); // change the state hints on the client
1317 // save the functions and remove them
1318 saved_func
= _functions
;
1319 _functions
= _functions
& (Func_Close
| Func_Fullscreen
| Func_Iconify
);
1320 // save the decorations and remove them
1321 saved_decor
= _decorations
;
1323 // save the area and adjust it (we don't call internal resize here for
1324 // constraints on the size, etc, we just make it fullscreen).
1326 const otk::ScreenInfo
*info
= otk::display
->screenInfo(_screen
);
1327 _area
.setRect(0, 0, info
->width(), info
->height());
1328 saved_logical_size
= _logical_size
;
1329 _logical_size
.setPoint((info
->width() - _base_size
.x()) / _size_inc
.x(),
1330 (info
->height() - _base_size
.y()) / _size_inc
.y());
1332 _functions
= saved_func
;
1333 _decorations
= saved_decor
;
1335 _logical_size
= saved_logical_size
;
1338 changeAllowedActions(); // based on the new _functions
1340 frame
->adjustSize(); // drop/replace the decor's and resize
1341 frame
->adjustPosition(); // get (back) in position!
1343 // raise (back) into our stacking layer
1344 openbox
->screen(_screen
)->raiseWindow(this);
1346 // try focus us when we go into fullscreen mode
1351 void Client::disableDecorations(DecorationFlags flags
)
1353 _disabled_decorations
= flags
;
1354 setupDecorAndFunctions();
1358 bool Client::focus()
1360 // won't try focus if the client doesn't want it, or if the window isn't
1361 // visible on the screen
1362 if (!(frame
->isVisible() && (_can_focus
|| _focus_notify
))) return false;
1364 if (_focused
) return true;
1366 // do a check to see if the window has already been unmapped or destroyed
1367 // do this intelligently while watching out for unmaps we've generated
1368 // (ignore_unmaps > 0)
1370 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &ev
)) {
1371 XPutBackEvent(**otk::display
, &ev
);
1374 while (XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &ev
)) {
1375 if (ignore_unmaps
) {
1376 unmapHandler(ev
.xunmap
);
1378 XPutBackEvent(**otk::display
, &ev
);
1384 XSetInputFocus(**otk::display
, _window
,
1385 RevertToNone
, CurrentTime
);
1387 if (_focus_notify
) {
1389 ce
.xclient
.type
= ClientMessage
;
1390 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1391 ce
.xclient
.display
= **otk::display
;
1392 ce
.xclient
.window
= _window
;
1393 ce
.xclient
.format
= 32;
1394 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_take_focus
;
1395 ce
.xclient
.data
.l
[1] = openbox
->lastTime();
1396 ce
.xclient
.data
.l
[2] = 0l;
1397 ce
.xclient
.data
.l
[3] = 0l;
1398 ce
.xclient
.data
.l
[4] = 0l;
1399 XSendEvent(**otk::display
, _window
, False
, NoEventMask
, &ce
);
1402 XSync(**otk::display
, False
);
1407 void Client::unfocus() const
1409 if (!_focused
) return;
1411 assert(openbox
->focusedClient() == this);
1412 openbox
->setFocusedClient(0);
1416 void Client::focusHandler(const XFocusChangeEvent
&e
)
1419 // printf("FocusIn for 0x%lx\n", e.window);
1422 otk::EventHandler::focusHandler(e
);
1427 openbox
->setFocusedClient(this);
1431 void Client::unfocusHandler(const XFocusChangeEvent
&e
)
1434 // printf("FocusOut for 0x%lx\n", e.window);
1437 otk::EventHandler::unfocusHandler(e
);
1442 if (openbox
->focusedClient() == this)
1443 openbox
->setFocusedClient(0);
1447 void Client::configureRequestHandler(const XConfigureRequestEvent
&e
)
1450 printf("ConfigureRequest for 0x%lx\n", e
.window
);
1453 otk::EventHandler::configureRequestHandler(e
);
1455 // if we are iconic (or shaded (fvwm does this)) ignore the event
1456 if (_iconic
|| _shaded
) return;
1458 if (e
.value_mask
& CWBorderWidth
)
1459 _border_width
= e
.border_width
;
1461 // resize, then move, as specified in the EWMH section 7.7
1462 if (e
.value_mask
& (CWWidth
| CWHeight
)) {
1463 int w
= (e
.value_mask
& CWWidth
) ? e
.width
: _area
.width();
1464 int h
= (e
.value_mask
& CWHeight
) ? e
.height
: _area
.height();
1468 case NorthEastGravity
:
1472 case SouthWestGravity
:
1474 corner
= BottomLeft
;
1476 case SouthEastGravity
:
1477 corner
= BottomRight
;
1479 default: // NorthWest, Static, etc
1483 // if moving AND resizing ...
1484 if (e
.value_mask
& (CWX
| CWY
)) {
1485 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1486 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1487 internal_resize(corner
, w
, h
, false, x
, y
);
1488 } else // if JUST resizing...
1489 internal_resize(corner
, w
, h
, false);
1490 } else if (e
.value_mask
& (CWX
| CWY
)) { // if JUST moving...
1491 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1492 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1493 internal_move(x
, y
);
1496 if (e
.value_mask
& CWStackMode
) {
1500 openbox
->screen(_screen
)->lowerWindow(this);
1506 openbox
->screen(_screen
)->raiseWindow(this);
1513 void Client::unmapHandler(const XUnmapEvent
&e
)
1515 if (ignore_unmaps
) {
1517 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1524 printf("UnmapNotify for 0x%lx\n", e
.window
);
1527 otk::EventHandler::unmapHandler(e
);
1529 // this deletes us etc
1530 openbox
->screen(_screen
)->unmanageWindow(this);
1534 void Client::destroyHandler(const XDestroyWindowEvent
&e
)
1537 printf("DestroyNotify for 0x%lx\n", e
.window
);
1540 otk::EventHandler::destroyHandler(e
);
1542 // this deletes us etc
1543 openbox
->screen(_screen
)->unmanageWindow(this);
1547 void Client::reparentHandler(const XReparentEvent
&e
)
1549 // this is when the client is first taken captive in the frame
1550 if (e
.parent
== frame
->plate()) return;
1553 printf("ReparentNotify for 0x%lx\n", e
.window
);
1556 otk::EventHandler::reparentHandler(e
);
1559 This event is quite rare and is usually handled in unmapHandler.
1560 However, if the window is unmapped when the reparent event occurs,
1561 the window manager never sees it because an unmap event is not sent
1562 to an already unmapped window.
1565 // we don't want the reparent event, put it back on the stack for the X
1566 // server to deal with after we unmanage the window
1569 XPutBackEvent(**otk::display
, &ev
);
1571 // this deletes us etc
1572 openbox
->screen(_screen
)->unmanageWindow(this);
1575 void Client::mapRequestHandler(const XMapRequestEvent
&e
)
1578 printf("MapRequest for already managed 0x%lx\n", e
.window
);
1581 assert(_iconic
); // we shouldn't be able to get this unless we're iconic
1583 // move to the current desktop (uniconify)
1584 setDesktop(openbox
->screen(_screen
)->desktop());
1585 // XXX: should we focus/raise the window? (basically a net_wm_active_window)