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 getGravity(); // get the attribute gravity
70 updateNormalHints(); // this may override the attribute gravity
72 // got the type, the mwmhints, the protocols, and the normal hints (min/max
73 // sizes), so we're ready to set up
74 // the decorations/functions
75 setupDecorAndFunctions();
77 // also get the initial_state and set _iconic if we aren't "starting"
78 // when we're "starting" that means we should use whatever state was already
79 // on the window over the initial map state, because it was already mapped
80 updateWMHints(openbox
->state() != Openbox::State_Starting
);
86 // this makes sure that these windows appear on all desktops
87 if (/*_type == Type_Dock ||*/ _type
== Type_Desktop
)
88 _desktop
= 0xffffffff;
90 // set the desktop hint, to make sure that it always exists, and to reflect
91 // any changes we've made here
92 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
93 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
101 // clean up childrens' references
102 while (!_transients
.empty()) {
103 _transients
.front()->_transient_for
= 0;
104 _transients
.pop_front();
107 // clean up parents reference to this
109 _transient_for
->_transients
.remove(this); // remove from old parent
111 if (openbox
->state() != Openbox::State_Exiting
) {
112 // these values should not be persisted across a window unmapping/mapping
113 otk::Property::erase(_window
, otk::Property::atoms
.net_wm_desktop
);
114 otk::Property::erase(_window
, otk::Property::atoms
.net_wm_state
);
116 // if we're left in an iconic state, the client wont be mapped. this is
117 // bad, since we will no longer be managing the window on restart
119 XMapWindow(**otk::display
, _window
);
124 bool Client::validate() const
126 XSync(**otk::display
, false); // get all events on the server
129 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &e
) ||
130 XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &e
)) {
131 XPutBackEvent(**otk::display
, &e
);
139 void Client::getGravity()
141 XWindowAttributes wattrib
;
144 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
145 assert(ret
!= BadWindow
);
146 _gravity
= wattrib
.win_gravity
;
150 void Client::getDesktop()
152 // defaults to the current desktop
153 _desktop
= openbox
->screen(_screen
)->desktop();
155 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_desktop
,
156 otk::Property::atoms
.cardinal
,
157 (long unsigned*)&_desktop
)) {
159 // printf("Window requested desktop: %ld\n", _desktop);
165 void Client::getType()
167 _type
= (WindowType
) -1;
170 unsigned long num
= (unsigned) -1;
171 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_window_type
,
172 otk::Property::atoms
.atom
, &num
, &val
)) {
173 // use the first value that we know about in the array
174 for (unsigned long i
= 0; i
< num
; ++i
) {
175 if (val
[i
] == otk::Property::atoms
.net_wm_window_type_desktop
)
176 _type
= Type_Desktop
;
177 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dock
)
179 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_toolbar
)
180 _type
= Type_Toolbar
;
181 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_menu
)
183 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_utility
)
184 _type
= Type_Utility
;
185 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_splash
)
187 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dialog
)
189 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_normal
)
191 // XXX: make this work again
192 // else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override)
193 // mwm_decorations = 0; // prevent this window from getting any decor
194 if (_type
!= (WindowType
) -1)
195 break; // grab the first known type
200 if (_type
== (WindowType
) -1) {
202 * the window type hint was not set, which means we either classify ourself
203 * as a normal window or a dialog, depending on if we are a transient.
213 void Client::setupDecorAndFunctions()
215 // start with everything (cept fullscreen)
216 _decorations
= Decor_Titlebar
| Decor_Handle
| Decor_Border
|
217 Decor_AllDesktops
| Decor_Iconify
| Decor_Maximize
;
218 _functions
= Func_Resize
| Func_Move
| Func_Iconify
| Func_Maximize
|
220 if (_delete_window
) {
221 _decorations
|= Decor_Close
;
222 _functions
|= Func_Close
;
225 if (!(_min_size
.x() < _max_size
.x() || _min_size
.y() < _max_size
.y())) {
226 _decorations
&= ~(Decor_Maximize
| Decor_Handle
);
227 _functions
&= ~(Func_Resize
| Func_Maximize
);
232 // normal windows retain all of the possible decorations and
233 // functionality, and are the only windows that you can fullscreen
234 _functions
|= Func_Fullscreen
;
237 // dialogs cannot be maximized
238 _decorations
&= ~Decor_Maximize
;
239 _functions
&= ~Func_Maximize
;
245 // these windows get less functionality
246 _decorations
&= ~(Decor_Iconify
| Decor_Handle
);
247 _functions
&= ~(Func_Iconify
| Func_Resize
);
253 // none of these windows are manipulated by the window manager
259 // Mwm Hints are applied subtractively to what has already been chosen for
260 // decor and functionality
261 if (_mwmhints
.flags
& MwmFlag_Decorations
) {
262 if (! (_mwmhints
.decorations
& MwmDecor_All
)) {
263 if (! (_mwmhints
.decorations
& MwmDecor_Border
))
264 _decorations
&= ~Decor_Border
;
265 if (! (_mwmhints
.decorations
& MwmDecor_Handle
))
266 _decorations
&= ~Decor_Handle
;
267 if (! (_mwmhints
.decorations
& MwmDecor_Title
)) {
268 _decorations
&= ~Decor_Titlebar
;
269 // if we don't have a titlebar, then we cannot shade!
270 _functions
&= ~Func_Shade
;
272 if (! (_mwmhints
.decorations
& MwmDecor_Iconify
))
273 _decorations
&= ~Decor_Iconify
;
274 if (! (_mwmhints
.decorations
& MwmDecor_Maximize
))
275 _decorations
&= ~Decor_Maximize
;
279 if (_mwmhints
.flags
& MwmFlag_Functions
) {
280 if (! (_mwmhints
.functions
& MwmFunc_All
)) {
281 if (! (_mwmhints
.functions
& MwmFunc_Resize
))
282 _functions
&= ~Func_Resize
;
283 if (! (_mwmhints
.functions
& MwmFunc_Move
))
284 _functions
&= ~Func_Move
;
285 if (! (_mwmhints
.functions
& MwmFunc_Iconify
))
286 _functions
&= ~Func_Iconify
;
287 if (! (_mwmhints
.functions
& MwmFunc_Maximize
))
288 _functions
&= ~Func_Maximize
;
289 // dont let mwm hints kill the close button
290 //if (! (_mwmhints.functions & MwmFunc_Close))
291 // _functions &= ~Func_Close;
295 // finally, user specified disabled decorations are applied to subtract
297 if (_disabled_decorations
& Decor_Titlebar
)
298 _decorations
&= ~Decor_Titlebar
;
299 if (_disabled_decorations
& Decor_Handle
)
300 _decorations
&= ~Decor_Handle
;
301 if (_disabled_decorations
& Decor_Border
)
302 _decorations
&= ~Decor_Border
;
303 if (_disabled_decorations
& Decor_Iconify
)
304 _decorations
&= ~Decor_Iconify
;
305 if (_disabled_decorations
& Decor_Maximize
)
306 _decorations
&= ~Decor_Maximize
;
307 if (_disabled_decorations
& Decor_AllDesktops
)
308 _decorations
&= ~Decor_AllDesktops
;
309 if (_disabled_decorations
& Decor_Close
)
310 _decorations
&= ~Decor_Close
;
312 changeAllowedActions();
315 frame
->adjustSize(); // change the decors on the frame
316 frame
->adjustPosition(); // with more/less decorations, we may need to be
322 void Client::getMwmHints()
324 unsigned long num
= MwmHints::elements
;
325 unsigned long *hints
;
327 _mwmhints
.flags
= 0; // default to none
329 if (!otk::Property::get(_window
, otk::Property::atoms
.motif_wm_hints
,
330 otk::Property::atoms
.motif_wm_hints
, &num
,
331 (unsigned long **)&hints
))
334 if (num
>= MwmHints::elements
) {
335 // retrieved the hints
336 _mwmhints
.flags
= hints
[0];
337 _mwmhints
.functions
= hints
[1];
338 _mwmhints
.decorations
= hints
[2];
345 void Client::getArea()
347 XWindowAttributes wattrib
;
350 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
351 assert(ret
!= BadWindow
);
353 _area
.setRect(wattrib
.x
, wattrib
.y
, wattrib
.width
, wattrib
.height
);
354 _border_width
= wattrib
.border_width
;
358 void Client::getState()
360 _modal
= _shaded
= _max_horz
= _max_vert
= _fullscreen
= _above
= _below
=
361 _iconic
= _skip_taskbar
= _skip_pager
= false;
363 unsigned long *state
;
364 unsigned long num
= (unsigned) -1;
366 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_state
,
367 otk::Property::atoms
.atom
, &num
, &state
)) {
368 for (unsigned long i
= 0; i
< num
; ++i
) {
369 if (state
[i
] == otk::Property::atoms
.net_wm_state_modal
)
371 else if (state
[i
] == otk::Property::atoms
.net_wm_state_shaded
)
373 else if (state
[i
] == otk::Property::atoms
.net_wm_state_hidden
)
375 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_taskbar
)
376 _skip_taskbar
= true;
377 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_pager
)
379 else if (state
[i
] == otk::Property::atoms
.net_wm_state_fullscreen
)
381 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_vert
)
383 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_horz
)
385 else if (state
[i
] == otk::Property::atoms
.net_wm_state_above
)
387 else if (state
[i
] == otk::Property::atoms
.net_wm_state_below
)
396 void Client::getShaped()
400 if (otk::display
->shape()) {
405 XShapeSelectInput(**otk::display
, _window
, ShapeNotifyMask
);
407 XShapeQueryExtents(**otk::display
, _window
, &s
, &foo
,
408 &foo
, &ufoo
, &ufoo
, &foo
, &foo
, &foo
, &ufoo
, &ufoo
);
415 void Client::calcLayer() {
418 if (_iconic
) l
= Layer_Icon
;
419 else if (_fullscreen
) l
= Layer_Fullscreen
;
420 else if (_type
== Type_Desktop
) l
= Layer_Desktop
;
421 else if (_type
== Type_Dock
) {
422 if (!_below
) l
= Layer_Top
;
423 else l
= Layer_Normal
;
425 else if (_above
) l
= Layer_Above
;
426 else if (_below
) l
= Layer_Below
;
427 else l
= Layer_Normal
;
433 if we don't have a frame, then we aren't mapped yet (and this would
436 openbox
->screen(_screen
)->raiseWindow(this);
442 void Client::updateProtocols()
447 _focus_notify
= false;
448 _delete_window
= false;
450 if (XGetWMProtocols(**otk::display
, _window
, &proto
, &num_return
)) {
451 for (int i
= 0; i
< num_return
; ++i
) {
452 if (proto
[i
] == otk::Property::atoms
.wm_delete_window
) {
453 // this means we can request the window to close
454 _delete_window
= true;
455 } else if (proto
[i
] == otk::Property::atoms
.wm_take_focus
)
456 // if this protocol is requested, then the window will be notified
457 // by the window manager whenever it receives focus
458 _focus_notify
= true;
465 void Client::updateNormalHints()
469 int oldgravity
= _gravity
;
474 _size_inc
.setPoint(1, 1);
475 _base_size
.setPoint(0, 0);
476 _min_size
.setPoint(0, 0);
477 _max_size
.setPoint(INT_MAX
, INT_MAX
);
479 // XXX: might want to cancel any interactive resizing of the window at this
482 // get the hints from the window
483 if (XGetWMNormalHints(**otk::display
, _window
, &size
, &ret
)) {
484 _positioned
= (size
.flags
& (PPosition
|USPosition
));
486 if (size
.flags
& PWinGravity
) {
487 _gravity
= size
.win_gravity
;
489 // if the client has a frame, i.e. has already been mapped and is
490 // changing its gravity
491 if (frame
&& _gravity
!= oldgravity
) {
492 // move our idea of the client's position based on its new gravity
494 frame
->frameGravity(x
, y
);
499 if (size
.flags
& PAspect
) {
500 if (size
.min_aspect
.y
) _min_ratio
= size
.min_aspect
.x
/size
.min_aspect
.y
;
501 if (size
.max_aspect
.y
) _max_ratio
= size
.max_aspect
.x
/size
.max_aspect
.y
;
504 if (size
.flags
& PMinSize
)
505 _min_size
.setPoint(size
.min_width
, size
.min_height
);
507 if (size
.flags
& PMaxSize
)
508 _max_size
.setPoint(size
.max_width
, size
.max_height
);
510 if (size
.flags
& PBaseSize
)
511 _base_size
.setPoint(size
.base_width
, size
.base_height
);
513 if (size
.flags
& PResizeInc
)
514 _size_inc
.setPoint(size
.width_inc
, size
.height_inc
);
519 void Client::updateWMHints(bool initstate
)
523 // assume a window takes input if it doesnt specify
527 if ((hints
= XGetWMHints(**otk::display
, _window
)) != NULL
) {
528 if (hints
->flags
& InputHint
)
529 _can_focus
= hints
->input
;
531 // only do this when initstate is true!
532 if (initstate
&& (hints
->flags
& StateHint
))
533 _iconic
= hints
->initial_state
== IconicState
;
535 if (hints
->flags
& XUrgencyHint
)
538 if (hints
->flags
& WindowGroupHint
) {
539 if (hints
->window_group
!= _group
) {
540 // XXX: remove from the old group if there was one
541 _group
= hints
->window_group
;
542 // XXX: do stuff with the group
553 printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
554 (long)_window
, _urgent
? "ON" : "OFF");
556 // fire the urgent callback if we're mapped, otherwise, wait until after
564 void Client::updateTitle()
569 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_name
,
570 otk::Property::utf8
, &_title
)) {
572 otk::Property::get(_window
, otk::Property::atoms
.wm_name
,
573 otk::Property::ascii
, &_title
);
577 _title
= _("Unnamed Window");
580 frame
->setTitle(_title
);
584 void Client::updateIconTitle()
589 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_icon_name
,
590 otk::Property::utf8
, &_icon_title
)) {
592 otk::Property::get(_window
, otk::Property::atoms
.wm_icon_name
,
593 otk::Property::ascii
, &_icon_title
);
597 _icon_title
= _("Unnamed Window");
601 void Client::updateClass()
604 _app_name
= _app_class
= _role
= "";
606 otk::Property::StringVect v
;
607 unsigned long num
= 2;
609 if (otk::Property::get(_window
, otk::Property::atoms
.wm_class
,
610 otk::Property::ascii
, &num
, &v
)) {
611 if (num
> 0) _app_name
= v
[0].c_str();
612 if (num
> 1) _app_class
= v
[1].c_str();
617 if (otk::Property::get(_window
, otk::Property::atoms
.wm_window_role
,
618 otk::Property::ascii
, &num
, &v
)) {
619 if (num
> 0) _role
= v
[0].c_str();
624 void Client::updateStrut()
626 unsigned long num
= 4;
628 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_strut
,
629 otk::Property::atoms
.cardinal
, &num
, &data
))
633 _strut
.left
= data
[0];
634 _strut
.right
= data
[1];
635 _strut
.top
= data
[2];
636 _strut
.bottom
= data
[3];
638 // updating here is pointless while we're being mapped cuz we're not in
639 // the screen's client list yet
641 openbox
->screen(_screen
)->updateStrut();
648 void Client::updateTransientFor()
653 if (XGetTransientForHint(**otk::display
, _window
, &t
) &&
654 t
!= _window
) { // cant be transient to itself!
655 c
= openbox
->findClient(t
);
656 assert(c
!= this); // if this happens then we need to check for it
658 if (!c
/*XXX: && _group*/) {
659 // not transient to a client, see if it is transient for a group
660 if (//t == _group->leader() ||
662 t
== otk::display
->screenInfo(_screen
)->rootWindow()) {
663 // window is a transient for its group!
664 // XXX: for now this is treated as non-transient.
665 // this needs to be fixed!
670 // if anything has changed...
671 if (c
!= _transient_for
) {
673 _transient_for
->_transients
.remove(this); // remove from old parent
676 _transient_for
->_transients
.push_back(this); // add to new parent
678 // XXX: change decor status?
683 void Client::propertyHandler(const XPropertyEvent
&e
)
685 otk::EventHandler::propertyHandler(e
);
687 // validate cuz we query stuff off the client here
688 if (!validate()) return;
690 // compress changes to a single property into a single change
692 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
693 // XXX: it would be nice to compress ALL changes to a property, not just
694 // changes in a row without other props between.
695 if (ce
.xproperty
.atom
!= e
.atom
) {
696 XPutBackEvent(**otk::display
, &ce
);
701 if (e
.atom
== XA_WM_NORMAL_HINTS
) {
703 setupDecorAndFunctions(); // normal hints can make a window non-resizable
704 } else if (e
.atom
== XA_WM_HINTS
)
706 else if (e
.atom
== XA_WM_TRANSIENT_FOR
) {
707 updateTransientFor();
709 calcLayer(); // type may have changed, so update the layer
710 setupDecorAndFunctions();
712 else if (e
.atom
== otk::Property::atoms
.net_wm_name
||
713 e
.atom
== otk::Property::atoms
.wm_name
)
715 else if (e
.atom
== otk::Property::atoms
.net_wm_icon_name
||
716 e
.atom
== otk::Property::atoms
.wm_icon_name
)
718 else if (e
.atom
== otk::Property::atoms
.wm_class
)
720 else if (e
.atom
== otk::Property::atoms
.wm_protocols
) {
722 setupDecorAndFunctions();
724 else if (e
.atom
== otk::Property::atoms
.net_wm_strut
)
729 void Client::setWMState(long state
)
731 if (state
== _wmstate
) return; // no change
735 setDesktop(ICONIC_DESKTOP
);
738 setDesktop(openbox
->screen(_screen
)->desktop());
744 void Client::setDesktop(long target
)
746 if (target
== _desktop
) return;
748 printf("Setting desktop %ld\n", target
);
750 if (!(target
>= 0 || target
== (signed)0xffffffff ||
751 target
== ICONIC_DESKTOP
))
756 // set the desktop hint, but not if we're iconifying
757 if (_desktop
!= ICONIC_DESKTOP
)
758 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
759 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
761 // 'move' the window to the new desktop
762 if (_desktop
== openbox
->screen(_screen
)->desktop() ||
763 _desktop
== (signed)0xffffffff)
768 // Handle Iconic state. Iconic state is maintained by the client being a
769 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
770 // uniconifying happen.
771 bool i
= _desktop
== ICONIC_DESKTOP
;
772 if (i
!= _iconic
) { // has the state changed?
775 _wmstate
= IconicState
;
777 // we unmap the client itself so that we can get MapRequest events, and
778 // because the ICCCM tells us to!
779 XUnmapWindow(**otk::display
, _window
);
781 _wmstate
= NormalState
;
782 XMapWindow(**otk::display
, _window
);
787 frame
->adjustState();
791 void Client::setState(StateAction action
, long data1
, long data2
)
793 bool shadestate
= _shaded
;
794 bool fsstate
= _fullscreen
;
796 if (!(action
== State_Add
|| action
== State_Remove
||
797 action
== State_Toggle
))
798 return; // an invalid action was passed to the client message, ignore it
800 for (int i
= 0; i
< 2; ++i
) {
801 Atom state
= i
== 0 ? data1
: data2
;
803 if (! state
) continue;
805 // if toggling, then pick whether we're adding or removing
806 if (action
== State_Toggle
) {
807 if (state
== otk::Property::atoms
.net_wm_state_modal
)
808 action
= _modal
? State_Remove
: State_Add
;
809 else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
)
810 action
= _max_vert
? State_Remove
: State_Add
;
811 else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
)
812 action
= _max_horz
? State_Remove
: State_Add
;
813 else if (state
== otk::Property::atoms
.net_wm_state_shaded
)
814 action
= _shaded
? State_Remove
: State_Add
;
815 else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
)
816 action
= _skip_taskbar
? State_Remove
: State_Add
;
817 else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
)
818 action
= _skip_pager
? State_Remove
: State_Add
;
819 else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
)
820 action
= _fullscreen
? State_Remove
: State_Add
;
821 else if (state
== otk::Property::atoms
.net_wm_state_above
)
822 action
= _above
? State_Remove
: State_Add
;
823 else if (state
== otk::Property::atoms
.net_wm_state_below
)
824 action
= _below
? State_Remove
: State_Add
;
827 if (action
== State_Add
) {
828 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
829 if (_modal
) continue;
831 // XXX: give it focus if another window has focus that shouldnt now
832 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
833 if (_max_vert
) continue;
835 // XXX: resize the window etc
836 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
837 if (_max_horz
) continue;
839 // XXX: resize the window etc
840 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
842 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
843 _skip_taskbar
= true;
844 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
846 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
848 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
849 if (_above
) continue;
851 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
852 if (_below
) continue;
856 } else { // action == State_Remove
857 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
858 if (!_modal
) continue;
860 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
861 if (!_max_vert
) continue;
863 // XXX: resize the window etc
864 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
865 if (!_max_horz
) continue;
867 // XXX: resize the window etc
868 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
870 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
871 _skip_taskbar
= false;
872 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
874 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
876 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
877 if (!_above
) continue;
879 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
880 if (!_below
) continue;
885 // change fullscreen state before shading, as it will affect if the window
887 if (fsstate
!= _fullscreen
)
889 if (shadestate
!= _shaded
)
895 void Client::toggleClientBorder(bool addborder
)
897 // adjust our idea of where the client is, based on its border. When the
898 // border is removed, the client should now be considered to be in a
899 // different position.
900 // when re-adding the border to the client, the same operation needs to be
902 int x
= _area
.x(), y
= _area
.y();
905 case NorthWestGravity
:
907 case SouthWestGravity
:
909 case NorthEastGravity
:
911 case SouthEastGravity
:
912 if (addborder
) x
-= _border_width
* 2;
913 else x
+= _border_width
* 2;
920 if (addborder
) x
-= _border_width
;
921 else x
+= _border_width
;
926 case NorthWestGravity
:
928 case NorthEastGravity
:
930 case SouthWestGravity
:
932 case SouthEastGravity
:
933 if (addborder
) y
-= _border_width
* 2;
934 else y
+= _border_width
* 2;
941 if (addborder
) y
-= _border_width
;
942 else y
+= _border_width
;
948 XSetWindowBorderWidth(**otk::display
, _window
, _border_width
);
950 // move the client so it is back it the right spot _with_ its border!
951 XMoveWindow(**otk::display
, _window
, x
, y
);
953 XSetWindowBorderWidth(**otk::display
, _window
, 0);
957 void Client::clientMessageHandler(const XClientMessageEvent
&e
)
959 otk::EventHandler::clientMessageHandler(e
);
961 // validate cuz we query stuff off the client here
962 if (!validate()) return;
964 if (e
.format
!= 32) return;
966 if (e
.message_type
== otk::Property::atoms
.wm_change_state
) {
967 // compress changes into a single change
968 bool compress
= false;
970 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
971 // XXX: it would be nice to compress ALL messages of a type, not just
972 // messages in a row without other message types between.
973 if (ce
.xclient
.message_type
!= e
.message_type
) {
974 XPutBackEvent(**otk::display
, &ce
);
980 setWMState(ce
.xclient
.data
.l
[0]); // use the found event
982 setWMState(e
.data
.l
[0]); // use the original event
983 } else if (e
.message_type
== otk::Property::atoms
.net_wm_desktop
) {
984 // compress changes into a single change
985 bool compress
= false;
987 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
988 // XXX: it would be nice to compress ALL messages of a type, not just
989 // messages in a row without other message types between.
990 if (ce
.xclient
.message_type
!= e
.message_type
) {
991 XPutBackEvent(**otk::display
, &ce
);
997 setDesktop(e
.data
.l
[0]); // use the found event
999 setDesktop(e
.data
.l
[0]); // use the original event
1000 } else if (e
.message_type
== otk::Property::atoms
.net_wm_state
) {
1001 // can't compress these
1003 printf("net_wm_state %s %ld %ld for 0x%lx\n",
1004 (e
.data
.l
[0] == 0 ? "Remove" : e
.data
.l
[0] == 1 ? "Add" :
1005 e
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
1006 e
.data
.l
[1], e
.data
.l
[2], _window
);
1008 setState((StateAction
)e
.data
.l
[0], e
.data
.l
[1], e
.data
.l
[2]);
1009 } else if (e
.message_type
== otk::Property::atoms
.net_close_window
) {
1011 printf("net_close_window for 0x%lx\n", _window
);
1014 } else if (e
.message_type
== otk::Property::atoms
.net_active_window
) {
1016 printf("net_active_window for 0x%lx\n", _window
);
1019 setDesktop(openbox
->screen(_screen
)->desktop());
1024 openbox
->screen(_screen
)->raiseWindow(this);
1030 void Client::shapeHandler(const XShapeEvent
&e
)
1032 otk::EventHandler::shapeHandler(e
);
1034 if (e
.kind
== ShapeBounding
) {
1036 frame
->adjustShape();
1042 void Client::resize(Corner anchor
, int w
, int h
)
1044 if (!(_functions
& Func_Resize
)) return;
1045 internal_resize(anchor
, w
, h
);
1049 void Client::internal_resize(Corner anchor
, int w
, int h
, bool user
,
1052 w
-= _base_size
.x();
1053 h
-= _base_size
.y();
1055 // for interactive resizing. have to move half an increment in each
1057 w
+= _size_inc
.x() / 2;
1058 h
+= _size_inc
.y() / 2;
1061 // if this is a user-requested resize, then check against min/max sizes
1062 // and aspect ratios
1064 // smaller than min size or bigger than max size?
1065 if (w
< _min_size
.x()) w
= _min_size
.x();
1066 else if (w
> _max_size
.x()) w
= _max_size
.x();
1067 if (h
< _min_size
.y()) h
= _min_size
.y();
1068 else if (h
> _max_size
.y()) h
= _max_size
.y();
1070 // adjust the height ot match the width for the aspect ratios
1072 if (h
* _min_ratio
> w
) h
= static_cast<int>(w
/ _min_ratio
);
1074 if (h
* _max_ratio
< w
) h
= static_cast<int>(w
/ _max_ratio
);
1077 // keep to the increments
1081 // you cannot resize to nothing
1085 // store the logical size
1086 _logical_size
.setPoint(w
, h
);
1091 w
+= _base_size
.x();
1092 h
+= _base_size
.y();
1094 if (x
== INT_MIN
|| y
== INT_MIN
) {
1101 x
-= w
- _area
.width();
1104 y
-= h
- _area
.height();
1107 x
-= w
- _area
.width();
1108 y
-= h
- _area
.height();
1113 _area
.setSize(w
, h
);
1115 XResizeWindow(**otk::display
, _window
, w
, h
);
1117 // resize the frame to match the request
1118 frame
->adjustSize();
1119 internal_move(x
, y
);
1123 void Client::move(int x
, int y
)
1125 if (!(_functions
& Func_Move
)) return;
1126 internal_move(x
, y
);
1130 void Client::internal_move(int x
, int y
)
1134 // move the frame to be in the requested position
1135 if (frame
) { // this can be called while mapping, before frame exists
1136 frame
->adjustPosition();
1138 // send synthetic configure notify (we don't need to if we aren't mapped
1141 event
.type
= ConfigureNotify
;
1142 event
.xconfigure
.display
= **otk::display
;
1143 event
.xconfigure
.event
= _window
;
1144 event
.xconfigure
.window
= _window
;
1145 event
.xconfigure
.x
= x
;
1146 event
.xconfigure
.y
= y
;
1147 event
.xconfigure
.width
= _area
.width();
1148 event
.xconfigure
.height
= _area
.height();
1149 event
.xconfigure
.border_width
= _border_width
;
1150 event
.xconfigure
.above
= frame
->window();
1151 event
.xconfigure
.override_redirect
= False
;
1152 XSendEvent(event
.xconfigure
.display
, event
.xconfigure
.window
, False
,
1153 StructureNotifyMask
, &event
);
1158 void Client::close()
1162 if (!(_functions
& Func_Close
)) return;
1164 // XXX: itd be cool to do timeouts and shit here for killing the client's
1166 // like... if the window is around after 5 seconds, then the close button
1167 // turns a nice red, and if this function is called again, the client is
1168 // explicitly killed.
1170 ce
.xclient
.type
= ClientMessage
;
1171 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1172 ce
.xclient
.display
= **otk::display
;
1173 ce
.xclient
.window
= _window
;
1174 ce
.xclient
.format
= 32;
1175 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_delete_window
;
1176 ce
.xclient
.data
.l
[1] = CurrentTime
;
1177 ce
.xclient
.data
.l
[2] = 0l;
1178 ce
.xclient
.data
.l
[3] = 0l;
1179 ce
.xclient
.data
.l
[4] = 0l;
1180 XSendEvent(**otk::display
, _window
, false, NoEventMask
, &ce
);
1184 void Client::changeState()
1186 unsigned long state
[2];
1187 state
[0] = _wmstate
;
1189 otk::Property::set(_window
, otk::Property::atoms
.wm_state
,
1190 otk::Property::atoms
.wm_state
, state
, 2);
1195 netstate
[num
++] = otk::Property::atoms
.net_wm_state_modal
;
1197 netstate
[num
++] = otk::Property::atoms
.net_wm_state_shaded
;
1199 netstate
[num
++] = otk::Property::atoms
.net_wm_state_hidden
;
1201 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_taskbar
;
1203 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_pager
;
1205 netstate
[num
++] = otk::Property::atoms
.net_wm_state_fullscreen
;
1207 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_vert
;
1209 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_horz
;
1211 netstate
[num
++] = otk::Property::atoms
.net_wm_state_above
;
1213 netstate
[num
++] = otk::Property::atoms
.net_wm_state_below
;
1214 otk::Property::set(_window
, otk::Property::atoms
.net_wm_state
,
1215 otk::Property::atoms
.atom
, netstate
, num
);
1220 frame
->adjustState();
1224 void Client::changeAllowedActions(void)
1229 actions
[num
++] = otk::Property::atoms
.net_wm_action_change_desktop
;
1231 if (_functions
& Func_Shade
)
1232 actions
[num
++] = otk::Property::atoms
.net_wm_action_shade
;
1233 if (_functions
& Func_Close
)
1234 actions
[num
++] = otk::Property::atoms
.net_wm_action_close
;
1235 if (_functions
& Func_Move
)
1236 actions
[num
++] = otk::Property::atoms
.net_wm_action_move
;
1237 if (_functions
& Func_Iconify
)
1238 actions
[num
++] = otk::Property::atoms
.net_wm_action_minimize
;
1239 if (_functions
& Func_Resize
)
1240 actions
[num
++] = otk::Property::atoms
.net_wm_action_resize
;
1241 if (_functions
& Func_Fullscreen
)
1242 actions
[num
++] = otk::Property::atoms
.net_wm_action_fullscreen
;
1243 if (_functions
& Func_Maximize
) {
1244 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_horz
;
1245 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_vert
;
1248 otk::Property::set(_window
, otk::Property::atoms
.net_wm_allowed_actions
,
1249 otk::Property::atoms
.atom
, actions
, num
);
1253 void Client::applyStartupState()
1255 // these are in a carefully crafted order..
1259 setDesktop(ICONIC_DESKTOP
);
1262 _fullscreen
= false;
1272 if (_max_vert
); // XXX: incomplete
1273 if (_max_horz
); // XXX: incomplete
1275 if (_skip_taskbar
); // nothing to do for this
1276 if (_skip_pager
); // nothing to do for this
1277 if (_modal
); // nothing to do for this
1278 if (_above
); // nothing to do for this
1279 if (_below
); // nothing to do for this
1283 void Client::fireUrgent()
1285 // call the python UrgentWindow callbacks
1286 EventData
data(_screen
, this, EventAction::UrgentWindow
, 0);
1287 openbox
->bindings()->fireEvent(&data
);
1291 void Client::shade(bool shade
)
1293 if (!(_functions
& Func_Shade
) || // can't
1294 _shaded
== shade
) return; // already done
1296 // when we're iconic, don't change the wmstate
1298 _wmstate
= shade
? IconicState
: NormalState
;
1301 frame
->adjustSize();
1305 void Client::fullscreen(bool fs
)
1307 static FunctionFlags saved_func
;
1308 static DecorationFlags saved_decor
;
1309 static otk::Rect saved_area
;
1310 static otk::Point saved_logical_size
;
1312 if (!(_functions
& Func_Fullscreen
) || // can't
1313 _fullscreen
== fs
) return; // already done
1316 changeState(); // change the state hints on the client
1319 // save the functions and remove them
1320 saved_func
= _functions
;
1321 _functions
= _functions
& (Func_Close
| Func_Fullscreen
| Func_Iconify
);
1322 // save the decorations and remove them
1323 saved_decor
= _decorations
;
1325 // save the area and adjust it (we don't call internal resize here for
1326 // constraints on the size, etc, we just make it fullscreen).
1328 const otk::ScreenInfo
*info
= otk::display
->screenInfo(_screen
);
1329 _area
.setRect(0, 0, info
->width(), info
->height());
1330 saved_logical_size
= _logical_size
;
1331 _logical_size
.setPoint((info
->width() - _base_size
.x()) / _size_inc
.x(),
1332 (info
->height() - _base_size
.y()) / _size_inc
.y());
1334 _functions
= saved_func
;
1335 _decorations
= saved_decor
;
1337 _logical_size
= saved_logical_size
;
1340 changeAllowedActions(); // based on the new _functions
1342 frame
->adjustSize(); // drop/replace the decor's and resize
1343 frame
->adjustPosition(); // get (back) in position!
1345 // raise (back) into our stacking layer
1346 openbox
->screen(_screen
)->raiseWindow(this);
1348 // try focus us when we go into fullscreen mode
1353 void Client::disableDecorations(DecorationFlags flags
)
1355 _disabled_decorations
= flags
;
1356 setupDecorAndFunctions();
1360 bool Client::focus()
1362 // won't try focus if the client doesn't want it, or if the window isn't
1363 // visible on the screen
1364 if (!(frame
->isVisible() && (_can_focus
|| _focus_notify
))) return false;
1366 if (_focused
) return true;
1368 // do a check to see if the window has already been unmapped or destroyed
1369 // do this intelligently while watching out for unmaps we've generated
1370 // (ignore_unmaps > 0)
1372 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &ev
)) {
1373 XPutBackEvent(**otk::display
, &ev
);
1376 while (XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &ev
)) {
1377 if (ignore_unmaps
) {
1378 unmapHandler(ev
.xunmap
);
1380 XPutBackEvent(**otk::display
, &ev
);
1386 XSetInputFocus(**otk::display
, _window
,
1387 RevertToNone
, CurrentTime
);
1389 if (_focus_notify
) {
1391 ce
.xclient
.type
= ClientMessage
;
1392 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1393 ce
.xclient
.display
= **otk::display
;
1394 ce
.xclient
.window
= _window
;
1395 ce
.xclient
.format
= 32;
1396 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_take_focus
;
1397 ce
.xclient
.data
.l
[1] = openbox
->lastTime();
1398 ce
.xclient
.data
.l
[2] = 0l;
1399 ce
.xclient
.data
.l
[3] = 0l;
1400 ce
.xclient
.data
.l
[4] = 0l;
1401 XSendEvent(**otk::display
, _window
, False
, NoEventMask
, &ce
);
1404 XSync(**otk::display
, False
);
1409 void Client::unfocus() const
1411 if (!_focused
) return;
1413 assert(openbox
->focusedClient() == this);
1414 openbox
->setFocusedClient(0);
1418 void Client::focusHandler(const XFocusChangeEvent
&e
)
1421 // printf("FocusIn for 0x%lx\n", e.window);
1424 otk::EventHandler::focusHandler(e
);
1429 openbox
->setFocusedClient(this);
1433 void Client::unfocusHandler(const XFocusChangeEvent
&e
)
1436 // printf("FocusOut for 0x%lx\n", e.window);
1439 otk::EventHandler::unfocusHandler(e
);
1444 if (openbox
->focusedClient() == this)
1445 openbox
->setFocusedClient(0);
1449 void Client::configureRequestHandler(const XConfigureRequestEvent
&e
)
1452 printf("ConfigureRequest for 0x%lx\n", e
.window
);
1455 otk::EventHandler::configureRequestHandler(e
);
1457 // if we are iconic (or shaded (fvwm does this)) ignore the event
1458 if (_iconic
|| _shaded
) return;
1460 if (e
.value_mask
& CWBorderWidth
)
1461 _border_width
= e
.border_width
;
1463 // resize, then move, as specified in the EWMH section 7.7
1464 if (e
.value_mask
& (CWWidth
| CWHeight
)) {
1465 int w
= (e
.value_mask
& CWWidth
) ? e
.width
: _area
.width();
1466 int h
= (e
.value_mask
& CWHeight
) ? e
.height
: _area
.height();
1470 case NorthEastGravity
:
1474 case SouthWestGravity
:
1476 corner
= BottomLeft
;
1478 case SouthEastGravity
:
1479 corner
= BottomRight
;
1481 default: // NorthWest, Static, etc
1485 // if moving AND resizing ...
1486 if (e
.value_mask
& (CWX
| CWY
)) {
1487 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1488 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1489 internal_resize(corner
, w
, h
, false, x
, y
);
1490 } else // if JUST resizing...
1491 internal_resize(corner
, w
, h
, false);
1492 } else if (e
.value_mask
& (CWX
| CWY
)) { // if JUST moving...
1493 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1494 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1495 internal_move(x
, y
);
1498 if (e
.value_mask
& CWStackMode
) {
1502 openbox
->screen(_screen
)->lowerWindow(this);
1508 openbox
->screen(_screen
)->raiseWindow(this);
1515 void Client::unmapHandler(const XUnmapEvent
&e
)
1517 if (ignore_unmaps
) {
1519 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1526 printf("UnmapNotify for 0x%lx\n", e
.window
);
1529 otk::EventHandler::unmapHandler(e
);
1531 // this deletes us etc
1532 openbox
->screen(_screen
)->unmanageWindow(this);
1536 void Client::destroyHandler(const XDestroyWindowEvent
&e
)
1539 printf("DestroyNotify for 0x%lx\n", e
.window
);
1542 otk::EventHandler::destroyHandler(e
);
1544 // this deletes us etc
1545 openbox
->screen(_screen
)->unmanageWindow(this);
1549 void Client::reparentHandler(const XReparentEvent
&e
)
1551 // this is when the client is first taken captive in the frame
1552 if (e
.parent
== frame
->plate()) return;
1555 printf("ReparentNotify for 0x%lx\n", e
.window
);
1558 otk::EventHandler::reparentHandler(e
);
1561 This event is quite rare and is usually handled in unmapHandler.
1562 However, if the window is unmapped when the reparent event occurs,
1563 the window manager never sees it because an unmap event is not sent
1564 to an already unmapped window.
1567 // we don't want the reparent event, put it back on the stack for the X
1568 // server to deal with after we unmanage the window
1571 XPutBackEvent(**otk::display
, &ev
);
1573 // this deletes us etc
1574 openbox
->screen(_screen
)->unmanageWindow(this);
1577 void Client::mapRequestHandler(const XMapRequestEvent
&e
)
1580 printf("MapRequest for already managed 0x%lx\n", e
.window
);
1583 assert(_iconic
); // we shouldn't be able to get this unless we're iconic
1585 // move to the current desktop (uniconify)
1586 setDesktop(openbox
->screen(_screen
)->desktop());
1587 // XXX: should we focus/raise the window? (basically a net_wm_active_window)