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
67 // got the type, the mwmhints, and the protocols, so we're ready to set up
68 // the decorations/functions
69 setupDecorAndFunctions();
71 getGravity(); // get the attribute gravity
72 updateNormalHints(); // this may override the attribute gravity
73 // also get the initial_state and set _iconic if we aren't "starting"
74 // when we're "starting" that means we should use whatever state was already
75 // on the window over the initial map state, because it was already mapped
76 updateWMHints(openbox
->state() != Openbox::State_Starting
);
82 // this makes sure that these windows appear on all desktops
83 if (/*_type == Type_Dock ||*/ _type
== Type_Desktop
)
84 _desktop
= 0xffffffff;
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 bool Client::validate() const
122 XSync(**otk::display
, false); // get all events on the server
125 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &e
) ||
126 XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &e
)) {
127 XPutBackEvent(**otk::display
, &e
);
135 void Client::getGravity()
137 XWindowAttributes wattrib
;
140 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
141 assert(ret
!= BadWindow
);
142 _gravity
= wattrib
.win_gravity
;
146 void Client::getDesktop()
148 // defaults to the current desktop
149 _desktop
= openbox
->screen(_screen
)->desktop();
151 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_desktop
,
152 otk::Property::atoms
.cardinal
,
153 (long unsigned*)&_desktop
)) {
155 // printf("Window requested desktop: %ld\n", _desktop);
161 void Client::getType()
163 _type
= (WindowType
) -1;
166 unsigned long num
= (unsigned) -1;
167 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_window_type
,
168 otk::Property::atoms
.atom
, &num
, &val
)) {
169 // use the first value that we know about in the array
170 for (unsigned long i
= 0; i
< num
; ++i
) {
171 if (val
[i
] == otk::Property::atoms
.net_wm_window_type_desktop
)
172 _type
= Type_Desktop
;
173 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dock
)
175 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_toolbar
)
176 _type
= Type_Toolbar
;
177 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_menu
)
179 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_utility
)
180 _type
= Type_Utility
;
181 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_splash
)
183 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_dialog
)
185 else if (val
[i
] == otk::Property::atoms
.net_wm_window_type_normal
)
187 // XXX: make this work again
188 // else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override)
189 // mwm_decorations = 0; // prevent this window from getting any decor
190 if (_type
!= (WindowType
) -1)
191 break; // grab the first known type
196 if (_type
== (WindowType
) -1) {
198 * the window type hint was not set, which means we either classify ourself
199 * as a normal window or a dialog, depending on if we are a transient.
209 void Client::setupDecorAndFunctions()
211 // start with everything (cept fullscreen)
212 _decorations
= Decor_Titlebar
| Decor_Handle
| Decor_Border
|
213 Decor_AllDesktops
| Decor_Iconify
| Decor_Maximize
;
214 _functions
= Func_Resize
| Func_Move
| Func_Iconify
| Func_Maximize
|
216 if (_delete_window
) {
217 _decorations
|= Decor_Close
;
218 _functions
|= Func_Close
;
221 if (_min_size
.x() > _max_size
.x() || _min_size
.y() > _max_size
.y()) {
222 _decorations
&= ~Decor_Maximize
;
223 _functions
&= ~(Func_Resize
| Func_Maximize
);
228 // normal windows retain all of the possible decorations and
229 // functionality, and are the only windows that you can fullscreen
230 _functions
|= Func_Fullscreen
;
233 // dialogs cannot be maximized
234 _decorations
&= ~Decor_Maximize
;
235 _functions
&= ~Func_Maximize
;
241 // these windows get less functionality
242 _decorations
&= ~(Decor_Iconify
| Decor_Handle
);
243 _functions
&= ~(Func_Iconify
| Func_Resize
);
249 // none of these windows are manipulated by the window manager
255 // Mwm Hints are applied subtractively to what has already been chosen for
256 // decor and functionality
257 if (_mwmhints
.flags
& MwmFlag_Decorations
) {
258 if (! (_mwmhints
.decorations
& MwmDecor_All
)) {
259 if (! (_mwmhints
.decorations
& MwmDecor_Border
))
260 _decorations
&= ~Decor_Border
;
261 if (! (_mwmhints
.decorations
& MwmDecor_Handle
))
262 _decorations
&= ~Decor_Handle
;
263 if (! (_mwmhints
.decorations
& MwmDecor_Title
)) {
264 _decorations
&= ~Decor_Titlebar
;
265 // if we don't have a titlebar, then we cannot shade!
266 _functions
&= ~Func_Shade
;
268 if (! (_mwmhints
.decorations
& MwmDecor_Iconify
))
269 _decorations
&= ~Decor_Iconify
;
270 if (! (_mwmhints
.decorations
& MwmDecor_Maximize
))
271 _decorations
&= ~Decor_Maximize
;
275 if (_mwmhints
.flags
& MwmFlag_Functions
) {
276 if (! (_mwmhints
.functions
& MwmFunc_All
)) {
277 if (! (_mwmhints
.functions
& MwmFunc_Resize
))
278 _functions
&= ~Func_Resize
;
279 if (! (_mwmhints
.functions
& MwmFunc_Move
))
280 _functions
&= ~Func_Move
;
281 if (! (_mwmhints
.functions
& MwmFunc_Iconify
))
282 _functions
&= ~Func_Iconify
;
283 if (! (_mwmhints
.functions
& MwmFunc_Maximize
))
284 _functions
&= ~Func_Maximize
;
285 // dont let mwm hints kill the close button
286 //if (! (_mwmhints.functions & MwmFunc_Close))
287 // _functions &= ~Func_Close;
291 // finally, user specified disabled decorations are applied to subtract
293 if (_disabled_decorations
& Decor_Titlebar
)
294 _decorations
&= ~Decor_Titlebar
;
295 if (_disabled_decorations
& Decor_Handle
)
296 _decorations
&= ~Decor_Handle
;
297 if (_disabled_decorations
& Decor_Border
)
298 _decorations
&= ~Decor_Border
;
299 if (_disabled_decorations
& Decor_Iconify
)
300 _decorations
&= ~Decor_Iconify
;
301 if (_disabled_decorations
& Decor_Maximize
)
302 _decorations
&= ~Decor_Maximize
;
303 if (_disabled_decorations
& Decor_AllDesktops
)
304 _decorations
&= ~Decor_AllDesktops
;
305 if (_disabled_decorations
& Decor_Close
)
306 _decorations
&= ~Decor_Close
;
308 changeAllowedActions();
312 void Client::getMwmHints()
314 unsigned long num
= MwmHints::elements
;
315 unsigned long *hints
;
317 _mwmhints
.flags
= 0; // default to none
319 if (!otk::Property::get(_window
, otk::Property::atoms
.motif_wm_hints
,
320 otk::Property::atoms
.motif_wm_hints
, &num
,
321 (unsigned long **)&hints
))
324 if (num
>= MwmHints::elements
) {
325 // retrieved the hints
326 _mwmhints
.flags
= hints
[0];
327 _mwmhints
.functions
= hints
[1];
328 _mwmhints
.decorations
= hints
[2];
335 void Client::getArea()
337 XWindowAttributes wattrib
;
340 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
341 assert(ret
!= BadWindow
);
343 _area
.setRect(wattrib
.x
, wattrib
.y
, wattrib
.width
, wattrib
.height
);
344 _border_width
= wattrib
.border_width
;
348 void Client::getState()
350 _modal
= _shaded
= _max_horz
= _max_vert
= _fullscreen
= _above
= _below
=
351 _iconic
= _skip_taskbar
= _skip_pager
= false;
353 unsigned long *state
;
354 unsigned long num
= (unsigned) -1;
356 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_state
,
357 otk::Property::atoms
.atom
, &num
, &state
)) {
358 for (unsigned long i
= 0; i
< num
; ++i
) {
359 if (state
[i
] == otk::Property::atoms
.net_wm_state_modal
)
361 else if (state
[i
] == otk::Property::atoms
.net_wm_state_shaded
)
363 else if (state
[i
] == otk::Property::atoms
.net_wm_state_hidden
)
365 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_taskbar
)
366 _skip_taskbar
= true;
367 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_pager
)
369 else if (state
[i
] == otk::Property::atoms
.net_wm_state_fullscreen
)
371 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_vert
)
373 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_horz
)
375 else if (state
[i
] == otk::Property::atoms
.net_wm_state_above
)
377 else if (state
[i
] == otk::Property::atoms
.net_wm_state_below
)
386 void Client::getShaped()
390 if (otk::display
->shape()) {
395 XShapeSelectInput(**otk::display
, _window
, ShapeNotifyMask
);
397 XShapeQueryExtents(**otk::display
, _window
, &s
, &foo
,
398 &foo
, &ufoo
, &ufoo
, &foo
, &foo
, &foo
, &ufoo
, &ufoo
);
405 void Client::calcLayer() {
408 if (_iconic
) l
= Layer_Icon
;
409 else if (_fullscreen
) l
= Layer_Fullscreen
;
410 else if (_type
== Type_Desktop
) l
= Layer_Desktop
;
411 else if (_type
== Type_Dock
) {
412 if (!_below
) l
= Layer_Top
;
413 else l
= Layer_Normal
;
415 else if (_above
) l
= Layer_Above
;
416 else if (_below
) l
= Layer_Below
;
417 else l
= Layer_Normal
;
423 if we don't have a frame, then we aren't mapped yet (and this would
426 openbox
->screen(_screen
)->raiseWindow(this);
432 void Client::updateProtocols()
437 _focus_notify
= false;
438 _delete_window
= false;
440 if (XGetWMProtocols(**otk::display
, _window
, &proto
, &num_return
)) {
441 for (int i
= 0; i
< num_return
; ++i
) {
442 if (proto
[i
] == otk::Property::atoms
.wm_delete_window
) {
443 // this means we can request the window to close
444 _delete_window
= true;
445 } else if (proto
[i
] == otk::Property::atoms
.wm_take_focus
)
446 // if this protocol is requested, then the window will be notified
447 // by the window manager whenever it receives focus
448 _focus_notify
= true;
455 void Client::updateNormalHints()
459 int oldgravity
= _gravity
;
464 _size_inc
.setPoint(1, 1);
465 _base_size
.setPoint(0, 0);
466 _min_size
.setPoint(0, 0);
467 _max_size
.setPoint(INT_MAX
, INT_MAX
);
469 // XXX: might want to cancel any interactive resizing of the window at this
472 // get the hints from the window
473 if (XGetWMNormalHints(**otk::display
, _window
, &size
, &ret
)) {
474 _positioned
= (size
.flags
& (PPosition
|USPosition
));
476 if (size
.flags
& PWinGravity
) {
477 _gravity
= size
.win_gravity
;
479 // if the client has a frame, i.e. has already been mapped and is
480 // changing its gravity
481 if (frame
&& _gravity
!= oldgravity
) {
482 // move our idea of the client's position based on its new gravity
484 frame
->frameGravity(x
, y
);
489 if (size
.flags
& PAspect
) {
490 if (size
.min_aspect
.y
) _min_ratio
= size
.min_aspect
.x
/size
.min_aspect
.y
;
491 if (size
.max_aspect
.y
) _max_ratio
= size
.max_aspect
.x
/size
.max_aspect
.y
;
494 if (size
.flags
& PMinSize
)
495 _min_size
.setPoint(size
.min_width
, size
.min_height
);
497 if (size
.flags
& PMaxSize
)
498 _max_size
.setPoint(size
.max_width
, size
.max_height
);
500 if (size
.flags
& PBaseSize
)
501 _base_size
.setPoint(size
.base_width
, size
.base_height
);
503 if (size
.flags
& PResizeInc
)
504 _size_inc
.setPoint(size
.width_inc
, size
.height_inc
);
509 void Client::updateWMHints(bool initstate
)
513 // assume a window takes input if it doesnt specify
517 if ((hints
= XGetWMHints(**otk::display
, _window
)) != NULL
) {
518 if (hints
->flags
& InputHint
)
519 _can_focus
= hints
->input
;
521 // only do this when initstate is true!
522 if (initstate
&& (hints
->flags
& StateHint
))
523 _iconic
= hints
->initial_state
== IconicState
;
525 if (hints
->flags
& XUrgencyHint
)
528 if (hints
->flags
& WindowGroupHint
) {
529 if (hints
->window_group
!= _group
) {
530 // XXX: remove from the old group if there was one
531 _group
= hints
->window_group
;
532 // XXX: do stuff with the group
543 printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
544 (long)_window
, _urgent
? "ON" : "OFF");
546 // fire the urgent callback if we're mapped, otherwise, wait until after
554 void Client::updateTitle()
559 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_name
,
560 otk::Property::utf8
, &_title
)) {
562 otk::Property::get(_window
, otk::Property::atoms
.wm_name
,
563 otk::Property::ascii
, &_title
);
567 _title
= _("Unnamed Window");
570 frame
->setTitle(_title
);
574 void Client::updateIconTitle()
579 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_icon_name
,
580 otk::Property::utf8
, &_icon_title
)) {
582 otk::Property::get(_window
, otk::Property::atoms
.wm_icon_name
,
583 otk::Property::ascii
, &_icon_title
);
587 _icon_title
= _("Unnamed Window");
591 void Client::updateClass()
594 _app_name
= _app_class
= _role
= "";
596 otk::Property::StringVect v
;
597 unsigned long num
= 2;
599 if (otk::Property::get(_window
, otk::Property::atoms
.wm_class
,
600 otk::Property::ascii
, &num
, &v
)) {
601 if (num
> 0) _app_name
= v
[0].c_str();
602 if (num
> 1) _app_class
= v
[1].c_str();
607 if (otk::Property::get(_window
, otk::Property::atoms
.wm_window_role
,
608 otk::Property::ascii
, &num
, &v
)) {
609 if (num
> 0) _role
= v
[0].c_str();
614 void Client::updateStrut()
616 unsigned long num
= 4;
618 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_strut
,
619 otk::Property::atoms
.cardinal
, &num
, &data
))
623 _strut
.left
= data
[0];
624 _strut
.right
= data
[1];
625 _strut
.top
= data
[2];
626 _strut
.bottom
= data
[3];
628 openbox
->screen(_screen
)->updateStrut();
635 void Client::updateTransientFor()
640 if (XGetTransientForHint(**otk::display
, _window
, &t
) &&
641 t
!= _window
) { // cant be transient to itself!
642 c
= openbox
->findClient(t
);
643 assert(c
!= this); // if this happens then we need to check for it
645 if (!c
/*XXX: && _group*/) {
646 // not transient to a client, see if it is transient for a group
647 if (//t == _group->leader() ||
649 t
== otk::display
->screenInfo(_screen
)->rootWindow()) {
650 // window is a transient for its group!
651 // XXX: for now this is treated as non-transient.
652 // this needs to be fixed!
657 // if anything has changed...
658 if (c
!= _transient_for
) {
660 _transient_for
->_transients
.remove(this); // remove from old parent
663 _transient_for
->_transients
.push_back(this); // add to new parent
665 // XXX: change decor status?
670 void Client::propertyHandler(const XPropertyEvent
&e
)
672 otk::EventHandler::propertyHandler(e
);
674 // validate cuz we query stuff off the client here
675 if (!validate()) return;
677 // compress changes to a single property into a single change
679 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
680 // XXX: it would be nice to compress ALL changes to a property, not just
681 // changes in a row without other props between.
682 if (ce
.xproperty
.atom
!= e
.atom
) {
683 XPutBackEvent(**otk::display
, &ce
);
688 if (e
.atom
== XA_WM_NORMAL_HINTS
) {
690 setupDecorAndFunctions(); // normal hints can make a window non-resizable
691 } else if (e
.atom
== XA_WM_HINTS
)
693 else if (e
.atom
== XA_WM_TRANSIENT_FOR
) {
694 updateTransientFor();
696 calcLayer(); // type may have changed, so update the layer
697 setupDecorAndFunctions();
698 frame
->adjustSize(); // this updates the frame for any new decor settings
700 else if (e
.atom
== otk::Property::atoms
.net_wm_name
||
701 e
.atom
== otk::Property::atoms
.wm_name
)
703 else if (e
.atom
== otk::Property::atoms
.net_wm_icon_name
||
704 e
.atom
== otk::Property::atoms
.wm_icon_name
)
706 else if (e
.atom
== otk::Property::atoms
.wm_class
)
708 else if (e
.atom
== otk::Property::atoms
.wm_protocols
) {
710 setupDecorAndFunctions();
711 frame
->adjustSize(); // update the decorations
713 else if (e
.atom
== otk::Property::atoms
.net_wm_strut
)
718 void Client::setWMState(long state
)
720 if (state
== _wmstate
) return; // no change
724 setDesktop(ICONIC_DESKTOP
);
727 setDesktop(openbox
->screen(_screen
)->desktop());
733 void Client::setDesktop(long target
)
735 if (target
== _desktop
) return;
737 printf("Setting desktop %ld\n", target
);
739 if (!(target
>= 0 || target
== (signed)0xffffffff ||
740 target
== ICONIC_DESKTOP
))
745 // set the desktop hint, but not if we're iconifying
746 if (_desktop
!= ICONIC_DESKTOP
)
747 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
748 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
750 // 'move' the window to the new desktop
751 if (_desktop
== openbox
->screen(_screen
)->desktop() ||
752 _desktop
== (signed)0xffffffff)
757 // Handle Iconic state. Iconic state is maintained by the client being a
758 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
759 // uniconifying happen.
760 bool i
= _desktop
== ICONIC_DESKTOP
;
761 if (i
!= _iconic
) { // has the state changed?
764 _wmstate
= IconicState
;
766 // we unmap the client itself so that we can get MapRequest events, and
767 // because the ICCCM tells us to!
768 XUnmapWindow(**otk::display
, _window
);
770 _wmstate
= NormalState
;
771 XMapWindow(**otk::display
, _window
);
776 frame
->adjustState();
780 void Client::setState(StateAction action
, long data1
, long data2
)
782 bool shadestate
= _shaded
;
783 bool fsstate
= _fullscreen
;
785 if (!(action
== State_Add
|| action
== State_Remove
||
786 action
== State_Toggle
))
787 return; // an invalid action was passed to the client message, ignore it
789 for (int i
= 0; i
< 2; ++i
) {
790 Atom state
= i
== 0 ? data1
: data2
;
792 if (! state
) continue;
794 // if toggling, then pick whether we're adding or removing
795 if (action
== State_Toggle
) {
796 if (state
== otk::Property::atoms
.net_wm_state_modal
)
797 action
= _modal
? State_Remove
: State_Add
;
798 else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
)
799 action
= _max_vert
? State_Remove
: State_Add
;
800 else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
)
801 action
= _max_horz
? State_Remove
: State_Add
;
802 else if (state
== otk::Property::atoms
.net_wm_state_shaded
)
803 action
= _shaded
? State_Remove
: State_Add
;
804 else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
)
805 action
= _skip_taskbar
? State_Remove
: State_Add
;
806 else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
)
807 action
= _skip_pager
? State_Remove
: State_Add
;
808 else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
)
809 action
= _fullscreen
? State_Remove
: State_Add
;
810 else if (state
== otk::Property::atoms
.net_wm_state_above
)
811 action
= _above
? State_Remove
: State_Add
;
812 else if (state
== otk::Property::atoms
.net_wm_state_below
)
813 action
= _below
? State_Remove
: State_Add
;
816 if (action
== State_Add
) {
817 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
818 if (_modal
) continue;
820 // XXX: give it focus if another window has focus that shouldnt now
821 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
822 if (_max_vert
) continue;
824 // XXX: resize the window etc
825 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
826 if (_max_horz
) continue;
828 // XXX: resize the window etc
829 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
831 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
832 _skip_taskbar
= true;
833 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
835 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
837 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
838 if (_above
) continue;
840 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
841 if (_below
) continue;
845 } else { // action == State_Remove
846 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
847 if (!_modal
) continue;
849 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
850 if (!_max_vert
) continue;
852 // XXX: resize the window etc
853 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
854 if (!_max_horz
) continue;
856 // XXX: resize the window etc
857 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
859 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
860 _skip_taskbar
= false;
861 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
863 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
865 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
866 if (!_above
) continue;
868 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
869 if (!_below
) continue;
874 // change fullscreen state before shading, as it will affect if the window
876 if (fsstate
!= _fullscreen
)
878 if (shadestate
!= _shaded
)
884 void Client::toggleClientBorder(bool addborder
)
886 // adjust our idea of where the client is, based on its border. When the
887 // border is removed, the client should now be considered to be in a
888 // different position.
889 // when re-adding the border to the client, the same operation needs to be
891 int x
= _area
.x(), y
= _area
.y();
894 case NorthWestGravity
:
896 case SouthWestGravity
:
898 case NorthEastGravity
:
900 case SouthEastGravity
:
901 if (addborder
) x
-= _border_width
* 2;
902 else x
+= _border_width
* 2;
909 if (addborder
) x
-= _border_width
;
910 else x
+= _border_width
;
915 case NorthWestGravity
:
917 case NorthEastGravity
:
919 case SouthWestGravity
:
921 case SouthEastGravity
:
922 if (addborder
) y
-= _border_width
* 2;
923 else y
+= _border_width
* 2;
930 if (addborder
) y
-= _border_width
;
931 else y
+= _border_width
;
937 XSetWindowBorderWidth(**otk::display
, _window
, _border_width
);
939 // move the client so it is back it the right spot _with_ its border!
940 XMoveWindow(**otk::display
, _window
, x
, y
);
942 XSetWindowBorderWidth(**otk::display
, _window
, 0);
946 void Client::clientMessageHandler(const XClientMessageEvent
&e
)
948 otk::EventHandler::clientMessageHandler(e
);
950 // validate cuz we query stuff off the client here
951 if (!validate()) return;
953 if (e
.format
!= 32) return;
955 if (e
.message_type
== otk::Property::atoms
.wm_change_state
) {
956 // compress changes into a single change
957 bool compress
= false;
959 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
960 // XXX: it would be nice to compress ALL messages of a type, not just
961 // messages in a row without other message types between.
962 if (ce
.xclient
.message_type
!= e
.message_type
) {
963 XPutBackEvent(**otk::display
, &ce
);
969 setWMState(ce
.xclient
.data
.l
[0]); // use the found event
971 setWMState(e
.data
.l
[0]); // use the original event
972 } else if (e
.message_type
== otk::Property::atoms
.net_wm_desktop
) {
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 setDesktop(e
.data
.l
[0]); // use the found event
988 setDesktop(e
.data
.l
[0]); // use the original event
989 } else if (e
.message_type
== otk::Property::atoms
.net_wm_state
) {
990 // can't compress these
992 printf("net_wm_state %s %ld %ld for 0x%lx\n",
993 (e
.data
.l
[0] == 0 ? "Remove" : e
.data
.l
[0] == 1 ? "Add" :
994 e
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
995 e
.data
.l
[1], e
.data
.l
[2], _window
);
997 setState((StateAction
)e
.data
.l
[0], e
.data
.l
[1], e
.data
.l
[2]);
998 } else if (e
.message_type
== otk::Property::atoms
.net_close_window
) {
1000 printf("net_close_window for 0x%lx\n", _window
);
1003 } else if (e
.message_type
== otk::Property::atoms
.net_active_window
) {
1005 printf("net_active_window for 0x%lx\n", _window
);
1008 setDesktop(openbox
->screen(_screen
)->desktop());
1013 openbox
->screen(_screen
)->raiseWindow(this);
1019 void Client::shapeHandler(const XShapeEvent
&e
)
1021 otk::EventHandler::shapeHandler(e
);
1023 if (e
.kind
== ShapeBounding
) {
1025 frame
->adjustShape();
1031 void Client::resize(Corner anchor
, int w
, int h
)
1033 if (!(_functions
& Func_Resize
)) return;
1034 internal_resize(anchor
, w
, h
);
1038 void Client::internal_resize(Corner anchor
, int w
, int h
, bool user
,
1041 w
-= _base_size
.x();
1042 h
-= _base_size
.y();
1044 // for interactive resizing. have to move half an increment in each
1046 w
+= _size_inc
.x() / 2;
1047 h
+= _size_inc
.y() / 2;
1050 // if this is a user-requested resize, then check against min/max sizes
1051 // and aspect ratios
1053 // smaller than min size or bigger than max size?
1054 if (w
< _min_size
.x()) w
= _min_size
.x();
1055 else if (w
> _max_size
.x()) w
= _max_size
.x();
1056 if (h
< _min_size
.y()) h
= _min_size
.y();
1057 else if (h
> _max_size
.y()) h
= _max_size
.y();
1059 // adjust the height ot match the width for the aspect ratios
1061 if (h
* _min_ratio
> w
) h
= static_cast<int>(w
/ _min_ratio
);
1063 if (h
* _max_ratio
< w
) h
= static_cast<int>(w
/ _max_ratio
);
1066 // keep to the increments
1070 // you cannot resize to nothing
1074 // store the logical size
1075 _logical_size
.setPoint(w
, h
);
1080 w
+= _base_size
.x();
1081 h
+= _base_size
.y();
1083 if (x
== INT_MIN
|| y
== INT_MIN
) {
1090 x
-= w
- _area
.width();
1093 y
-= h
- _area
.height();
1096 x
-= w
- _area
.width();
1097 y
-= h
- _area
.height();
1102 _area
.setSize(w
, h
);
1104 XResizeWindow(**otk::display
, _window
, w
, h
);
1106 // resize the frame to match the request
1107 frame
->adjustSize();
1108 internal_move(x
, y
);
1112 void Client::move(int x
, int y
)
1114 if (!(_functions
& Func_Move
)) return;
1115 internal_move(x
, y
);
1119 void Client::internal_move(int x
, int y
)
1123 // move the frame to be in the requested position
1124 if (frame
) { // this can be called while mapping, before frame exists
1125 frame
->adjustPosition();
1127 // send synthetic configure notify (we don't need to if we aren't mapped
1130 event
.type
= ConfigureNotify
;
1131 event
.xconfigure
.display
= **otk::display
;
1132 event
.xconfigure
.event
= _window
;
1133 event
.xconfigure
.window
= _window
;
1134 event
.xconfigure
.x
= x
;
1135 event
.xconfigure
.y
= y
;
1136 event
.xconfigure
.width
= _area
.width();
1137 event
.xconfigure
.height
= _area
.height();
1138 event
.xconfigure
.border_width
= _border_width
;
1139 event
.xconfigure
.above
= frame
->window();
1140 event
.xconfigure
.override_redirect
= False
;
1141 XSendEvent(event
.xconfigure
.display
, event
.xconfigure
.window
, False
,
1142 StructureNotifyMask
, &event
);
1147 void Client::close()
1151 if (!(_functions
& Func_Close
)) return;
1153 // XXX: itd be cool to do timeouts and shit here for killing the client's
1155 // like... if the window is around after 5 seconds, then the close button
1156 // turns a nice red, and if this function is called again, the client is
1157 // explicitly killed.
1159 ce
.xclient
.type
= ClientMessage
;
1160 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1161 ce
.xclient
.display
= **otk::display
;
1162 ce
.xclient
.window
= _window
;
1163 ce
.xclient
.format
= 32;
1164 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_delete_window
;
1165 ce
.xclient
.data
.l
[1] = CurrentTime
;
1166 ce
.xclient
.data
.l
[2] = 0l;
1167 ce
.xclient
.data
.l
[3] = 0l;
1168 ce
.xclient
.data
.l
[4] = 0l;
1169 XSendEvent(**otk::display
, _window
, false, NoEventMask
, &ce
);
1173 void Client::changeState()
1175 unsigned long state
[2];
1176 state
[0] = _wmstate
;
1178 otk::Property::set(_window
, otk::Property::atoms
.wm_state
,
1179 otk::Property::atoms
.wm_state
, state
, 2);
1184 netstate
[num
++] = otk::Property::atoms
.net_wm_state_modal
;
1186 netstate
[num
++] = otk::Property::atoms
.net_wm_state_shaded
;
1188 netstate
[num
++] = otk::Property::atoms
.net_wm_state_hidden
;
1190 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_taskbar
;
1192 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_pager
;
1194 netstate
[num
++] = otk::Property::atoms
.net_wm_state_fullscreen
;
1196 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_vert
;
1198 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_horz
;
1200 netstate
[num
++] = otk::Property::atoms
.net_wm_state_above
;
1202 netstate
[num
++] = otk::Property::atoms
.net_wm_state_below
;
1203 otk::Property::set(_window
, otk::Property::atoms
.net_wm_state
,
1204 otk::Property::atoms
.atom
, netstate
, num
);
1209 frame
->adjustState();
1213 void Client::changeAllowedActions(void)
1218 actions
[num
++] = otk::Property::atoms
.net_wm_action_change_desktop
;
1220 if (_functions
& Func_Shade
)
1221 actions
[num
++] = otk::Property::atoms
.net_wm_action_shade
;
1222 if (_functions
& Func_Close
)
1223 actions
[num
++] = otk::Property::atoms
.net_wm_action_close
;
1224 if (_functions
& Func_Move
)
1225 actions
[num
++] = otk::Property::atoms
.net_wm_action_move
;
1226 if (_functions
& Func_Iconify
)
1227 actions
[num
++] = otk::Property::atoms
.net_wm_action_minimize
;
1228 if (_functions
& Func_Resize
)
1229 actions
[num
++] = otk::Property::atoms
.net_wm_action_resize
;
1230 if (_functions
& Func_Fullscreen
)
1231 actions
[num
++] = otk::Property::atoms
.net_wm_action_fullscreen
;
1232 if (_functions
& Func_Maximize
) {
1233 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_horz
;
1234 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_vert
;
1237 otk::Property::set(_window
, otk::Property::atoms
.net_wm_allowed_actions
,
1238 otk::Property::atoms
.atom
, actions
, num
);
1242 void Client::applyStartupState()
1244 // these are in a carefully crafted order..
1248 setDesktop(ICONIC_DESKTOP
);
1251 _fullscreen
= false;
1261 if (_max_vert
); // XXX: incomplete
1262 if (_max_horz
); // XXX: incomplete
1264 if (_skip_taskbar
); // nothing to do for this
1265 if (_skip_pager
); // nothing to do for this
1266 if (_modal
); // nothing to do for this
1267 if (_above
); // nothing to do for this
1268 if (_below
); // nothing to do for this
1272 void Client::fireUrgent()
1274 // call the python UrgentWindow callbacks
1275 EventData
data(_screen
, this, EventAction::UrgentWindow
, 0);
1276 openbox
->bindings()->fireEvent(&data
);
1280 void Client::shade(bool shade
)
1282 if (!(_functions
& Func_Shade
) || // can't
1283 _shaded
== shade
) return; // already done
1285 // when we're iconic, don't change the wmstate
1287 _wmstate
= shade
? IconicState
: NormalState
;
1290 frame
->adjustSize();
1294 void Client::fullscreen(bool fs
)
1296 static FunctionFlags saved_func
;
1297 static DecorationFlags saved_decor
;
1298 static otk::Rect saved_area
;
1299 static otk::Point saved_logical_size
;
1301 if (!(_functions
& Func_Fullscreen
) || // can't
1302 _fullscreen
== fs
) return; // already done
1305 changeState(); // change the state hints on the client
1308 // save the functions and remove them
1309 saved_func
= _functions
;
1310 _functions
= _functions
& (Func_Close
| Func_Fullscreen
| Func_Iconify
);
1311 // save the decorations and remove them
1312 saved_decor
= _decorations
;
1314 // save the area and adjust it (we don't call internal resize here for
1315 // constraints on the size, etc, we just make it fullscreen).
1317 const otk::ScreenInfo
*info
= otk::display
->screenInfo(_screen
);
1318 _area
.setRect(0, 0, info
->width(), info
->height());
1319 saved_logical_size
= _logical_size
;
1320 _logical_size
.setPoint((info
->width() - _base_size
.x()) / _size_inc
.x(),
1321 (info
->height() - _base_size
.y()) / _size_inc
.y());
1323 _functions
= saved_func
;
1324 _decorations
= saved_decor
;
1326 _logical_size
= saved_logical_size
;
1329 changeAllowedActions(); // based on the new _functions
1331 frame
->adjustSize(); // drop/replace the decor's and resize
1332 frame
->adjustPosition(); // get (back) in position!
1334 // raise (back) into our stacking layer
1335 openbox
->screen(_screen
)->raiseWindow(this);
1337 // try focus us when we go into fullscreen mode
1342 void Client::disableDecorations(DecorationFlags flags
)
1344 _disabled_decorations
= flags
;
1345 setupDecorAndFunctions();
1347 frame
->adjustSize(); // change the decors on the frame
1351 bool Client::focus()
1353 // won't try focus if the client doesn't want it, or if the window isn't
1354 // visible on the screen
1355 if (!(frame
->isVisible() && (_can_focus
|| _focus_notify
))) return false;
1357 if (_focused
) return true;
1359 // do a check to see if the window has already been unmapped or destroyed
1360 // do this intelligently while watching out for unmaps we've generated
1361 // (ignore_unmaps > 0)
1363 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &ev
)) {
1364 XPutBackEvent(**otk::display
, &ev
);
1367 while (XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &ev
)) {
1368 if (ignore_unmaps
) {
1369 unmapHandler(ev
.xunmap
);
1371 XPutBackEvent(**otk::display
, &ev
);
1377 XSetInputFocus(**otk::display
, _window
,
1378 RevertToNone
, CurrentTime
);
1380 if (_focus_notify
) {
1382 ce
.xclient
.type
= ClientMessage
;
1383 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1384 ce
.xclient
.display
= **otk::display
;
1385 ce
.xclient
.window
= _window
;
1386 ce
.xclient
.format
= 32;
1387 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_take_focus
;
1388 ce
.xclient
.data
.l
[1] = openbox
->lastTime();
1389 ce
.xclient
.data
.l
[2] = 0l;
1390 ce
.xclient
.data
.l
[3] = 0l;
1391 ce
.xclient
.data
.l
[4] = 0l;
1392 XSendEvent(**otk::display
, _window
, False
, NoEventMask
, &ce
);
1395 XSync(**otk::display
, False
);
1400 void Client::unfocus() const
1402 if (!_focused
) return;
1404 assert(openbox
->focusedClient() == this);
1405 openbox
->setFocusedClient(0);
1409 void Client::focusHandler(const XFocusChangeEvent
&e
)
1412 // printf("FocusIn for 0x%lx\n", e.window);
1415 otk::EventHandler::focusHandler(e
);
1420 openbox
->setFocusedClient(this);
1424 void Client::unfocusHandler(const XFocusChangeEvent
&e
)
1427 // printf("FocusOut for 0x%lx\n", e.window);
1430 otk::EventHandler::unfocusHandler(e
);
1435 if (openbox
->focusedClient() == this)
1436 openbox
->setFocusedClient(0);
1440 void Client::configureRequestHandler(const XConfigureRequestEvent
&e
)
1443 printf("ConfigureRequest for 0x%lx\n", e
.window
);
1446 otk::EventHandler::configureRequestHandler(e
);
1448 // if we are iconic (or shaded (fvwm does this)) ignore the event
1449 if (_iconic
|| _shaded
) return;
1451 if (e
.value_mask
& CWBorderWidth
)
1452 _border_width
= e
.border_width
;
1454 // resize, then move, as specified in the EWMH section 7.7
1455 if (e
.value_mask
& (CWWidth
| CWHeight
)) {
1456 int w
= (e
.value_mask
& CWWidth
) ? e
.width
: _area
.width();
1457 int h
= (e
.value_mask
& CWHeight
) ? e
.height
: _area
.height();
1461 case NorthEastGravity
:
1465 case SouthWestGravity
:
1467 corner
= BottomLeft
;
1469 case SouthEastGravity
:
1470 corner
= BottomRight
;
1472 default: // NorthWest, Static, etc
1476 // if moving AND resizing ...
1477 if (e
.value_mask
& (CWX
| CWY
)) {
1478 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1479 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1480 internal_resize(corner
, w
, h
, false, x
, y
);
1481 } else // if JUST resizing...
1482 internal_resize(corner
, w
, h
, false);
1483 } else if (e
.value_mask
& (CWX
| CWY
)) { // if JUST moving...
1484 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1485 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1486 internal_move(x
, y
);
1489 if (e
.value_mask
& CWStackMode
) {
1493 openbox
->screen(_screen
)->lowerWindow(this);
1499 openbox
->screen(_screen
)->raiseWindow(this);
1506 void Client::unmapHandler(const XUnmapEvent
&e
)
1508 if (ignore_unmaps
) {
1510 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1517 printf("UnmapNotify for 0x%lx\n", e
.window
);
1520 otk::EventHandler::unmapHandler(e
);
1522 // this deletes us etc
1523 openbox
->screen(_screen
)->unmanageWindow(this);
1527 void Client::destroyHandler(const XDestroyWindowEvent
&e
)
1530 printf("DestroyNotify for 0x%lx\n", e
.window
);
1533 otk::EventHandler::destroyHandler(e
);
1535 // this deletes us etc
1536 openbox
->screen(_screen
)->unmanageWindow(this);
1540 void Client::reparentHandler(const XReparentEvent
&e
)
1542 // this is when the client is first taken captive in the frame
1543 if (e
.parent
== frame
->plate()) return;
1546 printf("ReparentNotify for 0x%lx\n", e
.window
);
1549 otk::EventHandler::reparentHandler(e
);
1552 This event is quite rare and is usually handled in unmapHandler.
1553 However, if the window is unmapped when the reparent event occurs,
1554 the window manager never sees it because an unmap event is not sent
1555 to an already unmapped window.
1558 // we don't want the reparent event, put it back on the stack for the X
1559 // server to deal with after we unmanage the window
1562 XPutBackEvent(**otk::display
, &ev
);
1564 // this deletes us etc
1565 openbox
->screen(_screen
)->unmanageWindow(this);
1568 void Client::mapRequestHandler(const XMapRequestEvent
&e
)
1571 printf("MapRequest for already managed 0x%lx\n", e
.window
);
1574 assert(_iconic
); // we shouldn't be able to get this unless we're iconic
1576 // move to the current desktop (uniconify)
1577 setDesktop(openbox
->screen(_screen
)->desktop());
1578 // XXX: should we focus/raise the window? (basically a net_wm_active_window)