2 * Window Maker window manager
4 * Copyright (c) 1997-2003 Alfredo K. Kojima
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include <X11/Xutil.h>
24 #include <X11/Xatom.h>
38 #include <X11/XKBlib.h>
40 #include <WINGs/WUtil.h>
41 #include <WINGs/WINGsP.h>
46 #include "WindowMaker.h"
54 #include "xmodifier.h"
60 #define ICON_SIZE wPreferences.icon_size
62 /**** Local prototypes *****/
63 static void UnescapeWM_CLASS(const char *str
, char **name
, char **class);
65 /* XFetchName Wrapper */
66 Bool
wFetchName(Display
*dpy
, Window win
, char **winname
)
68 XTextProperty text_prop
;
72 if (XGetWMName(dpy
, win
, &text_prop
)) {
73 if (text_prop
.value
&& text_prop
.nitems
> 0) {
74 if (text_prop
.encoding
== XA_STRING
) {
75 *winname
= wstrdup((char *)text_prop
.value
);
76 XFree(text_prop
.value
);
78 text_prop
.nitems
= strlen((char *)text_prop
.value
);
79 if (XmbTextPropertyToTextList(dpy
, &text_prop
, &list
, &num
) >=
80 Success
&& num
> 0 && *list
) {
81 XFree(text_prop
.value
);
82 *winname
= wstrdup(*list
);
83 XFreeStringList(list
);
85 *winname
= wstrdup((char *)text_prop
.value
);
86 XFree(text_prop
.value
);
90 /* the title is set, but it was set to none */
91 *winname
= wstrdup("");
95 /* the hint is probably not set */
102 /* XGetIconName Wrapper */
103 Bool
wGetIconName(Display
*dpy
, Window win
, char **iconname
)
105 XTextProperty text_prop
;
109 if (XGetWMIconName(dpy
, win
, &text_prop
) != 0 && text_prop
.value
&& text_prop
.nitems
> 0) {
110 if (text_prop
.encoding
== XA_STRING
)
111 *iconname
= (char *)text_prop
.value
;
113 text_prop
.nitems
= strlen((char *)text_prop
.value
);
114 if (XmbTextPropertyToTextList(dpy
, &text_prop
, &list
, &num
) >= Success
&& num
> 0 && *list
) {
115 XFree(text_prop
.value
);
116 *iconname
= wstrdup(*list
);
117 XFreeStringList(list
);
119 *iconname
= (char *)text_prop
.value
;
127 static void eatExpose(void)
131 /* compress all expose events into a single one */
133 if (XCheckMaskEvent(dpy
, ExposureMask
, &event
)) {
134 /* ignore other exposure events for this window */
135 while (XCheckWindowEvent(dpy
, event
.xexpose
.window
, ExposureMask
, &foo
)) ;
136 /* eat exposes for other windows */
139 event
.xexpose
.count
= 0;
140 XPutBackEvent(dpy
, &event
);
144 void move_window(Window win
, int from_x
, int from_y
, int to_x
, int to_y
)
146 #ifdef USE_ANIMATIONS
147 if (wPreferences
.no_animations
)
148 XMoveWindow(dpy
, win
, to_x
, to_y
);
150 slide_window(win
, from_x
, from_y
, to_x
, to_y
);
152 XMoveWindow(dpy
, win
, to_x
, to_y
);
154 /* Tell the compiler it is normal that those parameters are not used in this case */
160 /* wins is an array of Window, sorted from left to right, the first is
161 * going to be moved from (from_x,from_y) to (to_x,to_y) and the
162 * following windows are going to be offset by (ICON_SIZE*i,0) */
163 void slide_windows(Window wins
[], int n
, int from_x
, int from_y
, int to_x
, int to_y
)
165 time_t time0
= time(NULL
);
166 float dx
, dy
, x
= from_x
, y
= from_y
, px
, py
;
167 Bool is_dx_nul
, is_dy_nul
;
168 int dx_is_bigger
= 0, dx_int
, dy_int
;
169 int slide_delay
, slide_steps
, slide_slowdown
;
172 /* animation parameters */
173 static const struct {
178 {ICON_SLIDE_DELAY_UF
, ICON_SLIDE_STEPS_UF
, ICON_SLIDE_SLOWDOWN_UF
},
179 {ICON_SLIDE_DELAY_F
, ICON_SLIDE_STEPS_F
, ICON_SLIDE_SLOWDOWN_F
},
180 {ICON_SLIDE_DELAY_M
, ICON_SLIDE_STEPS_M
, ICON_SLIDE_SLOWDOWN_M
},
181 {ICON_SLIDE_DELAY_S
, ICON_SLIDE_STEPS_S
, ICON_SLIDE_SLOWDOWN_S
},
182 {ICON_SLIDE_DELAY_US
, ICON_SLIDE_STEPS_US
, ICON_SLIDE_SLOWDOWN_US
}
185 slide_slowdown
= apars
[(int)wPreferences
.icon_slide_speed
].slowdown
;
186 slide_steps
= apars
[(int)wPreferences
.icon_slide_speed
].steps
;
187 slide_delay
= apars
[(int)wPreferences
.icon_slide_speed
].delay
;
189 dx_int
= to_x
- from_x
;
190 dy_int
= to_y
- from_y
;
191 is_dx_nul
= (dx_int
== 0);
192 is_dy_nul
= (dy_int
== 0);
196 if (abs(dx_int
) > abs(dy_int
)) {
201 px
= dx
/ slide_slowdown
;
202 if (px
< slide_steps
&& px
> 0)
204 else if (px
> -slide_steps
&& px
< 0)
206 py
= (is_dx_nul
? 0.0F
: px
* dy
/ dx
);
208 py
= dy
/ slide_slowdown
;
209 if (py
< slide_steps
&& py
> 0)
211 else if (py
> -slide_steps
&& py
< 0)
213 px
= (is_dy_nul
? 0.0F
: py
* dx
/ dy
);
216 while (((int)x
) != to_x
||
220 if ((px
< 0 && (int)x
< to_x
) || (px
> 0 && (int)x
> to_x
))
222 if ((py
< 0 && (int)y
< to_y
) || (py
> 0 && (int)y
> to_y
))
226 px
= px
* (1.0F
- 1 / (float)slide_slowdown
);
227 if (px
< slide_steps
&& px
> 0)
229 else if (px
> -slide_steps
&& px
< 0)
231 py
= (is_dx_nul
? 0.0F
: px
* dy
/ dx
);
233 py
= py
* (1.0F
- 1 / (float)slide_slowdown
);
234 if (py
< slide_steps
&& py
> 0)
236 else if (py
> -slide_steps
&& py
< 0)
238 px
= (is_dy_nul
? 0.0F
: py
* dx
/ dy
);
241 for (i
= 0; i
< n
; i
++) {
242 XMoveWindow(dpy
, wins
[i
], (int)x
+ i
* ICON_SIZE
, (int)y
);
245 if (slide_delay
> 0) {
246 wusleep(slide_delay
* 1000L);
250 if (time(NULL
) - time0
> MAX_ANIMATION_TIME
)
253 for (i
= 0; i
< n
; i
++) {
254 XMoveWindow(dpy
, wins
[i
], to_x
+ i
* ICON_SIZE
, to_y
);
258 /* compress expose events */
262 char *ShrinkString(WMFont
*font
, const char *string
, int width
)
271 w
= WMWidthOfString(font
, string
, p
);
272 text
= wmalloc(strlen(string
) + 8);
273 strcpy(text
, string
);
277 pos
= strchr(text
, ' ');
279 pos
= strchr(text
, ':');
284 w1
= WMWidthOfString(font
, text
, p
);
300 width
-= WMWidthOfString(font
, "...", 3);
305 while (p2
> p1
&& p1
!= t
) {
306 w
= WMWidthOfString(font
, &string
[p
- t
], t
);
309 t
= p1
+ (p2
- p1
) / 2;
310 } else if (w
< width
) {
312 t
= p1
+ (p2
- p1
) / 2;
316 strcat(text
, &string
[p
- p1
]);
321 char *FindImage(const char *paths
, const char *file
)
323 char *tmp
, *path
= NULL
;
325 tmp
= strrchr(file
, ':');
328 path
= wfindfile(paths
, file
);
332 path
= wfindfile(paths
, file
);
337 static void timeoutHandler(void *data
)
342 static char *getTextSelection(WScreen
* screen
, Atom selection
)
376 data
= XFetchBuffer(dpy
, &size
, buffer
);
383 unsigned long len
, bytes
;
387 static Atom clipboard
= 0;
390 clipboard
= XInternAtom(dpy
, "CLIPBOARD", False
);
392 XDeleteProperty(dpy
, screen
->info_window
, clipboard
);
394 XConvertSelection(dpy
, selection
, XA_STRING
, clipboard
, screen
->info_window
, CurrentTime
);
396 timer
= WMAddTimerHandler(1000, timeoutHandler
, &timeout
);
398 while (!XCheckTypedWindowEvent(dpy
, screen
->info_window
, SelectionNotify
, &ev
) && !timeout
) ;
401 WMDeleteTimerHandler(timer
);
403 wwarning("selection retrieval timed out");
407 /* nobody owns the selection or the current owner has
408 * nothing to do with what we need */
409 if (ev
.xselection
.property
== None
) {
413 if (XGetWindowProperty(dpy
, screen
->info_window
,
415 False
, XA_STRING
, &rtype
, &bits
, &len
,
416 &bytes
, (unsigned char **)&data
) != Success
) {
419 if (rtype
!= XA_STRING
|| bits
!= 8) {
420 wwarning("invalid data in text selection");
429 static char *getselection(WScreen
* scr
)
433 tmp
= getTextSelection(scr
, XA_PRIMARY
);
435 tmp
= getTextSelection(scr
, XA_CUT_BUFFER0
);
440 parseuserinputpart(const char *line
, int *ptr
, const char *endchars
)
442 int depth
= 0, begin
;
446 while(line
[*ptr
] != '\0') {
447 if(line
[*ptr
] == '(') {
449 } else if(depth
> 0 && line
[*ptr
] == ')') {
451 } else if(depth
== 0 && strchr(endchars
, line
[*ptr
]) != NULL
) {
452 value
= wmalloc(*ptr
- begin
+ 1);
453 strncpy(value
, line
+ begin
, *ptr
- begin
);
454 value
[*ptr
- begin
] = '\0';
464 getuserinput(WScreen
*scr
, const char *line
, int *ptr
, Bool advanced
)
466 char *ret
= NULL
, *title
= NULL
, *prompt
= NULL
, *name
= NULL
;
469 if(line
[*ptr
] == '(')
470 title
= parseuserinputpart(line
, ptr
, ",)");
471 if(title
!= NULL
&& line
[*ptr
] == ',')
472 prompt
= parseuserinputpart(line
, ptr
, ",)");
473 if(prompt
!= NULL
&& line
[*ptr
] == ',')
474 name
= parseuserinputpart(line
, ptr
, ")");
477 rv
= wAdvancedInputDialog(scr
,
478 title
? _(title
):_("Program Arguments"),
479 prompt
? _(prompt
):_("Enter command arguments:"),
482 rv
= wInputDialog(scr
,
483 title
? _(title
):_("Program Arguments"),
484 prompt
? _(prompt
):_("Enter command arguments:"),
487 if(title
) wfree(title
);
488 if(prompt
) wfree(prompt
);
489 if(name
) wfree(name
);
491 return rv
? ret
: NULL
;
499 * state input new-state output
500 * NORMAL % OPTION <nil>
501 * NORMAL \ ESCAPE <nil>
502 * NORMAL etc. NORMAL <input>
503 * ESCAPE any NORMAL <input>
504 * OPTION s NORMAL <selection buffer>
505 * OPTION w NORMAL <selected window id>
506 * OPTION a NORMAL <input text>
507 * OPTION d NORMAL <OffiX DND selection object>
508 * OPTION W NORMAL <current workspace>
509 * OPTION etc. NORMAL %<input>
511 #define TMPBUFSIZE 64
512 char *ExpandOptions(WScreen
*scr
, const char *cmdline
)
514 int ptr
, optr
, state
, len
, olen
;
516 char *selection
= NULL
;
517 char *user_input
= NULL
;
518 char tmpbuf
[TMPBUFSIZE
];
521 len
= strlen(cmdline
);
525 wwarning(_("out of memory during expansion of \"%s\""), cmdline
);
529 ptr
= 0; /* input line pointer */
530 optr
= 0; /* output line pointer */
535 switch (cmdline
[ptr
]) {
544 out
[optr
++] = cmdline
[ptr
];
549 switch (cmdline
[ptr
]) {
563 out
[optr
++] = cmdline
[ptr
];
569 switch (cmdline
[ptr
]) {
571 if (scr
->focused_window
&& scr
->focused_window
->flags
.focused
) {
572 snprintf(tmpbuf
, sizeof(tmpbuf
), "0x%x",
573 (unsigned int)scr
->focused_window
->client_win
);
574 slen
= strlen(tmpbuf
);
576 nout
= realloc(out
, olen
);
578 wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%w", cmdline
);
590 snprintf(tmpbuf
, sizeof(tmpbuf
), "0x%x", (unsigned int)scr
->current_workspace
+ 1);
591 slen
= strlen(tmpbuf
);
593 nout
= realloc(out
, olen
);
595 wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%W", cmdline
);
606 user_input
= getuserinput(scr
, cmdline
, &ptr
, cmdline
[ptr
-1] == 'A');
608 slen
= strlen(user_input
);
610 nout
= realloc(out
, olen
);
612 wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%a", cmdline
);
616 strcat(out
, user_input
);
619 /* Not an error, but user has Canceled the dialog box.
620 * This will make the command to not be performed. */
627 if (!scr
->xdestring
) {
628 scr
->flags
.dnd_data_convertion_status
= 1;
631 slen
= strlen(scr
->xdestring
);
633 nout
= realloc(out
, olen
);
635 wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%d", cmdline
);
639 strcat(out
, scr
->xdestring
);
642 #endif /* USE_DOCK_XDND */
646 selection
= getselection(scr
);
649 wwarning(_("selection not available"));
652 slen
= strlen(selection
);
654 nout
= realloc(out
, olen
);
656 wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%s", cmdline
);
660 strcat(out
, selection
);
666 out
[optr
++] = cmdline
[ptr
];
684 void ExecuteExitCommand(WScreen
*scr
, long quickmode
)
686 static int inside
= 0;
689 /* prevent reentrant calls */
699 if (quickmode
== M_QUICK
) {
702 int r
, oldSaveSessionFlag
;
704 oldSaveSessionFlag
= wPreferences
.save_session_on_exit
;
705 r
= wExitDialog(scr
, _("Exit"),
706 _("Are you sure you want to quit Window Maker?"), _("Exit"), _("Cancel"), NULL
);
708 if (r
== WAPRDefault
) {
710 } else if (r
== WAPRAlternate
) {
711 /* Don't modify the "save session on exit" flag if the
712 * user canceled the operation. */
713 wPreferences
.save_session_on_exit
= oldSaveSessionFlag
;
716 if (result
== R_EXIT
)
717 Shutdown(WSExitMode
);
724 void ExecuteInputCommand(WScreen
*scr
, const char *cmdline
)
728 cmd
= ExpandOptions(scr
, cmdline
);
730 XGrabPointer(dpy
, scr
->root_win
, True
, 0,
731 GrabModeAsync
, GrabModeAsync
, None
, wPreferences
.cursor
[WCUR_WAIT
], CurrentTime
);
734 ExecuteShellCommand(scr
, cmd
);
737 XUngrabPointer(dpy
, CurrentTime
);
742 void ParseWindowName(WMPropList
*value
, char **winstance
, char **wclass
, const char *where
)
746 *winstance
= *wclass
= NULL
;
748 if (!WMIsPLString(value
)) {
749 wwarning(_("bad window name value in %s state info"), where
);
753 name
= WMGetFromPLString(value
);
754 if (!name
|| strlen(name
) == 0) {
755 wwarning(_("bad window name value in %s state info"), where
);
759 UnescapeWM_CLASS(name
, winstance
, wclass
);
763 static char *keysymToString(KeySym keysym
, unsigned int state
)
766 char *buf
= wmalloc(20);
771 kev
.send_event
= False
;
772 kev
.window
= DefaultRootWindow(dpy
);
773 kev
.root
= DefaultRootWindow(dpy
);
774 kev
.same_screen
= True
;
775 kev
.subwindow
= kev
.root
;
776 kev
.serial
= 0x12344321;
777 kev
.time
= CurrentTime
;
779 kev
.keycode
= XKeysymToKeycode(dpy
, keysym
);
780 count
= XLookupString(&kev
, buf
, 19, NULL
, NULL
);
787 char *GetShortcutString(const char *shortcut
)
790 char *k
, *tmp
, *text
;
792 tmp
= text
= wstrdup(shortcut
);
795 while ((k
= strchr(text
, '+')) != NULL
) {
800 mod
= wXModifierFromKey(text
);
803 return wstrdup("bug");
805 lbl
= wXModifierToShortcutLabel(mod
);
807 buffer
= wstrappend(buffer
, lbl
);
809 buffer
= wstrappend(buffer
, text
);
813 buffer
= wstrappend(buffer
, text
);
819 char *GetShortcutKey(WShortKey key
)
821 const char *key_name
;
825 void append_string(const char *text
)
827 const char *string
= text
;
830 if (wr
>= buffer
+ sizeof(buffer
) - 1)
836 void append_modifier(int modifier_index
, const char *fallback_name
)
838 if (wPreferences
.modifier_labels
[modifier_index
]) {
839 append_string(wPreferences
.modifier_labels
[modifier_index
]);
841 append_string(fallback_name
);
845 key_name
= XKeysymToString(W_KeycodeToKeysym(dpy
, key
.keycode
, 0));
850 if (key
.modifier
& ControlMask
) append_modifier(1, "Control+");
851 if (key
.modifier
& ShiftMask
) append_modifier(0, "Shift+");
852 if (key
.modifier
& Mod1Mask
) append_modifier(2, "Mod1+");
853 if (key
.modifier
& Mod2Mask
) append_modifier(3, "Mod2+");
854 if (key
.modifier
& Mod3Mask
) append_modifier(4, "Mod3+");
855 if (key
.modifier
& Mod4Mask
) append_modifier(5, "Mod4+");
856 if (key
.modifier
& Mod5Mask
) append_modifier(6, "Mod5+");
857 append_string(key_name
);
860 return GetShortcutString(buffer
);
863 char *EscapeWM_CLASS(const char *name
, const char *class)
866 char *ename
= NULL
, *eclass
= NULL
;
874 ename
= wmalloc(l
* 2 + 1);
876 for (i
= 0; i
< l
; i
++) {
877 if (name
[i
] == '\\') {
879 } else if (name
[i
] == '.') {
882 ename
[j
++] = name
[i
];
888 eclass
= wmalloc(l
* 2 + 1);
890 for (i
= 0; i
< l
; i
++) {
891 if (class[i
] == '\\') {
893 } else if (class[i
] == '.') {
896 eclass
[j
++] = class[i
];
901 if (ename
&& eclass
) {
902 int len
= strlen(ename
) + strlen(eclass
) + 4;
904 snprintf(ret
, len
, "%s.%s", ename
, eclass
);
908 ret
= wstrdup(ename
);
911 ret
= wstrdup(eclass
);
918 static void UnescapeWM_CLASS(const char *str
, char **name
, char **class)
925 /* separate string in 2 parts */
928 for (i
= 0; i
< j
; i
++, length_of_name
++) {
929 if (str
[i
] == '\\') {
932 } else if (str
[i
] == '.') {
938 /* unescape the name */
939 if (length_of_name
> 0) {
940 *name
= wmalloc(length_of_name
+ 1);
941 for (i
= 0, k
= 0; i
< dot
; i
++) {
943 (*name
)[k
++] = str
[i
];
950 /* unescape the class */
952 *class = wmalloc(j
- (dot
+ 1) + 1);
953 for (i
= dot
+ 1, k
= 0; i
< j
; i
++) {
955 (*class)[k
++] = str
[i
];
963 static void track_bg_helper_death(pid_t pid
, unsigned int status
, void *client_data
)
965 WScreen
*scr
= (WScreen
*) client_data
;
967 /* Parameter not used, but tell the compiler that it is ok */
971 close(scr
->helper_fd
);
974 scr
->flags
.backimage_helper_launched
= 0;
977 Bool
start_bg_helper(WScreen
*scr
)
982 if (pipe(filedes
) < 0) {
983 werror(_("%s failed, can't set workspace specific background image (%s)"),
984 "pipe()", strerror(errno
));
990 werror(_("%s failed, can't set workspace specific background image (%s)"),
991 "fork()", strerror(errno
));
996 } else if (pid
== 0) {
999 /* We don't need this side of the pipe in the child process */
1002 SetupEnvironment(scr
);
1004 close(STDIN_FILENO
);
1005 if (dup2(filedes
[0], STDIN_FILENO
) < 0) {
1006 werror(_("%s failed, can't set workspace specific background image (%s)"),
1007 "dup2()", strerror(errno
));
1012 dither
= wPreferences
.no_dithering
? "-m" : "-d";
1013 if (wPreferences
.smooth_workspace_back
)
1014 execlp("wmsetbg", "wmsetbg", "-helper", "-S", dither
, NULL
);
1016 execlp("wmsetbg", "wmsetbg", "-helper", dither
, NULL
);
1018 werror(_("could not execute \"%s\": %s"), "wmsetbg", strerror(errno
));
1022 /* We don't need this side of the pipe in the parent process */
1025 if (fcntl(filedes
[1], F_SETFD
, FD_CLOEXEC
) < 0)
1026 wwarning(_("could not set close-on-exec flag for bg_helper's communication file handle (%s)"),
1029 scr
->helper_fd
= filedes
[1];
1030 scr
->helper_pid
= pid
;
1031 scr
->flags
.backimage_helper_launched
= 1;
1033 wAddDeathHandler(pid
, track_bg_helper_death
, scr
);
1039 void SendHelperMessage(WScreen
*scr
, char type
, int workspace
, const char *msg
)
1046 if (!scr
->flags
.backimage_helper_launched
) {
1050 len
= (msg
? strlen(msg
) : 0) + (workspace
>= 0 ? 4 : 0) + 1;
1051 buffer
= wmalloc(len
+ 5);
1052 snprintf(buf
, sizeof(buf
), "%4i", len
);
1053 memcpy(buffer
, buf
, 4);
1056 if (workspace
>= 0) {
1057 snprintf(buf
, sizeof(buf
), "%4i", workspace
);
1058 memcpy(&buffer
[i
], buf
, 4);
1063 strcpy(&buffer
[i
], msg
);
1065 if (write(scr
->helper_fd
, buffer
, len
+ 4) < 0) {
1066 werror(_("could not send message to background image helper"));
1071 Bool
UpdateDomainFile(WDDomain
* domain
)
1074 char path
[PATH_MAX
];
1075 WMPropList
*shared_dict
, *dict
;
1076 Bool result
, freeDict
= False
;
1078 dict
= domain
->dictionary
;
1079 if (WMIsPLDictionary(domain
->dictionary
)) {
1080 /* retrieve global system dictionary */
1081 snprintf(path
, sizeof(path
), "%s/%s", PKGCONFDIR
, domain
->domain_name
);
1082 if (stat(path
, &stbuf
) >= 0) {
1083 shared_dict
= WMReadPropListFromFile(path
);
1085 if (WMIsPLDictionary(shared_dict
)) {
1087 dict
= WMDeepCopyPropList(domain
->dictionary
);
1088 WMSubtractPLDictionaries(dict
, shared_dict
, True
);
1090 WMReleasePropList(shared_dict
);
1095 result
= WMWritePropListToFile(dict
, domain
->path
);
1098 WMReleasePropList(dict
);
1104 char *StrConcatDot(const char *a
, const char *b
)
1114 len
= strlen(a
) + strlen(b
) + 4;
1117 snprintf(str
, len
, "%s.%s", a
, b
);
1122 static char *getCommandForWindow(Window win
, int elements
)
1124 char **argv
, *command
= NULL
;
1127 if (XGetCommand(dpy
, win
, &argv
, &argc
)) {
1128 if (argc
> 0 && argv
!= NULL
) {
1131 command
= wtokenjoin(argv
, WMIN(argc
, elements
));
1132 if (command
[0] == 0) {
1138 XFreeStringList(argv
);
1145 /* Free result when done */
1146 char *GetCommandForWindow(Window win
)
1148 return getCommandForWindow(win
, 0);