2 * ion/ioncore/clientwin.c
4 * Copyright (c) Tuomo Valkonen 1999-2009.
6 * See the included file LICENSE for details.
13 #include <libtu/objp.h>
14 #include <libtu/minmax.h>
15 #include <libextl/extl.h>
16 #include <libmainloop/defer.h>
17 #include <libmainloop/hooks.h>
24 #include "clientwin.h"
34 #include "fullscreen.h"
46 static void set_clientwin_state(WClientWin
*cwin
, int state
);
47 static bool send_clientmsg(Window win
, Atom a
, Time stmp
);
50 WHook
*clientwin_do_manage_alt
=NULL
;
51 WHook
*clientwin_mapped_hook
=NULL
;
52 WHook
*clientwin_unmapped_hook
=NULL
;
53 WHook
*clientwin_property_change_hook
=NULL
;
56 /*{{{ Get properties */
59 void clientwin_get_protocols(WClientWin
*cwin
)
61 Atom
*protocols
=NULL
, *p
;
64 cwin
->flags
&=~(CLIENTWIN_P_WM_DELETE
|CLIENTWIN_P_WM_TAKE_FOCUS
);
66 if(!XGetWMProtocols(ioncore_g
.dpy
, cwin
->win
, &protocols
, &n
))
69 for(p
=protocols
; n
; n
--, p
++){
70 if(*p
==ioncore_g
.atom_wm_delete
)
71 cwin
->flags
|=CLIENTWIN_P_WM_DELETE
;
72 else if(*p
==ioncore_g
.atom_wm_take_focus
)
73 cwin
->flags
|=CLIENTWIN_P_WM_TAKE_FOCUS
;
77 XFree((char*)protocols
);
81 static WSizePolicy
get_sizepolicy_winprop(WClientWin
*cwin
,
87 if(extl_table_gets_s(cwin
->proptab
, propname
, &szplcy
)){
88 string2sizepolicy(szplcy
, &value
);
95 #define SIZEHINT_PROPS (CLIENTWIN_PROP_MAXSIZE| \
96 CLIENTWIN_PROP_MINSIZE| \
97 CLIENTWIN_PROP_ASPECT| \
98 CLIENTWIN_PROP_RSZINC| \
99 CLIENTWIN_PROP_I_MAXSIZE| \
100 CLIENTWIN_PROP_I_MINSIZE| \
101 CLIENTWIN_PROP_I_ASPECT| \
102 CLIENTWIN_PROP_I_RSZINC)
105 #define DO_SZH(NAME, FLAG, IFLAG, SZHFLAG, W, H, C) \
106 if(extl_table_is_bool_set(tab, "ignore_" NAME)){ \
107 cwin->flags|=IFLAG; \
108 }else if(extl_table_gets_t(tab, NAME, &tab2)){ \
109 if(extl_table_gets_i(tab2, "w", &i1) && \
110 extl_table_gets_i(tab2, "h", &i2)){ \
111 cwin->size_hints.W=i1; \
112 cwin->size_hints.H=i2; \
114 cwin->size_hints.flags|=SZHFLAG; \
117 extl_unref_table(tab2); \
121 static void clientwin_get_winprops(WClientWin
*cwin
)
127 tab
=ioncore_get_winprop(cwin
);
131 if(tab
==extl_table_none())
134 if(extl_table_is_bool_set(tab
, "transparent"))
135 cwin
->flags
|=CLIENTWIN_PROP_TRANSPARENT
;
137 if(extl_table_is_bool_set(tab
, "acrobatic"))
138 cwin
->flags
|=CLIENTWIN_PROP_ACROBATIC
;
140 if(extl_table_is_bool_set(tab
, "lazy_resize"))
141 cwin
->flags
|=CLIENTWIN_PROP_LAZY_RESIZE
;
143 DO_SZH("max_size", CLIENTWIN_PROP_MAXSIZE
, CLIENTWIN_PROP_I_MAXSIZE
,
144 PMaxSize
, max_width
, max_height
, { });
146 DO_SZH("min_size", CLIENTWIN_PROP_MINSIZE
, CLIENTWIN_PROP_I_MINSIZE
,
147 PMinSize
, min_width
, min_height
, { });
149 DO_SZH("resizeinc", CLIENTWIN_PROP_RSZINC
, CLIENTWIN_PROP_I_RSZINC
,
150 PResizeInc
, width_inc
, height_inc
, { });
152 DO_SZH("aspect", CLIENTWIN_PROP_ASPECT
, CLIENTWIN_PROP_I_ASPECT
,
153 PAspect
, min_aspect
.x
, min_aspect
.y
,
154 { cwin
->size_hints
.max_aspect
.x
=i1
;
155 cwin
->size_hints
.max_aspect
.y
=i2
;
158 if(extl_table_is_bool_set(tab
, "ignore_cfgrq"))
159 cwin
->flags
|=CLIENTWIN_PROP_IGNORE_CFGRQ
;
161 if(extl_table_gets_s(tab
, "orientation", &s
)){
162 if(strcmp(s
, "vertical")==0)
163 cwin
->flags
|=CLIENTWIN_PROP_O_VERT
;
164 else if(strcmp(s
, "horizontal")==0)
165 cwin
->flags
|=CLIENTWIN_PROP_O_HORIZ
;
171 void clientwin_get_size_hints(WClientWin
*cwin
)
173 XSizeHints tmp
=cwin
->size_hints
;
175 xwindow_get_sizehints(cwin
->win
, &(cwin
->size_hints
));
177 if(cwin
->flags
&CLIENTWIN_PROP_I_MAXSIZE
){
178 cwin
->size_hints
.flags
&=~PMaxSize
;
179 }else if(cwin
->flags
&CLIENTWIN_PROP_MAXSIZE
){
180 cwin
->size_hints
.max_width
=tmp
.max_width
;
181 cwin
->size_hints
.max_height
=tmp
.max_height
;
182 cwin
->size_hints
.flags
|=PMaxSize
;
185 if(cwin
->flags
&CLIENTWIN_PROP_I_MINSIZE
){
186 cwin
->size_hints
.flags
&=~PMinSize
;
187 }else if(cwin
->flags
&CLIENTWIN_PROP_MINSIZE
){
188 cwin
->size_hints
.min_width
=tmp
.min_width
;
189 cwin
->size_hints
.min_height
=tmp
.min_height
;
190 cwin
->size_hints
.flags
|=PMinSize
;
193 if(cwin
->flags
&CLIENTWIN_PROP_I_ASPECT
){
194 cwin
->size_hints
.flags
&=~PAspect
;
195 }else if(cwin
->flags
&CLIENTWIN_PROP_ASPECT
){
196 cwin
->size_hints
.min_aspect
=tmp
.min_aspect
;
197 cwin
->size_hints
.max_aspect
=tmp
.max_aspect
;
198 cwin
->size_hints
.flags
|=PAspect
;
201 if(cwin
->flags
&CLIENTWIN_PROP_I_RSZINC
){
202 cwin
->size_hints
.flags
&=~PResizeInc
;
203 }else if(cwin
->flags
&CLIENTWIN_PROP_RSZINC
){
204 cwin
->size_hints
.width_inc
=tmp
.width_inc
;
205 cwin
->size_hints
.height_inc
=tmp
.height_inc
;
206 cwin
->size_hints
.flags
|=PResizeInc
;
211 void clientwin_get_set_name(WClientWin
*cwin
)
217 list
=netwm_get_name(cwin
);
220 list
=xwindow_get_text_property(cwin
->win
, XA_WM_NAME
, &n
);
222 cwin
->flags
|=CLIENTWIN_USE_NET_WM_NAME
;
226 /* Special condition kludge: property exists, but couldn't
227 * be converted to a string list.
229 clientwin_set_name(cwin
, (n
==-1 ? "???" : NULL
));
231 clientwin_set_name(cwin
, *list
);
232 XFreeStringList(list
);
237 /* Some standard winprops */
240 bool clientwin_get_switchto(const WClientWin
*cwin
)
244 if(ioncore_g
.opmode
==IONCORE_OPMODE_INIT
)
247 if(extl_table_gets_b(cwin
->proptab
, "switchto", &b
))
250 return ioncore_g
.switchto_new
;
254 int clientwin_get_transient_mode(const WClientWin
*cwin
)
257 int mode
=TRANSIENT_MODE_NORMAL
;
259 if(extl_table_gets_s(cwin
->proptab
, "transient_mode", &s
)){
260 if(strcmp(s
, "current")==0)
261 mode
=TRANSIENT_MODE_CURRENT
;
262 else if(strcmp(s
, "off")==0)
263 mode
=TRANSIENT_MODE_OFF
;
273 /*{{{ Manage/create */
276 static void configure_cwin_bw(Window win
, int bw
)
279 ulong wcmask
=CWBorderWidth
;
282 XConfigureWindow(ioncore_g
.dpy
, win
, wcmask
, &wc
);
286 static void set_sane_gravity(Window win
)
288 XSetWindowAttributes attr
;
290 attr
.win_gravity
=NorthWestGravity
;
292 XChangeWindowAttributes(ioncore_g
.dpy
, win
,
293 CWWinGravity
, &attr
);
297 static bool clientwin_init(WClientWin
*cwin
, WWindow
*par
, Window win
,
298 XWindowAttributes
*attr
)
304 cwin
->state
=WithdrawnState
;
310 fp
.mode
=REGION_FIT_EXACT
;
312 /* The idiot who invented special server-supported window borders that
313 * are not accounted for in the window size should be "taken behind a
316 cwin
->orig_bw
=attr
->border_width
;
317 configure_cwin_bw(cwin
->win
, 0);
318 if(cwin
->orig_bw
!=0 && cwin
->size_hints
.flags
&PWinGravity
){
319 fp
.g
.x
+=xgravity_deltax(cwin
->size_hints
.win_gravity
,
320 -cwin
->orig_bw
, -cwin
->orig_bw
);
321 fp
.g
.y
+=xgravity_deltay(cwin
->size_hints
.win_gravity
,
322 -cwin
->orig_bw
, -cwin
->orig_bw
);
325 set_sane_gravity(cwin
->win
);
328 cwin
->cmap
=attr
->colormap
;
332 cwin
->event_mask
=IONCORE_EVENTMASK_CLIENTWIN
;
334 region_init(&(cwin
->region
), par
, &fp
);
336 cwin
->region
.flags
|=REGION_GRAB_ON_PARENT
;
337 region_add_bindmap(&cwin
->region
, ioncore_clientwin_bindmap
);
339 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
);
341 clientwin_register(cwin
);
342 clientwin_get_set_name(cwin
);
343 clientwin_get_colormaps(cwin
);
344 clientwin_get_protocols(cwin
);
345 clientwin_get_winprops(cwin
);
346 clientwin_get_size_hints(cwin
);
348 netwm_update_allowed_actions(cwin
);
350 XSaveContext(ioncore_g
.dpy
, win
, ioncore_g
.win_context
, (XPointer
)cwin
);
351 XAddToSaveSet(ioncore_g
.dpy
, win
);
357 static WClientWin
*create_clientwin(WWindow
*par
, Window win
,
358 XWindowAttributes
*attr
)
360 CREATEOBJ_IMPL(WClientWin
, clientwin
, (p
, par
, win
, attr
));
365 WClientWin
*clientwin_get_transient_for(const WClientWin
*cwin
)
368 WClientWin
*tfor
=NULL
;
370 if(clientwin_get_transient_mode(cwin
)!=TRANSIENT_MODE_NORMAL
)
373 if(!XGetTransientForHint(ioncore_g
.dpy
, cwin
->win
, &tforwin
))
379 tfor
=XWINDOW_REGION_OF_T(tforwin
, WClientWin
);
382 warn(TR("The transient_for hint for \"%s\" points to itself."),
383 region_name((WRegion
*)cwin
));
384 }else if(tfor
==NULL
){
385 if(xwindow_region_of(tforwin
)!=NULL
){
386 warn(TR("Client window \"%s\" has broken transient_for hint. "
387 "(\"Extended WM hints\" multi-parent brain damage?)"),
388 region_name((WRegion
*)cwin
));
390 }else if(!region_same_rootwin((WRegion
*)cwin
, (WRegion
*)tfor
)){
391 warn(TR("The transient_for window for \"%s\" is not on the same "
392 "screen."), region_name((WRegion
*)cwin
));
401 static bool postmanage_check(WClientWin
*cwin
, XWindowAttributes
*attr
)
403 /* Check that the window exists. The previous check and selectinput
404 * do not seem to catch all cases of window destroyal.
406 XSync(ioncore_g
.dpy
, False
);
408 if(XGetWindowAttributes(ioncore_g
.dpy
, cwin
->win
, attr
))
411 warn(TR("Window %#x disappeared."), cwin
->win
);
417 static bool do_manage_mrsh(bool (*fn
)(WClientWin
*cwin
, WManageParams
*pm
),
420 return fn((WClientWin
*)p
[0], (WManageParams
*)p
[1]);
425 static bool do_manage_mrsh_extl(ExtlFn fn
, void **p
)
427 WClientWin
*cwin
=(WClientWin
*)p
[0];
428 WManageParams
*mp
=(WManageParams
*)p
[1];
429 ExtlTab t
=manageparams_to_table(mp
);
432 extl_call(fn
, "ot", "b", cwin
, t
, &ret
);
436 return (ret
&& REGION_MANAGER(cwin
)!=NULL
);
440 /* This is called when a window is mapped on the root window.
441 * We want to check if we should manage the window and how and
444 WClientWin
* ioncore_manage_clientwin(Window win
, bool maprq
)
447 WClientWin
*cwin
=NULL
;
448 XWindowAttributes attr
;
450 int init_state
=NormalState
;
451 WManageParams param
=MANAGEPARAMS_INIT
;
456 /* Is the window already being managed? */
457 cwin
=XWINDOW_REGION_OF_T(win
, WClientWin
);
461 /* Select for UnmapNotify and DestroyNotify as the
462 * window might get destroyed or unmapped in the meanwhile.
464 xwindow_unmanaged_selectinput(win
, StructureNotifyMask
);
466 if(!XGetWindowAttributes(ioncore_g
.dpy
, win
, &attr
)){
468 warn(TR("Window %#x disappeared."), win
);
474 hints
=XGetWMHints(ioncore_g
.dpy
, win
);
477 if(hints
->flags
&StateHint
)
478 init_state
=hints
->initial_state
;
480 if(!param
.dockapp
&& init_state
==WithdrawnState
&&
481 hints
->flags
&IconWindowHint
&& hints
->icon_window
!=None
){
482 Window icon_win
=hints
->icon_window
;
483 XWindowAttributes icon_attr
;
485 if(!XGetWindowAttributes(ioncore_g
.dpy
, icon_win
, &icon_attr
)){
487 warn(TR("Window %#x disappeared."), win
);
493 if(attr
.map_state
==IsViewable
){
494 /* The dockapp might be displaying its "main" window if no
495 * wm that understands dockapps has been managing it.
497 XUnmapWindow(ioncore_g
.dpy
, win
);
500 /* Main window is unmapped on initial scan, but icon window
501 * is mapped. Let's hope it's a dockapp left by e.g. us.
503 if(icon_attr
.map_state
==IsViewable
)
514 xwindow_unmanaged_selectinput(win
, 0);
515 xwindow_unmanaged_selectinput(icon_win
, StructureNotifyMask
);
517 /* Copy WM_CLASS as _ION_DOCKAPP_HACK */
519 p
=xwindow_get_text_property(win
, XA_WM_CLASS
, &n
);
522 xwindow_set_text_property(icon_win
, ioncore_g
.atom_dockapp_hack
,
523 (const char **)p
, n
);
526 const char *pdummy
[2]={"unknowndockapp", "UnknownDockapp"};
527 xwindow_set_text_property(icon_win
, ioncore_g
.atom_dockapp_hack
,
539 /* Do we really want to manage it? */
540 if(!param
.dockapp
&& (attr
.override_redirect
||
541 (!maprq
&& attr
.map_state
!=IsViewable
))){
545 attr
.width
=maxof(attr
.width
, 1);
546 attr
.height
=maxof(attr
.height
, 1);
548 /* Find root window */
549 FOR_ALL_ROOTWINS(rootwin
){
550 if(WROOTWIN_ROOT(rootwin
)==attr
.root
)
555 warn(TR("Unable to find a matching root window!"));
559 /* Allocate and initialize */
560 cwin
=create_clientwin((WWindow
*)rootwin
, win
, &attr
);
567 param
.geom
=REGION_GEOM(cwin
);
569 param
.jumpto
=extl_table_is_bool_set(cwin
->proptab
, "jumpto");
570 param
.switchto
=(init_state
!=IconicState
&&
571 (param
.jumpto
|| clientwin_get_switchto(cwin
)));
572 param
.gravity
=(cwin
->size_hints
.flags
&PWinGravity
573 ? cwin
->size_hints
.win_gravity
575 param
.tfor
=clientwin_get_transient_for(cwin
);
577 if(!extl_table_gets_b(cwin
->proptab
, "userpos", ¶m
.userpos
))
578 param
.userpos
=(cwin
->size_hints
.flags
&USPosition
);
580 if(cwin
->flags
&SIZEHINT_PROPS
){
581 /* If size hints have been messed with, readjust requested geometry
582 * here. If programs themselves give incompatible geometries and
583 * things don't look good then, it's their fault.
585 region_size_hints_correct((WRegion
*)cwin
, ¶m
.geom
.w
, ¶m
.geom
.h
,
590 netwm_check_manage_user_time(cwin
, ¶m
);
595 if(!hook_call_alt(clientwin_do_manage_alt
, &mrshpm
,
596 (WHookMarshall
*)do_manage_mrsh
,
597 (WHookMarshallExtl
*)do_manage_mrsh_extl
)){
598 warn(TR("Unable to manage client window %#x."), win
);
602 if(ioncore_g
.opmode
==IONCORE_OPMODE_NORMAL
&&
603 !region_is_fully_mapped((WRegion
*)cwin
) &&
604 !region_skip_focus((WRegion
*)cwin
)){
605 region_set_activity((WRegion
*)cwin
, SETPARAM_SET
);
608 if(postmanage_check(cwin
, &attr
)){
609 /* Check for focus_next==NULL does not play nicely with
610 * pointer_focus_hack.
612 /*if(param.jumpto && ioncore_g.focus_next==NULL)*/
613 if(param
.jumpto
&& !region_manager_is_focusnext((WRegion
*)cwin
))
614 region_goto((WRegion
*)cwin
);
615 hook_call_o(clientwin_mapped_hook
, (Obj
*)cwin
);
620 clientwin_destroyed(cwin
);
624 xwindow_unmanaged_selectinput(win
, 0);
629 void clientwin_tfor_changed(WClientWin
*cwin
)
632 WManageParams param
=MANAGEPARAMS_INIT
;
633 bool succeeded
=FALSE
;
634 param
.tfor
=clientwin_get_transient_for(cwin
);
638 region_rootpos((WRegion
*)cwin
, &(param
.geom
.x
), &(param
.geom
.y
));
639 param
.geom
.w
=REGION_GEOM(cwin
).w
;
640 param
.geom
.h
=REGION_GEOM(cwin
).h
;
643 param
.switchto
=region_may_control_focus((WRegion
*)cwin
);
644 param
.jumpto
=extl_table_is_bool_set(cwin
->proptab
, "jumpto");
645 param
.gravity
=ForgetGravity
;
647 CALL_ALT_B(succeeded
, clientwin_do_manage_alt
, (cwin
, ¶m
));
648 warn("WM_TRANSIENT_FOR changed for \"%s\".",
649 region_name((WRegion
*)cwin
));
651 warn(TR("Changes is WM_TRANSIENT_FOR property are unsupported."));
659 /*{{{ Unmanage/destroy */
662 static bool reparent_root(WClientWin
*cwin
)
664 XWindowAttributes attr
;
669 if(!XGetWindowAttributes(ioncore_g
.dpy
, cwin
->win
, &attr
))
672 par
=REGION_PARENT(cwin
);
675 x
=REGION_GEOM(cwin
).x
;
676 y
=REGION_GEOM(cwin
).y
;
678 int dr
=REGION_GEOM(par
).w
-REGION_GEOM(cwin
).w
-REGION_GEOM(cwin
).x
;
679 int db
=REGION_GEOM(par
).h
-REGION_GEOM(cwin
).h
-REGION_GEOM(cwin
).y
;
683 XTranslateCoordinates(ioncore_g
.dpy
, par
->win
, attr
.root
, 0, 0,
686 x
-=xgravity_deltax(cwin
->size_hints
.win_gravity
,
687 maxof(0, REGION_GEOM(cwin
).x
), dr
);
688 y
-=xgravity_deltay(cwin
->size_hints
.win_gravity
,
689 maxof(0, REGION_GEOM(cwin
).y
), db
);
692 XReparentWindow(ioncore_g
.dpy
, cwin
->win
, attr
.root
, x
, y
);
698 void clientwin_deinit(WClientWin
*cwin
)
703 region_pointer_focus_hack(&cwin
->region
);
705 xwindow_unmanaged_selectinput(cwin
->win
, 0);
706 XUnmapWindow(ioncore_g
.dpy
, cwin
->win
);
709 configure_cwin_bw(cwin
->win
, cwin
->orig_bw
);
711 if(reparent_root(cwin
)){
712 if(ioncore_g
.opmode
==IONCORE_OPMODE_DEINIT
){
713 XMapWindow(ioncore_g
.dpy
, cwin
->win
);
714 /* Make sure the topmost window has focus; it doesn't really
715 * matter which one has as long as some has.
717 xwindow_do_set_focus(cwin
->win
);
719 set_clientwin_state(cwin
, WithdrawnState
);
720 netwm_delete_state(cwin
);
724 XRemoveFromSaveSet(ioncore_g
.dpy
, cwin
->win
);
725 XDeleteContext(ioncore_g
.dpy
, cwin
->win
, ioncore_g
.win_context
);
728 clientwin_clear_colormaps(cwin
);
730 region_deinit((WRegion
*)cwin
);
735 static bool mrsh_u_c(WHookDummy
*fn
, void *param
)
741 static bool mrsh_u_extl(ExtlFn fn
, void *param
)
743 double d
=*(Window
*)param
;
744 extl_call(fn
, "d", NULL
, d
);
748 static void clientwin_do_unmapped(WClientWin
*cwin
, Window win
)
750 cwin
->flags
|=CLIENTWIN_UNMAP_RQ
;
752 /* First try a graceful chain-dispose */
753 if(!region_rqdispose((WRegion
*)cwin
)){
754 /* But force dispose anyway */
755 region_dispose((WRegion
*)cwin
);
758 hook_call(clientwin_unmapped_hook
, &win
, mrsh_u_c
, mrsh_u_extl
);
761 /* Used when the window was unmapped */
762 void clientwin_unmapped(WClientWin
*cwin
)
764 clientwin_do_unmapped(cwin
, cwin
->win
);
768 /* Used when the window was deastroyed */
769 void clientwin_destroyed(WClientWin
*cwin
)
771 Window win
=cwin
->win
;
772 XRemoveFromSaveSet(ioncore_g
.dpy
, cwin
->win
);
773 XDeleteContext(ioncore_g
.dpy
, cwin
->win
, ioncore_g
.win_context
);
774 xwindow_unmanaged_selectinput(cwin
->win
, 0);
776 clientwin_do_unmapped(cwin
, win
);
786 static bool send_clientmsg(Window win
, Atom a
, Time stmp
)
788 XClientMessageEvent ev
;
790 ev
.type
=ClientMessage
;
792 ev
.message_type
=ioncore_g
.atom_wm_protocols
;
797 return (XSendEvent(ioncore_g
.dpy
, win
, False
, 0L, (XEvent
*)&ev
)!=0);
802 * Attempt to kill (with \code{XKillWindow}) the client that owns
803 * the X window correspoding to \var{cwin}.
806 void clientwin_kill(WClientWin
*cwin
)
808 XKillClient(ioncore_g
.dpy
, cwin
->win
);
812 void clientwin_rqclose(WClientWin
*cwin
, bool relocate_ignored
)
814 /* Ignore relocate parameter -- client windows can always be
815 * destroyed by the application in any case, so way may just as
816 * well assume relocate is always set.
819 if(cwin
->flags
&CLIENTWIN_P_WM_DELETE
){
820 send_clientmsg(cwin
->win
, ioncore_g
.atom_wm_delete
,
821 ioncore_get_timestamp());
823 warn(TR("Client does not support the WM_DELETE protocol."));
831 /*{{{ State (hide/show) */
834 static void set_clientwin_state(WClientWin
*cwin
, int state
)
836 if(cwin
->state
!=state
){
838 xwindow_set_state_property(cwin
->win
, state
);
843 static void hide_clientwin(WClientWin
*cwin
)
845 region_pointer_focus_hack(&cwin
->region
);
847 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
){
848 XMoveWindow(ioncore_g
.dpy
, cwin
->win
,
849 -2*REGION_GEOM(cwin
).w
, -2*REGION_GEOM(cwin
).h
);
853 set_clientwin_state(cwin
, IconicState
);
854 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
855 cwin
->event_mask
&~(StructureNotifyMask
|EnterWindowMask
));
856 XUnmapWindow(ioncore_g
.dpy
, cwin
->win
);
857 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
861 static void show_clientwin(WClientWin
*cwin
)
863 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
){
864 XMoveWindow(ioncore_g
.dpy
, cwin
->win
,
865 REGION_GEOM(cwin
).x
, REGION_GEOM(cwin
).y
);
866 if(cwin
->state
==NormalState
)
870 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
871 cwin
->event_mask
&~(StructureNotifyMask
|EnterWindowMask
));
872 XMapWindow(ioncore_g
.dpy
, cwin
->win
);
873 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
874 set_clientwin_state(cwin
, NormalState
);
881 /*{{{ Resize/reparent/reconf helpers */
884 void clientwin_notify_rootpos(WClientWin
*cwin
, int rootx
, int rooty
)
894 ce
.xconfigure
.type
=ConfigureNotify
;
895 ce
.xconfigure
.event
=win
;
896 ce
.xconfigure
.window
=win
;
897 ce
.xconfigure
.x
=rootx
-cwin
->orig_bw
;
898 ce
.xconfigure
.y
=rooty
-cwin
->orig_bw
;
899 ce
.xconfigure
.width
=REGION_GEOM(cwin
).w
;
900 ce
.xconfigure
.height
=REGION_GEOM(cwin
).h
;
901 ce
.xconfigure
.border_width
=cwin
->orig_bw
;
902 ce
.xconfigure
.above
=None
;
903 ce
.xconfigure
.override_redirect
=False
;
905 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
&~StructureNotifyMask
);
906 XSendEvent(ioncore_g
.dpy
, win
, False
, StructureNotifyMask
, &ce
);
907 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
);
911 static void sendconfig_clientwin(WClientWin
*cwin
)
915 region_rootpos(&cwin
->region
, &rootx
, &rooty
);
916 clientwin_notify_rootpos(cwin
, rootx
, rooty
);
920 static void do_reparent_clientwin(WClientWin
*cwin
, Window win
, int x
, int y
)
922 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
923 cwin
->event_mask
&~StructureNotifyMask
);
924 XReparentWindow(ioncore_g
.dpy
, cwin
->win
, win
, x
, y
);
925 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
929 static void convert_geom(const WFitParams
*fp
,
930 WClientWin
*cwin
, WRectangle
*geom
)
932 WFitParams fptmp
=*fp
;
933 WSizePolicy szplcy
=SIZEPOLICY_FULL_EXACT
;
935 /*if(cwin->szplcy!=SIZEPOLICY_DEFAULT)
936 szplcy=cwin->szplcy;*/
938 sizepolicy(&szplcy
, (WRegion
*)cwin
, NULL
, REGION_RQGEOM_WEAK_ALL
, &fptmp
);
947 /*{{{ Region dynfuns */
950 static bool postpone_resize(WClientWin
*cwin
)
952 return cwin
->state
== IconicState
&& cwin
->flags
&CLIENTWIN_PROP_LAZY_RESIZE
;
956 static bool clientwin_fitrep(WClientWin
*cwin
, WWindow
*np
,
957 const WFitParams
*fp
)
963 if(np
!=NULL
&& !region_same_rootwin((WRegion
*)cwin
, (WRegion
*)np
))
966 if(fp
->mode
®ION_FIT_WHATEVER
){
969 geom
.w
=REGION_GEOM(cwin
).w
;
970 geom
.h
=REGION_GEOM(cwin
).h
;
975 changes
=(REGION_GEOM(cwin
).x
!=geom
.x
||
976 REGION_GEOM(cwin
).y
!=geom
.y
||
977 REGION_GEOM(cwin
).w
!=geom
.w
||
978 REGION_GEOM(cwin
).h
!=geom
.h
);
980 if(np
==NULL
&& !changes
)
984 region_unset_parent((WRegion
*)cwin
);
987 * update netwm properties before mapping, because some apps check the
988 * netwm state directly when mapped.
990 * also, update netwm properties after setting the parent, because
991 * the new state of _NET_WM_STATE_FULLSCREEN is determined based on
992 * the parent of the cwin.
994 region_set_parent((WRegion
*)cwin
, np
);
995 netwm_update_state(cwin
);
997 do_reparent_clientwin(cwin
, np
->win
, geom
.x
, geom
.y
);
998 sendconfig_clientwin(cwin
);
1000 if(!REGION_IS_FULLSCREEN(cwin
))
1001 cwin
->flags
&=~CLIENTWIN_FS_RQ
;
1004 if (postpone_resize(cwin
))
1007 REGION_GEOM(cwin
)=geom
;
1012 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
&& !REGION_IS_MAPPED(cwin
)){
1013 XMoveResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1014 -2*REGION_GEOM(cwin
).w
, -2*REGION_GEOM(cwin
).h
,
1017 XMoveResizeWindow(ioncore_g
.dpy
, cwin
->win
, geom
.x
, geom
.y
, w
, h
);
1020 cwin
->flags
&=~CLIENTWIN_NEED_CFGNTFY
;
1026 static void clientwin_map(WClientWin
*cwin
)
1028 show_clientwin(cwin
);
1029 REGION_MARK_MAPPED(cwin
);
1033 static void clientwin_unmap(WClientWin
*cwin
)
1035 hide_clientwin(cwin
);
1036 REGION_MARK_UNMAPPED(cwin
);
1040 static void clientwin_do_set_focus(WClientWin
*cwin
, bool warp
)
1042 if(cwin
->flags
&CLIENTWIN_P_WM_TAKE_FOCUS
){
1043 Time stmp
=ioncore_get_timestamp();
1044 region_finalise_focusing((WRegion
*)cwin
, cwin
->win
, warp
, stmp
);
1045 send_clientmsg(cwin
->win
, ioncore_g
.atom_wm_take_focus
, stmp
);
1047 region_finalise_focusing((WRegion
*)cwin
, cwin
->win
, warp
, CurrentTime
);
1050 XSync(ioncore_g
.dpy
, 0);
1054 void clientwin_restack(WClientWin
*cwin
, Window other
, int mode
)
1056 xwindow_restack(cwin
->win
, other
, mode
);
1060 void clientwin_stacking(WClientWin
*cwin
, Window
*bottomret
, Window
*topret
)
1062 *bottomret
=cwin
->win
;
1067 static Window
clientwin_x_window(WClientWin
*cwin
)
1073 static void clientwin_activated(WClientWin
*cwin
)
1075 clientwin_install_colormap(cwin
);
1079 static void clientwin_size_hints(WClientWin
*cwin
, WSizeHints
*hints_ret
)
1081 if(cwin
->flags
&CLIENTWIN_FS_RQ
){
1082 /* Do not use size hints, when full screen mode has been
1083 * requested by the client window itself.
1085 sizehints_clear(hints_ret
);
1087 xsizehints_to_sizehints(&cwin
->size_hints
, hints_ret
);
1092 static int clientwin_orientation(WClientWin
*cwin
)
1094 return (cwin
->flags
&CLIENTWIN_PROP_O_VERT
1095 ? REGION_ORIENTATION_VERTICAL
1096 : (cwin
->flags
&CLIENTWIN_PROP_O_HORIZ
1097 ? REGION_ORIENTATION_HORIZONTAL
1098 : REGION_ORIENTATION_NONE
));
1105 /*{{{ Identity & lookup */
1109 * Returns a table containing the properties \code{WM_CLASS} (table entries
1110 * \var{instance} and \var{class}) and \code{WM_WINDOW_ROLE} (\var{role})
1111 * properties for \var{cwin}. If a property is not set, the corresponding
1112 * field(s) are unset in the table.
1116 ExtlTab
clientwin_get_ident(WClientWin
*cwin
)
1118 char **p
=NULL
, **p2
=NULL
, *wrole
=NULL
;
1119 int n
=0, n2
=0, n3
=0, tmp
=0;
1120 Window tforwin
=None
;
1122 bool dockapp_hack
=FALSE
;
1124 p
=xwindow_get_text_property(cwin
->win
, XA_WM_CLASS
, &n
);
1126 p2
=xwindow_get_text_property(cwin
->win
, ioncore_g
.atom_dockapp_hack
, &n2
);
1128 dockapp_hack
=(n2
>0);
1131 /* Some dockapps do actually have WM_CLASS, so use it. */
1137 wrole
=xwindow_get_string_property(cwin
->win
, ioncore_g
.atom_wm_window_role
,
1140 tab
=extl_create_table();
1141 if(n
>=2 && p
[1]!=NULL
)
1142 extl_table_sets_s(tab
, "class", p
[1]);
1143 if(n
>=1 && p
[0]!=NULL
)
1144 extl_table_sets_s(tab
, "instance", p
[0]);
1146 extl_table_sets_s(tab
, "role", wrole
);
1148 if(XGetTransientForHint(ioncore_g
.dpy
, cwin
->win
, &tforwin
)
1150 extl_table_sets_b(tab
, "is_transient", TRUE
);
1154 extl_table_sets_b(tab
, "is_dockapp", TRUE
);
1159 XFreeStringList(p2
);
1170 /*{{{ ConfigureRequest */
1173 static bool check_fs_cfgrq(WClientWin
*cwin
, XConfigureRequestEvent
*ev
)
1175 /* check full screen request */
1176 if((ev
->value_mask
&(CWWidth
|CWHeight
))==(CWWidth
|CWHeight
)){
1177 WRegion
*grp
=region_groupleader_of((WRegion
*)cwin
);
1178 WScreen
*scr
=clientwin_fullscreen_chkrq(cwin
, ev
->width
, ev
->height
);
1180 if(scr
!=NULL
&& REGION_MANAGER(grp
)!=(WRegion
*)scr
){
1181 bool sw
=clientwin_fullscreen_may_switchto(cwin
);
1183 cwin
->flags
|=CLIENTWIN_FS_RQ
;
1185 if(!region_fullscreen_scr(grp
, scr
, sw
))
1186 cwin
->flags
&=~CLIENTWIN_FS_RQ
;
1195 WMPlex
* find_mplexer(WRegion
*cwin
)
1199 if(obj_is((Obj
*)cwin
, &CLASSDESCR(WMPlex
)))
1200 return (WMPlex
*) cwin
;
1201 return find_mplexer(cwin
->manager
);
1204 /* Returns whether anything was actually changed. */
1205 static bool check_normal_cfgrq(WClientWin
*cwin
, XConfigureRequestEvent
*ev
)
1207 bool result
= FALSE
;
1209 if(ev
->value_mask
&(CWX
|CWY
|CWWidth
|CWHeight
)){
1210 WRQGeomParams rq
=RQGEOMPARAMS_INIT
;
1213 rq
.flags
=REGION_RQGEOM_WEAK_ALL
|REGION_RQGEOM_ABSOLUTE
;
1215 if(cwin
->size_hints
.flags
&PWinGravity
){
1216 rq
.flags
|=REGION_RQGEOM_GRAVITY
;
1217 rq
.gravity
=cwin
->size_hints
.win_gravity
;
1220 /* Do I need to insert another disparaging comment on the person who
1221 * invented special server-supported window borders that are not
1222 * accounted for in the window size? Keep it simple, stupid!
1224 if(cwin
->size_hints
.flags
&PWinGravity
){
1225 gdx
=xgravity_deltax(cwin
->size_hints
.win_gravity
,
1226 -cwin
->orig_bw
, -cwin
->orig_bw
);
1227 gdy
=xgravity_deltay(cwin
->size_hints
.win_gravity
,
1228 -cwin
->orig_bw
, -cwin
->orig_bw
);
1231 region_rootpos((WRegion
*)cwin
, &(rq
.geom
.x
), &(rq
.geom
.y
));
1232 rq
.geom
.w
=REGION_GEOM(cwin
).w
;
1233 rq
.geom
.h
=REGION_GEOM(cwin
).h
;
1235 if(ev
->value_mask
&CWWidth
){
1236 /* If x was not changed, keep reference point where it was */
1237 if(cwin
->size_hints
.flags
&PWinGravity
){
1238 rq
.geom
.x
+=xgravity_deltax(cwin
->size_hints
.win_gravity
, 0,
1239 ev
->width
-rq
.geom
.w
);
1241 rq
.geom
.w
=maxof(ev
->width
, 1);
1242 rq
.flags
&=~REGION_RQGEOM_WEAK_W
;
1244 if(ev
->value_mask
&CWHeight
){
1245 /* If y was not changed, keep reference point where it was */
1246 if(cwin
->size_hints
.flags
&PWinGravity
){
1247 rq
.geom
.y
+=xgravity_deltay(cwin
->size_hints
.win_gravity
, 0,
1248 ev
->height
-rq
.geom
.h
);
1250 rq
.geom
.h
=maxof(ev
->height
, 1);
1251 rq
.flags
&=~REGION_RQGEOM_WEAK_H
;
1253 if(ev
->value_mask
&CWX
){
1254 rq
.geom
.x
=ev
->x
+gdx
;
1255 rq
.flags
&=~REGION_RQGEOM_WEAK_X
;
1256 rq
.flags
&=~REGION_RQGEOM_WEAK_W
;
1258 if(ev
->value_mask
&CWY
){
1259 rq
.geom
.y
=ev
->y
+gdy
;
1260 rq
.flags
&=~REGION_RQGEOM_WEAK_Y
;
1261 rq
.flags
&=~REGION_RQGEOM_WEAK_H
;
1264 region_rqgeom((WRegion
*)cwin
, &rq
, NULL
);
1269 if(ev
->value_mask
&CWStackMode
){
1272 region_set_activity((WRegion
*) cwin
, SETPARAM_SET
);
1273 /* TODO we should be more conservative here - but what does/should
1274 * region_set_activity return? */
1290 void clientwin_handle_configure_request(WClientWin
*cwin
,
1291 XConfigureRequestEvent
*ev
)
1293 if(ev
->value_mask
&CWBorderWidth
)
1294 cwin
->orig_bw
=ev
->border_width
;
1296 cwin
->flags
|=CLIENTWIN_NEED_CFGNTFY
;
1298 if(!(cwin
->flags
&CLIENTWIN_PROP_IGNORE_CFGRQ
)){
1299 if(!check_fs_cfgrq(cwin
, ev
))
1300 check_normal_cfgrq(cwin
, ev
);
1303 if(cwin
->flags
&CLIENTWIN_NEED_CFGNTFY
){
1304 sendconfig_clientwin(cwin
);
1305 cwin
->flags
&=~CLIENTWIN_NEED_CFGNTFY
;
1317 * Attempts to fix window size problems with non-ICCCM compliant
1321 void clientwin_nudge(WClientWin
*cwin
)
1323 XResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1324 2*REGION_GEOM(cwin
).w
, 2*REGION_GEOM(cwin
).h
);
1325 XFlush(ioncore_g
.dpy
);
1326 XResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1327 REGION_GEOM(cwin
).w
, REGION_GEOM(cwin
).h
);
1338 * Return the X window id for the client window.
1342 double clientwin_xid(WClientWin
*cwin
)
1354 static int last_checkcode
=1;
1357 static ExtlTab
clientwin_get_configuration(WClientWin
*cwin
)
1361 SMCfgCallback
*cfg_cb
;
1362 SMAddCallback
*add_cb
;
1364 tab
=region_get_base_configuration((WRegion
*)cwin
);
1366 extl_table_sets_d(tab
, "windowid", (double)(cwin
->win
));
1368 if(last_checkcode
!=0){
1369 chkc
=last_checkcode
++;
1370 xwindow_set_integer_property(cwin
->win
, ioncore_g
.atom_checkcode
,
1372 extl_table_sets_i(tab
, "checkcode", chkc
);
1375 ioncore_get_sm_callbacks(&add_cb
, &cfg_cb
);
1384 static void do_sm(ExtlTab tab
)
1386 SMAddCallback
*add_cb
;
1387 SMCfgCallback
*cfg_cb
;
1390 ioncore_get_sm_callbacks(&add_cb
, &cfg_cb
);
1393 ph
=ioncore_get_load_pholder();
1396 if(!add_cb(ph
, tab
))
1397 destroy_obj((Obj
*)ph
);
1403 WRegion
*clientwin_load(WWindow
*par
, const WFitParams
*fp
, ExtlTab tab
)
1407 int chkc
=0, real_chkc
=0;
1408 WClientWin
*cwin
=NULL
;
1409 XWindowAttributes attr
;
1411 bool got_chkc
=FALSE
;
1413 if(!extl_table_gets_d(tab
, "windowid", &wind
) ||
1414 !extl_table_gets_i(tab
, "checkcode", &chkc
)){
1420 if(XWINDOW_REGION_OF(win
)!=NULL
){
1421 warn("Client window %x already managed.", win
);
1425 got_chkc
=xwindow_get_integer_property(win
, ioncore_g
.atom_checkcode
,
1428 if(!got_chkc
|| real_chkc
!=chkc
){
1435 if(!XGetWindowAttributes(ioncore_g
.dpy
, win
, &attr
)){
1436 warn(TR("Window %#x disappeared."), win
);
1440 if(attr
.root
!=region_root_of((WRegion
*)par
))
1443 if(attr
.override_redirect
||
1444 (ioncore_g
.opmode
==IONCORE_OPMODE_INIT
&& attr
.map_state
!=IsViewable
)){
1445 warn(TR("Saved client window does not want to be managed."));
1449 cwin
=create_clientwin(par
, win
, &attr
);
1454 /* Reparent and resize taking limits set by size hints into account */
1455 convert_geom(fp
, cwin
, &rg
);
1456 REGION_GEOM(cwin
)=rg
;
1457 do_reparent_clientwin(cwin
, par
->win
, rg
.x
, rg
.y
);
1458 XResizeWindow(ioncore_g
.dpy
, win
, maxof(1, rg
.w
), maxof(1, rg
.h
));
1460 if(!postmanage_check(cwin
, &attr
)){
1461 clientwin_destroyed(cwin
);
1465 return (WRegion
*)cwin
;
1472 /*{{{ Dynfuntab and class info */
1475 static DynFunTab clientwin_dynfuntab
[]={
1476 {(DynFun
*)region_fitrep
,
1477 (DynFun
*)clientwin_fitrep
},
1485 {region_do_set_focus
,
1486 clientwin_do_set_focus
},
1488 {region_notify_rootpos
,
1489 clientwin_notify_rootpos
},
1495 clientwin_stacking
},
1497 {(DynFun
*)region_xwindow
,
1498 (DynFun
*)clientwin_x_window
},
1501 clientwin_activated
},
1504 clientwin_size_hints
},
1506 {(DynFun
*)region_orientation
,
1507 (DynFun
*)clientwin_orientation
},
1509 {(DynFun
*)region_rqclose
,
1510 (DynFun
*)clientwin_rqclose
},
1512 {(DynFun
*)region_get_configuration
,
1513 (DynFun
*)clientwin_get_configuration
},
1520 IMPLCLASS(WClientWin
, WRegion
, clientwin_deinit
, clientwin_dynfuntab
);