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 changeAllowedActions();
295 void Client::getMwmHints()
297 unsigned long num
= MwmHints::elements
;
298 unsigned long *hints
;
300 _mwmhints
.flags
= 0; // default to none
302 if (!otk::Property::get(_window
, otk::Property::atoms
.motif_wm_hints
,
303 otk::Property::atoms
.motif_wm_hints
, &num
,
304 (unsigned long **)&hints
))
307 if (num
>= MwmHints::elements
) {
308 // retrieved the hints
309 _mwmhints
.flags
= hints
[0];
310 _mwmhints
.functions
= hints
[1];
311 _mwmhints
.decorations
= hints
[2];
318 void Client::getArea()
320 XWindowAttributes wattrib
;
323 ret
= XGetWindowAttributes(**otk::display
, _window
, &wattrib
);
324 assert(ret
!= BadWindow
);
326 _area
.setRect(wattrib
.x
, wattrib
.y
, wattrib
.width
, wattrib
.height
);
327 _border_width
= wattrib
.border_width
;
331 void Client::getState()
333 _modal
= _shaded
= _max_horz
= _max_vert
= _fullscreen
= _above
= _below
=
334 _iconic
= _skip_taskbar
= _skip_pager
= false;
336 unsigned long *state
;
337 unsigned long num
= (unsigned) -1;
339 if (otk::Property::get(_window
, otk::Property::atoms
.net_wm_state
,
340 otk::Property::atoms
.atom
, &num
, &state
)) {
341 for (unsigned long i
= 0; i
< num
; ++i
) {
342 if (state
[i
] == otk::Property::atoms
.net_wm_state_modal
)
344 else if (state
[i
] == otk::Property::atoms
.net_wm_state_shaded
)
346 else if (state
[i
] == otk::Property::atoms
.net_wm_state_hidden
)
348 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_taskbar
)
349 _skip_taskbar
= true;
350 else if (state
[i
] == otk::Property::atoms
.net_wm_state_skip_pager
)
352 else if (state
[i
] == otk::Property::atoms
.net_wm_state_fullscreen
)
354 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_vert
)
356 else if (state
[i
] == otk::Property::atoms
.net_wm_state_maximized_horz
)
358 else if (state
[i
] == otk::Property::atoms
.net_wm_state_above
)
360 else if (state
[i
] == otk::Property::atoms
.net_wm_state_below
)
369 void Client::getShaped()
373 if (otk::display
->shape()) {
378 XShapeSelectInput(**otk::display
, _window
, ShapeNotifyMask
);
380 XShapeQueryExtents(**otk::display
, _window
, &s
, &foo
,
381 &foo
, &ufoo
, &ufoo
, &foo
, &foo
, &foo
, &ufoo
, &ufoo
);
388 void Client::calcLayer() {
391 if (_iconic
) l
= Layer_Icon
;
392 else if (_fullscreen
) l
= Layer_Fullscreen
;
393 else if (_type
== Type_Desktop
) l
= Layer_Desktop
;
394 else if (_type
== Type_Dock
) {
395 if (!_below
) l
= Layer_Top
;
396 else l
= Layer_Normal
;
398 else if (_above
) l
= Layer_Above
;
399 else if (_below
) l
= Layer_Below
;
400 else l
= Layer_Normal
;
406 if we don't have a frame, then we aren't mapped yet (and this would
409 openbox
->screen(_screen
)->raiseWindow(this);
415 void Client::updateProtocols()
420 _focus_notify
= false;
421 _delete_window
= false;
423 if (XGetWMProtocols(**otk::display
, _window
, &proto
, &num_return
)) {
424 for (int i
= 0; i
< num_return
; ++i
) {
425 if (proto
[i
] == otk::Property::atoms
.wm_delete_window
) {
426 // this means we can request the window to close
427 _delete_window
= true;
428 } else if (proto
[i
] == otk::Property::atoms
.wm_take_focus
)
429 // if this protocol is requested, then the window will be notified
430 // by the window manager whenever it receives focus
431 _focus_notify
= true;
438 void Client::updateNormalHints()
442 int oldgravity
= _gravity
;
447 _size_inc
.setPoint(1, 1);
448 _base_size
.setPoint(0, 0);
449 _min_size
.setPoint(0, 0);
450 _max_size
.setPoint(INT_MAX
, INT_MAX
);
452 // XXX: might want to cancel any interactive resizing of the window at this
455 // get the hints from the window
456 if (XGetWMNormalHints(**otk::display
, _window
, &size
, &ret
)) {
457 _positioned
= (size
.flags
& (PPosition
|USPosition
));
459 if (size
.flags
& PWinGravity
) {
460 _gravity
= size
.win_gravity
;
462 // if the client has a frame, i.e. has already been mapped and is
463 // changing its gravity
464 if (frame
&& _gravity
!= oldgravity
) {
465 // move our idea of the client's position based on its new gravity
467 frame
->frameGravity(x
, y
);
472 if (size
.flags
& PAspect
) {
473 if (size
.min_aspect
.y
) _min_ratio
= size
.min_aspect
.x
/size
.min_aspect
.y
;
474 if (size
.max_aspect
.y
) _max_ratio
= size
.max_aspect
.x
/size
.max_aspect
.y
;
477 if (size
.flags
& PMinSize
)
478 _min_size
.setPoint(size
.min_width
, size
.min_height
);
480 if (size
.flags
& PMaxSize
)
481 _max_size
.setPoint(size
.max_width
, size
.max_height
);
483 if (size
.flags
& PBaseSize
)
484 _base_size
.setPoint(size
.base_width
, size
.base_height
);
486 if (size
.flags
& PResizeInc
)
487 _size_inc
.setPoint(size
.width_inc
, size
.height_inc
);
492 void Client::updateWMHints(bool initstate
)
496 // assume a window takes input if it doesnt specify
500 if ((hints
= XGetWMHints(**otk::display
, _window
)) != NULL
) {
501 if (hints
->flags
& InputHint
)
502 _can_focus
= hints
->input
;
504 // only do this when initstate is true!
505 if (initstate
&& (hints
->flags
& StateHint
))
506 _iconic
= hints
->initial_state
== IconicState
;
508 if (hints
->flags
& XUrgencyHint
)
511 if (hints
->flags
& WindowGroupHint
) {
512 if (hints
->window_group
!= _group
) {
513 // XXX: remove from the old group if there was one
514 _group
= hints
->window_group
;
515 // XXX: do stuff with the group
526 printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
527 (long)_window
, _urgent
? "ON" : "OFF");
529 // fire the urgent callback if we're mapped, otherwise, wait until after
537 void Client::updateTitle()
542 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_name
,
543 otk::Property::utf8
, &_title
)) {
545 otk::Property::get(_window
, otk::Property::atoms
.wm_name
,
546 otk::Property::ascii
, &_title
);
550 _title
= _("Unnamed Window");
553 frame
->setTitle(_title
);
557 void Client::updateIconTitle()
562 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_icon_name
,
563 otk::Property::utf8
, &_icon_title
)) {
565 otk::Property::get(_window
, otk::Property::atoms
.wm_icon_name
,
566 otk::Property::ascii
, &_icon_title
);
570 _icon_title
= _("Unnamed Window");
574 void Client::updateClass()
577 _app_name
= _app_class
= _role
= "";
579 otk::Property::StringVect v
;
580 unsigned long num
= 2;
582 if (otk::Property::get(_window
, otk::Property::atoms
.wm_class
,
583 otk::Property::ascii
, &num
, &v
)) {
584 if (num
> 0) _app_name
= v
[0].c_str();
585 if (num
> 1) _app_class
= v
[1].c_str();
590 if (otk::Property::get(_window
, otk::Property::atoms
.wm_window_role
,
591 otk::Property::ascii
, &num
, &v
)) {
592 if (num
> 0) _role
= v
[0].c_str();
597 void Client::updateStrut()
599 unsigned long num
= 4;
601 if (!otk::Property::get(_window
, otk::Property::atoms
.net_wm_strut
,
602 otk::Property::atoms
.cardinal
, &num
, &data
))
606 _strut
.left
= data
[0];
607 _strut
.right
= data
[1];
608 _strut
.top
= data
[2];
609 _strut
.bottom
= data
[3];
611 openbox
->screen(_screen
)->updateStrut();
618 void Client::updateTransientFor()
623 if (XGetTransientForHint(**otk::display
, _window
, &t
) &&
624 t
!= _window
) { // cant be transient to itself!
625 c
= openbox
->findClient(t
);
626 assert(c
!= this); // if this happens then we need to check for it
628 if (!c
/*XXX: && _group*/) {
629 // not transient to a client, see if it is transient for a group
630 if (//t == _group->leader() ||
632 t
== otk::display
->screenInfo(_screen
)->rootWindow()) {
633 // window is a transient for its group!
634 // XXX: for now this is treated as non-transient.
635 // this needs to be fixed!
640 // if anything has changed...
641 if (c
!= _transient_for
) {
643 _transient_for
->_transients
.remove(this); // remove from old parent
646 _transient_for
->_transients
.push_back(this); // add to new parent
648 // XXX: change decor status?
653 void Client::propertyHandler(const XPropertyEvent
&e
)
655 otk::EventHandler::propertyHandler(e
);
657 // validate cuz we query stuff off the client here
658 if (!validate()) return;
660 // compress changes to a single property into a single change
662 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
663 // XXX: it would be nice to compress ALL changes to a property, not just
664 // changes in a row without other props between.
665 if (ce
.xproperty
.atom
!= e
.atom
) {
666 XPutBackEvent(**otk::display
, &ce
);
671 if (e
.atom
== XA_WM_NORMAL_HINTS
) {
673 setupDecorAndFunctions(); // normal hints can make a window non-resizable
674 } else if (e
.atom
== XA_WM_HINTS
)
676 else if (e
.atom
== XA_WM_TRANSIENT_FOR
) {
677 updateTransientFor();
679 calcLayer(); // type may have changed, so update the layer
680 setupDecorAndFunctions();
681 frame
->adjustSize(); // this updates the frame for any new decor settings
683 else if (e
.atom
== otk::Property::atoms
.net_wm_name
||
684 e
.atom
== otk::Property::atoms
.wm_name
)
686 else if (e
.atom
== otk::Property::atoms
.net_wm_icon_name
||
687 e
.atom
== otk::Property::atoms
.wm_icon_name
)
689 else if (e
.atom
== otk::Property::atoms
.wm_class
)
691 else if (e
.atom
== otk::Property::atoms
.wm_protocols
) {
693 setupDecorAndFunctions();
694 frame
->adjustSize(); // update the decorations
696 else if (e
.atom
== otk::Property::atoms
.net_wm_strut
)
701 void Client::setWMState(long state
)
703 if (state
== _wmstate
) return; // no change
707 setDesktop(ICONIC_DESKTOP
);
710 setDesktop(openbox
->screen(_screen
)->desktop());
716 void Client::setDesktop(long target
)
718 if (target
== _desktop
) return;
720 printf("Setting desktop %ld\n", target
);
722 if (!(target
>= 0 || target
== (signed)0xffffffff ||
723 target
== ICONIC_DESKTOP
))
728 // set the desktop hint, but not if we're iconifying
729 if (_desktop
!= ICONIC_DESKTOP
)
730 otk::Property::set(_window
, otk::Property::atoms
.net_wm_desktop
,
731 otk::Property::atoms
.cardinal
, (unsigned)_desktop
);
733 // 'move' the window to the new desktop
734 if (_desktop
== openbox
->screen(_screen
)->desktop() ||
735 _desktop
== (signed)0xffffffff)
740 // Handle Iconic state. Iconic state is maintained by the client being a
741 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
742 // uniconifying happen.
743 bool i
= _desktop
== ICONIC_DESKTOP
;
744 if (i
!= _iconic
) { // has the state changed?
747 _wmstate
= IconicState
;
749 // we unmap the client itself so that we can get MapRequest events, and
750 // because the ICCCM tells us to!
751 XUnmapWindow(**otk::display
, _window
);
753 _wmstate
= NormalState
;
754 XMapWindow(**otk::display
, _window
);
759 frame
->adjustState();
763 void Client::setState(StateAction action
, long data1
, long data2
)
765 bool shadestate
= _shaded
;
766 bool fsstate
= _fullscreen
;
768 if (!(action
== State_Add
|| action
== State_Remove
||
769 action
== State_Toggle
))
770 return; // an invalid action was passed to the client message, ignore it
772 for (int i
= 0; i
< 2; ++i
) {
773 Atom state
= i
== 0 ? data1
: data2
;
775 if (! state
) continue;
777 // if toggling, then pick whether we're adding or removing
778 if (action
== State_Toggle
) {
779 if (state
== otk::Property::atoms
.net_wm_state_modal
)
780 action
= _modal
? State_Remove
: State_Add
;
781 else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
)
782 action
= _max_vert
? State_Remove
: State_Add
;
783 else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
)
784 action
= _max_horz
? State_Remove
: State_Add
;
785 else if (state
== otk::Property::atoms
.net_wm_state_shaded
)
786 action
= _shaded
? State_Remove
: State_Add
;
787 else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
)
788 action
= _skip_taskbar
? State_Remove
: State_Add
;
789 else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
)
790 action
= _skip_pager
? State_Remove
: State_Add
;
791 else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
)
792 action
= _fullscreen
? State_Remove
: State_Add
;
793 else if (state
== otk::Property::atoms
.net_wm_state_above
)
794 action
= _above
? State_Remove
: State_Add
;
795 else if (state
== otk::Property::atoms
.net_wm_state_below
)
796 action
= _below
? State_Remove
: State_Add
;
799 if (action
== State_Add
) {
800 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
801 if (_modal
) continue;
803 // XXX: give it focus if another window has focus that shouldnt now
804 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_vert
) {
805 if (_max_vert
) continue;
807 // XXX: resize the window etc
808 } else if (state
== otk::Property::atoms
.net_wm_state_maximized_horz
) {
809 if (_max_horz
) continue;
811 // XXX: resize the window etc
812 } else if (state
== otk::Property::atoms
.net_wm_state_shaded
) {
814 } else if (state
== otk::Property::atoms
.net_wm_state_skip_taskbar
) {
815 _skip_taskbar
= true;
816 } else if (state
== otk::Property::atoms
.net_wm_state_skip_pager
) {
818 } else if (state
== otk::Property::atoms
.net_wm_state_fullscreen
) {
820 } else if (state
== otk::Property::atoms
.net_wm_state_above
) {
821 if (_above
) continue;
823 } else if (state
== otk::Property::atoms
.net_wm_state_below
) {
824 if (_below
) continue;
828 } else { // action == State_Remove
829 if (state
== otk::Property::atoms
.net_wm_state_modal
) {
830 if (!_modal
) continue;
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
= false;
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;
857 // change fullscreen state before shading, as it will affect if the window
859 if (fsstate
!= _fullscreen
)
861 if (shadestate
!= _shaded
)
867 void Client::toggleClientBorder(bool addborder
)
869 // adjust our idea of where the client is, based on its border. When the
870 // border is removed, the client should now be considered to be in a
871 // different position.
872 // when re-adding the border to the client, the same operation needs to be
874 int x
= _area
.x(), y
= _area
.y();
877 case NorthWestGravity
:
879 case SouthWestGravity
:
881 case NorthEastGravity
:
883 case SouthEastGravity
:
884 if (addborder
) x
-= _border_width
* 2;
885 else x
+= _border_width
* 2;
892 if (addborder
) x
-= _border_width
;
893 else x
+= _border_width
;
898 case NorthWestGravity
:
900 case NorthEastGravity
:
902 case SouthWestGravity
:
904 case SouthEastGravity
:
905 if (addborder
) y
-= _border_width
* 2;
906 else y
+= _border_width
* 2;
913 if (addborder
) y
-= _border_width
;
914 else y
+= _border_width
;
920 XSetWindowBorderWidth(**otk::display
, _window
, _border_width
);
922 // move the client so it is back it the right spot _with_ its border!
923 XMoveWindow(**otk::display
, _window
, x
, y
);
925 XSetWindowBorderWidth(**otk::display
, _window
, 0);
929 void Client::clientMessageHandler(const XClientMessageEvent
&e
)
931 otk::EventHandler::clientMessageHandler(e
);
933 // validate cuz we query stuff off the client here
934 if (!validate()) return;
936 if (e
.format
!= 32) return;
938 if (e
.message_type
== otk::Property::atoms
.wm_change_state
) {
939 // compress changes into a single change
940 bool compress
= false;
942 while (XCheckTypedEvent(**otk::display
, e
.type
, &ce
)) {
943 // XXX: it would be nice to compress ALL messages of a type, not just
944 // messages in a row without other message types between.
945 if (ce
.xclient
.message_type
!= e
.message_type
) {
946 XPutBackEvent(**otk::display
, &ce
);
952 setWMState(ce
.xclient
.data
.l
[0]); // use the found event
954 setWMState(e
.data
.l
[0]); // use the original event
955 } else if (e
.message_type
== otk::Property::atoms
.net_wm_desktop
) {
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 setDesktop(e
.data
.l
[0]); // use the found event
971 setDesktop(e
.data
.l
[0]); // use the original event
972 } else if (e
.message_type
== otk::Property::atoms
.net_wm_state
) {
973 // can't compress these
975 printf("net_wm_state %s %ld %ld for 0x%lx\n",
976 (e
.data
.l
[0] == 0 ? "Remove" : e
.data
.l
[0] == 1 ? "Add" :
977 e
.data
.l
[0] == 2 ? "Toggle" : "INVALID"),
978 e
.data
.l
[1], e
.data
.l
[2], _window
);
980 setState((StateAction
)e
.data
.l
[0], e
.data
.l
[1], e
.data
.l
[2]);
981 } else if (e
.message_type
== otk::Property::atoms
.net_close_window
) {
983 printf("net_close_window for 0x%lx\n", _window
);
986 } else if (e
.message_type
== otk::Property::atoms
.net_active_window
) {
988 printf("net_active_window for 0x%lx\n", _window
);
991 setDesktop(openbox
->screen(_screen
)->desktop());
996 openbox
->screen(_screen
)->raiseWindow(this);
1002 void Client::shapeHandler(const XShapeEvent
&e
)
1004 otk::EventHandler::shapeHandler(e
);
1006 if (e
.kind
== ShapeBounding
) {
1008 frame
->adjustShape();
1014 void Client::resize(Corner anchor
, int w
, int h
)
1016 if (!(_functions
& Func_Resize
)) return;
1017 internal_resize(anchor
, w
, h
);
1021 void Client::internal_resize(Corner anchor
, int w
, int h
, bool user
,
1024 w
-= _base_size
.x();
1025 h
-= _base_size
.y();
1027 // for interactive resizing. have to move half an increment in each
1029 w
+= _size_inc
.x() / 2;
1030 h
+= _size_inc
.y() / 2;
1033 // if this is a user-requested resize, then check against min/max sizes
1034 // and aspect ratios
1036 // smaller than min size or bigger than max size?
1037 if (w
< _min_size
.x()) w
= _min_size
.x();
1038 else if (w
> _max_size
.x()) w
= _max_size
.x();
1039 if (h
< _min_size
.y()) h
= _min_size
.y();
1040 else if (h
> _max_size
.y()) h
= _max_size
.y();
1042 // adjust the height ot match the width for the aspect ratios
1044 if (h
* _min_ratio
> w
) h
= static_cast<int>(w
/ _min_ratio
);
1046 if (h
* _max_ratio
< w
) h
= static_cast<int>(w
/ _max_ratio
);
1049 // keep to the increments
1053 // you cannot resize to nothing
1057 // store the logical size
1058 _logical_size
.setPoint(w
, h
);
1063 w
+= _base_size
.x();
1064 h
+= _base_size
.y();
1066 if (x
== INT_MIN
|| y
== INT_MIN
) {
1073 x
-= w
- _area
.width();
1076 y
-= h
- _area
.height();
1079 x
-= w
- _area
.width();
1080 y
-= h
- _area
.height();
1085 _area
.setSize(w
, h
);
1087 XResizeWindow(**otk::display
, _window
, w
, h
);
1089 // resize the frame to match the request
1090 frame
->adjustSize();
1091 internal_move(x
, y
);
1095 void Client::move(int x
, int y
)
1097 if (!(_functions
& Func_Move
)) return;
1098 internal_move(x
, y
);
1102 void Client::internal_move(int x
, int y
)
1106 // move the frame to be in the requested position
1107 if (frame
) { // this can be called while mapping, before frame exists
1108 frame
->adjustPosition();
1110 // send synthetic configure notify (we don't need to if we aren't mapped
1113 event
.type
= ConfigureNotify
;
1114 event
.xconfigure
.display
= **otk::display
;
1115 event
.xconfigure
.event
= _window
;
1116 event
.xconfigure
.window
= _window
;
1117 event
.xconfigure
.x
= x
;
1118 event
.xconfigure
.y
= y
;
1119 event
.xconfigure
.width
= _area
.width();
1120 event
.xconfigure
.height
= _area
.height();
1121 event
.xconfigure
.border_width
= _border_width
;
1122 event
.xconfigure
.above
= frame
->window();
1123 event
.xconfigure
.override_redirect
= False
;
1124 XSendEvent(event
.xconfigure
.display
, event
.xconfigure
.window
, False
,
1125 StructureNotifyMask
, &event
);
1130 void Client::close()
1134 if (!(_functions
& Func_Close
)) return;
1136 // XXX: itd be cool to do timeouts and shit here for killing the client's
1138 // like... if the window is around after 5 seconds, then the close button
1139 // turns a nice red, and if this function is called again, the client is
1140 // explicitly killed.
1142 ce
.xclient
.type
= ClientMessage
;
1143 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1144 ce
.xclient
.display
= **otk::display
;
1145 ce
.xclient
.window
= _window
;
1146 ce
.xclient
.format
= 32;
1147 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_delete_window
;
1148 ce
.xclient
.data
.l
[1] = CurrentTime
;
1149 ce
.xclient
.data
.l
[2] = 0l;
1150 ce
.xclient
.data
.l
[3] = 0l;
1151 ce
.xclient
.data
.l
[4] = 0l;
1152 XSendEvent(**otk::display
, _window
, false, NoEventMask
, &ce
);
1156 void Client::changeState()
1158 unsigned long state
[2];
1159 state
[0] = _wmstate
;
1161 otk::Property::set(_window
, otk::Property::atoms
.wm_state
,
1162 otk::Property::atoms
.wm_state
, state
, 2);
1167 netstate
[num
++] = otk::Property::atoms
.net_wm_state_modal
;
1169 netstate
[num
++] = otk::Property::atoms
.net_wm_state_shaded
;
1171 netstate
[num
++] = otk::Property::atoms
.net_wm_state_hidden
;
1173 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_taskbar
;
1175 netstate
[num
++] = otk::Property::atoms
.net_wm_state_skip_pager
;
1177 netstate
[num
++] = otk::Property::atoms
.net_wm_state_fullscreen
;
1179 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_vert
;
1181 netstate
[num
++] = otk::Property::atoms
.net_wm_state_maximized_horz
;
1183 netstate
[num
++] = otk::Property::atoms
.net_wm_state_above
;
1185 netstate
[num
++] = otk::Property::atoms
.net_wm_state_below
;
1186 otk::Property::set(_window
, otk::Property::atoms
.net_wm_state
,
1187 otk::Property::atoms
.atom
, netstate
, num
);
1192 frame
->adjustState();
1196 void Client::changeAllowedActions(void)
1201 actions
[num
++] = otk::Property::atoms
.net_wm_action_change_desktop
;
1203 if (_functions
& Func_Shade
)
1204 actions
[num
++] = otk::Property::atoms
.net_wm_action_shade
;
1205 if (_functions
& Func_Close
)
1206 actions
[num
++] = otk::Property::atoms
.net_wm_action_close
;
1207 if (_functions
& Func_Move
)
1208 actions
[num
++] = otk::Property::atoms
.net_wm_action_move
;
1209 if (_functions
& Func_Iconify
)
1210 actions
[num
++] = otk::Property::atoms
.net_wm_action_minimize
;
1211 if (_functions
& Func_Resize
)
1212 actions
[num
++] = otk::Property::atoms
.net_wm_action_resize
;
1213 if (_functions
& Func_Fullscreen
)
1214 actions
[num
++] = otk::Property::atoms
.net_wm_action_fullscreen
;
1215 if (_functions
& Func_Maximize
) {
1216 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_horz
;
1217 actions
[num
++] = otk::Property::atoms
.net_wm_action_maximize_vert
;
1220 otk::Property::set(_window
, otk::Property::atoms
.net_wm_allowed_actions
,
1221 otk::Property::atoms
.atom
, actions
, num
);
1225 void Client::applyStartupState()
1227 // these are in a carefully crafted order..
1231 setDesktop(ICONIC_DESKTOP
);
1234 _fullscreen
= false;
1244 if (_max_vert
); // XXX: incomplete
1245 if (_max_horz
); // XXX: incomplete
1247 if (_skip_taskbar
); // nothing to do for this
1248 if (_skip_pager
); // nothing to do for this
1249 if (_modal
); // nothing to do for this
1250 if (_above
); // nothing to do for this
1251 if (_below
); // nothing to do for this
1255 void Client::fireUrgent()
1257 // call the python UrgentWindow callbacks
1258 EventData
data(_screen
, this, EventAction::UrgentWindow
, 0);
1259 openbox
->bindings()->fireEvent(&data
);
1263 void Client::shade(bool shade
)
1265 if (!(_functions
& Func_Shade
) || // can't
1266 _shaded
== shade
) return; // already done
1268 // when we're iconic, don't change the wmstate
1270 _wmstate
= shade
? IconicState
: NormalState
;
1273 frame
->adjustSize();
1277 void Client::fullscreen(bool fs
)
1279 static FunctionFlags saved_func
;
1280 static DecorationFlags saved_decor
;
1281 static otk::Rect saved_area
;
1282 static otk::Point saved_logical_size
;
1284 if (!(_functions
& Func_Fullscreen
) || // can't
1285 _fullscreen
== fs
) return; // already done
1288 changeState(); // change the state hints on the client
1291 // save the functions and remove them
1292 saved_func
= _functions
;
1293 _functions
= _functions
& (Func_Close
| Func_Fullscreen
| Func_Iconify
);
1294 // save the decorations and remove them
1295 saved_decor
= _decorations
;
1297 // save the area and adjust it (we don't call internal resize here for
1298 // constraints on the size, etc, we just make it fullscreen).
1300 const otk::ScreenInfo
*info
= otk::display
->screenInfo(_screen
);
1301 _area
.setRect(0, 0, info
->width(), info
->height());
1302 saved_logical_size
= _logical_size
;
1303 _logical_size
.setPoint((info
->width() - _base_size
.x()) / _size_inc
.x(),
1304 (info
->height() - _base_size
.y()) / _size_inc
.y());
1306 _functions
= saved_func
;
1307 _decorations
= saved_decor
;
1309 _logical_size
= saved_logical_size
;
1312 changeAllowedActions(); // based on the new _functions
1314 frame
->adjustSize(); // drop/replace the decor's and resize
1315 frame
->adjustPosition(); // get (back) in position!
1317 // raise (back) into our stacking layer
1318 openbox
->screen(_screen
)->raiseWindow(this);
1320 // try focus us when we go into fullscreen mode
1325 bool Client::focus()
1327 // won't try focus if the client doesn't want it, or if the window isn't
1328 // visible on the screen
1329 if (!(frame
->isVisible() && (_can_focus
|| _focus_notify
))) return false;
1331 if (_focused
) return true;
1333 // do a check to see if the window has already been unmapped or destroyed
1334 // do this intelligently while watching out for unmaps we've generated
1335 // (ignore_unmaps > 0)
1337 if (XCheckTypedWindowEvent(**otk::display
, _window
, DestroyNotify
, &ev
)) {
1338 XPutBackEvent(**otk::display
, &ev
);
1341 while (XCheckTypedWindowEvent(**otk::display
, _window
, UnmapNotify
, &ev
)) {
1342 if (ignore_unmaps
) {
1343 unmapHandler(ev
.xunmap
);
1345 XPutBackEvent(**otk::display
, &ev
);
1351 XSetInputFocus(**otk::display
, _window
,
1352 RevertToNone
, CurrentTime
);
1354 if (_focus_notify
) {
1356 ce
.xclient
.type
= ClientMessage
;
1357 ce
.xclient
.message_type
= otk::Property::atoms
.wm_protocols
;
1358 ce
.xclient
.display
= **otk::display
;
1359 ce
.xclient
.window
= _window
;
1360 ce
.xclient
.format
= 32;
1361 ce
.xclient
.data
.l
[0] = otk::Property::atoms
.wm_take_focus
;
1362 ce
.xclient
.data
.l
[1] = openbox
->lastTime();
1363 ce
.xclient
.data
.l
[2] = 0l;
1364 ce
.xclient
.data
.l
[3] = 0l;
1365 ce
.xclient
.data
.l
[4] = 0l;
1366 XSendEvent(**otk::display
, _window
, False
, NoEventMask
, &ce
);
1369 XSync(**otk::display
, False
);
1374 void Client::unfocus() const
1376 if (!_focused
) return;
1378 assert(openbox
->focusedClient() == this);
1379 openbox
->setFocusedClient(0);
1383 void Client::focusHandler(const XFocusChangeEvent
&e
)
1386 // printf("FocusIn for 0x%lx\n", e.window);
1389 otk::EventHandler::focusHandler(e
);
1394 openbox
->setFocusedClient(this);
1398 void Client::unfocusHandler(const XFocusChangeEvent
&e
)
1401 // printf("FocusOut for 0x%lx\n", e.window);
1404 otk::EventHandler::unfocusHandler(e
);
1409 if (openbox
->focusedClient() == this)
1410 openbox
->setFocusedClient(0);
1414 void Client::configureRequestHandler(const XConfigureRequestEvent
&e
)
1417 printf("ConfigureRequest for 0x%lx\n", e
.window
);
1420 otk::EventHandler::configureRequestHandler(e
);
1422 // if we are iconic (or shaded (fvwm does this)) ignore the event
1423 if (_iconic
|| _shaded
) return;
1425 if (e
.value_mask
& CWBorderWidth
)
1426 _border_width
= e
.border_width
;
1428 // resize, then move, as specified in the EWMH section 7.7
1429 if (e
.value_mask
& (CWWidth
| CWHeight
)) {
1430 int w
= (e
.value_mask
& CWWidth
) ? e
.width
: _area
.width();
1431 int h
= (e
.value_mask
& CWHeight
) ? e
.height
: _area
.height();
1435 case NorthEastGravity
:
1439 case SouthWestGravity
:
1441 corner
= BottomLeft
;
1443 case SouthEastGravity
:
1444 corner
= BottomRight
;
1446 default: // NorthWest, Static, etc
1450 // if moving AND resizing ...
1451 if (e
.value_mask
& (CWX
| CWY
)) {
1452 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1453 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1454 internal_resize(corner
, w
, h
, false, x
, y
);
1455 } else // if JUST resizing...
1456 internal_resize(corner
, w
, h
, false);
1457 } else if (e
.value_mask
& (CWX
| CWY
)) { // if JUST moving...
1458 int x
= (e
.value_mask
& CWX
) ? e
.x
: _area
.x();
1459 int y
= (e
.value_mask
& CWY
) ? e
.y
: _area
.y();
1460 internal_move(x
, y
);
1463 if (e
.value_mask
& CWStackMode
) {
1467 openbox
->screen(_screen
)->lowerWindow(this);
1473 openbox
->screen(_screen
)->raiseWindow(this);
1480 void Client::unmapHandler(const XUnmapEvent
&e
)
1482 if (ignore_unmaps
) {
1484 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1491 printf("UnmapNotify for 0x%lx\n", e
.window
);
1494 otk::EventHandler::unmapHandler(e
);
1496 // this deletes us etc
1497 openbox
->screen(_screen
)->unmanageWindow(this);
1501 void Client::destroyHandler(const XDestroyWindowEvent
&e
)
1504 printf("DestroyNotify for 0x%lx\n", e
.window
);
1507 otk::EventHandler::destroyHandler(e
);
1509 // this deletes us etc
1510 openbox
->screen(_screen
)->unmanageWindow(this);
1514 void Client::reparentHandler(const XReparentEvent
&e
)
1516 // this is when the client is first taken captive in the frame
1517 if (e
.parent
== frame
->plate()) return;
1520 printf("ReparentNotify for 0x%lx\n", e
.window
);
1523 otk::EventHandler::reparentHandler(e
);
1526 This event is quite rare and is usually handled in unmapHandler.
1527 However, if the window is unmapped when the reparent event occurs,
1528 the window manager never sees it because an unmap event is not sent
1529 to an already unmapped window.
1532 // we don't want the reparent event, put it back on the stack for the X
1533 // server to deal with after we unmanage the window
1536 XPutBackEvent(**otk::display
, &ev
);
1538 // this deletes us etc
1539 openbox
->screen(_screen
)->unmanageWindow(this);
1542 void Client::mapRequestHandler(const XMapRequestEvent
&e
)
1545 printf("MapRequest for already managed 0x%lx\n", e
.window
);
1548 assert(_iconic
); // we shouldn't be able to get this unless we're iconic
1550 // move to the current desktop (uniconify)
1551 setDesktop(openbox
->screen(_screen
)->desktop());
1552 // XXX: should we focus/raise the window? (basically a net_wm_active_window)