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
;
157 void clientwin_get_size_hints(WClientWin
*cwin
)
159 XSizeHints tmp
=cwin
->size_hints
;
161 xwindow_get_sizehints(cwin
->win
, &(cwin
->size_hints
));
163 if(cwin
->flags
&CLIENTWIN_PROP_I_MAXSIZE
){
164 cwin
->size_hints
.flags
&=~PMaxSize
;
165 }else if(cwin
->flags
&CLIENTWIN_PROP_MAXSIZE
){
166 cwin
->size_hints
.max_width
=tmp
.max_width
;
167 cwin
->size_hints
.max_height
=tmp
.max_height
;
168 cwin
->size_hints
.flags
|=PMaxSize
;
171 if(cwin
->flags
&CLIENTWIN_PROP_I_MINSIZE
){
172 cwin
->size_hints
.flags
&=~PMinSize
;
173 }else if(cwin
->flags
&CLIENTWIN_PROP_MINSIZE
){
174 cwin
->size_hints
.min_width
=tmp
.min_width
;
175 cwin
->size_hints
.min_height
=tmp
.min_height
;
176 cwin
->size_hints
.flags
|=PMinSize
;
179 if(cwin
->flags
&CLIENTWIN_PROP_I_ASPECT
){
180 cwin
->size_hints
.flags
&=~PAspect
;
181 }else if(cwin
->flags
&CLIENTWIN_PROP_ASPECT
){
182 cwin
->size_hints
.min_aspect
=tmp
.min_aspect
;
183 cwin
->size_hints
.max_aspect
=tmp
.max_aspect
;
184 cwin
->size_hints
.flags
|=PAspect
;
187 if(cwin
->flags
&CLIENTWIN_PROP_I_RSZINC
){
188 cwin
->size_hints
.flags
&=~PResizeInc
;
189 }else if(cwin
->flags
&CLIENTWIN_PROP_RSZINC
){
190 cwin
->size_hints
.width_inc
=tmp
.width_inc
;
191 cwin
->size_hints
.height_inc
=tmp
.height_inc
;
192 cwin
->size_hints
.flags
|=PResizeInc
;
196 void clientwin_get_input_wm_hint(WClientWin
*cwin
)
200 hints
=XGetWMHints(ioncore_g
.dpy
, cwin
->win
);
202 cwin
->flags
|=CLIENTWIN_SET_INPUT
;
204 if(hints
->flags
&InputHint
&& !hints
->input
)
205 cwin
->flags
&=~CLIENTWIN_SET_INPUT
;
210 void clientwin_get_set_name(WClientWin
*cwin
)
216 list
=netwm_get_name(cwin
);
219 list
=xwindow_get_text_property(cwin
->win
, XA_WM_NAME
, &n
);
221 cwin
->flags
|=CLIENTWIN_USE_NET_WM_NAME
;
225 /* Special condition kludge: property exists, but couldn't
226 * be converted to a string list.
228 clientwin_set_name(cwin
, (n
==-1 ? "???" : NULL
));
230 clientwin_set_name(cwin
, *list
);
231 XFreeStringList(list
);
236 /* Some standard winprops */
239 bool clientwin_get_switchto(const WClientWin
*cwin
)
243 if(ioncore_g
.opmode
==IONCORE_OPMODE_INIT
)
246 if(extl_table_gets_b(cwin
->proptab
, "switchto", &b
))
249 return ioncore_g
.switchto_new
;
253 int clientwin_get_transient_mode(const WClientWin
*cwin
)
256 int mode
=TRANSIENT_MODE_NORMAL
;
258 if(extl_table_gets_s(cwin
->proptab
, "transient_mode", &s
)){
259 if(strcmp(s
, "current")==0)
260 mode
=TRANSIENT_MODE_CURRENT
;
261 else if(strcmp(s
, "off")==0)
262 mode
=TRANSIENT_MODE_OFF
;
272 /*{{{ Manage/create */
275 static void configure_cwin_bw(Window win
, int bw
)
278 ulong wcmask
=CWBorderWidth
;
281 XConfigureWindow(ioncore_g
.dpy
, win
, wcmask
, &wc
);
285 static void set_sane_gravity(Window win
)
287 XSetWindowAttributes attr
;
289 attr
.win_gravity
=NorthWestGravity
;
291 XChangeWindowAttributes(ioncore_g
.dpy
, win
,
292 CWWinGravity
, &attr
);
296 static bool clientwin_init(WClientWin
*cwin
, WWindow
*par
, Window win
,
297 XWindowAttributes
*attr
)
303 cwin
->state
=WithdrawnState
;
309 fp
.mode
=REGION_FIT_EXACT
;
311 /* The idiot who invented special server-supported window borders that
312 * are not accounted for in the window size should be "taken behind a
315 cwin
->orig_bw
=attr
->border_width
;
316 configure_cwin_bw(cwin
->win
, 0);
317 if(cwin
->orig_bw
!=0 && cwin
->size_hints
.flags
&PWinGravity
){
318 fp
.g
.x
+=xgravity_deltax(cwin
->size_hints
.win_gravity
,
319 -cwin
->orig_bw
, -cwin
->orig_bw
);
320 fp
.g
.y
+=xgravity_deltay(cwin
->size_hints
.win_gravity
,
321 -cwin
->orig_bw
, -cwin
->orig_bw
);
324 set_sane_gravity(cwin
->win
);
327 cwin
->cmap
=attr
->colormap
;
331 cwin
->event_mask
=IONCORE_EVENTMASK_CLIENTWIN
;
333 region_init(&(cwin
->region
), par
, &fp
);
335 cwin
->region
.flags
|=REGION_GRAB_ON_PARENT
;
336 region_add_bindmap(&cwin
->region
, ioncore_clientwin_bindmap
);
338 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
);
340 clientwin_register(cwin
);
341 clientwin_get_set_name(cwin
);
342 clientwin_get_colormaps(cwin
);
343 clientwin_get_protocols(cwin
);
344 clientwin_get_winprops(cwin
);
345 clientwin_get_size_hints(cwin
);
346 clientwin_get_input_wm_hint(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
)
701 region_pointer_focus_hack(&cwin
->region
);
703 xwindow_unmanaged_selectinput(cwin
->win
, 0);
704 XUnmapWindow(ioncore_g
.dpy
, cwin
->win
);
707 configure_cwin_bw(cwin
->win
, cwin
->orig_bw
);
709 if(reparent_root(cwin
)){
710 if(ioncore_g
.opmode
==IONCORE_OPMODE_DEINIT
){
711 XMapWindow(ioncore_g
.dpy
, cwin
->win
);
712 /* Make sure the topmost window has focus; it doesn't really
713 * matter which one has as long as some has.
715 xwindow_do_set_focus(cwin
->win
);
717 set_clientwin_state(cwin
, WithdrawnState
);
718 netwm_delete_state(cwin
);
722 XRemoveFromSaveSet(ioncore_g
.dpy
, cwin
->win
);
723 XDeleteContext(ioncore_g
.dpy
, cwin
->win
, ioncore_g
.win_context
);
726 clientwin_clear_colormaps(cwin
);
728 region_deinit((WRegion
*)cwin
);
733 static bool mrsh_u_c(WHookDummy
*fn
, void *param
)
739 static bool mrsh_u_extl(ExtlFn fn
, void *param
)
741 double d
=*(Window
*)param
;
742 extl_call(fn
, "d", NULL
, d
);
746 static void clientwin_do_unmapped(WClientWin
*cwin
, Window win
)
748 cwin
->flags
|=CLIENTWIN_UNMAP_RQ
;
750 /* First try a graceful chain-dispose */
751 if(!region_rqdispose((WRegion
*)cwin
)){
752 /* But force dispose anyway */
753 region_dispose((WRegion
*)cwin
);
756 hook_call(clientwin_unmapped_hook
, &win
, mrsh_u_c
, mrsh_u_extl
);
759 /* Used when the window was unmapped */
760 void clientwin_unmapped(WClientWin
*cwin
)
762 clientwin_do_unmapped(cwin
, cwin
->win
);
766 /* Used when the window was deastroyed */
767 void clientwin_destroyed(WClientWin
*cwin
)
769 Window win
=cwin
->win
;
770 XRemoveFromSaveSet(ioncore_g
.dpy
, cwin
->win
);
771 XDeleteContext(ioncore_g
.dpy
, cwin
->win
, ioncore_g
.win_context
);
772 xwindow_unmanaged_selectinput(cwin
->win
, 0);
774 clientwin_do_unmapped(cwin
, win
);
784 static bool send_clientmsg(Window win
, Atom a
, Time stmp
)
786 XClientMessageEvent ev
;
788 ev
.type
=ClientMessage
;
790 ev
.message_type
=ioncore_g
.atom_wm_protocols
;
795 return (XSendEvent(ioncore_g
.dpy
, win
, False
, 0L, (XEvent
*)&ev
)!=0);
800 * Attempt to kill (with \code{XKillWindow}) the client that owns
801 * the X window correspoding to \var{cwin}.
804 void clientwin_kill(WClientWin
*cwin
)
806 XKillClient(ioncore_g
.dpy
, cwin
->win
);
810 void clientwin_rqclose(WClientWin
*cwin
, bool relocate_ignored
)
812 /* Ignore relocate parameter -- client windows can always be
813 * destroyed by the application in any case, so way may just as
814 * well assume relocate is always set.
817 if(cwin
->flags
&CLIENTWIN_P_WM_DELETE
){
818 send_clientmsg(cwin
->win
, ioncore_g
.atom_wm_delete
,
819 ioncore_get_timestamp());
821 warn(TR("Client does not support the WM_DELETE protocol."));
829 /*{{{ State (hide/show) */
832 static void set_clientwin_state(WClientWin
*cwin
, int state
)
834 if(cwin
->state
!=state
){
836 xwindow_set_state_property(cwin
->win
, state
);
841 static void hide_clientwin(WClientWin
*cwin
)
843 region_pointer_focus_hack(&cwin
->region
);
845 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
){
846 XMoveWindow(ioncore_g
.dpy
, cwin
->win
,
847 -2*REGION_GEOM(cwin
).w
, -2*REGION_GEOM(cwin
).h
);
851 set_clientwin_state(cwin
, IconicState
);
852 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
853 cwin
->event_mask
&~(StructureNotifyMask
|EnterWindowMask
));
854 XUnmapWindow(ioncore_g
.dpy
, cwin
->win
);
855 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
859 static void show_clientwin(WClientWin
*cwin
)
861 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
){
862 XMoveWindow(ioncore_g
.dpy
, cwin
->win
,
863 REGION_GEOM(cwin
).x
, REGION_GEOM(cwin
).y
);
864 if(cwin
->state
==NormalState
)
868 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
869 cwin
->event_mask
&~(StructureNotifyMask
|EnterWindowMask
));
870 XMapWindow(ioncore_g
.dpy
, cwin
->win
);
871 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
872 set_clientwin_state(cwin
, NormalState
);
879 /*{{{ Resize/reparent/reconf helpers */
882 void clientwin_notify_rootpos(WClientWin
*cwin
, int rootx
, int rooty
)
892 ce
.xconfigure
.type
=ConfigureNotify
;
893 ce
.xconfigure
.event
=win
;
894 ce
.xconfigure
.window
=win
;
895 ce
.xconfigure
.x
=rootx
-cwin
->orig_bw
;
896 ce
.xconfigure
.y
=rooty
-cwin
->orig_bw
;
897 ce
.xconfigure
.width
=REGION_GEOM(cwin
).w
;
898 ce
.xconfigure
.height
=REGION_GEOM(cwin
).h
;
899 ce
.xconfigure
.border_width
=cwin
->orig_bw
;
900 ce
.xconfigure
.above
=None
;
901 ce
.xconfigure
.override_redirect
=False
;
903 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
&~StructureNotifyMask
);
904 XSendEvent(ioncore_g
.dpy
, win
, False
, StructureNotifyMask
, &ce
);
905 XSelectInput(ioncore_g
.dpy
, win
, cwin
->event_mask
);
909 static void sendconfig_clientwin(WClientWin
*cwin
)
913 region_rootpos(&cwin
->region
, &rootx
, &rooty
);
914 clientwin_notify_rootpos(cwin
, rootx
, rooty
);
918 static void do_reparent_clientwin(WClientWin
*cwin
, Window win
, int x
, int y
)
920 XSelectInput(ioncore_g
.dpy
, cwin
->win
,
921 cwin
->event_mask
&~StructureNotifyMask
);
922 XReparentWindow(ioncore_g
.dpy
, cwin
->win
, win
, x
, y
);
923 XSelectInput(ioncore_g
.dpy
, cwin
->win
, cwin
->event_mask
);
927 static void convert_geom(const WFitParams
*fp
,
928 WClientWin
*cwin
, WRectangle
*geom
)
930 WFitParams fptmp
=*fp
;
931 WSizePolicy szplcy
=SIZEPOLICY_FULL_EXACT
;
933 /*if(cwin->szplcy!=SIZEPOLICY_DEFAULT)
934 szplcy=cwin->szplcy;*/
936 sizepolicy(&szplcy
, (WRegion
*)cwin
, NULL
, REGION_RQGEOM_WEAK_ALL
, &fptmp
);
945 /*{{{ Region dynfuns */
948 static bool postpone_resize(WClientWin
*cwin
)
950 return cwin
->state
== IconicState
&& cwin
->flags
&CLIENTWIN_PROP_LAZY_RESIZE
;
954 static bool clientwin_fitrep(WClientWin
*cwin
, WWindow
*np
,
955 const WFitParams
*fp
)
961 if(np
!=NULL
&& !region_same_rootwin((WRegion
*)cwin
, (WRegion
*)np
))
964 if(fp
->mode
®ION_FIT_WHATEVER
){
967 geom
.w
=REGION_GEOM(cwin
).w
;
968 geom
.h
=REGION_GEOM(cwin
).h
;
973 changes
=(REGION_GEOM(cwin
).x
!=geom
.x
||
974 REGION_GEOM(cwin
).y
!=geom
.y
||
975 REGION_GEOM(cwin
).w
!=geom
.w
||
976 REGION_GEOM(cwin
).h
!=geom
.h
);
978 if(np
==NULL
&& !changes
)
982 region_unset_parent((WRegion
*)cwin
);
985 * update netwm properties before mapping, because some apps check the
986 * netwm state directly when mapped.
988 * also, update netwm properties after setting the parent, because
989 * the new state of _NET_WM_STATE_FULLSCREEN is determined based on
990 * the parent of the cwin.
992 region_set_parent((WRegion
*)cwin
, np
);
993 netwm_update_state(cwin
);
995 do_reparent_clientwin(cwin
, np
->win
, geom
.x
, geom
.y
);
996 sendconfig_clientwin(cwin
);
998 if(!REGION_IS_FULLSCREEN(cwin
))
999 cwin
->flags
&=~CLIENTWIN_FS_RQ
;
1002 if (postpone_resize(cwin
))
1005 REGION_GEOM(cwin
)=geom
;
1010 if(cwin
->flags
&CLIENTWIN_PROP_ACROBATIC
&& !REGION_IS_MAPPED(cwin
)){
1011 XMoveResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1012 -2*REGION_GEOM(cwin
).w
, -2*REGION_GEOM(cwin
).h
,
1015 XMoveResizeWindow(ioncore_g
.dpy
, cwin
->win
, geom
.x
, geom
.y
, w
, h
);
1018 cwin
->flags
&=~CLIENTWIN_NEED_CFGNTFY
;
1024 static void clientwin_map(WClientWin
*cwin
)
1026 show_clientwin(cwin
);
1027 REGION_MARK_MAPPED(cwin
);
1031 static void clientwin_unmap(WClientWin
*cwin
)
1033 hide_clientwin(cwin
);
1034 REGION_MARK_UNMAPPED(cwin
);
1038 static void clientwin_do_set_focus(WClientWin
*cwin
, bool warp
)
1040 if(cwin
->flags
&CLIENTWIN_P_WM_TAKE_FOCUS
){
1041 Time stmp
=ioncore_get_timestamp();
1042 region_finalise_focusing((WRegion
*)cwin
, cwin
->win
, warp
, stmp
, cwin
->flags
&CLIENTWIN_SET_INPUT
);
1043 send_clientmsg(cwin
->win
, ioncore_g
.atom_wm_take_focus
, stmp
);
1045 region_finalise_focusing((WRegion
*)cwin
, cwin
->win
, warp
, CurrentTime
, cwin
->flags
&CLIENTWIN_SET_INPUT
);
1048 XSync(ioncore_g
.dpy
, 0);
1052 void clientwin_restack(WClientWin
*cwin
, Window other
, int mode
)
1054 xwindow_restack(cwin
->win
, other
, mode
);
1058 void clientwin_stacking(WClientWin
*cwin
, Window
*bottomret
, Window
*topret
)
1060 *bottomret
=cwin
->win
;
1065 static Window
clientwin_x_window(WClientWin
*cwin
)
1071 static void clientwin_activated(WClientWin
*cwin
)
1073 clientwin_install_colormap(cwin
);
1077 static void clientwin_size_hints(WClientWin
*cwin
, WSizeHints
*hints_ret
)
1079 if(cwin
->flags
&CLIENTWIN_FS_RQ
){
1080 /* Do not use size hints, when full screen mode has been
1081 * requested by the client window itself.
1083 sizehints_clear(hints_ret
);
1085 xsizehints_to_sizehints(&cwin
->size_hints
, hints_ret
);
1090 static int clientwin_orientation(WClientWin
*cwin
)
1092 return (cwin
->flags
&CLIENTWIN_PROP_O_VERT
1093 ? REGION_ORIENTATION_VERTICAL
1094 : (cwin
->flags
&CLIENTWIN_PROP_O_HORIZ
1095 ? REGION_ORIENTATION_HORIZONTAL
1096 : REGION_ORIENTATION_NONE
));
1103 /*{{{ Identity & lookup */
1107 * Returns a table containing the properties \code{WM_CLASS} (table entries
1108 * \var{instance} and \var{class}) and \code{WM_WINDOW_ROLE} (\var{role})
1109 * properties for \var{cwin}. If a property is not set, the corresponding
1110 * field(s) are unset in the table.
1114 ExtlTab
clientwin_get_ident(WClientWin
*cwin
)
1116 char **p
=NULL
, **p2
=NULL
, *wrole
=NULL
;
1117 int n
=0, n2
=0, n3
=0;
1118 Window tforwin
=None
;
1120 bool dockapp_hack
=FALSE
;
1122 p
=xwindow_get_text_property(cwin
->win
, XA_WM_CLASS
, &n
);
1124 p2
=xwindow_get_text_property(cwin
->win
, ioncore_g
.atom_dockapp_hack
, &n2
);
1126 dockapp_hack
=(n2
>0);
1129 /* Some dockapps do actually have WM_CLASS, so use it. */
1135 wrole
=xwindow_get_string_property(cwin
->win
, ioncore_g
.atom_wm_window_role
,
1138 tab
=extl_create_table();
1139 if(n
>=2 && p
[1]!=NULL
)
1140 extl_table_sets_s(tab
, "class", p
[1]);
1141 if(n
>=1 && p
[0]!=NULL
)
1142 extl_table_sets_s(tab
, "instance", p
[0]);
1144 extl_table_sets_s(tab
, "role", wrole
);
1146 if(XGetTransientForHint(ioncore_g
.dpy
, cwin
->win
, &tforwin
)
1148 extl_table_sets_b(tab
, "is_transient", TRUE
);
1152 extl_table_sets_b(tab
, "is_dockapp", TRUE
);
1157 XFreeStringList(p2
);
1168 /*{{{ ConfigureRequest */
1171 static bool check_fs_cfgrq(WClientWin
*cwin
, XConfigureRequestEvent
*ev
)
1173 /* check full screen request */
1174 if((ev
->value_mask
&(CWWidth
|CWHeight
))==(CWWidth
|CWHeight
)){
1175 WRegion
*grp
=region_groupleader_of((WRegion
*)cwin
);
1176 WScreen
*scr
=clientwin_fullscreen_chkrq(cwin
, ev
->width
, ev
->height
);
1178 if(scr
!=NULL
&& REGION_MANAGER(grp
)!=(WRegion
*)scr
){
1179 bool sw
=clientwin_fullscreen_may_switchto(cwin
);
1181 cwin
->flags
|=CLIENTWIN_FS_RQ
;
1183 if(!region_fullscreen_scr(grp
, scr
, sw
))
1184 cwin
->flags
&=~CLIENTWIN_FS_RQ
;
1193 WMPlex
* find_mplexer(WRegion
*cwin
)
1197 if(obj_is((Obj
*)cwin
, &CLASSDESCR(WMPlex
)))
1198 return (WMPlex
*) cwin
;
1199 return find_mplexer(cwin
->manager
);
1202 /* Returns whether anything was actually changed. */
1203 static bool check_normal_cfgrq(WClientWin
*cwin
, XConfigureRequestEvent
*ev
)
1205 bool result
= FALSE
;
1207 if(ev
->value_mask
&(CWX
|CWY
|CWWidth
|CWHeight
)){
1208 WRQGeomParams rq
=RQGEOMPARAMS_INIT
;
1211 rq
.flags
=REGION_RQGEOM_WEAK_ALL
|REGION_RQGEOM_ABSOLUTE
;
1213 if(cwin
->size_hints
.flags
&PWinGravity
){
1214 rq
.flags
|=REGION_RQGEOM_GRAVITY
;
1215 rq
.gravity
=cwin
->size_hints
.win_gravity
;
1218 /* Do I need to insert another disparaging comment on the person who
1219 * invented special server-supported window borders that are not
1220 * accounted for in the window size? Keep it simple, stupid!
1222 if(cwin
->size_hints
.flags
&PWinGravity
){
1223 gdx
=xgravity_deltax(cwin
->size_hints
.win_gravity
,
1224 -cwin
->orig_bw
, -cwin
->orig_bw
);
1225 gdy
=xgravity_deltay(cwin
->size_hints
.win_gravity
,
1226 -cwin
->orig_bw
, -cwin
->orig_bw
);
1229 region_rootpos((WRegion
*)cwin
, &(rq
.geom
.x
), &(rq
.geom
.y
));
1230 rq
.geom
.w
=REGION_GEOM(cwin
).w
;
1231 rq
.geom
.h
=REGION_GEOM(cwin
).h
;
1233 if(ev
->value_mask
&CWWidth
){
1234 /* If x was not changed, keep reference point where it was */
1235 if(cwin
->size_hints
.flags
&PWinGravity
){
1236 rq
.geom
.x
+=xgravity_deltax(cwin
->size_hints
.win_gravity
, 0,
1237 ev
->width
-rq
.geom
.w
);
1239 rq
.geom
.w
=maxof(ev
->width
, 1);
1240 rq
.flags
&=~REGION_RQGEOM_WEAK_W
;
1242 if(ev
->value_mask
&CWHeight
){
1243 /* If y was not changed, keep reference point where it was */
1244 if(cwin
->size_hints
.flags
&PWinGravity
){
1245 rq
.geom
.y
+=xgravity_deltay(cwin
->size_hints
.win_gravity
, 0,
1246 ev
->height
-rq
.geom
.h
);
1248 rq
.geom
.h
=maxof(ev
->height
, 1);
1249 rq
.flags
&=~REGION_RQGEOM_WEAK_H
;
1251 if(ev
->value_mask
&CWX
){
1252 rq
.geom
.x
=ev
->x
+gdx
;
1253 rq
.flags
&=~REGION_RQGEOM_WEAK_X
;
1254 rq
.flags
&=~REGION_RQGEOM_WEAK_W
;
1256 if(ev
->value_mask
&CWY
){
1257 rq
.geom
.y
=ev
->y
+gdy
;
1258 rq
.flags
&=~REGION_RQGEOM_WEAK_Y
;
1259 rq
.flags
&=~REGION_RQGEOM_WEAK_H
;
1262 region_rqgeom((WRegion
*)cwin
, &rq
, NULL
);
1267 if(ioncore_g
.window_stacking_request
!= IONCORE_WINDOWSTACKINGREQUEST_IGNORE
&&
1268 ev
->value_mask
&CWStackMode
){
1271 region_set_activity((WRegion
*) cwin
, SETPARAM_SET
);
1272 /* TODO we should be more conservative here - but what does/should
1273 * region_set_activity return? */
1289 void clientwin_handle_configure_request(WClientWin
*cwin
,
1290 XConfigureRequestEvent
*ev
)
1292 if(ev
->value_mask
&CWBorderWidth
)
1293 cwin
->orig_bw
=ev
->border_width
;
1295 cwin
->flags
|=CLIENTWIN_NEED_CFGNTFY
;
1297 if(!(cwin
->flags
&CLIENTWIN_PROP_IGNORE_CFGRQ
)){
1298 if(!check_fs_cfgrq(cwin
, ev
))
1299 check_normal_cfgrq(cwin
, ev
);
1302 if(cwin
->flags
&CLIENTWIN_NEED_CFGNTFY
){
1303 sendconfig_clientwin(cwin
);
1304 cwin
->flags
&=~CLIENTWIN_NEED_CFGNTFY
;
1316 * Attempts to fix window size problems with non-ICCCM compliant
1320 void clientwin_nudge(WClientWin
*cwin
)
1322 XResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1323 2*REGION_GEOM(cwin
).w
, 2*REGION_GEOM(cwin
).h
);
1324 XFlush(ioncore_g
.dpy
);
1325 XResizeWindow(ioncore_g
.dpy
, cwin
->win
,
1326 REGION_GEOM(cwin
).w
, REGION_GEOM(cwin
).h
);
1337 * Return the X window id for the client window.
1341 double clientwin_xid(WClientWin
*cwin
)
1353 static int last_checkcode
=1;
1356 static ExtlTab
clientwin_get_configuration(WClientWin
*cwin
)
1360 SMCfgCallback
*cfg_cb
;
1361 SMAddCallback
*add_cb
;
1363 tab
=region_get_base_configuration((WRegion
*)cwin
);
1365 extl_table_sets_d(tab
, "windowid", (double)(cwin
->win
));
1367 if(last_checkcode
!=0){
1368 chkc
=last_checkcode
++;
1369 xwindow_set_integer_property(cwin
->win
, ioncore_g
.atom_checkcode
,
1371 extl_table_sets_i(tab
, "checkcode", chkc
);
1374 ioncore_get_sm_callbacks(&add_cb
, &cfg_cb
);
1383 static void do_sm(ExtlTab tab
)
1385 SMAddCallback
*add_cb
;
1386 SMCfgCallback
*cfg_cb
;
1389 ioncore_get_sm_callbacks(&add_cb
, &cfg_cb
);
1392 ph
=ioncore_get_load_pholder();
1395 if(!add_cb(ph
, tab
))
1396 destroy_obj((Obj
*)ph
);
1402 WRegion
*clientwin_load(WWindow
*par
, const WFitParams
*fp
, ExtlTab tab
)
1406 int chkc
=0, real_chkc
=0;
1407 WClientWin
*cwin
=NULL
;
1408 XWindowAttributes attr
;
1410 bool got_chkc
=FALSE
;
1412 if(!extl_table_gets_d(tab
, "windowid", &wind
) ||
1413 !extl_table_gets_i(tab
, "checkcode", &chkc
)){
1419 if(XWINDOW_REGION_OF(win
)!=NULL
){
1420 warn("Client window %x already managed.", win
);
1424 got_chkc
=xwindow_get_integer_property(win
, ioncore_g
.atom_checkcode
,
1427 if(!got_chkc
|| real_chkc
!=chkc
){
1434 if(!XGetWindowAttributes(ioncore_g
.dpy
, win
, &attr
)){
1435 warn(TR("Window %#x disappeared."), win
);
1439 if(attr
.root
!=region_root_of((WRegion
*)par
))
1442 if(attr
.override_redirect
||
1443 (ioncore_g
.opmode
==IONCORE_OPMODE_INIT
&& attr
.map_state
!=IsViewable
)){
1444 warn(TR("Saved client window does not want to be managed."));
1448 cwin
=create_clientwin(par
, win
, &attr
);
1453 /* Reparent and resize taking limits set by size hints into account */
1454 convert_geom(fp
, cwin
, &rg
);
1455 REGION_GEOM(cwin
)=rg
;
1456 do_reparent_clientwin(cwin
, par
->win
, rg
.x
, rg
.y
);
1457 XResizeWindow(ioncore_g
.dpy
, win
, maxof(1, rg
.w
), maxof(1, rg
.h
));
1459 if(!postmanage_check(cwin
, &attr
)){
1460 clientwin_destroyed(cwin
);
1464 return (WRegion
*)cwin
;
1471 /*{{{ Dynfuntab and class info */
1474 static DynFunTab clientwin_dynfuntab
[]={
1475 {(DynFun
*)region_fitrep
,
1476 (DynFun
*)clientwin_fitrep
},
1484 {region_do_set_focus
,
1485 clientwin_do_set_focus
},
1487 {region_notify_rootpos
,
1488 clientwin_notify_rootpos
},
1494 clientwin_stacking
},
1496 {(DynFun
*)region_xwindow
,
1497 (DynFun
*)clientwin_x_window
},
1500 clientwin_activated
},
1503 clientwin_size_hints
},
1505 {(DynFun
*)region_orientation
,
1506 (DynFun
*)clientwin_orientation
},
1508 {(DynFun
*)region_rqclose
,
1509 (DynFun
*)clientwin_rqclose
},
1511 {(DynFun
*)region_get_configuration
,
1512 (DynFun
*)clientwin_get_configuration
},
1519 IMPLCLASS(WClientWin
, WRegion
, clientwin_deinit
, clientwin_dynfuntab
);