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 #define SIZEHINT_PROPS (CLIENTWIN_PROP_MAXSIZE| \
82 CLIENTWIN_PROP_MINSIZE| \
83 CLIENTWIN_PROP_ASPECT| \
84 CLIENTWIN_PROP_RSZINC| \
85 CLIENTWIN_PROP_I_MAXSIZE| \
86 CLIENTWIN_PROP_I_MINSIZE| \
87 CLIENTWIN_PROP_I_ASPECT| \
88 CLIENTWIN_PROP_I_RSZINC)
91 #define DO_SZH(NAME, FLAG, IFLAG, SZHFLAG, W, H, C) \
92 if(extl_table_is_bool_set(tab, "ignore_" NAME)){ \
94 }else if(extl_table_gets_t(tab, NAME, &tab2)){ \
95 if(extl_table_gets_i(tab2, "w", &i1) && \
96 extl_table_gets_i(tab2, "h", &i2)){ \
97 cwin->size_hints.W=i1; \
98 cwin->size_hints.H=i2; \
100 cwin->size_hints.flags|=SZHFLAG; \
103 extl_unref_table(tab2); \
107 static void clientwin_get_winprops(WClientWin
*cwin
)
113 tab
=ioncore_get_winprop(cwin
);
117 if(tab
==extl_table_none())
120 if(extl_table_is_bool_set(tab
, "transparent"))
121 cwin
->flags
|=CLIENTWIN_PROP_TRANSPARENT
;
123 if(extl_table_is_bool_set(tab
, "acrobatic"))
124 cwin
->flags
|=CLIENTWIN_PROP_ACROBATIC
;
126 if(extl_table_is_bool_set(tab
, "lazy_resize"))
127 cwin
->flags
|=CLIENTWIN_PROP_LAZY_RESIZE
;
129 DO_SZH("max_size", CLIENTWIN_PROP_MAXSIZE
, CLIENTWIN_PROP_I_MAXSIZE
,
130 PMaxSize
, max_width
, max_height
, { });
132 DO_SZH("min_size", CLIENTWIN_PROP_MINSIZE
, CLIENTWIN_PROP_I_MINSIZE
,
133 PMinSize
, min_width
, min_height
, { });
135 DO_SZH("resizeinc", CLIENTWIN_PROP_RSZINC
, CLIENTWIN_PROP_I_RSZINC
,
136 PResizeInc
, width_inc
, height_inc
, { });
138 DO_SZH("aspect", CLIENTWIN_PROP_ASPECT
, CLIENTWIN_PROP_I_ASPECT
,
139 PAspect
, min_aspect
.x
, min_aspect
.y
,
140 { cwin
->size_hints
.max_aspect
.x
=i1
;
141 cwin
->size_hints
.max_aspect
.y
=i2
;
144 if(extl_table_is_bool_set(tab
, "ignore_cfgrq"))
145 cwin
->flags
|=CLIENTWIN_PROP_IGNORE_CFGRQ
;
147 if(extl_table_gets_s(tab
, "orientation", &s
)){
148 if(strcmp(s
, "vertical")==0)
149 cwin
->flags
|=CLIENTWIN_PROP_O_VERT
;
150 else if(strcmp(s
, "horizontal")==0)
151 cwin
->flags
|=CLIENTWIN_PROP_O_HORIZ
;
156 void clientwin_apply_size_hint_winprops(XSizeHints original
, WClientWin
*cwin
)
158 if(cwin
->flags
&CLIENTWIN_PROP_I_MAXSIZE
){
159 cwin
->size_hints
.flags
&=~PMaxSize
;
160 }else if(cwin
->flags
&CLIENTWIN_PROP_MAXSIZE
){
161 cwin
->size_hints
.max_width
=original
.max_width
;
162 cwin
->size_hints
.max_height
=original
.max_height
;
163 cwin
->size_hints
.flags
|=PMaxSize
;
166 if(cwin
->flags
&CLIENTWIN_PROP_I_MINSIZE
){
167 cwin
->size_hints
.flags
&=~PMinSize
;
168 }else if(cwin
->flags
&CLIENTWIN_PROP_MINSIZE
){
169 cwin
->size_hints
.min_width
=original
.min_width
;
170 cwin
->size_hints
.min_height
=original
.min_height
;
171 cwin
->size_hints
.flags
|=PMinSize
;
174 if(cwin
->flags
&CLIENTWIN_PROP_I_ASPECT
){
175 cwin
->size_hints
.flags
&=~PAspect
;
176 }else if(cwin
->flags
&CLIENTWIN_PROP_ASPECT
){
177 cwin
->size_hints
.min_aspect
=original
.min_aspect
;
178 cwin
->size_hints
.max_aspect
=original
.max_aspect
;
179 cwin
->size_hints
.flags
|=PAspect
;
182 if(cwin
->flags
&CLIENTWIN_PROP_I_RSZINC
){
183 cwin
->size_hints
.flags
&=~PResizeInc
;
184 }else if(cwin
->flags
&CLIENTWIN_PROP_RSZINC
){
185 cwin
->size_hints
.width_inc
=original
.width_inc
;
186 cwin
->size_hints
.height_inc
=original
.height_inc
;
187 cwin
->size_hints
.flags
|=PResizeInc
;
191 int clientwin_get_size_hints(WClientWin
*cwin
)
193 XSizeHints original
=cwin
->size_hints
;
195 int ret
=xwindow_get_sizehints(cwin
->win
, &(cwin
->size_hints
));
197 clientwin_apply_size_hint_winprops(original
, cwin
);
202 void clientwin_reset_size_hints(WClientWin
*cwin
)
204 XSizeHints original
=cwin
->size_hints
;
206 memset(&(cwin
->size_hints
), 0, sizeof(cwin
->size_hints
));
207 clientwin_apply_size_hint_winprops(original
, cwin
);
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
)
715 region_pointer_focus_hack(&cwin
->region
);
717 xwindow_unmanaged_selectinput(cwin
->win
, 0);
718 XUnmapWindow(ioncore_g
.dpy
, cwin
->win
);
721 configure_cwin_bw(cwin
->win
, cwin
->orig_bw
);
723 if(reparent_root(cwin
)){
724 if(ioncore_g
.opmode
==IONCORE_OPMODE_DEINIT
){
725 XMapWindow(ioncore_g
.dpy
, cwin
->win
);
726 /* Make sure the topmost window has focus; it doesn't really
727 * matter which one has as long as some has.
729 xwindow_do_set_focus(cwin
->win
);
731 set_clientwin_state(cwin
, WithdrawnState
);
732 netwm_delete_state(cwin
);
736 XRemoveFromSaveSet(ioncore_g
.dpy
, cwin
->win
);
737 XDeleteContext(ioncore_g
.dpy
, cwin
->win
, ioncore_g
.win_context
);
740 clientwin_clear_colormaps(cwin
);
742 region_deinit((WRegion
*)cwin
);
747 static bool mrsh_u_c(WHookDummy
*fn
, void *param
)
753 static bool mrsh_u_extl(ExtlFn fn
, void *param
)
755 double d
=*(Window
*)param
;
756 extl_call(fn
, "d", NULL
, d
);
760 static void clientwin_do_unmapped(WClientWin
*cwin
, Window win
)
762 cwin
->flags
|=CLIENTWIN_UNMAP_RQ
;
764 /* First try a graceful chain-dispose */
765 if(!region_rqdispose((WRegion
*)cwin
)){
766 /* But force dispose anyway */
767 region_dispose((WRegion
*)cwin
);
770 hook_call(clientwin_unmapped_hook
, &win
, mrsh_u_c
, mrsh_u_extl
);
773 /* Used when the window was unmapped */
774 void clientwin_unmapped(WClientWin
*cwin
)
776 clientwin_do_unmapped(cwin
, cwin
->win
);
780 /* Used when the window was deastroyed */
781 void clientwin_destroyed(WClientWin
*cwin
)
783 Window win
=cwin
->win
;
784 XRemoveFromSaveSet(ioncore_g
.dpy
, cwin
->win
);
785 XDeleteContext(ioncore_g
.dpy
, cwin
->win
, ioncore_g
.win_context
);
786 xwindow_unmanaged_selectinput(cwin
->win
, 0);
788 clientwin_do_unmapped(cwin
, win
);
798 static bool send_clientmsg(Window win
, Atom a
, Time stmp
)
800 XClientMessageEvent ev
;
802 ev
.type
=ClientMessage
;
804 ev
.message_type
=ioncore_g
.atom_wm_protocols
;
809 return (XSendEvent(ioncore_g
.dpy
, win
, False
, 0L, (XEvent
*)&ev
)!=0);
814 * Attempt to kill (with \code{XKillWindow}) the client that owns
815 * the X window correspoding to \var{cwin}.
818 void clientwin_kill(WClientWin
*cwin
)
820 XKillClient(ioncore_g
.dpy
, cwin
->win
);
824 void clientwin_rqclose(WClientWin
*cwin
, bool UNUSED(relocate_ignored
))
826 /* Ignore relocate parameter -- client windows can always be
827 * destroyed by the application in any case, so way may just as
828 * well assume relocate is always set.
831 if(cwin
->flags
&CLIENTWIN_P_WM_DELETE
){
832 send_clientmsg(cwin
->win
, ioncore_g
.atom_wm_delete
,
833 ioncore_get_timestamp());
835 warn(TR("Client does not support the WM_DELETE protocol."));
843 /*{{{ State (hide/show) */
846 static void set_clientwin_state(WClientWin
*cwin
, int state
)
848 if(cwin
->state
!=state
){
850 xwindow_set_state_property(cwin
->win
, state
);
855 static void hide_clientwin(WClientWin
*cwin
)
857 region_pointer_focus_hack(&cwin
->region
);
859 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
){
860 XMoveWindow(ioncore_g
.dpy
, cwin
->win
,
861 -2*REGION_GEOM(cwin
).w
, -2*REGION_GEOM(cwin
).h
);
865 set_clientwin_state(cwin
, IconicState
);
866 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
867 cwin
->event_mask
&~(StructureNotifyMask
|EnterWindowMask
));
868 XUnmapWindow(ioncore_g
.dpy
, cwin
->win
);
869 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
873 static void show_clientwin(WClientWin
*cwin
)
875 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
){
876 XMoveWindow(ioncore_g
.dpy
, cwin
->win
,
877 REGION_GEOM(cwin
).x
, REGION_GEOM(cwin
).y
);
878 if(cwin
->state
==NormalState
)
882 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
883 cwin
->event_mask
&~(StructureNotifyMask
|EnterWindowMask
));
884 XMapWindow(ioncore_g
.dpy
, cwin
->win
);
885 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
886 set_clientwin_state(cwin
, NormalState
);
893 /*{{{ Resize/reparent/reconf helpers */
896 void clientwin_notify_rootpos(WClientWin
*cwin
, int rootx
, int rooty
)
906 ce
.xconfigure
.type
=ConfigureNotify
;
907 ce
.xconfigure
.event
=win
;
908 ce
.xconfigure
.window
=win
;
909 ce
.xconfigure
.x
=rootx
-cwin
->orig_bw
;
910 ce
.xconfigure
.y
=rooty
-cwin
->orig_bw
;
911 ce
.xconfigure
.width
=REGION_GEOM(cwin
).w
;
912 ce
.xconfigure
.height
=REGION_GEOM(cwin
).h
;
913 ce
.xconfigure
.border_width
=cwin
->orig_bw
;
914 ce
.xconfigure
.above
=None
;
915 ce
.xconfigure
.override_redirect
=False
;
917 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
&~StructureNotifyMask
);
918 XSendEvent(ioncore_g
.dpy
, win
, False
, StructureNotifyMask
, &ce
);
919 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
);
923 static void sendconfig_clientwin(WClientWin
*cwin
)
927 region_rootpos(&cwin
->region
, &rootx
, &rooty
);
928 clientwin_notify_rootpos(cwin
, rootx
, rooty
);
932 static void do_reparent_clientwin(WClientWin
*cwin
, Window win
, int x
, int y
)
934 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
935 cwin
->event_mask
&~StructureNotifyMask
);
936 XReparentWindow(ioncore_g
.dpy
, cwin
->win
, win
, x
, y
);
937 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
941 static void convert_geom(const WFitParams
*fp
,
942 WClientWin
*cwin
, WRectangle
*geom
)
944 WFitParams fptmp
=*fp
;
945 WSizePolicy szplcy
=SIZEPOLICY_FULL_EXACT
;
947 /*if(cwin->szplcy!=SIZEPOLICY_DEFAULT)
948 szplcy=cwin->szplcy;*/
950 sizepolicy(&szplcy
, (WRegion
*)cwin
, NULL
, REGION_RQGEOM_WEAK_ALL
, &fptmp
);
959 /*{{{ Region dynfuns */
962 static bool postpone_resize(WClientWin
*cwin
)
964 return cwin
->state
== IconicState
&& cwin
->flags
&CLIENTWIN_PROP_LAZY_RESIZE
;
968 static bool clientwin_fitrep(WClientWin
*cwin
, WWindow
*np
,
969 const WFitParams
*fp
)
975 if(np
!=NULL
&& !region_same_rootwin((WRegion
*)cwin
, (WRegion
*)np
))
978 if(fp
->mode
®ION_FIT_WHATEVER
){
981 geom
.w
=REGION_GEOM(cwin
).w
;
982 geom
.h
=REGION_GEOM(cwin
).h
;
987 changes
=(REGION_GEOM(cwin
).x
!=geom
.x
||
988 REGION_GEOM(cwin
).y
!=geom
.y
||
989 REGION_GEOM(cwin
).w
!=geom
.w
||
990 REGION_GEOM(cwin
).h
!=geom
.h
);
992 if(np
==NULL
&& !changes
)
996 region_unset_parent((WRegion
*)cwin
);
999 * update netwm properties before mapping, because some apps check the
1000 * netwm state directly when mapped.
1002 * also, update netwm properties after setting the parent, because
1003 * the new state of _NET_WM_STATE_FULLSCREEN is determined based on
1004 * the parent of the cwin.
1006 region_set_parent((WRegion
*)cwin
, np
);
1007 netwm_update_state(cwin
);
1009 do_reparent_clientwin(cwin
, np
->win
, geom
.x
, geom
.y
);
1010 sendconfig_clientwin(cwin
);
1012 if(!REGION_IS_FULLSCREEN(cwin
))
1013 cwin
->flags
&=~CLIENTWIN_FS_RQ
;
1016 if (postpone_resize(cwin
))
1019 REGION_GEOM(cwin
)=geom
;
1024 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
&& !REGION_IS_MAPPED(cwin
)){
1025 XMoveResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1026 -2*REGION_GEOM(cwin
).w
, -2*REGION_GEOM(cwin
).h
,
1029 XMoveResizeWindow(ioncore_g
.dpy
, cwin
->win
, geom
.x
, geom
.y
, w
, h
);
1032 cwin
->flags
&=~CLIENTWIN_NEED_CFGNTFY
;
1038 static void clientwin_map(WClientWin
*cwin
)
1040 show_clientwin(cwin
);
1041 REGION_MARK_MAPPED(cwin
);
1045 static void clientwin_unmap(WClientWin
*cwin
)
1047 hide_clientwin(cwin
);
1048 REGION_MARK_UNMAPPED(cwin
);
1052 static void clientwin_do_set_focus(WClientWin
*cwin
, bool warp
)
1054 if(cwin
->flags
&CLIENTWIN_P_WM_TAKE_FOCUS
){
1055 Time stmp
=ioncore_get_timestamp();
1056 region_finalise_focusing((WRegion
*)cwin
, cwin
->win
, warp
, stmp
, cwin
->flags
&CLIENTWIN_SET_INPUT
);
1057 send_clientmsg(cwin
->win
, ioncore_g
.atom_wm_take_focus
, stmp
);
1059 region_finalise_focusing((WRegion
*)cwin
, cwin
->win
, warp
, CurrentTime
, cwin
->flags
&CLIENTWIN_SET_INPUT
);
1062 XSync(ioncore_g
.dpy
, 0);
1066 void clientwin_restack(WClientWin
*cwin
, Window other
, int mode
)
1068 xwindow_restack(cwin
->win
, other
, mode
);
1072 void clientwin_stacking(WClientWin
*cwin
, Window
*bottomret
, Window
*topret
)
1074 *bottomret
=cwin
->win
;
1079 static Window
clientwin_x_window(WClientWin
*cwin
)
1085 static void clientwin_activated(WClientWin
*cwin
)
1087 clientwin_install_colormap(cwin
);
1091 static void clientwin_size_hints(WClientWin
*cwin
, WSizeHints
*hints_ret
)
1093 if(cwin
->flags
&CLIENTWIN_FS_RQ
){
1094 /* Do not use size hints, when full screen mode has been
1095 * requested by the client window itself.
1097 sizehints_clear(hints_ret
);
1099 xsizehints_to_sizehints(&cwin
->size_hints
, hints_ret
);
1104 static int clientwin_orientation(WClientWin
*cwin
)
1106 return (cwin
->flags
&CLIENTWIN_PROP_O_VERT
1107 ? REGION_ORIENTATION_VERTICAL
1108 : (cwin
->flags
&CLIENTWIN_PROP_O_HORIZ
1109 ? REGION_ORIENTATION_HORIZONTAL
1110 : REGION_ORIENTATION_NONE
));
1117 /*{{{ Identity & lookup */
1121 * Returns a table containing the properties \code{WM_CLASS} (table entries
1122 * \var{instance} and \var{class}) and \code{WM_WINDOW_ROLE} (\var{role})
1123 * properties for \var{cwin}. If a property is not set, the corresponding
1124 * field(s) are unset in the table.
1128 ExtlTab
clientwin_get_ident(WClientWin
*cwin
)
1130 char **p
=NULL
, **p2
=NULL
, *wrole
=NULL
;
1131 int n
=0, n2
=0, n3
=0;
1132 Window tforwin
=None
;
1134 bool dockapp_hack
=FALSE
;
1136 p
=xwindow_get_text_property(cwin
->win
, XA_WM_CLASS
, &n
);
1138 p2
=xwindow_get_text_property(cwin
->win
, ioncore_g
.atom_dockapp_hack
, &n2
);
1140 dockapp_hack
=(n2
>0);
1143 /* Some dockapps do actually have WM_CLASS, so use it. */
1149 wrole
=xwindow_get_string_property(cwin
->win
, ioncore_g
.atom_wm_window_role
,
1152 tab
=extl_create_table();
1153 if(n
>=2 && p
[1]!=NULL
)
1154 extl_table_sets_s(tab
, "class", p
[1]);
1155 if(n
>=1 && p
[0]!=NULL
)
1156 extl_table_sets_s(tab
, "instance", p
[0]);
1158 extl_table_sets_s(tab
, "role", wrole
);
1160 if(XGetTransientForHint(ioncore_g
.dpy
, cwin
->win
, &tforwin
)
1162 extl_table_sets_b(tab
, "is_transient", TRUE
);
1166 extl_table_sets_b(tab
, "is_dockapp", TRUE
);
1171 XFreeStringList(p2
);
1182 /*{{{ ConfigureRequest */
1185 static bool check_fs_cfgrq(WClientWin
*cwin
, XConfigureRequestEvent
*ev
)
1187 /* check full screen request */
1188 if((ev
->value_mask
&(CWWidth
|CWHeight
))==(CWWidth
|CWHeight
)){
1189 WRegion
*grp
=region_groupleader_of((WRegion
*)cwin
);
1190 WScreen
*scr
=clientwin_fullscreen_chkrq(cwin
, ev
->width
, ev
->height
);
1192 if(scr
!=NULL
&& REGION_MANAGER(grp
)!=(WRegion
*)scr
){
1193 bool sw
=clientwin_fullscreen_may_switchto(cwin
);
1195 cwin
->flags
|=CLIENTWIN_FS_RQ
;
1197 if(!region_fullscreen_scr(grp
, scr
, sw
))
1198 cwin
->flags
&=~CLIENTWIN_FS_RQ
;
1207 WMPlex
* find_mplexer(WRegion
*cwin
)
1211 if(obj_is((Obj
*)cwin
, &CLASSDESCR(WMPlex
)))
1212 return (WMPlex
*) cwin
;
1213 return find_mplexer(cwin
->manager
);
1216 /* Returns whether anything was actually changed. */
1217 static bool check_normal_cfgrq(WClientWin
*cwin
, XConfigureRequestEvent
*ev
)
1219 bool result
= FALSE
;
1221 if(ev
->value_mask
&(CWX
|CWY
|CWWidth
|CWHeight
)){
1222 WRQGeomParams rq
=RQGEOMPARAMS_INIT
;
1225 rq
.flags
=REGION_RQGEOM_WEAK_ALL
|REGION_RQGEOM_ABSOLUTE
;
1227 if(cwin
->size_hints
.flags
&PWinGravity
){
1228 rq
.flags
|=REGION_RQGEOM_GRAVITY
;
1229 rq
.gravity
=cwin
->size_hints
.win_gravity
;
1232 /* Do I need to insert another disparaging comment on the person who
1233 * invented special server-supported window borders that are not
1234 * accounted for in the window size? Keep it simple, stupid!
1236 if(cwin
->size_hints
.flags
&PWinGravity
){
1237 gdx
=xgravity_deltax(cwin
->size_hints
.win_gravity
,
1238 -cwin
->orig_bw
, -cwin
->orig_bw
);
1239 gdy
=xgravity_deltay(cwin
->size_hints
.win_gravity
,
1240 -cwin
->orig_bw
, -cwin
->orig_bw
);
1243 region_rootpos((WRegion
*)cwin
, &(rq
.geom
.x
), &(rq
.geom
.y
));
1244 rq
.geom
.w
=REGION_GEOM(cwin
).w
;
1245 rq
.geom
.h
=REGION_GEOM(cwin
).h
;
1247 if(ev
->value_mask
&CWWidth
){
1248 /* If x was not changed, keep reference point where it was */
1249 if(cwin
->size_hints
.flags
&PWinGravity
){
1250 rq
.geom
.x
+=xgravity_deltax(cwin
->size_hints
.win_gravity
, 0,
1251 ev
->width
-rq
.geom
.w
);
1253 rq
.geom
.w
=MAXOF(ev
->width
, 1);
1254 rq
.flags
&=~REGION_RQGEOM_WEAK_W
;
1256 if(ev
->value_mask
&CWHeight
){
1257 /* If y was not changed, keep reference point where it was */
1258 if(cwin
->size_hints
.flags
&PWinGravity
){
1259 rq
.geom
.y
+=xgravity_deltay(cwin
->size_hints
.win_gravity
, 0,
1260 ev
->height
-rq
.geom
.h
);
1262 rq
.geom
.h
=MAXOF(ev
->height
, 1);
1263 rq
.flags
&=~REGION_RQGEOM_WEAK_H
;
1265 if(ev
->value_mask
&CWX
){
1266 rq
.geom
.x
=ev
->x
+gdx
;
1267 rq
.flags
&=~REGION_RQGEOM_WEAK_X
;
1268 rq
.flags
&=~REGION_RQGEOM_WEAK_W
;
1270 if(ev
->value_mask
&CWY
){
1271 rq
.geom
.y
=ev
->y
+gdy
;
1272 rq
.flags
&=~REGION_RQGEOM_WEAK_Y
;
1273 rq
.flags
&=~REGION_RQGEOM_WEAK_H
;
1276 region_rqgeom((WRegion
*)cwin
, &rq
, NULL
);
1281 if(ioncore_g
.window_stacking_request
!= IONCORE_WINDOWSTACKINGREQUEST_IGNORE
&&
1282 ev
->value_mask
&CWStackMode
){
1285 region_set_activity((WRegion
*) cwin
, SETPARAM_SET
);
1286 /* TODO we should be more conservative here - but what does/should
1287 * region_set_activity return? */
1303 void clientwin_handle_configure_request(WClientWin
*cwin
,
1304 XConfigureRequestEvent
*ev
)
1306 if(ev
->value_mask
&CWBorderWidth
)
1307 cwin
->orig_bw
=ev
->border_width
;
1309 cwin
->flags
|=CLIENTWIN_NEED_CFGNTFY
;
1311 if(!(cwin
->flags
&CLIENTWIN_PROP_IGNORE_CFGRQ
)){
1312 if(!check_fs_cfgrq(cwin
, ev
))
1313 check_normal_cfgrq(cwin
, ev
);
1316 if(cwin
->flags
&CLIENTWIN_NEED_CFGNTFY
){
1317 sendconfig_clientwin(cwin
);
1318 cwin
->flags
&=~CLIENTWIN_NEED_CFGNTFY
;
1330 * Attempts to fix window size problems with non-ICCCM compliant
1334 void clientwin_nudge(WClientWin
*cwin
)
1336 XResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1337 2*REGION_GEOM(cwin
).w
, 2*REGION_GEOM(cwin
).h
);
1338 XFlush(ioncore_g
.dpy
);
1339 XResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1340 REGION_GEOM(cwin
).w
, REGION_GEOM(cwin
).h
);
1351 * Return the X window id for the client window.
1355 double clientwin_xid(WClientWin
*cwin
)
1367 static int last_checkcode
=1;
1370 static ExtlTab
clientwin_get_configuration(WClientWin
*cwin
)
1374 SMCfgCallback
*cfg_cb
;
1375 SMAddCallback
*add_cb
;
1377 tab
=region_get_base_configuration((WRegion
*)cwin
);
1379 extl_table_sets_d(tab
, "windowid", (double)(cwin
->win
));
1381 if(last_checkcode
!=0){
1382 chkc
=last_checkcode
++;
1383 xwindow_set_integer_property(cwin
->win
, ioncore_g
.atom_checkcode
,
1385 extl_table_sets_i(tab
, "checkcode", chkc
);
1388 ioncore_get_sm_callbacks(&add_cb
, &cfg_cb
);
1397 static void do_sm(ExtlTab tab
)
1399 SMAddCallback
*add_cb
;
1400 SMCfgCallback
*cfg_cb
;
1403 ioncore_get_sm_callbacks(&add_cb
, &cfg_cb
);
1406 ph
=ioncore_get_load_pholder();
1409 if(!add_cb(ph
, tab
))
1410 destroy_obj((Obj
*)ph
);
1416 WRegion
*clientwin_load(WWindow
*par
, const WFitParams
*fp
, ExtlTab tab
)
1420 int chkc
=0, real_chkc
=0;
1421 WClientWin
*cwin
=NULL
;
1422 XWindowAttributes attr
;
1424 bool got_chkc
=FALSE
;
1426 if(!extl_table_gets_d(tab
, "windowid", &wind
) ||
1427 !extl_table_gets_i(tab
, "checkcode", &chkc
)){
1433 if(XWINDOW_REGION_OF(win
)!=NULL
){
1434 warn("Client window %x already managed.", win
);
1438 got_chkc
=xwindow_get_integer_property(win
, ioncore_g
.atom_checkcode
,
1441 if(!got_chkc
|| real_chkc
!=chkc
){
1448 if(!XGetWindowAttributes(ioncore_g
.dpy
, win
, &attr
)){
1449 warn(TR("Window %#x disappeared."), win
);
1453 if(attr
.root
!=region_root_of((WRegion
*)par
))
1456 if(attr
.override_redirect
||
1457 (ioncore_g
.opmode
==IONCORE_OPMODE_INIT
&& attr
.map_state
!=IsViewable
)){
1458 warn(TR("Saved client window does not want to be managed."));
1462 cwin
=create_clientwin(par
, win
, &attr
);
1467 /* Reparent and resize taking limits set by size hints into account */
1468 convert_geom(fp
, cwin
, &rg
);
1469 REGION_GEOM(cwin
)=rg
;
1470 do_reparent_clientwin(cwin
, par
->win
, rg
.x
, rg
.y
);
1471 XResizeWindow(ioncore_g
.dpy
, win
, MAXOF(1, rg
.w
), MAXOF(1, rg
.h
));
1473 if(!postmanage_check(cwin
, &attr
)){
1474 clientwin_destroyed(cwin
);
1478 return (WRegion
*)cwin
;
1485 /*{{{ Dynfuntab and class info */
1488 static DynFunTab clientwin_dynfuntab
[]={
1489 {(DynFun
*)region_fitrep
,
1490 (DynFun
*)clientwin_fitrep
},
1498 {region_do_set_focus
,
1499 clientwin_do_set_focus
},
1501 {region_notify_rootpos
,
1502 clientwin_notify_rootpos
},
1508 clientwin_stacking
},
1510 {(DynFun
*)region_xwindow
,
1511 (DynFun
*)clientwin_x_window
},
1514 clientwin_activated
},
1517 clientwin_size_hints
},
1519 {(DynFun
*)region_orientation
,
1520 (DynFun
*)clientwin_orientation
},
1522 {(DynFun
*)region_rqclose
,
1523 (DynFun
*)clientwin_rqclose
},
1525 {(DynFun
*)region_get_configuration
,
1526 (DynFun
*)clientwin_get_configuration
},
1533 IMPLCLASS(WClientWin
, WRegion
, clientwin_deinit
, clientwin_dynfuntab
);