1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
4 # include "../config.h"
11 #include "otk/display.hh"
12 #include "otk/property.hh"
16 #include <X11/Xutil.h>
17 #include <X11/Xatom.h>
22 #define _(str) gettext(str)
27 Client::Client(int screen
, Window window
)
28 : otk::EventHandler(),
29 WidgetBase(WidgetBase::Type_Client
),
30 frame(0), _screen(screen
), _window(window
)
37 // update EVERYTHING the first time!!
39 // we default to NormalState, visible
40 _wmstate
= NormalState
; _iconic
= false;
41 // no default decors or functions, each has to be enabled
42 _decorations
= _functions
= 0;
45 // not a transient by default of course
47 // pick a layer to start from
48 _layer
= Layer_Normal
;
57 getState(); // gets all the states except for iconic, which is found from
58 // the desktop == ICONIC_DESKTOP
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 updateWMHints(true); // also get the initial_state and set _iconic
75 // this makes sure that these windows:
76 // a) appear on all desktops
77 // b) don't start iconified
78 if (_type
== Type_Dock
|| _type
== Type_Desktop
) {
79 _desktop
= 0xffffffff;
82 // restores iconic state when we restart.
83 // this will override the initial_state if that was set
84 if (_desktop
== ICONIC_DESKTOP
) _iconic
= true;
86 // set the desktop hint, to make sure that it always exists, and to reflect
87 // any changes we've made here
88 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
89 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
97 // clean up childrens' references
98 while (!_transients
.empty()) {
99 _transients
.front()->_transient_for
= 0;
100 _transients
.pop_front();
103 // clean up parents reference to this
105 _transient_for
->_transients
.remove(this); // remove from old parent
107 if (openbox
->state() != Openbox::State_Exiting
) {
108 // these values should not be persisted across a window unmapping/mapping
109 otk::Property::erase(_window
, otk::Property::atoms
.net_wm_desktop
);
110 otk::Property::erase(_window
, otk::Property::atoms
.net_wm_state
);
112 // if we're left in an iconic state, the client wont be mapped. this is
113 // bad, since we will no longer be managing the window on restart
115 XMapWindow(**otk::display
, _window
);
120 void Client::getGravity()
122 XWindowAttributes wattrib
;
125 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
126 assert(ret
!= BadWindow
);
127 _gravity
= wattrib
.win_gravity
;
131 void Client::getDesktop()
133 // defaults to the current desktop
134 _desktop
= openbox
->screen(_screen
)->desktop();
136 otk::Property::get(_window
, otk::Property::atoms
.net_wm_desktop
,
137 otk::Property::atoms
.cardinal
,
138 (long unsigned*)&_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 _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_skip_taskbar
)
323 _skip_taskbar
= true;
324 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_pager
)
326 else if (state
[i
] == otk::Property::atoms
.net_wm_state_fullscreen
)
328 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_vert
)
330 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_horz
)
332 else if (state
[i
] == otk::Property::atoms
.net_wm_state_above
)
334 else if (state
[i
] == otk::Property::atoms
.net_wm_state_below
)
343 void Client::getShaped()
347 if (otk::display
->shape()) {
352 XShapeSelectInput(**otk::display
, _window
, ShapeNotifyMask
);
354 XShapeQueryExtents(**otk::display
, _window
, &s
, &foo
,
355 &foo
, &ufoo
, &ufoo
, &foo
, &foo
, &foo
, &ufoo
, &ufoo
);
362 void Client::calcLayer() {
365 if (_iconic
) l
= Layer_Icon
;
366 else if (_fullscreen
) l
= Layer_Fullscreen
;
367 else if (_type
== Type_Desktop
) l
= Layer_Desktop
;
368 else if (_type
== Type_Dock
) {
369 if (!_below
) l
= Layer_Top
;
370 else l
= Layer_Normal
;
372 else if (_above
) l
= Layer_Above
;
373 else if (_below
) l
= Layer_Below
;
374 else l
= Layer_Normal
;
380 if we don't have a frame, then we aren't mapped yet (and this would
383 openbox
->screen(_screen
)->raiseWindow(this);
389 void Client::updateProtocols()
394 _focus_notify
= false;
395 _delete_window
= false;
397 if (XGetWMProtocols(**otk::display
, _window
, &proto
, &num_return
)) {
398 for (int i
= 0; i
< num_return
; ++i
) {
399 if (proto
[i
] == otk::Property::atoms
.wm_delete_window
) {
400 // this means we can request the window to close
401 _delete_window
= true;
402 } else if (proto
[i
] == otk::Property::atoms
.wm_take_focus
)
403 // if this protocol is requested, then the window will be notified
404 // by the window manager whenever it receives focus
405 _focus_notify
= true;
412 void Client::updateNormalHints()
416 int oldgravity
= _gravity
;
419 _size_inc
.setPoint(1, 1);
420 _base_size
.setPoint(0, 0);
421 _min_size
.setPoint(0, 0);
422 _max_size
.setPoint(INT_MAX
, INT_MAX
);
424 // XXX: might want to cancel any interactive resizing of the window at this
427 // get the hints from the window
428 if (XGetWMNormalHints(**otk::display
, _window
, &size
, &ret
)) {
429 _positioned
= (size
.flags
& (PPosition
|USPosition
));
431 if (size
.flags
& PWinGravity
) {
432 _gravity
= size
.win_gravity
;
434 // if the client has a frame, i.e. has already been mapped and is
435 // changing its gravity
436 if (frame
&& _gravity
!= oldgravity
) {
437 // move our idea of the client's position based on its new gravity
439 frame
->frameGravity(x
, y
);
444 if (size
.flags
& PMinSize
)
445 _min_size
.setPoint(size
.min_width
, size
.min_height
);
447 if (size
.flags
& PMaxSize
)
448 _max_size
.setPoint(size
.max_width
, size
.max_height
);
450 if (size
.flags
& PBaseSize
)
451 _base_size
.setPoint(size
.base_width
, size
.base_height
);
453 if (size
.flags
& PResizeInc
)
454 _size_inc
.setPoint(size
.width_inc
, size
.height_inc
);
459 void Client::updateWMHints(bool initstate
)
463 // assume a window takes input if it doesnt specify
467 if ((hints
= XGetWMHints(**otk::display
, _window
)) != NULL
) {
468 if (hints
->flags
& InputHint
)
469 _can_focus
= hints
->input
;
471 // only do this when initstate is true!
472 if (initstate
&& (hints
->flags
& StateHint
))
473 _iconic
= hints
->initial_state
== IconicState
;
475 if (hints
->flags
& XUrgencyHint
)
478 if (hints
->flags
& WindowGroupHint
) {
479 if (hints
->window_group
!= _group
) {
480 // XXX: remove from the old group if there was one
481 _group
= hints
->window_group
;
482 // XXX: do stuff with the group
492 void Client::updateTitle()
497 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_name
,
498 otk::Property::utf8
, &_title
)) {
500 otk::Property::get(_window
, otk::Property::atoms
.wm_name
,
501 otk::Property::ascii
, &_title
);
505 _title
= _("Unnamed Window");
508 frame
->setTitle(_title
);
512 void Client::updateIconTitle()
517 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_icon_name
,
518 otk::Property::utf8
, &_icon_title
)) {
520 otk::Property::get(_window
, otk::Property::atoms
.wm_icon_name
,
521 otk::Property::ascii
, &_icon_title
);
525 _icon_title
= _("Unnamed Window");
529 void Client::updateClass()
532 _app_name
= _app_class
= _role
= "";
534 otk::Property::StringVect v
;
535 unsigned long num
= 2;
537 if (otk::Property::get(_window
, otk::Property::atoms
.wm_class
,
538 otk::Property::ascii
, &num
, &v
)) {
539 if (num
> 0) _app_name
= v
[0].c_str();
540 if (num
> 1) _app_class
= v
[1].c_str();
545 if (otk::Property::get(_window
, otk::Property::atoms
.wm_window_role
,
546 otk::Property::ascii
, &num
, &v
)) {
547 if (num
> 0) _role
= v
[0].c_str();
552 void Client::updateStrut()
554 unsigned long num
= 4;
556 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_strut
,
557 otk::Property::atoms
.cardinal
, &num
, &data
))
561 _strut
.left
= data
[0];
562 _strut
.right
= data
[1];
563 _strut
.top
= data
[2];
564 _strut
.bottom
= data
[3];
566 openbox
->screen(_screen
)->updateStrut();
573 void Client::updateTransientFor()
578 if (XGetTransientForHint(**otk::display
, _window
, &t
) &&
579 t
!= _window
) { // cant be transient to itself!
580 c
= openbox
->findClient(t
);
581 assert(c
!= this); // if this happens then we need to check for it
583 if (!c
/*XXX: && _group*/) {
584 // not transient to a client, see if it is transient for a group
585 if (//t == _group->leader() ||
587 t
== otk::display
->screenInfo(_screen
)->rootWindow()) {
588 // window is a transient for its group!
589 // XXX: for now this is treated as non-transient.
590 // this needs to be fixed!
595 // if anything has changed...
596 if (c
!= _transient_for
) {
598 _transient_for
->_transients
.remove(this); // remove from old parent
601 _transient_for
->_transients
.push_back(this); // add to new parent
603 // XXX: change decor status?
608 void Client::propertyHandler(const XPropertyEvent
&e
)
610 otk::EventHandler::propertyHandler(e
);
612 // compress changes to a single property into a single change
614 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
615 // XXX: it would be nice to compress ALL changes to a property, not just
616 // changes in a row without other props between.
617 if (ce
.xproperty
.atom
!= e
.atom
) {
618 XPutBackEvent(**otk::display
, &ce
);
623 if (e
.atom
== XA_WM_NORMAL_HINTS
)
625 else if (e
.atom
== XA_WM_HINTS
)
627 else if (e
.atom
== XA_WM_TRANSIENT_FOR
) {
628 updateTransientFor();
630 calcLayer(); // type may have changed, so update the layer
631 setupDecorAndFunctions();
632 frame
->adjustSize(); // this updates the frame for any new decor settings
634 else if (e
.atom
== otk::Property::atoms
.net_wm_name
||
635 e
.atom
== otk::Property::atoms
.wm_name
)
637 else if (e
.atom
== otk::Property::atoms
.net_wm_icon_name
||
638 e
.atom
== otk::Property::atoms
.wm_icon_name
)
640 else if (e
.atom
== otk::Property::atoms
.wm_class
)
642 else if (e
.atom
== otk::Property::atoms
.wm_protocols
) {
644 setupDecorAndFunctions();
645 frame
->adjustSize(); // update the decorations
647 else if (e
.atom
== otk::Property::atoms
.net_wm_strut
)
652 void Client::setWMState(long state
)
654 if (state
== _wmstate
) return; // no change
658 setDesktop(ICONIC_DESKTOP
);
661 setDesktop(openbox
->screen(_screen
)->desktop());
667 void Client::setDesktop(long target
)
669 if (target
== _desktop
) return;
671 printf("Setting desktop %ld\n", target
);
673 if (!(target
>= 0 || target
== (signed)0xffffffff ||
674 target
== ICONIC_DESKTOP
))
679 // set the desktop hint
680 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
681 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
683 // 'move' the window to the new desktop
684 if (_desktop
== openbox
->screen(_screen
)->desktop() ||
685 _desktop
== (signed)0xffffffff)
690 // Handle Iconic state. Iconic state is maintained by the client being a
691 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
692 // uniconifying happen.
693 bool i
= _desktop
== ICONIC_DESKTOP
;
694 if (i
!= _iconic
) { // has the state changed?
697 _wmstate
= IconicState
;
699 // we unmap the client itself so that we can get MapRequest events, and
700 // because the ICCCM tells us to!
701 XUnmapWindow(**otk::display
, _window
);
703 _wmstate
= NormalState
;
704 XMapWindow(**otk::display
, _window
);
709 frame
->adjustState();
713 void Client::setState(StateAction action
, long data1
, long data2
)
715 bool shadestate
= _shaded
;
716 bool fsstate
= _fullscreen
;
718 if (!(action
== State_Add
|| action
== State_Remove
||
719 action
== State_Toggle
))
720 return; // an invalid action was passed to the client message, ignore it
722 for (int i
= 0; i
< 2; ++i
) {
723 Atom state
= i
== 0 ? data1
: data2
;
725 if (! state
) continue;
727 // if toggling, then pick whether we're adding or removing
728 if (action
== State_Toggle
) {
729 if (state
== otk::Property::atoms
.net_wm_state_modal
)
730 action
= _modal
? State_Remove
: State_Add
;
731 else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
)
732 action
= _max_vert
? State_Remove
: State_Add
;
733 else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
)
734 action
= _max_horz
? State_Remove
: State_Add
;
735 else if (state
== otk::Property::atoms
.net_wm_state_shaded
)
736 action
= _shaded
? State_Remove
: State_Add
;
737 else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
)
738 action
= _skip_taskbar
? State_Remove
: State_Add
;
739 else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
)
740 action
= _skip_pager
? State_Remove
: State_Add
;
741 else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
)
742 action
= _fullscreen
? State_Remove
: State_Add
;
743 else if (state
== otk::Property::atoms
.net_wm_state_above
)
744 action
= _above
? State_Remove
: State_Add
;
745 else if (state
== otk::Property::atoms
.net_wm_state_below
)
746 action
= _below
? State_Remove
: State_Add
;
749 if (action
== State_Add
) {
750 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
751 if (_modal
) continue;
753 // XXX: give it focus if another window has focus that shouldnt now
754 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
755 if (_max_vert
) continue;
757 // XXX: resize the window etc
758 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
759 if (_max_horz
) continue;
761 // XXX: resize the window etc
762 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
764 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
765 _skip_taskbar
= true;
766 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
768 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
770 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
771 if (_above
) continue;
773 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
774 if (_below
) continue;
778 } else { // action == State_Remove
779 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
780 if (!_modal
) continue;
782 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
783 if (!_max_vert
) continue;
785 // XXX: resize the window etc
786 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
787 if (!_max_horz
) continue;
789 // XXX: resize the window etc
790 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
792 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
793 _skip_taskbar
= false;
794 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
796 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
798 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
799 if (!_above
) continue;
801 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
802 if (!_below
) continue;
807 // change fullscreen state before shading, as it will affect if the window
809 if (fsstate
!= _fullscreen
)
811 if (shadestate
!= _shaded
)
817 void Client::toggleClientBorder(bool addborder
)
819 // adjust our idea of where the client is, based on its border. When the
820 // border is removed, the client should now be considered to be in a
821 // different position.
822 // when re-adding the border to the client, the same operation needs to be
824 int x
= _area
.x(), y
= _area
.y();
827 case NorthWestGravity
:
829 case SouthWestGravity
:
831 case NorthEastGravity
:
833 case SouthEastGravity
:
834 if (addborder
) x
-= _border_width
* 2;
835 else x
+= _border_width
* 2;
842 if (addborder
) x
-= _border_width
;
843 else x
+= _border_width
;
848 case NorthWestGravity
:
850 case NorthEastGravity
:
852 case SouthWestGravity
:
854 case SouthEastGravity
:
855 if (addborder
) y
-= _border_width
* 2;
856 else y
+= _border_width
* 2;
863 if (addborder
) y
-= _border_width
;
864 else y
+= _border_width
;
870 XSetWindowBorderWidth(**otk::display
, _window
, _border_width
);
872 // move the client so it is back it the right spot _with_ its border!
873 XMoveWindow(**otk::display
, _window
, x
, y
);
875 XSetWindowBorderWidth(**otk::display
, _window
, 0);
879 void Client::clientMessageHandler(const XClientMessageEvent
&e
)
881 otk::EventHandler::clientMessageHandler(e
);
883 if (e
.format
!= 32) return;
885 if (e
.message_type
== otk::Property::atoms
.wm_change_state
) {
886 // compress changes into a single change
887 bool compress
= false;
889 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
890 // XXX: it would be nice to compress ALL messages of a type, not just
891 // messages in a row without other message types between.
892 if (ce
.xclient
.message_type
!= e
.message_type
) {
893 XPutBackEvent(**otk::display
, &ce
);
899 setWMState(ce
.xclient
.data
.l
[0]); // use the found event
901 setWMState(e
.data
.l
[0]); // use the original event
902 } else if (e
.message_type
== otk::Property::atoms
.net_wm_desktop
) {
903 // compress changes into a single change
904 bool compress
= false;
906 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
907 // XXX: it would be nice to compress ALL messages of a type, not just
908 // messages in a row without other message types between.
909 if (ce
.xclient
.message_type
!= e
.message_type
) {
910 XPutBackEvent(**otk::display
, &ce
);
916 setDesktop(e
.data
.l
[0]); // use the found event
918 setDesktop(e
.data
.l
[0]); // use the original event
919 } else if (e
.message_type
== otk::Property::atoms
.net_wm_state
) {
920 // can't compress these
922 printf("net_wm_state %s %ld %ld for 0x%lx\n",
923 (e
.data
.l
[0] == 0 ? "Remove" : e
.data
.l
[0] == 1 ? "Add" :
924 e
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
925 e
.data
.l
[1], e
.data
.l
[2], _window
);
927 setState((StateAction
)e
.data
.l
[0], e
.data
.l
[1], e
.data
.l
[2]);
928 } else if (e
.message_type
== otk::Property::atoms
.net_close_window
) {
930 printf("net_close_window for 0x%lx\n", _window
);
933 } else if (e
.message_type
== otk::Property::atoms
.net_active_window
) {
935 printf("net_active_window for 0x%lx\n", _window
);
938 setDesktop(openbox
->screen(_screen
)->desktop());
943 openbox
->screen(_screen
)->raiseWindow(this);
949 void Client::shapeHandler(const XShapeEvent
&e
)
951 otk::EventHandler::shapeHandler(e
);
953 if (e
.kind
== ShapeBounding
) {
955 frame
->adjustShape();
961 void Client::resize(Corner anchor
, int w
, int h
)
963 if (!(_functions
& Func_Resize
)) return;
964 internal_resize(anchor
, w
, h
);
968 void Client::internal_resize(Corner anchor
, int w
, int h
, int x
, int y
)
973 // for interactive resizing. have to move half an increment in each
975 w
+= _size_inc
.x() / 2;
976 h
+= _size_inc
.y() / 2;
978 // is the window resizable? if it is not, then don't check its sizes, the
979 // client can do what it wants and the user can't change it anyhow
980 if (_min_size
.x() <= _max_size
.x() && _min_size
.y() <= _max_size
.y()) {
981 // smaller than min size or bigger than max size?
982 if (w
< _min_size
.x()) w
= _min_size
.x();
983 else if (w
> _max_size
.x()) w
= _max_size
.x();
984 if (h
< _min_size
.y()) h
= _min_size
.y();
985 else if (h
> _max_size
.y()) h
= _max_size
.y();
988 // keep to the increments
992 // you cannot resize to nothing
996 // store the logical size
997 _logical_size
.setPoint(w
, h
);
1002 w
+= _base_size
.x();
1003 h
+= _base_size
.y();
1005 if (x
== INT_MIN
|| y
== INT_MIN
) {
1012 x
-= w
- _area
.width();
1015 y
-= h
- _area
.height();
1018 x
-= w
- _area
.width();
1019 y
-= h
- _area
.height();
1024 _area
.setSize(w
, h
);
1026 XResizeWindow(**otk::display
, _window
, w
, h
);
1028 // resize the frame to match the request
1029 frame
->adjustSize();
1030 internal_move(x
, y
);
1034 void Client::move(int x
, int y
)
1036 if (!(_functions
& Func_Move
)) return;
1037 internal_move(x
, y
);
1041 void Client::internal_move(int x
, int y
)
1045 // move the frame to be in the requested position
1046 if (frame
) { // this can be called while mapping, before frame exists
1047 frame
->adjustPosition();
1049 // send synthetic configure notify (we don't need to if we aren't mapped
1052 event
.type
= ConfigureNotify
;
1053 event
.xconfigure
.display
= **otk::display
;
1054 event
.xconfigure
.event
= _window
;
1055 event
.xconfigure
.window
= _window
;
1056 event
.xconfigure
.x
= x
;
1057 event
.xconfigure
.y
= y
;
1058 event
.xconfigure
.width
= _area
.width();
1059 event
.xconfigure
.height
= _area
.height();
1060 event
.xconfigure
.border_width
= _border_width
;
1061 event
.xconfigure
.above
= frame
->window();
1062 event
.xconfigure
.override_redirect
= False
;
1063 XSendEvent(event
.xconfigure
.display
, event
.xconfigure
.window
, False
,
1064 StructureNotifyMask
, &event
);
1069 void Client::close()
1073 if (!(_functions
& Func_Close
)) return;
1075 // XXX: itd be cool to do timeouts and shit here for killing the client's
1077 // like... if the window is around after 5 seconds, then the close button
1078 // turns a nice red, and if this function is called again, the client is
1079 // explicitly killed.
1081 ce
.xclient
.type
= ClientMessage
;
1082 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1083 ce
.xclient
.display
= **otk::display
;
1084 ce
.xclient
.window
= _window
;
1085 ce
.xclient
.format
= 32;
1086 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_delete_window
;
1087 ce
.xclient
.data
.l
[1] = CurrentTime
;
1088 ce
.xclient
.data
.l
[2] = 0l;
1089 ce
.xclient
.data
.l
[3] = 0l;
1090 ce
.xclient
.data
.l
[4] = 0l;
1091 XSendEvent(**otk::display
, _window
, false, NoEventMask
, &ce
);
1095 void Client::changeState()
1097 unsigned long state
[2];
1098 state
[0] = _wmstate
;
1100 otk::Property::set(_window
, otk::Property::atoms
.wm_state
,
1101 otk::Property::atoms
.wm_state
, state
, 2);
1106 netstate
[num
++] = otk::Property::atoms
.net_wm_state_modal
;
1108 netstate
[num
++] = otk::Property::atoms
.net_wm_state_shaded
;
1110 netstate
[num
++] = otk::Property::atoms
.net_wm_state_hidden
;
1112 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_taskbar
;
1114 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_pager
;
1116 netstate
[num
++] = otk::Property::atoms
.net_wm_state_fullscreen
;
1118 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_vert
;
1120 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_horz
;
1122 netstate
[num
++] = otk::Property::atoms
.net_wm_state_above
;
1124 netstate
[num
++] = otk::Property::atoms
.net_wm_state_below
;
1125 otk::Property::set(_window
, otk::Property::atoms
.net_wm_state
,
1126 otk::Property::atoms
.atom
, netstate
, num
);
1131 frame
->adjustState();
1135 void Client::changeAllowedActions(void)
1140 actions
[num
++] = otk::Property::atoms
.net_wm_action_change_desktop
;
1142 if (_functions
& Func_Shade
)
1143 actions
[num
++] = otk::Property::atoms
.net_wm_action_shade
;
1144 if (_functions
& Func_Close
)
1145 actions
[num
++] = otk::Property::atoms
.net_wm_action_close
;
1146 if (_functions
& Func_Move
)
1147 actions
[num
++] = otk::Property::atoms
.net_wm_action_move
;
1148 if (_functions
& Func_Iconify
)
1149 actions
[num
++] = otk::Property::atoms
.net_wm_action_minimize
;
1150 if (_functions
& Func_Resize
)
1151 actions
[num
++] = otk::Property::atoms
.net_wm_action_resize
;
1152 if (_functions
& Func_Fullscreen
)
1153 actions
[num
++] = otk::Property::atoms
.net_wm_action_fullscreen
;
1154 if (_functions
& Func_Maximize
) {
1155 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_horz
;
1156 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_vert
;
1159 otk::Property::set(_window
, otk::Property::atoms
.net_wm_allowed_actions
,
1160 otk::Property::atoms
.atom
, actions
, num
);
1164 void Client::applyStartupState()
1166 // these are in a carefully crafted order..
1170 _desktop
= 0; // set some other source desktop so this goes through
1171 setDesktop(ICONIC_DESKTOP
);
1174 _fullscreen
= false;
1182 if (_max_vert
); // XXX: incomplete
1183 if (_max_horz
); // XXX: incomplete
1185 if (_skip_taskbar
); // nothing to do for this
1186 if (_skip_pager
); // nothing to do for this
1187 if (_modal
); // nothing to do for this
1188 if (_above
); // nothing to do for this
1189 if (_below
); // nothing to do for this
1193 void Client::shade(bool shade
)
1195 if (!(_functions
& Func_Shade
) || // can't
1196 _shaded
== shade
) return; // already done
1198 // when we're iconic, don't change the wmstate
1200 _wmstate
= shade
? IconicState
: NormalState
;
1203 frame
->adjustSize();
1207 void Client::fullscreen(bool fs
)
1209 static FunctionFlags saved_func
;
1210 static DecorationFlags saved_decor
;
1211 static otk::Rect saved_area
;
1212 static otk::Point saved_logical_size
;
1214 if (!(_functions
& Func_Fullscreen
) || // can't
1215 _fullscreen
== fs
) return; // already done
1218 changeState(); // change the state hints on the client
1221 // save the functions and remove them
1222 saved_func
= _functions
;
1223 _functions
= _functions
& (Func_Close
| Func_Fullscreen
| Func_Iconify
);
1224 // save the decorations and remove them
1225 saved_decor
= _decorations
;
1227 // save the area and adjust it (we don't call internal resize here for
1228 // constraints on the size, etc, we just make it fullscreen).
1230 const otk::ScreenInfo
*info
= otk::display
->screenInfo(_screen
);
1231 _area
.setRect(0, 0, info
->width(), info
->height());
1232 saved_logical_size
= _logical_size
;
1233 _logical_size
.setPoint((info
->width() - _base_size
.x()) / _size_inc
.x(),
1234 (info
->height() - _base_size
.y()) / _size_inc
.y());
1236 _functions
= saved_func
;
1237 _decorations
= saved_decor
;
1239 _logical_size
= saved_logical_size
;
1242 changeAllowedActions(); // based on the new _functions
1244 frame
->adjustSize(); // drop/replace the decor's and resize
1245 frame
->adjustPosition(); // get (back) in position!
1247 // raise (back) into our stacking layer
1248 openbox
->screen(_screen
)->raiseWindow(this);
1250 // try focus us when we go into fullscreen mode
1255 bool Client::focus() const
1257 // won't try focus if the client doesn't want it, or if the window isn't
1258 // visible on the screen
1259 if (!(frame
->isVisible() && (_can_focus
|| _focus_notify
))) return false;
1261 if (_focused
) return true;
1263 // do a check to see if the window has already been unmapped or destroyed
1265 if (XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &ev
) ||
1266 XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &ev
)) {
1267 XPutBackEvent(**otk::display
, &ev
);
1272 XSetInputFocus(**otk::display
, _window
,
1273 RevertToNone
, CurrentTime
);
1275 if (_focus_notify
) {
1277 ce
.xclient
.type
= ClientMessage
;
1278 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1279 ce
.xclient
.display
= **otk::display
;
1280 ce
.xclient
.window
= _window
;
1281 ce
.xclient
.format
= 32;
1282 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_take_focus
;
1283 ce
.xclient
.data
.l
[1] = openbox
->lastTime();
1284 ce
.xclient
.data
.l
[2] = 0l;
1285 ce
.xclient
.data
.l
[3] = 0l;
1286 ce
.xclient
.data
.l
[4] = 0l;
1287 XSendEvent(**otk::display
, _window
, False
, NoEventMask
, &ce
);
1294 void Client::unfocus() const
1296 if (!_focused
) return;
1298 assert(openbox
->focusedClient() == this);
1299 openbox
->setFocusedClient(0);
1303 void Client::focusHandler(const XFocusChangeEvent
&e
)
1306 // printf("FocusIn for 0x%lx\n", e.window);
1309 otk::EventHandler::focusHandler(e
);
1314 openbox
->setFocusedClient(this);
1318 void Client::unfocusHandler(const XFocusChangeEvent
&e
)
1321 // printf("FocusOut for 0x%lx\n", e.window);
1324 otk::EventHandler::unfocusHandler(e
);
1329 if (openbox
->focusedClient() == this)
1330 openbox
->setFocusedClient(0);
1334 void Client::configureRequestHandler(const XConfigureRequestEvent
&e
)
1337 printf("ConfigureRequest for 0x%lx\n", e
.window
);
1340 otk::EventHandler::configureRequestHandler(e
);
1342 // if we are iconic (or shaded (fvwm does this)) ignore the event
1343 if (_iconic
|| _shaded
) return;
1345 if (e
.value_mask
& CWBorderWidth
)
1346 _border_width
= e
.border_width
;
1348 // resize, then move, as specified in the EWMH section 7.7
1349 if (e
.value_mask
& (CWWidth
| CWHeight
)) {
1350 int w
= (e
.value_mask
& CWWidth
) ? e
.width
: _area
.width();
1351 int h
= (e
.value_mask
& CWHeight
) ? e
.height
: _area
.height();
1355 case NorthEastGravity
:
1359 case SouthWestGravity
:
1361 corner
= BottomLeft
;
1363 case SouthEastGravity
:
1364 corner
= BottomRight
;
1366 default: // NorthWest, Static, etc
1370 // if moving AND resizing ...
1371 if (e
.value_mask
& (CWX
| CWY
)) {
1372 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1373 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1374 internal_resize(corner
, w
, h
, x
, y
);
1375 } else // if JUST resizing...
1376 internal_resize(corner
, w
, h
);
1377 } else if (e
.value_mask
& (CWX
| CWY
)) { // if JUST moving...
1378 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1379 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1380 internal_move(x
, y
);
1383 if (e
.value_mask
& CWStackMode
) {
1387 openbox
->screen(_screen
)->lowerWindow(this);
1393 openbox
->screen(_screen
)->raiseWindow(this);
1400 void Client::unmapHandler(const XUnmapEvent
&e
)
1402 if (ignore_unmaps
) {
1404 printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e
.window
, e
.event
);
1411 printf("UnmapNotify for 0x%lx\n", e
.window
);
1414 otk::EventHandler::unmapHandler(e
);
1416 // this deletes us etc
1417 openbox
->screen(_screen
)->unmanageWindow(this);
1421 void Client::destroyHandler(const XDestroyWindowEvent
&e
)
1424 printf("DestroyNotify for 0x%lx\n", e
.window
);
1427 otk::EventHandler::destroyHandler(e
);
1429 // this deletes us etc
1430 openbox
->screen(_screen
)->unmanageWindow(this);
1434 void Client::reparentHandler(const XReparentEvent
&e
)
1436 // this is when the client is first taken captive in the frame
1437 if (e
.parent
== frame
->plate()) return;
1440 printf("ReparentNotify for 0x%lx\n", e
.window
);
1443 otk::EventHandler::reparentHandler(e
);
1446 This event is quite rare and is usually handled in unmapHandler.
1447 However, if the window is unmapped when the reparent event occurs,
1448 the window manager never sees it because an unmap event is not sent
1449 to an already unmapped window.
1452 // we don't want the reparent event, put it back on the stack for the X
1453 // server to deal with after we unmanage the window
1456 XPutBackEvent(**otk::display
, &ev
);
1458 // this deletes us etc
1459 openbox
->screen(_screen
)->unmanageWindow(this);
1462 void Client::mapRequestHandler(const XMapRequestEvent
&e
)
1465 printf("MapRequest for already managed 0x%lx\n", e
.window
);
1468 assert(_iconic
); // we shouldn't be able to get this unless we're iconic
1470 // move to the current desktop (uniconify)
1471 setDesktop(openbox
->screen(_screen
)->desktop());
1472 // XXX: should we focus/raise the window? (basically a net_wm_active_window)