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 // You can't shade without a titlebar
313 if (!(_decorations
& Decor_Titlebar
))
314 _functions
&= ~Func_Shade
;
316 changeAllowedActions();
319 frame
->adjustSize(); // change the decors on the frame
320 frame
->adjustPosition(); // with more/less decorations, we may need to be
326 void Client::getMwmHints()
328 unsigned long num
= MwmHints::elements
;
329 unsigned long *hints
;
331 _mwmhints
.flags
= 0; // default to none
333 if (!otk::Property::get(_window
, otk::Property::atoms
.motif_wm_hints
,
334 otk::Property::atoms
.motif_wm_hints
, &num
,
335 (unsigned long **)&hints
))
338 if (num
>= MwmHints::elements
) {
339 // retrieved the hints
340 _mwmhints
.flags
= hints
[0];
341 _mwmhints
.functions
= hints
[1];
342 _mwmhints
.decorations
= hints
[2];
349 void Client::getArea()
351 XWindowAttributes wattrib
;
354 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
355 assert(ret
!= BadWindow
);
357 _area
.setRect(wattrib
.x
, wattrib
.y
, wattrib
.width
, wattrib
.height
);
358 _border_width
= wattrib
.border_width
;
362 void Client::getState()
364 _modal
= _shaded
= _max_horz
= _max_vert
= _fullscreen
= _above
= _below
=
365 _iconic
= _skip_taskbar
= _skip_pager
= false;
367 unsigned long *state
;
368 unsigned long num
= (unsigned) -1;
370 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_state
,
371 otk::Property::atoms
.atom
, &num
, &state
)) {
372 for (unsigned long i
= 0; i
< num
; ++i
) {
373 if (state
[i
] == otk::Property::atoms
.net_wm_state_modal
)
375 else if (state
[i
] == otk::Property::atoms
.net_wm_state_shaded
)
377 else if (state
[i
] == otk::Property::atoms
.net_wm_state_hidden
)
379 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_taskbar
)
380 _skip_taskbar
= true;
381 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_pager
)
383 else if (state
[i
] == otk::Property::atoms
.net_wm_state_fullscreen
)
385 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_vert
)
387 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_horz
)
389 else if (state
[i
] == otk::Property::atoms
.net_wm_state_above
)
391 else if (state
[i
] == otk::Property::atoms
.net_wm_state_below
)
400 void Client::getShaped()
404 if (otk::display
->shape()) {
409 XShapeSelectInput(**otk::display
, _window
, ShapeNotifyMask
);
411 XShapeQueryExtents(**otk::display
, _window
, &s
, &foo
,
412 &foo
, &ufoo
, &ufoo
, &foo
, &foo
, &foo
, &ufoo
, &ufoo
);
419 void Client::calcLayer() {
422 if (_iconic
) l
= Layer_Icon
;
423 else if (_fullscreen
) l
= Layer_Fullscreen
;
424 else if (_type
== Type_Desktop
) l
= Layer_Desktop
;
425 else if (_type
== Type_Dock
) {
426 if (!_below
) l
= Layer_Top
;
427 else l
= Layer_Normal
;
429 else if (_above
) l
= Layer_Above
;
430 else if (_below
) l
= Layer_Below
;
431 else l
= Layer_Normal
;
437 if we don't have a frame, then we aren't mapped yet (and this would
440 openbox
->screen(_screen
)->raiseWindow(this);
446 void Client::updateProtocols()
451 _focus_notify
= false;
452 _delete_window
= false;
454 if (XGetWMProtocols(**otk::display
, _window
, &proto
, &num_return
)) {
455 for (int i
= 0; i
< num_return
; ++i
) {
456 if (proto
[i
] == otk::Property::atoms
.wm_delete_window
) {
457 // this means we can request the window to close
458 _delete_window
= true;
459 } else if (proto
[i
] == otk::Property::atoms
.wm_take_focus
)
460 // if this protocol is requested, then the window will be notified
461 // by the window manager whenever it receives focus
462 _focus_notify
= true;
469 void Client::updateNormalHints()
473 int oldgravity
= _gravity
;
478 _size_inc
.setPoint(1, 1);
479 _base_size
.setPoint(0, 0);
480 _min_size
.setPoint(0, 0);
481 _max_size
.setPoint(INT_MAX
, INT_MAX
);
483 // XXX: might want to cancel any interactive resizing of the window at this
486 // get the hints from the window
487 if (XGetWMNormalHints(**otk::display
, _window
, &size
, &ret
)) {
488 _positioned
= (size
.flags
& (PPosition
|USPosition
));
490 if (size
.flags
& PWinGravity
) {
491 _gravity
= size
.win_gravity
;
493 // if the client has a frame, i.e. has already been mapped and is
494 // changing its gravity
495 if (frame
&& _gravity
!= oldgravity
) {
496 // move our idea of the client's position based on its new gravity
498 frame
->frameGravity(x
, y
);
503 if (size
.flags
& PAspect
) {
504 if (size
.min_aspect
.y
) _min_ratio
= size
.min_aspect
.x
/size
.min_aspect
.y
;
505 if (size
.max_aspect
.y
) _max_ratio
= size
.max_aspect
.x
/size
.max_aspect
.y
;
508 if (size
.flags
& PMinSize
)
509 _min_size
.setPoint(size
.min_width
, size
.min_height
);
511 if (size
.flags
& PMaxSize
)
512 _max_size
.setPoint(size
.max_width
, size
.max_height
);
514 if (size
.flags
& PBaseSize
)
515 _base_size
.setPoint(size
.base_width
, size
.base_height
);
517 if (size
.flags
& PResizeInc
)
518 _size_inc
.setPoint(size
.width_inc
, size
.height_inc
);
523 void Client::updateWMHints(bool initstate
)
527 // assume a window takes input if it doesnt specify
531 if ((hints
= XGetWMHints(**otk::display
, _window
)) != NULL
) {
532 if (hints
->flags
& InputHint
)
533 _can_focus
= hints
->input
;
535 // only do this when initstate is true!
536 if (initstate
&& (hints
->flags
& StateHint
))
537 _iconic
= hints
->initial_state
== IconicState
;
539 if (hints
->flags
& XUrgencyHint
)
542 if (hints
->flags
& WindowGroupHint
) {
543 if (hints
->window_group
!= _group
) {
544 // XXX: remove from the old group if there was one
545 _group
= hints
->window_group
;
546 // XXX: do stuff with the group
557 printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
558 (long)_window
, _urgent
? "ON" : "OFF");
560 // fire the urgent callback if we're mapped, otherwise, wait until after
568 void Client::updateTitle()
573 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_name
,
574 otk::Property::utf8
, &_title
)) {
576 otk::Property::get(_window
, otk::Property::atoms
.wm_name
,
577 otk::Property::ascii
, &_title
);
581 _title
= _("Unnamed Window");
584 frame
->setTitle(_title
);
588 void Client::updateIconTitle()
593 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_icon_name
,
594 otk::Property::utf8
, &_icon_title
)) {
596 otk::Property::get(_window
, otk::Property::atoms
.wm_icon_name
,
597 otk::Property::ascii
, &_icon_title
);
601 _icon_title
= _("Unnamed Window");
605 void Client::updateClass()
608 _app_name
= _app_class
= _role
= "";
610 otk::Property::StringVect v
;
611 unsigned long num
= 2;
613 if (otk::Property::get(_window
, otk::Property::atoms
.wm_class
,
614 otk::Property::ascii
, &num
, &v
)) {
615 if (num
> 0) _app_name
= v
[0].c_str();
616 if (num
> 1) _app_class
= v
[1].c_str();
621 if (otk::Property::get(_window
, otk::Property::atoms
.wm_window_role
,
622 otk::Property::ascii
, &num
, &v
)) {
623 if (num
> 0) _role
= v
[0].c_str();
628 void Client::updateStrut()
630 unsigned long num
= 4;
632 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_strut
,
633 otk::Property::atoms
.cardinal
, &num
, &data
))
637 _strut
.left
= data
[0];
638 _strut
.right
= data
[1];
639 _strut
.top
= data
[2];
640 _strut
.bottom
= data
[3];
642 // updating here is pointless while we're being mapped cuz we're not in
643 // the screen's client list yet
645 openbox
->screen(_screen
)->updateStrut();
652 void Client::updateTransientFor()
657 if (XGetTransientForHint(**otk::display
, _window
, &t
) &&
658 t
!= _window
) { // cant be transient to itself!
659 c
= openbox
->findClient(t
);
660 assert(c
!= this); // if this happens then we need to check for it
662 if (!c
/*XXX: && _group*/) {
663 // not transient to a client, see if it is transient for a group
664 if (//t == _group->leader() ||
666 t
== otk::display
->screenInfo(_screen
)->rootWindow()) {
667 // window is a transient for its group!
668 // XXX: for now this is treated as non-transient.
669 // this needs to be fixed!
674 // if anything has changed...
675 if (c
!= _transient_for
) {
677 _transient_for
->_transients
.remove(this); // remove from old parent
680 _transient_for
->_transients
.push_back(this); // add to new parent
682 // XXX: change decor status?
687 void Client::propertyHandler(const XPropertyEvent
&e
)
689 otk::EventHandler::propertyHandler(e
);
691 // validate cuz we query stuff off the client here
692 if (!validate()) return;
694 // compress changes to a single property into a single change
696 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
697 // XXX: it would be nice to compress ALL changes to a property, not just
698 // changes in a row without other props between.
699 if (ce
.xproperty
.atom
!= e
.atom
) {
700 XPutBackEvent(**otk::display
, &ce
);
705 if (e
.atom
== XA_WM_NORMAL_HINTS
) {
707 setupDecorAndFunctions(); // normal hints can make a window non-resizable
708 } else if (e
.atom
== XA_WM_HINTS
)
710 else if (e
.atom
== XA_WM_TRANSIENT_FOR
) {
711 updateTransientFor();
713 calcLayer(); // type may have changed, so update the layer
714 setupDecorAndFunctions();
716 else if (e
.atom
== otk::Property::atoms
.net_wm_name
||
717 e
.atom
== otk::Property::atoms
.wm_name
)
719 else if (e
.atom
== otk::Property::atoms
.net_wm_icon_name
||
720 e
.atom
== otk::Property::atoms
.wm_icon_name
)
722 else if (e
.atom
== otk::Property::atoms
.wm_class
)
724 else if (e
.atom
== otk::Property::atoms
.wm_protocols
) {
726 setupDecorAndFunctions();
728 else if (e
.atom
== otk::Property::atoms
.net_wm_strut
)
733 void Client::setWMState(long state
)
735 if (state
== _wmstate
) return; // no change
739 setDesktop(ICONIC_DESKTOP
);
742 setDesktop(openbox
->screen(_screen
)->desktop());
748 void Client::setDesktop(long target
)
750 if (target
== _desktop
) return;
752 printf("Setting desktop %ld\n", target
);
754 if (!(target
>= 0 || target
== (signed)0xffffffff ||
755 target
== ICONIC_DESKTOP
))
760 // set the desktop hint, but not if we're iconifying
761 if (_desktop
!= ICONIC_DESKTOP
)
762 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
763 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
765 // 'move' the window to the new desktop
766 if (_desktop
== openbox
->screen(_screen
)->desktop() ||
767 _desktop
== (signed)0xffffffff)
772 // Handle Iconic state. Iconic state is maintained by the client being a
773 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
774 // uniconifying happen.
775 bool i
= _desktop
== ICONIC_DESKTOP
;
776 if (i
!= _iconic
) { // has the state changed?
779 _wmstate
= IconicState
;
781 // we unmap the client itself so that we can get MapRequest events, and
782 // because the ICCCM tells us to!
783 XUnmapWindow(**otk::display
, _window
);
785 _wmstate
= NormalState
;
786 XMapWindow(**otk::display
, _window
);
791 frame
->adjustState();
795 void Client::setState(StateAction action
, long data1
, long data2
)
797 bool shadestate
= _shaded
;
798 bool fsstate
= _fullscreen
;
800 if (!(action
== State_Add
|| action
== State_Remove
||
801 action
== State_Toggle
))
802 return; // an invalid action was passed to the client message, ignore it
804 for (int i
= 0; i
< 2; ++i
) {
805 Atom state
= i
== 0 ? data1
: data2
;
807 if (! state
) continue;
809 // if toggling, then pick whether we're adding or removing
810 if (action
== State_Toggle
) {
811 if (state
== otk::Property::atoms
.net_wm_state_modal
)
812 action
= _modal
? State_Remove
: State_Add
;
813 else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
)
814 action
= _max_vert
? State_Remove
: State_Add
;
815 else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
)
816 action
= _max_horz
? State_Remove
: State_Add
;
817 else if (state
== otk::Property::atoms
.net_wm_state_shaded
)
818 action
= _shaded
? State_Remove
: State_Add
;
819 else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
)
820 action
= _skip_taskbar
? State_Remove
: State_Add
;
821 else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
)
822 action
= _skip_pager
? State_Remove
: State_Add
;
823 else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
)
824 action
= _fullscreen
? State_Remove
: State_Add
;
825 else if (state
== otk::Property::atoms
.net_wm_state_above
)
826 action
= _above
? State_Remove
: State_Add
;
827 else if (state
== otk::Property::atoms
.net_wm_state_below
)
828 action
= _below
? State_Remove
: State_Add
;
831 if (action
== State_Add
) {
832 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
833 if (_modal
) continue;
835 // XXX: give it focus if another window has focus that shouldnt now
836 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
837 if (_max_vert
) continue;
839 // XXX: resize the window etc
840 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
841 if (_max_horz
) continue;
843 // XXX: resize the window etc
844 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
846 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
847 _skip_taskbar
= true;
848 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
850 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
852 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
853 if (_above
) continue;
855 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
856 if (_below
) continue;
860 } else { // action == State_Remove
861 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
862 if (!_modal
) continue;
864 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
865 if (!_max_vert
) continue;
867 // XXX: resize the window etc
868 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
869 if (!_max_horz
) continue;
871 // XXX: resize the window etc
872 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
874 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
875 _skip_taskbar
= false;
876 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
878 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
880 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
881 if (!_above
) continue;
883 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
884 if (!_below
) continue;
889 // change fullscreen state before shading, as it will affect if the window
891 if (fsstate
!= _fullscreen
)
893 if (shadestate
!= _shaded
)
899 void Client::toggleClientBorder(bool addborder
)
901 // adjust our idea of where the client is, based on its border. When the
902 // border is removed, the client should now be considered to be in a
903 // different position.
904 // when re-adding the border to the client, the same operation needs to be
906 int oldx
= _area
.x(), oldy
= _area
.y();
907 int x
= oldx
, y
= oldy
;
910 case NorthWestGravity
:
912 case SouthWestGravity
:
914 case NorthEastGravity
:
916 case SouthEastGravity
:
917 if (addborder
) x
-= _border_width
* 2;
918 else x
+= _border_width
* 2;
925 if (addborder
) x
-= _border_width
;
926 else x
+= _border_width
;
931 case NorthWestGravity
:
933 case NorthEastGravity
:
935 case SouthWestGravity
:
937 case SouthEastGravity
:
938 if (addborder
) y
-= _border_width
* 2;
939 else y
+= _border_width
* 2;
946 if (addborder
) y
-= _border_width
;
947 else y
+= _border_width
;
953 XSetWindowBorderWidth(**otk::display
, _window
, _border_width
);
955 // move the client so it is back it the right spot _with_ its border!
956 if (x
!= oldx
|| y
!= oldy
)
957 XMoveWindow(**otk::display
, _window
, x
, y
);
959 XSetWindowBorderWidth(**otk::display
, _window
, 0);
963 void Client::clientMessageHandler(const XClientMessageEvent
&e
)
965 otk::EventHandler::clientMessageHandler(e
);
967 // validate cuz we query stuff off the client here
968 if (!validate()) return;
970 if (e
.format
!= 32) return;
972 if (e
.message_type
== otk::Property::atoms
.wm_change_state
) {
973 // compress changes into a single change
974 bool compress
= false;
976 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
977 // XXX: it would be nice to compress ALL messages of a type, not just
978 // messages in a row without other message types between.
979 if (ce
.xclient
.message_type
!= e
.message_type
) {
980 XPutBackEvent(**otk::display
, &ce
);
986 setWMState(ce
.xclient
.data
.l
[0]); // use the found event
988 setWMState(e
.data
.l
[0]); // use the original event
989 } else if (e
.message_type
== otk::Property::atoms
.net_wm_desktop
) {
990 // compress changes into a single change
991 bool compress
= false;
993 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
994 // XXX: it would be nice to compress ALL messages of a type, not just
995 // messages in a row without other message types between.
996 if (ce
.xclient
.message_type
!= e
.message_type
) {
997 XPutBackEvent(**otk::display
, &ce
);
1003 setDesktop(e
.data
.l
[0]); // use the found event
1005 setDesktop(e
.data
.l
[0]); // use the original event
1006 } else if (e
.message_type
== otk::Property::atoms
.net_wm_state
) {
1007 // can't compress these
1009 printf("net_wm_state %s %ld %ld for 0x%lx\n",
1010 (e
.data
.l
[0] == 0 ? "Remove" : e
.data
.l
[0] == 1 ? "Add" :
1011 e
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
1012 e
.data
.l
[1], e
.data
.l
[2], _window
);
1014 setState((StateAction
)e
.data
.l
[0], e
.data
.l
[1], e
.data
.l
[2]);
1015 } else if (e
.message_type
== otk::Property::atoms
.net_close_window
) {
1017 printf("net_close_window for 0x%lx\n", _window
);
1020 } else if (e
.message_type
== otk::Property::atoms
.net_active_window
) {
1022 printf("net_active_window for 0x%lx\n", _window
);
1025 setDesktop(openbox
->screen(_screen
)->desktop());
1030 openbox
->screen(_screen
)->raiseWindow(this);
1036 void Client::shapeHandler(const XShapeEvent
&e
)
1038 otk::EventHandler::shapeHandler(e
);
1040 if (e
.kind
== ShapeBounding
) {
1042 frame
->adjustShape();
1048 void Client::resize(Corner anchor
, int w
, int h
)
1050 if (!(_functions
& Func_Resize
)) return;
1051 internal_resize(anchor
, w
, h
);
1055 void Client::internal_resize(Corner anchor
, int w
, int h
, bool user
,
1058 w
-= _base_size
.x();
1059 h
-= _base_size
.y();
1061 // for interactive resizing. have to move half an increment in each
1063 w
+= _size_inc
.x() / 2;
1064 h
+= _size_inc
.y() / 2;
1067 // if this is a user-requested resize, then check against min/max sizes
1068 // and aspect ratios
1070 // smaller than min size or bigger than max size?
1071 if (w
< _min_size
.x()) w
= _min_size
.x();
1072 else if (w
> _max_size
.x()) w
= _max_size
.x();
1073 if (h
< _min_size
.y()) h
= _min_size
.y();
1074 else if (h
> _max_size
.y()) h
= _max_size
.y();
1076 // adjust the height ot match the width for the aspect ratios
1078 if (h
* _min_ratio
> w
) h
= static_cast<int>(w
/ _min_ratio
);
1080 if (h
* _max_ratio
< w
) h
= static_cast<int>(w
/ _max_ratio
);
1083 // keep to the increments
1087 // you cannot resize to nothing
1091 // store the logical size
1092 _logical_size
.setPoint(w
, h
);
1097 w
+= _base_size
.x();
1098 h
+= _base_size
.y();
1100 if (x
== INT_MIN
|| y
== INT_MIN
) {
1107 x
-= w
- _area
.width();
1110 y
-= h
- _area
.height();
1113 x
-= w
- _area
.width();
1114 y
-= h
- _area
.height();
1119 _area
.setSize(w
, h
);
1121 XResizeWindow(**otk::display
, _window
, w
, h
);
1123 // resize the frame to match the request
1124 frame
->adjustSize();
1125 internal_move(x
, y
);
1129 void Client::move(int x
, int y
)
1131 if (!(_functions
& Func_Move
)) return;
1132 internal_move(x
, y
);
1136 void Client::internal_move(int x
, int y
)
1140 // move the frame to be in the requested position
1141 if (frame
) { // this can be called while mapping, before frame exists
1142 frame
->adjustPosition();
1144 // send synthetic configure notify (we don't need to if we aren't mapped
1147 event
.type
= ConfigureNotify
;
1148 event
.xconfigure
.display
= **otk::display
;
1149 event
.xconfigure
.event
= _window
;
1150 event
.xconfigure
.window
= _window
;
1152 // root window coords with border in mind
1153 event
.xconfigure
.x
= x
- _border_width
+ frame
->size().left
;
1154 event
.xconfigure
.y
= y
- _border_width
+ frame
->size().top
;
1156 event
.xconfigure
.width
= _area
.width();
1157 event
.xconfigure
.height
= _area
.height();
1158 event
.xconfigure
.border_width
= _border_width
;
1159 event
.xconfigure
.above
= frame
->plate();
1160 event
.xconfigure
.override_redirect
= False
;
1161 XSendEvent(event
.xconfigure
.display
, event
.xconfigure
.window
, False
,
1162 StructureNotifyMask
, &event
);
1164 printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1165 event
.xconfigure
.x
, event
.xconfigure
.y
, event
.xconfigure
.width
,
1166 event
.xconfigure
.height
, event
.xconfigure
.window
);
1172 void Client::close()
1176 if (!(_functions
& Func_Close
)) return;
1178 // XXX: itd be cool to do timeouts and shit here for killing the client's
1180 // like... if the window is around after 5 seconds, then the close button
1181 // turns a nice red, and if this function is called again, the client is
1182 // explicitly killed.
1184 ce
.xclient
.type
= ClientMessage
;
1185 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1186 ce
.xclient
.display
= **otk::display
;
1187 ce
.xclient
.window
= _window
;
1188 ce
.xclient
.format
= 32;
1189 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_delete_window
;
1190 ce
.xclient
.data
.l
[1] = CurrentTime
;
1191 ce
.xclient
.data
.l
[2] = 0l;
1192 ce
.xclient
.data
.l
[3] = 0l;
1193 ce
.xclient
.data
.l
[4] = 0l;
1194 XSendEvent(**otk::display
, _window
, false, NoEventMask
, &ce
);
1198 void Client::changeState()
1200 unsigned long state
[2];
1201 state
[0] = _wmstate
;
1203 otk::Property::set(_window
, otk::Property::atoms
.wm_state
,
1204 otk::Property::atoms
.wm_state
, state
, 2);
1209 netstate
[num
++] = otk::Property::atoms
.net_wm_state_modal
;
1211 netstate
[num
++] = otk::Property::atoms
.net_wm_state_shaded
;
1213 netstate
[num
++] = otk::Property::atoms
.net_wm_state_hidden
;
1215 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_taskbar
;
1217 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_pager
;
1219 netstate
[num
++] = otk::Property::atoms
.net_wm_state_fullscreen
;
1221 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_vert
;
1223 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_horz
;
1225 netstate
[num
++] = otk::Property::atoms
.net_wm_state_above
;
1227 netstate
[num
++] = otk::Property::atoms
.net_wm_state_below
;
1228 otk::Property::set(_window
, otk::Property::atoms
.net_wm_state
,
1229 otk::Property::atoms
.atom
, netstate
, num
);
1234 frame
->adjustState();
1238 void Client::changeAllowedActions(void)
1243 actions
[num
++] = otk::Property::atoms
.net_wm_action_change_desktop
;
1245 if (_functions
& Func_Shade
)
1246 actions
[num
++] = otk::Property::atoms
.net_wm_action_shade
;
1247 if (_functions
& Func_Close
)
1248 actions
[num
++] = otk::Property::atoms
.net_wm_action_close
;
1249 if (_functions
& Func_Move
)
1250 actions
[num
++] = otk::Property::atoms
.net_wm_action_move
;
1251 if (_functions
& Func_Iconify
)
1252 actions
[num
++] = otk::Property::atoms
.net_wm_action_minimize
;
1253 if (_functions
& Func_Resize
)
1254 actions
[num
++] = otk::Property::atoms
.net_wm_action_resize
;
1255 if (_functions
& Func_Fullscreen
)
1256 actions
[num
++] = otk::Property::atoms
.net_wm_action_fullscreen
;
1257 if (_functions
& Func_Maximize
) {
1258 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_horz
;
1259 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_vert
;
1262 otk::Property::set(_window
, otk::Property::atoms
.net_wm_allowed_actions
,
1263 otk::Property::atoms
.atom
, actions
, num
);
1267 void Client::applyStartupState()
1269 // these are in a carefully crafted order..
1273 setDesktop(ICONIC_DESKTOP
);
1276 _fullscreen
= false;
1286 if (_max_vert
); // XXX: incomplete
1287 if (_max_horz
); // XXX: incomplete
1289 if (_skip_taskbar
); // nothing to do for this
1290 if (_skip_pager
); // nothing to do for this
1291 if (_modal
); // nothing to do for this
1292 if (_above
); // nothing to do for this
1293 if (_below
); // nothing to do for this
1297 void Client::fireUrgent()
1299 // call the python UrgentWindow callbacks
1300 EventData
data(_screen
, this, EventAction::UrgentWindow
, 0);
1301 openbox
->bindings()->fireEvent(&data
);
1305 void Client::shade(bool shade
)
1307 if (!(_functions
& Func_Shade
) || // can't
1308 _shaded
== shade
) return; // already done
1310 // when we're iconic, don't change the wmstate
1312 _wmstate
= shade
? IconicState
: NormalState
;
1315 frame
->adjustSize();
1319 void Client::fullscreen(bool fs
)
1321 static FunctionFlags saved_func
;
1322 static DecorationFlags saved_decor
;
1323 static otk::Rect saved_area
;
1324 static otk::Point saved_logical_size
;
1326 if (!(_functions
& Func_Fullscreen
) || // can't
1327 _fullscreen
== fs
) return; // already done
1330 changeState(); // change the state hints on the client
1333 // save the functions and remove them
1334 saved_func
= _functions
;
1335 _functions
= _functions
& (Func_Close
| Func_Fullscreen
| Func_Iconify
);
1336 // save the decorations and remove them
1337 saved_decor
= _decorations
;
1339 // save the area and adjust it (we don't call internal resize here for
1340 // constraints on the size, etc, we just make it fullscreen).
1342 const otk::ScreenInfo
*info
= otk::display
->screenInfo(_screen
);
1343 _area
.setRect(0, 0, info
->width(), info
->height());
1344 saved_logical_size
= _logical_size
;
1345 _logical_size
.setPoint((info
->width() - _base_size
.x()) / _size_inc
.x(),
1346 (info
->height() - _base_size
.y()) / _size_inc
.y());
1348 _functions
= saved_func
;
1349 _decorations
= saved_decor
;
1351 _logical_size
= saved_logical_size
;
1354 changeAllowedActions(); // based on the new _functions
1356 frame
->adjustSize(); // drop/replace the decor's and resize
1357 frame
->adjustPosition(); // get (back) in position!
1359 // raise (back) into our stacking layer
1360 openbox
->screen(_screen
)->raiseWindow(this);
1362 // try focus us when we go into fullscreen mode
1367 void Client::disableDecorations(DecorationFlags flags
)
1369 _disabled_decorations
= flags
;
1370 setupDecorAndFunctions();
1374 bool Client::focus()
1376 // won't try focus if the client doesn't want it, or if the window isn't
1377 // visible on the screen
1378 if (!(frame
->isVisible() && (_can_focus
|| _focus_notify
))) return false;
1380 if (_focused
) return true;
1382 // do a check to see if the window has already been unmapped or destroyed
1383 // do this intelligently while watching out for unmaps we've generated
1384 // (ignore_unmaps > 0)
1386 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &ev
)) {
1387 XPutBackEvent(**otk::display
, &ev
);
1390 while (XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &ev
)) {
1391 if (ignore_unmaps
) {
1392 unmapHandler(ev
.xunmap
);
1394 XPutBackEvent(**otk::display
, &ev
);
1400 XSetInputFocus(**otk::display
, _window
,
1401 RevertToNone
, CurrentTime
);
1403 if (_focus_notify
) {
1405 ce
.xclient
.type
= ClientMessage
;
1406 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1407 ce
.xclient
.display
= **otk::display
;
1408 ce
.xclient
.window
= _window
;
1409 ce
.xclient
.format
= 32;
1410 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_take_focus
;
1411 ce
.xclient
.data
.l
[1] = openbox
->lastTime();
1412 ce
.xclient
.data
.l
[2] = 0l;
1413 ce
.xclient
.data
.l
[3] = 0l;
1414 ce
.xclient
.data
.l
[4] = 0l;
1415 XSendEvent(**otk::display
, _window
, False
, NoEventMask
, &ce
);
1418 XSync(**otk::display
, False
);
1423 void Client::unfocus() const
1425 if (!_focused
) return;
1427 assert(openbox
->focusedClient() == this);
1428 openbox
->setFocusedClient(0);
1432 void Client::focusHandler(const XFocusChangeEvent
&e
)
1435 // printf("FocusIn for 0x%lx\n", e.window);
1438 otk::EventHandler::focusHandler(e
);
1443 openbox
->setFocusedClient(this);
1447 void Client::unfocusHandler(const XFocusChangeEvent
&e
)
1450 // printf("FocusOut for 0x%lx\n", e.window);
1453 otk::EventHandler::unfocusHandler(e
);
1458 if (openbox
->focusedClient() == this)
1459 openbox
->setFocusedClient(0);
1463 void Client::configureRequestHandler(const XConfigureRequestEvent
&e
)
1466 printf("ConfigureRequest for 0x%lx\n", e
.window
);
1469 otk::EventHandler::configureRequestHandler(e
);
1471 // if we are iconic (or shaded (fvwm does this)) ignore the event
1472 if (_iconic
|| _shaded
) return;
1474 if (e
.value_mask
& CWBorderWidth
)
1475 _border_width
= e
.border_width
;
1477 // resize, then move, as specified in the EWMH section 7.7
1478 if (e
.value_mask
& (CWWidth
| CWHeight
)) {
1479 int w
= (e
.value_mask
& CWWidth
) ? e
.width
: _area
.width();
1480 int h
= (e
.value_mask
& CWHeight
) ? e
.height
: _area
.height();
1484 case NorthEastGravity
:
1488 case SouthWestGravity
:
1490 corner
= BottomLeft
;
1492 case SouthEastGravity
:
1493 corner
= BottomRight
;
1495 default: // NorthWest, Static, etc
1499 // if moving AND resizing ...
1500 if (e
.value_mask
& (CWX
| CWY
)) {
1501 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1502 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1503 internal_resize(corner
, w
, h
, false, x
, y
);
1504 } else // if JUST resizing...
1505 internal_resize(corner
, w
, h
, false);
1506 } else if (e
.value_mask
& (CWX
| CWY
)) { // if JUST moving...
1507 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1508 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1509 internal_move(x
, y
);
1512 if (e
.value_mask
& CWStackMode
) {
1516 openbox
->screen(_screen
)->lowerWindow(this);
1522 openbox
->screen(_screen
)->raiseWindow(this);
1529 void Client::unmapHandler(const XUnmapEvent
&e
)
1531 if (ignore_unmaps
) {
1533 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1540 printf("UnmapNotify for 0x%lx\n", e
.window
);
1543 otk::EventHandler::unmapHandler(e
);
1545 // this deletes us etc
1546 openbox
->screen(_screen
)->unmanageWindow(this);
1550 void Client::destroyHandler(const XDestroyWindowEvent
&e
)
1553 printf("DestroyNotify for 0x%lx\n", e
.window
);
1556 otk::EventHandler::destroyHandler(e
);
1558 // this deletes us etc
1559 openbox
->screen(_screen
)->unmanageWindow(this);
1563 void Client::reparentHandler(const XReparentEvent
&e
)
1565 // this is when the client is first taken captive in the frame
1566 if (e
.parent
== frame
->plate()) return;
1569 printf("ReparentNotify for 0x%lx\n", e
.window
);
1572 otk::EventHandler::reparentHandler(e
);
1575 This event is quite rare and is usually handled in unmapHandler.
1576 However, if the window is unmapped when the reparent event occurs,
1577 the window manager never sees it because an unmap event is not sent
1578 to an already unmapped window.
1581 // we don't want the reparent event, put it back on the stack for the X
1582 // server to deal with after we unmanage the window
1585 XPutBackEvent(**otk::display
, &ev
);
1587 // this deletes us etc
1588 openbox
->screen(_screen
)->unmanageWindow(this);
1591 void Client::mapRequestHandler(const XMapRequestEvent
&e
)
1594 printf("MapRequest for already managed 0x%lx\n", e
.window
);
1597 assert(_iconic
); // we shouldn't be able to get this unless we're iconic
1599 // move to the current desktop (uniconify)
1600 setDesktop(openbox
->screen(_screen
)->desktop());
1601 // XXX: should we focus/raise the window? (basically a net_wm_active_window)