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
;
210 void clientwin_get_input_wm_hint(WClientWin
*cwin
)
214 hints
=XGetWMHints(ioncore_g
.dpy
, cwin
->win
);
216 cwin
->flags
|=CLIENTWIN_SET_INPUT
;
218 if(hints
->flags
&InputHint
&& !hints
->input
)
219 cwin
->flags
&=~CLIENTWIN_SET_INPUT
;
224 void clientwin_get_set_name(WClientWin
*cwin
)
230 list
=netwm_get_name(cwin
);
233 list
=xwindow_get_text_property(cwin
->win
, XA_WM_NAME
, &n
);
235 cwin
->flags
|=CLIENTWIN_USE_NET_WM_NAME
;
239 /* Special condition kludge: property exists, but couldn't
240 * be converted to a string list.
242 clientwin_set_name(cwin
, (n
==-1 ? "???" : NULL
));
244 clientwin_set_name(cwin
, *list
);
245 XFreeStringList(list
);
250 /* Some standard winprops */
253 bool clientwin_get_switchto(const WClientWin
*cwin
)
257 if(ioncore_g
.opmode
==IONCORE_OPMODE_INIT
)
260 if(extl_table_gets_b(cwin
->proptab
, "switchto", &b
))
263 return ioncore_g
.switchto_new
;
267 int clientwin_get_transient_mode(const WClientWin
*cwin
)
270 int mode
=TRANSIENT_MODE_NORMAL
;
272 if(extl_table_gets_s(cwin
->proptab
, "transient_mode", &s
)){
273 if(strcmp(s
, "current")==0)
274 mode
=TRANSIENT_MODE_CURRENT
;
275 else if(strcmp(s
, "off")==0)
276 mode
=TRANSIENT_MODE_OFF
;
286 /*{{{ Manage/create */
289 static void configure_cwin_bw(Window win
, int bw
)
292 ulong wcmask
=CWBorderWidth
;
295 XConfigureWindow(ioncore_g
.dpy
, win
, wcmask
, &wc
);
299 static void set_sane_gravity(Window win
)
301 XSetWindowAttributes attr
;
303 attr
.win_gravity
=NorthWestGravity
;
305 XChangeWindowAttributes(ioncore_g
.dpy
, win
,
306 CWWinGravity
, &attr
);
310 static bool clientwin_init(WClientWin
*cwin
, WWindow
*par
, Window win
,
311 XWindowAttributes
*attr
)
317 cwin
->state
=WithdrawnState
;
323 fp
.mode
=REGION_FIT_EXACT
;
325 /* The idiot who invented special server-supported window borders that
326 * are not accounted for in the window size should be "taken behind a
329 cwin
->orig_bw
=attr
->border_width
;
330 configure_cwin_bw(cwin
->win
, 0);
331 if(cwin
->orig_bw
!=0 && cwin
->size_hints
.flags
&PWinGravity
){
332 fp
.g
.x
+=xgravity_deltax(cwin
->size_hints
.win_gravity
,
333 -cwin
->orig_bw
, -cwin
->orig_bw
);
334 fp
.g
.y
+=xgravity_deltay(cwin
->size_hints
.win_gravity
,
335 -cwin
->orig_bw
, -cwin
->orig_bw
);
338 set_sane_gravity(cwin
->win
);
341 cwin
->cmap
=attr
->colormap
;
345 cwin
->event_mask
=IONCORE_EVENTMASK_CLIENTWIN
;
347 region_init(&(cwin
->region
), par
, &fp
);
349 cwin
->region
.flags
|=REGION_GRAB_ON_PARENT
;
350 region_add_bindmap(&cwin
->region
, ioncore_clientwin_bindmap
);
352 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
);
354 clientwin_register(cwin
);
355 clientwin_get_set_name(cwin
);
356 clientwin_get_colormaps(cwin
);
357 clientwin_get_protocols(cwin
);
358 clientwin_get_winprops(cwin
);
359 clientwin_get_size_hints(cwin
);
360 clientwin_get_input_wm_hint(cwin
);
362 netwm_update_allowed_actions(cwin
);
364 XSaveContext(ioncore_g
.dpy
, win
, ioncore_g
.win_context
, (XPointer
)cwin
);
365 XAddToSaveSet(ioncore_g
.dpy
, win
);
371 static WClientWin
*create_clientwin(WWindow
*par
, Window win
,
372 XWindowAttributes
*attr
)
374 CREATEOBJ_IMPL(WClientWin
, clientwin
, (p
, par
, win
, attr
));
379 WClientWin
*clientwin_get_transient_for(const WClientWin
*cwin
)
382 WClientWin
*tfor
=NULL
;
384 if(clientwin_get_transient_mode(cwin
)!=TRANSIENT_MODE_NORMAL
)
387 if(!XGetTransientForHint(ioncore_g
.dpy
, cwin
->win
, &tforwin
))
393 tfor
=XWINDOW_REGION_OF_T(tforwin
, WClientWin
);
396 warn(TR("The transient_for hint for \"%s\" points to itself."),
397 region_name((WRegion
*)cwin
));
398 }else if(tfor
==NULL
){
399 if(xwindow_region_of(tforwin
)!=NULL
){
400 warn(TR("Client window \"%s\" has broken transient_for hint. "
401 "(\"Extended WM hints\" multi-parent brain damage?)"),
402 region_name((WRegion
*)cwin
));
404 }else if(!region_same_rootwin((WRegion
*)cwin
, (WRegion
*)tfor
)){
405 warn(TR("The transient_for window for \"%s\" is not on the same "
406 "screen."), region_name((WRegion
*)cwin
));
415 static bool postmanage_check(WClientWin
*cwin
, XWindowAttributes
*attr
)
417 /* Check that the window exists. The previous check and selectinput
418 * do not seem to catch all cases of window destroyal.
420 XSync(ioncore_g
.dpy
, False
);
422 if(XGetWindowAttributes(ioncore_g
.dpy
, cwin
->win
, attr
))
425 warn(TR("Window %#x disappeared."), cwin
->win
);
431 static bool do_manage_mrsh(bool (*fn
)(WClientWin
*cwin
, WManageParams
*pm
),
434 return fn((WClientWin
*)p
[0], (WManageParams
*)p
[1]);
439 static bool do_manage_mrsh_extl(ExtlFn fn
, void **p
)
441 WClientWin
*cwin
=(WClientWin
*)p
[0];
442 WManageParams
*mp
=(WManageParams
*)p
[1];
443 ExtlTab t
=manageparams_to_table(mp
);
446 extl_call(fn
, "ot", "b", cwin
, t
, &ret
);
450 return (ret
&& REGION_MANAGER(cwin
)!=NULL
);
454 /* This is called when a window is mapped on the root window.
455 * We want to check if we should manage the window and how and
458 WClientWin
* ioncore_manage_clientwin(Window win
, bool maprq
)
461 WClientWin
*cwin
=NULL
;
462 XWindowAttributes attr
;
464 int init_state
=NormalState
;
465 WManageParams param
=MANAGEPARAMS_INIT
;
470 /* Is the window already being managed? */
471 cwin
=XWINDOW_REGION_OF_T(win
, WClientWin
);
475 /* Select for UnmapNotify and DestroyNotify as the
476 * window might get destroyed or unmapped in the meanwhile.
478 xwindow_unmanaged_selectinput(win
, StructureNotifyMask
);
480 if(!XGetWindowAttributes(ioncore_g
.dpy
, win
, &attr
)){
482 warn(TR("Window %#x disappeared."), win
);
488 hints
=XGetWMHints(ioncore_g
.dpy
, win
);
491 if(hints
->flags
&StateHint
)
492 init_state
=hints
->initial_state
;
494 if(!param
.dockapp
&& init_state
==WithdrawnState
&&
495 hints
->flags
&IconWindowHint
&& hints
->icon_window
!=None
){
496 Window icon_win
=hints
->icon_window
;
497 XWindowAttributes icon_attr
;
499 if(!XGetWindowAttributes(ioncore_g
.dpy
, icon_win
, &icon_attr
)){
501 warn(TR("Window %#x disappeared."), win
);
507 if(attr
.map_state
==IsViewable
){
508 /* The dockapp might be displaying its "main" window if no
509 * wm that understands dockapps has been managing it.
511 XUnmapWindow(ioncore_g
.dpy
, win
);
514 /* Main window is unmapped on initial scan, but icon window
515 * is mapped. Let's hope it's a dockapp left by e.g. us.
517 if(icon_attr
.map_state
==IsViewable
)
528 xwindow_unmanaged_selectinput(win
, 0);
529 xwindow_unmanaged_selectinput(icon_win
, StructureNotifyMask
);
531 /* Copy WM_CLASS as _ION_DOCKAPP_HACK */
533 p
=xwindow_get_text_property(win
, XA_WM_CLASS
, &n
);
536 xwindow_set_text_property(icon_win
, ioncore_g
.atom_dockapp_hack
,
537 (const char **)p
, n
);
540 const char *pdummy
[2]={"unknowndockapp", "UnknownDockapp"};
541 xwindow_set_text_property(icon_win
, ioncore_g
.atom_dockapp_hack
,
553 /* Do we really want to manage it? */
554 if(!param
.dockapp
&& (attr
.override_redirect
||
555 (!maprq
&& attr
.map_state
!=IsViewable
))){
559 attr
.width
=maxof(attr
.width
, 1);
560 attr
.height
=maxof(attr
.height
, 1);
562 /* Find root window */
563 FOR_ALL_ROOTWINS(rootwin
){
564 if(WROOTWIN_ROOT(rootwin
)==attr
.root
)
569 warn(TR("Unable to find a matching root window!"));
573 /* Allocate and initialize */
574 cwin
=create_clientwin((WWindow
*)rootwin
, win
, &attr
);
581 param
.geom
=REGION_GEOM(cwin
);
583 param
.jumpto
=extl_table_is_bool_set(cwin
->proptab
, "jumpto");
584 param
.switchto
=(init_state
!=IconicState
&&
585 (param
.jumpto
|| clientwin_get_switchto(cwin
)));
586 param
.gravity
=(cwin
->size_hints
.flags
&PWinGravity
587 ? cwin
->size_hints
.win_gravity
589 param
.tfor
=clientwin_get_transient_for(cwin
);
591 if(!extl_table_gets_b(cwin
->proptab
, "userpos", ¶m
.userpos
))
592 param
.userpos
=(cwin
->size_hints
.flags
&USPosition
);
594 if(cwin
->flags
&SIZEHINT_PROPS
){
595 /* If size hints have been messed with, readjust requested geometry
596 * here. If programs themselves give incompatible geometries and
597 * things don't look good then, it's their fault.
599 region_size_hints_correct((WRegion
*)cwin
, ¶m
.geom
.w
, ¶m
.geom
.h
,
604 netwm_check_manage_user_time(cwin
, ¶m
);
609 if(!hook_call_alt(clientwin_do_manage_alt
, &mrshpm
,
610 (WHookMarshall
*)do_manage_mrsh
,
611 (WHookMarshallExtl
*)do_manage_mrsh_extl
)){
612 warn(TR("Unable to manage client window %#x."), win
);
616 if(ioncore_g
.opmode
==IONCORE_OPMODE_NORMAL
&&
617 !region_is_fully_mapped((WRegion
*)cwin
) &&
618 !region_skip_focus((WRegion
*)cwin
)){
619 region_set_activity((WRegion
*)cwin
, SETPARAM_SET
);
622 if(postmanage_check(cwin
, &attr
)){
623 /* Check for focus_next==NULL does not play nicely with
624 * pointer_focus_hack.
626 /*if(param.jumpto && ioncore_g.focus_next==NULL)*/
627 if(param
.jumpto
&& !region_manager_is_focusnext((WRegion
*)cwin
))
628 region_goto((WRegion
*)cwin
);
629 hook_call_o(clientwin_mapped_hook
, (Obj
*)cwin
);
634 clientwin_destroyed(cwin
);
638 xwindow_unmanaged_selectinput(win
, 0);
643 void clientwin_tfor_changed(WClientWin
*cwin
)
646 WManageParams param
=MANAGEPARAMS_INIT
;
647 bool succeeded
=FALSE
;
648 param
.tfor
=clientwin_get_transient_for(cwin
);
652 region_rootpos((WRegion
*)cwin
, &(param
.geom
.x
), &(param
.geom
.y
));
653 param
.geom
.w
=REGION_GEOM(cwin
).w
;
654 param
.geom
.h
=REGION_GEOM(cwin
).h
;
657 param
.switchto
=region_may_control_focus((WRegion
*)cwin
);
658 param
.jumpto
=extl_table_is_bool_set(cwin
->proptab
, "jumpto");
659 param
.gravity
=ForgetGravity
;
661 CALL_ALT_B(succeeded
, clientwin_do_manage_alt
, (cwin
, ¶m
));
662 warn("WM_TRANSIENT_FOR changed for \"%s\".",
663 region_name((WRegion
*)cwin
));
665 warn(TR("Changes is WM_TRANSIENT_FOR property are unsupported."));
673 /*{{{ Unmanage/destroy */
676 static bool reparent_root(WClientWin
*cwin
)
678 XWindowAttributes attr
;
683 if(!XGetWindowAttributes(ioncore_g
.dpy
, cwin
->win
, &attr
))
686 par
=REGION_PARENT(cwin
);
689 x
=REGION_GEOM(cwin
).x
;
690 y
=REGION_GEOM(cwin
).y
;
692 int dr
=REGION_GEOM(par
).w
-REGION_GEOM(cwin
).w
-REGION_GEOM(cwin
).x
;
693 int db
=REGION_GEOM(par
).h
-REGION_GEOM(cwin
).h
-REGION_GEOM(cwin
).y
;
697 XTranslateCoordinates(ioncore_g
.dpy
, par
->win
, attr
.root
, 0, 0,
700 x
-=xgravity_deltax(cwin
->size_hints
.win_gravity
,
701 maxof(0, REGION_GEOM(cwin
).x
), dr
);
702 y
-=xgravity_deltay(cwin
->size_hints
.win_gravity
,
703 maxof(0, REGION_GEOM(cwin
).y
), db
);
706 XReparentWindow(ioncore_g
.dpy
, cwin
->win
, attr
.root
, x
, y
);
712 void clientwin_deinit(WClientWin
*cwin
)
717 region_pointer_focus_hack(&cwin
->region
);
719 xwindow_unmanaged_selectinput(cwin
->win
, 0);
720 XUnmapWindow(ioncore_g
.dpy
, cwin
->win
);
723 configure_cwin_bw(cwin
->win
, cwin
->orig_bw
);
725 if(reparent_root(cwin
)){
726 if(ioncore_g
.opmode
==IONCORE_OPMODE_DEINIT
){
727 XMapWindow(ioncore_g
.dpy
, cwin
->win
);
728 /* Make sure the topmost window has focus; it doesn't really
729 * matter which one has as long as some has.
731 xwindow_do_set_focus(cwin
->win
);
733 set_clientwin_state(cwin
, WithdrawnState
);
734 netwm_delete_state(cwin
);
738 XRemoveFromSaveSet(ioncore_g
.dpy
, cwin
->win
);
739 XDeleteContext(ioncore_g
.dpy
, cwin
->win
, ioncore_g
.win_context
);
742 clientwin_clear_colormaps(cwin
);
744 region_deinit((WRegion
*)cwin
);
749 static bool mrsh_u_c(WHookDummy
*fn
, void *param
)
755 static bool mrsh_u_extl(ExtlFn fn
, void *param
)
757 double d
=*(Window
*)param
;
758 extl_call(fn
, "d", NULL
, d
);
762 static void clientwin_do_unmapped(WClientWin
*cwin
, Window win
)
764 cwin
->flags
|=CLIENTWIN_UNMAP_RQ
;
766 /* First try a graceful chain-dispose */
767 if(!region_rqdispose((WRegion
*)cwin
)){
768 /* But force dispose anyway */
769 region_dispose((WRegion
*)cwin
);
772 hook_call(clientwin_unmapped_hook
, &win
, mrsh_u_c
, mrsh_u_extl
);
775 /* Used when the window was unmapped */
776 void clientwin_unmapped(WClientWin
*cwin
)
778 clientwin_do_unmapped(cwin
, cwin
->win
);
782 /* Used when the window was deastroyed */
783 void clientwin_destroyed(WClientWin
*cwin
)
785 Window win
=cwin
->win
;
786 XRemoveFromSaveSet(ioncore_g
.dpy
, cwin
->win
);
787 XDeleteContext(ioncore_g
.dpy
, cwin
->win
, ioncore_g
.win_context
);
788 xwindow_unmanaged_selectinput(cwin
->win
, 0);
790 clientwin_do_unmapped(cwin
, win
);
800 static bool send_clientmsg(Window win
, Atom a
, Time stmp
)
802 XClientMessageEvent ev
;
804 ev
.type
=ClientMessage
;
806 ev
.message_type
=ioncore_g
.atom_wm_protocols
;
811 return (XSendEvent(ioncore_g
.dpy
, win
, False
, 0L, (XEvent
*)&ev
)!=0);
816 * Attempt to kill (with \code{XKillWindow}) the client that owns
817 * the X window correspoding to \var{cwin}.
820 void clientwin_kill(WClientWin
*cwin
)
822 XKillClient(ioncore_g
.dpy
, cwin
->win
);
826 void clientwin_rqclose(WClientWin
*cwin
, bool relocate_ignored
)
828 /* Ignore relocate parameter -- client windows can always be
829 * destroyed by the application in any case, so way may just as
830 * well assume relocate is always set.
833 if(cwin
->flags
&CLIENTWIN_P_WM_DELETE
){
834 send_clientmsg(cwin
->win
, ioncore_g
.atom_wm_delete
,
835 ioncore_get_timestamp());
837 warn(TR("Client does not support the WM_DELETE protocol."));
845 /*{{{ State (hide/show) */
848 static void set_clientwin_state(WClientWin
*cwin
, int state
)
850 if(cwin
->state
!=state
){
852 xwindow_set_state_property(cwin
->win
, state
);
857 static void hide_clientwin(WClientWin
*cwin
)
859 region_pointer_focus_hack(&cwin
->region
);
861 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
){
862 XMoveWindow(ioncore_g
.dpy
, cwin
->win
,
863 -2*REGION_GEOM(cwin
).w
, -2*REGION_GEOM(cwin
).h
);
867 set_clientwin_state(cwin
, IconicState
);
868 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
869 cwin
->event_mask
&~(StructureNotifyMask
|EnterWindowMask
));
870 XUnmapWindow(ioncore_g
.dpy
, cwin
->win
);
871 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
875 static void show_clientwin(WClientWin
*cwin
)
877 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
){
878 XMoveWindow(ioncore_g
.dpy
, cwin
->win
,
879 REGION_GEOM(cwin
).x
, REGION_GEOM(cwin
).y
);
880 if(cwin
->state
==NormalState
)
884 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
885 cwin
->event_mask
&~(StructureNotifyMask
|EnterWindowMask
));
886 XMapWindow(ioncore_g
.dpy
, cwin
->win
);
887 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
888 set_clientwin_state(cwin
, NormalState
);
895 /*{{{ Resize/reparent/reconf helpers */
898 void clientwin_notify_rootpos(WClientWin
*cwin
, int rootx
, int rooty
)
908 ce
.xconfigure
.type
=ConfigureNotify
;
909 ce
.xconfigure
.event
=win
;
910 ce
.xconfigure
.window
=win
;
911 ce
.xconfigure
.x
=rootx
-cwin
->orig_bw
;
912 ce
.xconfigure
.y
=rooty
-cwin
->orig_bw
;
913 ce
.xconfigure
.width
=REGION_GEOM(cwin
).w
;
914 ce
.xconfigure
.height
=REGION_GEOM(cwin
).h
;
915 ce
.xconfigure
.border_width
=cwin
->orig_bw
;
916 ce
.xconfigure
.above
=None
;
917 ce
.xconfigure
.override_redirect
=False
;
919 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
&~StructureNotifyMask
);
920 XSendEvent(ioncore_g
.dpy
, win
, False
, StructureNotifyMask
, &ce
);
921 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
);
925 static void sendconfig_clientwin(WClientWin
*cwin
)
929 region_rootpos(&cwin
->region
, &rootx
, &rooty
);
930 clientwin_notify_rootpos(cwin
, rootx
, rooty
);
934 static void do_reparent_clientwin(WClientWin
*cwin
, Window win
, int x
, int y
)
936 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
937 cwin
->event_mask
&~StructureNotifyMask
);
938 XReparentWindow(ioncore_g
.dpy
, cwin
->win
, win
, x
, y
);
939 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
943 static void convert_geom(const WFitParams
*fp
,
944 WClientWin
*cwin
, WRectangle
*geom
)
946 WFitParams fptmp
=*fp
;
947 WSizePolicy szplcy
=SIZEPOLICY_FULL_EXACT
;
949 /*if(cwin->szplcy!=SIZEPOLICY_DEFAULT)
950 szplcy=cwin->szplcy;*/
952 sizepolicy(&szplcy
, (WRegion
*)cwin
, NULL
, REGION_RQGEOM_WEAK_ALL
, &fptmp
);
961 /*{{{ Region dynfuns */
964 static bool postpone_resize(WClientWin
*cwin
)
966 return cwin
->state
== IconicState
&& cwin
->flags
&CLIENTWIN_PROP_LAZY_RESIZE
;
970 static bool clientwin_fitrep(WClientWin
*cwin
, WWindow
*np
,
971 const WFitParams
*fp
)
977 if(np
!=NULL
&& !region_same_rootwin((WRegion
*)cwin
, (WRegion
*)np
))
980 if(fp
->mode
®ION_FIT_WHATEVER
){
983 geom
.w
=REGION_GEOM(cwin
).w
;
984 geom
.h
=REGION_GEOM(cwin
).h
;
989 changes
=(REGION_GEOM(cwin
).x
!=geom
.x
||
990 REGION_GEOM(cwin
).y
!=geom
.y
||
991 REGION_GEOM(cwin
).w
!=geom
.w
||
992 REGION_GEOM(cwin
).h
!=geom
.h
);
994 if(np
==NULL
&& !changes
)
998 region_unset_parent((WRegion
*)cwin
);
1001 * update netwm properties before mapping, because some apps check the
1002 * netwm state directly when mapped.
1004 * also, update netwm properties after setting the parent, because
1005 * the new state of _NET_WM_STATE_FULLSCREEN is determined based on
1006 * the parent of the cwin.
1008 region_set_parent((WRegion
*)cwin
, np
);
1009 netwm_update_state(cwin
);
1011 do_reparent_clientwin(cwin
, np
->win
, geom
.x
, geom
.y
);
1012 sendconfig_clientwin(cwin
);
1014 if(!REGION_IS_FULLSCREEN(cwin
))
1015 cwin
->flags
&=~CLIENTWIN_FS_RQ
;
1018 if (postpone_resize(cwin
))
1021 REGION_GEOM(cwin
)=geom
;
1026 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
&& !REGION_IS_MAPPED(cwin
)){
1027 XMoveResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1028 -2*REGION_GEOM(cwin
).w
, -2*REGION_GEOM(cwin
).h
,
1031 XMoveResizeWindow(ioncore_g
.dpy
, cwin
->win
, geom
.x
, geom
.y
, w
, h
);
1034 cwin
->flags
&=~CLIENTWIN_NEED_CFGNTFY
;
1040 static void clientwin_map(WClientWin
*cwin
)
1042 show_clientwin(cwin
);
1043 REGION_MARK_MAPPED(cwin
);
1047 static void clientwin_unmap(WClientWin
*cwin
)
1049 hide_clientwin(cwin
);
1050 REGION_MARK_UNMAPPED(cwin
);
1054 static void clientwin_do_set_focus(WClientWin
*cwin
, bool warp
)
1056 if(cwin
->flags
&CLIENTWIN_P_WM_TAKE_FOCUS
){
1057 Time stmp
=ioncore_get_timestamp();
1058 region_finalise_focusing((WRegion
*)cwin
, cwin
->win
, warp
, stmp
, cwin
->flags
&CLIENTWIN_SET_INPUT
);
1059 send_clientmsg(cwin
->win
, ioncore_g
.atom_wm_take_focus
, stmp
);
1061 region_finalise_focusing((WRegion
*)cwin
, cwin
->win
, warp
, CurrentTime
, cwin
->flags
&CLIENTWIN_SET_INPUT
);
1064 XSync(ioncore_g
.dpy
, 0);
1068 void clientwin_restack(WClientWin
*cwin
, Window other
, int mode
)
1070 xwindow_restack(cwin
->win
, other
, mode
);
1074 void clientwin_stacking(WClientWin
*cwin
, Window
*bottomret
, Window
*topret
)
1076 *bottomret
=cwin
->win
;
1081 static Window
clientwin_x_window(WClientWin
*cwin
)
1087 static void clientwin_activated(WClientWin
*cwin
)
1089 clientwin_install_colormap(cwin
);
1093 static void clientwin_size_hints(WClientWin
*cwin
, WSizeHints
*hints_ret
)
1095 if(cwin
->flags
&CLIENTWIN_FS_RQ
){
1096 /* Do not use size hints, when full screen mode has been
1097 * requested by the client window itself.
1099 sizehints_clear(hints_ret
);
1101 xsizehints_to_sizehints(&cwin
->size_hints
, hints_ret
);
1106 static int clientwin_orientation(WClientWin
*cwin
)
1108 return (cwin
->flags
&CLIENTWIN_PROP_O_VERT
1109 ? REGION_ORIENTATION_VERTICAL
1110 : (cwin
->flags
&CLIENTWIN_PROP_O_HORIZ
1111 ? REGION_ORIENTATION_HORIZONTAL
1112 : REGION_ORIENTATION_NONE
));
1119 /*{{{ Identity & lookup */
1123 * Returns a table containing the properties \code{WM_CLASS} (table entries
1124 * \var{instance} and \var{class}) and \code{WM_WINDOW_ROLE} (\var{role})
1125 * properties for \var{cwin}. If a property is not set, the corresponding
1126 * field(s) are unset in the table.
1130 ExtlTab
clientwin_get_ident(WClientWin
*cwin
)
1132 char **p
=NULL
, **p2
=NULL
, *wrole
=NULL
;
1133 int n
=0, n2
=0, n3
=0, tmp
=0;
1134 Window tforwin
=None
;
1136 bool dockapp_hack
=FALSE
;
1138 p
=xwindow_get_text_property(cwin
->win
, XA_WM_CLASS
, &n
);
1140 p2
=xwindow_get_text_property(cwin
->win
, ioncore_g
.atom_dockapp_hack
, &n2
);
1142 dockapp_hack
=(n2
>0);
1145 /* Some dockapps do actually have WM_CLASS, so use it. */
1151 wrole
=xwindow_get_string_property(cwin
->win
, ioncore_g
.atom_wm_window_role
,
1154 tab
=extl_create_table();
1155 if(n
>=2 && p
[1]!=NULL
)
1156 extl_table_sets_s(tab
, "class", p
[1]);
1157 if(n
>=1 && p
[0]!=NULL
)
1158 extl_table_sets_s(tab
, "instance", p
[0]);
1160 extl_table_sets_s(tab
, "role", wrole
);
1162 if(XGetTransientForHint(ioncore_g
.dpy
, cwin
->win
, &tforwin
)
1164 extl_table_sets_b(tab
, "is_transient", TRUE
);
1168 extl_table_sets_b(tab
, "is_dockapp", TRUE
);
1173 XFreeStringList(p2
);
1184 /*{{{ ConfigureRequest */
1187 static bool check_fs_cfgrq(WClientWin
*cwin
, XConfigureRequestEvent
*ev
)
1189 /* check full screen request */
1190 if((ev
->value_mask
&(CWWidth
|CWHeight
))==(CWWidth
|CWHeight
)){
1191 WRegion
*grp
=region_groupleader_of((WRegion
*)cwin
);
1192 WScreen
*scr
=clientwin_fullscreen_chkrq(cwin
, ev
->width
, ev
->height
);
1194 if(scr
!=NULL
&& REGION_MANAGER(grp
)!=(WRegion
*)scr
){
1195 bool sw
=clientwin_fullscreen_may_switchto(cwin
);
1197 cwin
->flags
|=CLIENTWIN_FS_RQ
;
1199 if(!region_fullscreen_scr(grp
, scr
, sw
))
1200 cwin
->flags
&=~CLIENTWIN_FS_RQ
;
1209 WMPlex
* find_mplexer(WRegion
*cwin
)
1213 if(obj_is((Obj
*)cwin
, &CLASSDESCR(WMPlex
)))
1214 return (WMPlex
*) cwin
;
1215 return find_mplexer(cwin
->manager
);
1218 /* Returns whether anything was actually changed. */
1219 static bool check_normal_cfgrq(WClientWin
*cwin
, XConfigureRequestEvent
*ev
)
1221 bool result
= FALSE
;
1223 if(ev
->value_mask
&(CWX
|CWY
|CWWidth
|CWHeight
)){
1224 WRQGeomParams rq
=RQGEOMPARAMS_INIT
;
1227 rq
.flags
=REGION_RQGEOM_WEAK_ALL
|REGION_RQGEOM_ABSOLUTE
;
1229 if(cwin
->size_hints
.flags
&PWinGravity
){
1230 rq
.flags
|=REGION_RQGEOM_GRAVITY
;
1231 rq
.gravity
=cwin
->size_hints
.win_gravity
;
1234 /* Do I need to insert another disparaging comment on the person who
1235 * invented special server-supported window borders that are not
1236 * accounted for in the window size? Keep it simple, stupid!
1238 if(cwin
->size_hints
.flags
&PWinGravity
){
1239 gdx
=xgravity_deltax(cwin
->size_hints
.win_gravity
,
1240 -cwin
->orig_bw
, -cwin
->orig_bw
);
1241 gdy
=xgravity_deltay(cwin
->size_hints
.win_gravity
,
1242 -cwin
->orig_bw
, -cwin
->orig_bw
);
1245 region_rootpos((WRegion
*)cwin
, &(rq
.geom
.x
), &(rq
.geom
.y
));
1246 rq
.geom
.w
=REGION_GEOM(cwin
).w
;
1247 rq
.geom
.h
=REGION_GEOM(cwin
).h
;
1249 if(ev
->value_mask
&CWWidth
){
1250 /* If x was not changed, keep reference point where it was */
1251 if(cwin
->size_hints
.flags
&PWinGravity
){
1252 rq
.geom
.x
+=xgravity_deltax(cwin
->size_hints
.win_gravity
, 0,
1253 ev
->width
-rq
.geom
.w
);
1255 rq
.geom
.w
=maxof(ev
->width
, 1);
1256 rq
.flags
&=~REGION_RQGEOM_WEAK_W
;
1258 if(ev
->value_mask
&CWHeight
){
1259 /* If y was not changed, keep reference point where it was */
1260 if(cwin
->size_hints
.flags
&PWinGravity
){
1261 rq
.geom
.y
+=xgravity_deltay(cwin
->size_hints
.win_gravity
, 0,
1262 ev
->height
-rq
.geom
.h
);
1264 rq
.geom
.h
=maxof(ev
->height
, 1);
1265 rq
.flags
&=~REGION_RQGEOM_WEAK_H
;
1267 if(ev
->value_mask
&CWX
){
1268 rq
.geom
.x
=ev
->x
+gdx
;
1269 rq
.flags
&=~REGION_RQGEOM_WEAK_X
;
1270 rq
.flags
&=~REGION_RQGEOM_WEAK_W
;
1272 if(ev
->value_mask
&CWY
){
1273 rq
.geom
.y
=ev
->y
+gdy
;
1274 rq
.flags
&=~REGION_RQGEOM_WEAK_Y
;
1275 rq
.flags
&=~REGION_RQGEOM_WEAK_H
;
1278 region_rqgeom((WRegion
*)cwin
, &rq
, NULL
);
1283 if(ioncore_g
.window_stacking_request
!= IONCORE_WINDOWSTACKINGREQUEST_IGNORE
&&
1284 ev
->value_mask
&CWStackMode
){
1287 region_set_activity((WRegion
*) cwin
, SETPARAM_SET
);
1288 /* TODO we should be more conservative here - but what does/should
1289 * region_set_activity return? */
1305 void clientwin_handle_configure_request(WClientWin
*cwin
,
1306 XConfigureRequestEvent
*ev
)
1308 if(ev
->value_mask
&CWBorderWidth
)
1309 cwin
->orig_bw
=ev
->border_width
;
1311 cwin
->flags
|=CLIENTWIN_NEED_CFGNTFY
;
1313 if(!(cwin
->flags
&CLIENTWIN_PROP_IGNORE_CFGRQ
)){
1314 if(!check_fs_cfgrq(cwin
, ev
))
1315 check_normal_cfgrq(cwin
, ev
);
1318 if(cwin
->flags
&CLIENTWIN_NEED_CFGNTFY
){
1319 sendconfig_clientwin(cwin
);
1320 cwin
->flags
&=~CLIENTWIN_NEED_CFGNTFY
;
1332 * Attempts to fix window size problems with non-ICCCM compliant
1336 void clientwin_nudge(WClientWin
*cwin
)
1338 XResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1339 2*REGION_GEOM(cwin
).w
, 2*REGION_GEOM(cwin
).h
);
1340 XFlush(ioncore_g
.dpy
);
1341 XResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1342 REGION_GEOM(cwin
).w
, REGION_GEOM(cwin
).h
);
1353 * Return the X window id for the client window.
1357 double clientwin_xid(WClientWin
*cwin
)
1369 static int last_checkcode
=1;
1372 static ExtlTab
clientwin_get_configuration(WClientWin
*cwin
)
1376 SMCfgCallback
*cfg_cb
;
1377 SMAddCallback
*add_cb
;
1379 tab
=region_get_base_configuration((WRegion
*)cwin
);
1381 extl_table_sets_d(tab
, "windowid", (double)(cwin
->win
));
1383 if(last_checkcode
!=0){
1384 chkc
=last_checkcode
++;
1385 xwindow_set_integer_property(cwin
->win
, ioncore_g
.atom_checkcode
,
1387 extl_table_sets_i(tab
, "checkcode", chkc
);
1390 ioncore_get_sm_callbacks(&add_cb
, &cfg_cb
);
1399 static void do_sm(ExtlTab tab
)
1401 SMAddCallback
*add_cb
;
1402 SMCfgCallback
*cfg_cb
;
1405 ioncore_get_sm_callbacks(&add_cb
, &cfg_cb
);
1408 ph
=ioncore_get_load_pholder();
1411 if(!add_cb(ph
, tab
))
1412 destroy_obj((Obj
*)ph
);
1418 WRegion
*clientwin_load(WWindow
*par
, const WFitParams
*fp
, ExtlTab tab
)
1422 int chkc
=0, real_chkc
=0;
1423 WClientWin
*cwin
=NULL
;
1424 XWindowAttributes attr
;
1426 bool got_chkc
=FALSE
;
1428 if(!extl_table_gets_d(tab
, "windowid", &wind
) ||
1429 !extl_table_gets_i(tab
, "checkcode", &chkc
)){
1435 if(XWINDOW_REGION_OF(win
)!=NULL
){
1436 warn("Client window %x already managed.", win
);
1440 got_chkc
=xwindow_get_integer_property(win
, ioncore_g
.atom_checkcode
,
1443 if(!got_chkc
|| real_chkc
!=chkc
){
1450 if(!XGetWindowAttributes(ioncore_g
.dpy
, win
, &attr
)){
1451 warn(TR("Window %#x disappeared."), win
);
1455 if(attr
.root
!=region_root_of((WRegion
*)par
))
1458 if(attr
.override_redirect
||
1459 (ioncore_g
.opmode
==IONCORE_OPMODE_INIT
&& attr
.map_state
!=IsViewable
)){
1460 warn(TR("Saved client window does not want to be managed."));
1464 cwin
=create_clientwin(par
, win
, &attr
);
1469 /* Reparent and resize taking limits set by size hints into account */
1470 convert_geom(fp
, cwin
, &rg
);
1471 REGION_GEOM(cwin
)=rg
;
1472 do_reparent_clientwin(cwin
, par
->win
, rg
.x
, rg
.y
);
1473 XResizeWindow(ioncore_g
.dpy
, win
, maxof(1, rg
.w
), maxof(1, rg
.h
));
1475 if(!postmanage_check(cwin
, &attr
)){
1476 clientwin_destroyed(cwin
);
1480 return (WRegion
*)cwin
;
1487 /*{{{ Dynfuntab and class info */
1490 static DynFunTab clientwin_dynfuntab
[]={
1491 {(DynFun
*)region_fitrep
,
1492 (DynFun
*)clientwin_fitrep
},
1500 {region_do_set_focus
,
1501 clientwin_do_set_focus
},
1503 {region_notify_rootpos
,
1504 clientwin_notify_rootpos
},
1510 clientwin_stacking
},
1512 {(DynFun
*)region_xwindow
,
1513 (DynFun
*)clientwin_x_window
},
1516 clientwin_activated
},
1519 clientwin_size_hints
},
1521 {(DynFun
*)region_orientation
,
1522 (DynFun
*)clientwin_orientation
},
1524 {(DynFun
*)region_rqclose
,
1525 (DynFun
*)clientwin_rqclose
},
1527 {(DynFun
*)region_get_configuration
,
1528 (DynFun
*)clientwin_get_configuration
},
1535 IMPLCLASS(WClientWin
, WRegion
, clientwin_deinit
, clientwin_dynfuntab
);