2 * awesome-menu.c - menu window for awesome
4 * Copyright © 2008 Julien Danjou <julien@danjou.info>
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.
22 /* getline(), asprintf() */
25 #define CHUNK_SIZE 4096
34 #include <sys/types.h>
39 #include <X11/Xutil.h>
41 #include "common/swindow.h"
42 #include "common/util.h"
43 #include "common/version.h"
44 #include "common/configopts.h"
45 #include "common/xutil.h"
46 #include "common/xscreen.c"
48 #define PROGNAME "awesome-menu"
50 #define CLEANMASK(mask) (mask & ~(globalconf.numlockmask | LockMask))
59 static status_t status
= RUN
;
61 /** Import awesome config file format */
62 extern cfg_opt_t awesome_opts
[];
64 typedef struct item_t item_t
;
73 item_delete(item_t
**item
)
75 p_delete(&(*item
)->data
);
79 DO_SLIST(item_t
, item
, item_delete
)
81 /** awesome-run global configuration structure */
88 /** The draw contet */
97 unsigned int numlockmask
;
100 /** The text length */
105 item_t
*item_selected
;
106 /** What to do with the result text */
112 static AwesomeMenuConf globalconf
;
114 static void __attribute__ ((noreturn
))
115 exit_help(int exit_code
)
117 FILE *outfile
= (exit_code
== EXIT_SUCCESS
) ? stdout
: stderr
;
118 fprintf(outfile
, "Usage: %s [-c config] [-e command] <message>\n",
124 config_parse(int screen
, const char *confpatharg
,
125 const char *menu_title
, area_t
*geometry
)
129 cfg_t
*cfg
, *cfg_menu
= NULL
, *cfg_screen
= NULL
,
130 *cfg_styles
, *cfg_menu_styles
= NULL
;
133 confpath
= config_file();
135 confpath
= a_strdup(confpatharg
);
139 switch((ret
= cfg_parse(cfg
, confpath
)))
142 warn("parsing configuration file failed: %s\n", strerror(errno
));
144 case CFG_PARSE_ERROR
:
145 cfg_error(cfg
, "W: awesome: parsing configuration file %s failed.\n", confpath
);
152 if(menu_title
&& !(cfg_menu
= cfg_gettsec(cfg
, "menu", menu_title
)))
153 warn("no definition for menu %s in configuration file: using default\n",
156 /* get global screen section */
157 if(!(cfg_screen
= cfg_getnsec(cfg
, "screen", screen
)))
158 cfg_screen
= cfg_getsec(cfg
, "screen");
162 cfg_menu_styles
= cfg_getsec(cfg_menu
, "styles");
163 if((i
= cfg_getint(cfg_menu
, "x")) != (int) 0xffffffff)
165 if((i
= cfg_getint(cfg_menu
, "y")) != (int) 0xffffffff)
167 if((i
= cfg_getint(cfg_menu
, "width")) > 0)
169 if((i
= cfg_getint(cfg_menu
, "height")) > 0)
170 geometry
->height
= i
;
174 && (cfg_styles
= cfg_getsec(cfg_screen
, "styles")))
176 /* Grab default styles */
177 draw_style_init(globalconf
.display
, DefaultScreen(globalconf
.display
),
178 cfg_getsec(cfg_styles
, "normal"),
179 &globalconf
.styles
.normal
, NULL
);
181 draw_style_init(globalconf
.display
, DefaultScreen(globalconf
.display
),
182 cfg_getsec(cfg_styles
, "focus"),
183 &globalconf
.styles
.focus
, &globalconf
.styles
.normal
);
186 /* Now grab menu styles if any */
189 draw_style_init(globalconf
.display
, DefaultScreen(globalconf
.display
),
190 cfg_getsec(cfg_menu_styles
, "normal"),
191 &globalconf
.styles
.normal
, NULL
);
193 draw_style_init(globalconf
.display
, DefaultScreen(globalconf
.display
),
194 cfg_getsec(cfg_menu_styles
, "focus"),
195 &globalconf
.styles
.focus
, &globalconf
.styles
.normal
);
198 if(!globalconf
.styles
.normal
.font
)
199 eprint("no default font available\n");
207 get_last_word(char *text
)
211 if((last_word
= strrchr(text
, ' ')))
220 item_list_fill_file(const char *directory
)
222 char *cwd
, *home
, *user
, *filename
;
225 struct dirent
*dirinfo
;
227 ssize_t len
, lenfile
;
228 struct passwd
*passwd
= NULL
;
231 item_list_wipe(&globalconf
.items
);
234 cwd
= a_strdup("./");
235 else if(a_strlen(directory
) > 1 && directory
[0] == '~')
237 if(directory
[1] == '/')
239 home
= getenv("HOME");
240 asprintf(&cwd
, "%s%s", (home
? home
: ""), directory
+ 1);
244 if(!(file
= strchr(directory
, '/')))
245 file
= directory
+ a_strlen(directory
);
246 len
= (file
- directory
) + 1;
247 user
= p_new(char, len
);
248 a_strncpy(user
, len
, directory
+ 1, (file
- directory
) - 1);
249 if((passwd
= getpwnam(user
)))
251 asprintf(&cwd
, "%s%s", passwd
->pw_dir
, file
);
262 cwd
= a_strdup(directory
);
264 if(!(dir
= opendir(cwd
)))
270 while((dirinfo
= readdir(dir
)))
272 item
= p_new(item_t
, 1);
274 /* + 1 for \0 + 1 for / if directory */
275 len
= a_strlen(directory
) + a_strlen(dirinfo
->d_name
) + 2;
277 item
->data
= p_new(char, len
);
278 if(a_strlen(directory
))
279 a_strcpy(item
->data
, len
, directory
);
280 a_strcat(item
->data
, len
, dirinfo
->d_name
);
282 lenfile
= a_strlen(cwd
) + a_strlen(dirinfo
->d_name
) + 2;
284 filename
= p_new(char, lenfile
);
285 a_strcpy(filename
, lenfile
, cwd
);
286 a_strcat(filename
, lenfile
, dirinfo
->d_name
);
288 if(!stat(filename
, &st
) && S_ISDIR(st
.st_mode
))
289 a_strcat(item
->data
, len
, "/");
293 item_list_push(&globalconf
.items
, item
);
303 complete(Bool reverse
)
308 item_t
*(*item_iter
)(item_t
**, item_t
*) = item_list_next_cycle
;
311 item_iter
= item_list_prev_cycle
;
313 if(globalconf
.item_selected
)
314 item
= item_iter(&globalconf
.items
, globalconf
.item_selected
);
316 item
= globalconf
.items
;
318 for(; item
&& loop
; item
= item_iter(&globalconf
.items
, item
))
322 word
= get_last_word(globalconf
.text
);
323 a_strcpy(word
, globalconf
.text_size
- (word
- globalconf
.text
), item
->data
);
324 globalconf
.item_selected
= item
;
328 * Since loop is 2, it will be 1 at first iter, and then 0 if we
329 * get back before matching an item (i.e. no items match) to the
330 * first elem: so it will break the loop, otherwise it loops for
333 if(item
== globalconf
.items
)
339 compute_match(const char *word
)
341 ssize_t len
= a_strlen(word
);
344 /* reset the selected item to NULL */
345 globalconf
.item_selected
= NULL
;
349 if(word
[len
- 1] == '/'
350 || word
[len
- 1] == ' ')
351 item_list_fill_file(word
);
353 for(item
= globalconf
.items
; item
; item
= item
->next
)
354 if(!a_strncmp(word
, item
->data
, a_strlen(word
)))
361 if(a_strlen(globalconf
.text
))
362 item_list_fill_file(NULL
);
363 for(item
= globalconf
.items
; item
; item
= item
->next
)
375 area_t geometry
= { 0, 0, 0, 0, NULL
, NULL
};
376 Bool selected_item_is_drawn
= False
;
377 int len
, prompt_len
, x_of_previous_item
;
380 geometry
.width
= globalconf
.sw
->geometry
.width
;
381 geometry
.height
= globalconf
.sw
->geometry
.height
;
383 if(a_strlen(globalconf
.prompt
))
385 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
386 MARGIN
, globalconf
.prompt
, globalconf
.styles
.focus
);
388 len
= MARGIN
* 2 + draw_textwidth(globalconf
.display
, globalconf
.styles
.focus
.font
, globalconf
.prompt
);
390 geometry
.width
-= len
;
393 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
394 MARGIN
, globalconf
.text
, globalconf
.styles
.normal
);
396 len
= MARGIN
* 2 + MAX(draw_textwidth(globalconf
.display
, globalconf
.styles
.normal
.font
, globalconf
.text
),
397 geometry
.width
/ 20);
399 geometry
.width
-= len
;
400 prompt_len
= geometry
.x
;
402 for(item
= globalconf
.items
; item
&& geometry
.width
> 0; item
= item
->next
)
405 style
= item
== globalconf
.item_selected
? globalconf
.styles
.focus
: globalconf
.styles
.normal
;
406 len
= MARGIN
+ draw_textwidth(globalconf
.display
, style
.font
, item
->data
);
407 if(item
== globalconf
.item_selected
)
409 if(len
> geometry
.width
)
412 selected_item_is_drawn
= True
;
414 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
415 MARGIN
/ 2, item
->data
, style
);
417 geometry
.width
-= len
;
420 /* we have an item selected but not drawn, so redraw in the other side */
421 if(globalconf
.item_selected
&& !selected_item_is_drawn
)
423 geometry
.x
= globalconf
.sw
->geometry
.width
;
425 for(item
= globalconf
.item_selected
; item
; item
= item_list_prev(&globalconf
.items
, item
))
428 style
= item
== globalconf
.item_selected
? globalconf
.styles
.focus
: globalconf
.styles
.normal
;
429 x_of_previous_item
= geometry
.x
;
430 geometry
.width
= MARGIN
+ draw_textwidth(globalconf
.display
, style
.font
, item
->data
);
431 geometry
.x
-= geometry
.width
;
433 if(geometry
.x
< prompt_len
)
436 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
437 MARGIN
/ 2, item
->data
, style
);
442 geometry
.x
= prompt_len
;
443 geometry
.width
= x_of_previous_item
- prompt_len
;
444 draw_rectangle(globalconf
.ctx
, geometry
, 1.0, True
, globalconf
.styles
.normal
.bg
);
447 else if(geometry
.width
)
448 draw_rectangle(globalconf
.ctx
, geometry
, 1.0, True
, globalconf
.styles
.normal
.bg
);
450 simplewindow_refresh_drawable(globalconf
.sw
, DefaultScreen(globalconf
.display
));
451 XSync(globalconf
.display
, False
);
455 handle_kpress(XKeyEvent
*e
)
463 len
= a_strlen(globalconf
.text
);
464 num
= XLookupString(e
, buf
, sizeof(buf
), &ksym
, 0);
465 if(e
->state
& ControlMask
)
495 while(i
>= 0 && globalconf
.text
[i
] == ' ')
496 globalconf
.text
[i
--] = '\0';
497 while(i
>= 0 && globalconf
.text
[i
] != ' ')
498 globalconf
.text
[i
--] = '\0';
499 compute_match(get_last_word(globalconf
.text
));
504 else if(CLEANMASK(e
->state
) & Mod1Mask
)
520 if(num
&& !iscntrl((int) buf
[0]))
522 if(buf
[0] != '/' || globalconf
.text
[len
- 1] != '/')
526 /* Reallocate text string if needed to hold
527 * concatenation of text and buf */
528 if((text_dst_len
= (a_strlen(globalconf
.text
) + num
- 1)) > globalconf
.text_size
)
530 globalconf
.text_size
+= ((int) (text_dst_len
/ globalconf
.text_size
)) * CHUNK_SIZE
;
531 p_realloc(&globalconf
.text
, globalconf
.text_size
);
533 a_strncat(globalconf
.text
, globalconf
.text_size
, buf
, num
);
535 compute_match(get_last_word(globalconf
.text
));
542 globalconf
.text
[--len
] = '\0';
543 compute_match(get_last_word(globalconf
.text
));
547 case XK_ISO_Left_Tab
:
567 item_list_fill_stdin(void)
574 Bool has_entry
= False
;
576 item_list_init(&globalconf
.items
);
578 if((line_len
= getline(&buf
, &len
, stdin
)) != -1)
584 buf
[line_len
- 1] = '\0';
585 newitem
= p_new(item_t
, 1);
586 newitem
->data
= a_strdup(buf
);
587 newitem
->match
= True
;
588 item_list_append(&globalconf
.items
, newitem
);
590 while((line_len
= getline(&buf
, &len
, stdin
)) != -1);
599 main(int argc
, char **argv
)
603 int opt
, ret
, x
, y
, i
, screen
= 0;
604 char *configfile
= NULL
, *cmd
;
606 const char *shell
= getenv("SHELL");
607 area_t geometry
= { 0, 0, 0, 0, NULL
, NULL
};
611 static struct option long_options
[] =
613 {"help", 0, NULL
, 'h'},
614 {"version", 0, NULL
, 'v'},
615 {"exec", 0, NULL
, 'e'},
619 if(!(disp
= XOpenDisplay(NULL
)))
620 eprint("unable to open display");
622 globalconf
.display
= disp
;
624 while((opt
= getopt_long(argc
, argv
, "vhf:b:x:y:n:c:e:",
625 long_options
, NULL
)) != -1)
629 eprint_version(PROGNAME
);
632 exit_help(EXIT_SUCCESS
);
635 configfile
= a_strdup(optarg
);
638 globalconf
.exec
= a_strdup(optarg
);
642 if(argc
- optind
>= 1)
643 globalconf
.prompt
= a_strdup(argv
[optind
]);
645 /* Get the numlock mask */
646 globalconf
.numlockmask
= xgetnumlockmask(disp
);
648 si
= screensinfo_new(disp
);
649 if(si
->xinerama_is_active
)
651 if(XQueryPointer(disp
, RootWindow(disp
, DefaultScreen(disp
)),
652 &dummy
, &dummy
, &x
, &y
, &i
, &i
, &ui
))
654 screen
= screen_get_bycoord(si
, 0, x
, y
);
656 geometry
.x
= si
->geometry
[screen
].x
;
657 geometry
.y
= si
->geometry
[screen
].y
;
658 geometry
.width
= si
->geometry
[screen
].width
;
663 screen
= DefaultScreen(disp
);
665 geometry
.width
= DisplayWidth(disp
, DefaultScreen(disp
));
668 if((ret
= config_parse(screen
, configfile
, globalconf
.prompt
, &geometry
)))
671 /* Init the geometry */
673 geometry
.height
= 1.5 * MAX(globalconf
.styles
.normal
.font
->height
,
674 globalconf
.styles
.focus
.font
->height
);
676 screensinfo_delete(&si
);
678 /* Create the window */
679 globalconf
.sw
= simplewindow_new(disp
, DefaultScreen(disp
),
680 geometry
.x
, geometry
.y
, geometry
.width
, geometry
.height
, 0);
682 XStoreName(disp
, globalconf
.sw
->window
, PROGNAME
);
683 XMapRaised(disp
, globalconf
.sw
->window
);
685 /* Create the drawing context */
686 globalconf
.ctx
= draw_context_new(disp
, DefaultScreen(disp
),
687 geometry
.width
, geometry
.height
,
688 globalconf
.sw
->drawable
);
691 if(!item_list_fill_stdin())
692 item_list_fill_file(NULL
);
694 /* Allocate a default size for the text on the heap instead of
695 * using stack allocation with PATH_MAX (may not been defined
696 * according to POSIX). This string size may be increased if
698 globalconf
.text
= p_new(char, CHUNK_SIZE
);
699 globalconf
.text_size
= CHUNK_SIZE
;
702 for(opt
= 1000; opt
; opt
--)
704 if(XGrabKeyboard(disp
, DefaultRootWindow(disp
), True
,
705 GrabModeAsync
, GrabModeAsync
, CurrentTime
) == GrabSuccess
)
710 eprint("cannot grab keyboard");
716 XNextEvent(disp
, &ev
);
723 handle_kpress(&ev
.xkey
);
726 if(!ev
.xexpose
.count
)
727 simplewindow_refresh_drawable(globalconf
.sw
, DefaultScreen(disp
));
737 printf("%s\n", globalconf
.text
);
741 shell
= a_strdup("/bin/sh");
742 len
= a_strlen(globalconf
.exec
) + a_strlen(globalconf
.text
) + 1;
743 cmd
= p_new(char, len
);
744 a_strcpy(cmd
, len
, globalconf
.exec
);
745 a_strcat(cmd
, len
, globalconf
.text
);
746 execl(shell
, shell
, "-c", cmd
, NULL
);
750 p_delete(&globalconf
.text
);
751 draw_context_delete(&globalconf
.ctx
);
752 simplewindow_delete(&globalconf
.sw
);
758 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80