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() */
32 #include <sys/types.h>
37 #include "common/swindow.h"
38 #include "common/util.h"
39 #include "common/version.h"
40 #include "common/configopts.h"
41 #include "common/xutil.h"
42 #include "common/xscreen.c"
44 #define PROGNAME "awesome-menu"
46 #define CLEANMASK(mask) (mask & ~(globalconf.numlockmask | LockMask))
55 static status_t status
= RUN
;
57 /** Import awesome config file format */
58 extern cfg_opt_t awesome_opts
[];
60 typedef struct item_t item_t
;
69 item_delete(item_t
**item
)
71 p_delete(&(*item
)->data
);
75 DO_SLIST(item_t
, item
, item_delete
)
77 /** awesome-run global configuration structure */
84 /** The draw contet */
93 unsigned int numlockmask
;
99 item_t
*item_selected
;
100 /** What to do with the result text */
106 static AwesomeMenuConf globalconf
;
108 static void __attribute__ ((noreturn
))
109 exit_help(int exit_code
)
111 FILE *outfile
= (exit_code
== EXIT_SUCCESS
) ? stdout
: stderr
;
112 fprintf(outfile
, "Usage: %s [-e command] <message>\n",
118 config_parse(int screen
, const char *confpatharg
,
119 const char *menu_title
, Area
*geometry
)
123 cfg_t
*cfg
, *cfg_menu
= NULL
, *cfg_screen
= NULL
,
124 *cfg_styles
, *cfg_menu_styles
= NULL
;
127 confpath
= config_file();
129 confpath
= a_strdup(confpatharg
);
131 cfg
= cfg_init(awesome_opts
, CFGF_NONE
);
133 switch((ret
= cfg_parse(cfg
, confpath
)))
136 perror("awesome-message: parsing configuration file failed");
138 case CFG_PARSE_ERROR
:
139 cfg_error(cfg
, "awesome: parsing configuration file %s failed.\n", confpath
);
146 if(menu_title
&& !(cfg_menu
= cfg_gettsec(cfg
, "menu", menu_title
)))
147 warn("no definition for menu %s in configuration file: using default\n",
150 /* get global screen section */
151 if(!(cfg_screen
= cfg_getnsec(cfg
, "screen", screen
)))
152 cfg_screen
= cfg_getsec(cfg
, "screen");
156 cfg_menu_styles
= cfg_getsec(cfg_menu
, "styles");
157 if((i
= cfg_getint(cfg_menu
, "x")) != (int) 0xffffffff)
159 if((i
= cfg_getint(cfg_menu
, "y")) != (int) 0xffffffff)
161 if((i
= cfg_getint(cfg_menu
, "width")) > 0)
163 if((i
= cfg_getint(cfg_menu
, "height")) > 0)
164 geometry
->height
= i
;
168 && (cfg_styles
= cfg_getsec(cfg_screen
, "styles")))
170 /* Grab default styles */
171 draw_style_init(globalconf
.display
, DefaultScreen(globalconf
.display
),
172 cfg_getsec(cfg_styles
, "normal"),
173 &globalconf
.styles
.normal
, NULL
);
175 draw_style_init(globalconf
.display
, DefaultScreen(globalconf
.display
),
176 cfg_getsec(cfg_styles
, "focus"),
177 &globalconf
.styles
.focus
, &globalconf
.styles
.normal
);
180 /* Now grab menu styles if any */
183 draw_style_init(globalconf
.display
, DefaultScreen(globalconf
.display
),
184 cfg_getsec(cfg_menu_styles
, "normal"),
185 &globalconf
.styles
.normal
, NULL
);
187 draw_style_init(globalconf
.display
, DefaultScreen(globalconf
.display
),
188 cfg_getsec(cfg_menu_styles
, "focus"),
189 &globalconf
.styles
.focus
, &globalconf
.styles
.normal
);
192 if(!globalconf
.styles
.normal
.font
)
193 eprint("no default font available\n");
201 get_last_word(char *text
)
205 if((last_word
= strrchr(text
, ' ')))
214 item_list_fill_file(const char *directory
)
216 char *cwd
, *home
, *user
, *filename
;
219 struct dirent
*dirinfo
;
221 ssize_t len
, lenfile
;
222 struct passwd
*passwd
= NULL
;
225 item_list_wipe(&globalconf
.items
);
228 cwd
= a_strdup("./");
229 else if(a_strlen(directory
) > 1 && directory
[0] == '~')
231 if(directory
[1] == '/')
233 home
= getenv("HOME");
234 asprintf(&cwd
, "%s%s", (home
? home
: ""), directory
+ 1);
238 if(!(file
= strchr(directory
, '/')))
239 file
= directory
+ a_strlen(directory
);
240 len
= (file
- directory
) + 1;
241 user
= p_new(char, len
);
242 a_strncpy(user
, len
, directory
+ 1, (file
- directory
) - 1);
243 if((passwd
= getpwnam(user
)))
245 asprintf(&cwd
, "%s%s", passwd
->pw_dir
, file
);
256 cwd
= a_strdup(directory
);
258 if(!(dir
= opendir(cwd
)))
264 while((dirinfo
= readdir(dir
)))
266 item
= p_new(item_t
, 1);
268 /* + 1 for \0 + 1 for / if directory */
269 len
= a_strlen(directory
) + a_strlen(dirinfo
->d_name
) + 2;
271 item
->data
= p_new(char, len
);
272 if(a_strlen(directory
))
273 a_strcpy(item
->data
, len
, directory
);
274 a_strcat(item
->data
, len
, dirinfo
->d_name
);
276 lenfile
= a_strlen(cwd
) + a_strlen(dirinfo
->d_name
) + 2;
278 filename
= p_new(char, lenfile
);
279 a_strcpy(filename
, lenfile
, cwd
);
280 a_strcat(filename
, lenfile
, dirinfo
->d_name
);
282 if(!stat(filename
, &st
) && S_ISDIR(st
.st_mode
))
283 a_strcat(item
->data
, len
, "/");
287 item_list_push(&globalconf
.items
, item
);
297 complete(Bool reverse
)
302 item_t
*(*item_iter
)(item_t
**, item_t
*) = item_list_next_cycle
;
305 item_iter
= item_list_prev_cycle
;
307 if(globalconf
.item_selected
)
308 item
= item_iter(&globalconf
.items
, globalconf
.item_selected
);
310 item
= globalconf
.items
;
312 for(; item
&& loop
; item
= item_iter(&globalconf
.items
, item
))
316 word
= get_last_word(globalconf
.text
);
317 a_strcpy(word
, sizeof(globalconf
.text
) - (word
- globalconf
.text
), item
->data
);
318 globalconf
.item_selected
= item
;
322 * Since loop is 2, it will be 1 at first iter, and then 0 if we
323 * get back before matching an item (i.e. no items match) to the
324 * first elem: so it will break the loop, otherwise it loops for
327 if(item
== globalconf
.items
)
333 compute_match(const char *word
)
335 ssize_t len
= a_strlen(word
);
338 /* reset the selected item to NULL */
339 globalconf
.item_selected
= NULL
;
343 if(word
[len
- 1] == '/'
344 || word
[len
- 1] == ' ')
345 item_list_fill_file(word
);
347 for(item
= globalconf
.items
; item
; item
= item
->next
)
348 if(!a_strncmp(word
, item
->data
, a_strlen(word
)))
355 if(a_strlen(globalconf
.text
))
356 item_list_fill_file(NULL
);
357 for(item
= globalconf
.items
; item
; item
= item
->next
)
369 Area geometry
= { 0, 0, 0, 0, NULL
, NULL
};
370 Bool selected_item_is_drawn
= False
;
371 int len
, prompt_len
, x_of_previous_item
;
374 geometry
.width
= globalconf
.sw
->geometry
.width
;
375 geometry
.height
= globalconf
.sw
->geometry
.height
;
377 if(a_strlen(globalconf
.prompt
))
379 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
380 MARGIN
, globalconf
.prompt
, globalconf
.styles
.focus
);
382 len
= MARGIN
* 2 + draw_textwidth(globalconf
.display
, globalconf
.styles
.focus
.font
, globalconf
.prompt
);
384 geometry
.width
-= len
;
387 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
388 MARGIN
, globalconf
.text
, globalconf
.styles
.normal
);
390 len
= MARGIN
* 2 + MAX(draw_textwidth(globalconf
.display
, globalconf
.styles
.normal
.font
, globalconf
.text
),
391 geometry
.width
/ 20);
393 geometry
.width
-= len
;
394 prompt_len
= geometry
.x
;
396 for(item
= globalconf
.items
; item
&& geometry
.width
> 0; item
= item
->next
)
399 style
= item
== globalconf
.item_selected
? globalconf
.styles
.focus
: globalconf
.styles
.normal
;
400 len
= MARGIN
+ draw_textwidth(globalconf
.display
, style
.font
, item
->data
);
401 if(item
== globalconf
.item_selected
)
403 if(len
> geometry
.width
)
406 selected_item_is_drawn
= True
;
408 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
409 MARGIN
/ 2, item
->data
, style
);
411 geometry
.width
-= len
;
414 /* we have an item selected but not drawn, so redraw in the other side */
415 if(globalconf
.item_selected
&& !selected_item_is_drawn
)
417 geometry
.x
= globalconf
.sw
->geometry
.width
;
419 for(item
= globalconf
.item_selected
; item
; item
= item_list_prev(&globalconf
.items
, item
))
422 style
= item
== globalconf
.item_selected
? globalconf
.styles
.focus
: globalconf
.styles
.normal
;
423 x_of_previous_item
= geometry
.x
;
424 geometry
.width
= MARGIN
+ draw_textwidth(globalconf
.display
, style
.font
, item
->data
);
425 geometry
.x
-= geometry
.width
;
427 if(geometry
.x
< prompt_len
)
430 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
431 MARGIN
/ 2, item
->data
, style
);
436 geometry
.x
= prompt_len
;
437 geometry
.width
= x_of_previous_item
- prompt_len
;
438 draw_rectangle(globalconf
.ctx
, geometry
, True
, globalconf
.styles
.normal
.bg
);
441 else if(geometry
.width
)
442 draw_rectangle(globalconf
.ctx
, geometry
, True
, globalconf
.styles
.normal
.bg
);
444 simplewindow_refresh_drawable(globalconf
.sw
, DefaultScreen(globalconf
.display
));
445 XSync(globalconf
.display
, False
);
449 handle_kpress(XKeyEvent
*e
)
456 len
= a_strlen(globalconf
.text
);
457 num
= XLookupString(e
, buf
, sizeof(buf
), &ksym
, 0);
458 if(e
->state
& ControlMask
)
488 while(i
>= 0 && globalconf
.text
[i
] == ' ')
489 globalconf
.text
[i
--] = '\0';
490 while(i
>= 0 && globalconf
.text
[i
] != ' ')
491 globalconf
.text
[i
--] = '\0';
492 compute_match(get_last_word(globalconf
.text
));
497 else if(CLEANMASK(e
->state
) & Mod1Mask
)
513 if(num
&& !iscntrl((int) buf
[0]))
515 if(buf
[0] != '/' || globalconf
.text
[len
- 1] != '/')
518 a_strncat(globalconf
.text
, sizeof(globalconf
.text
), buf
, num
);
520 compute_match(get_last_word(globalconf
.text
));
527 globalconf
.text
[--len
] = '\0';
528 compute_match(get_last_word(globalconf
.text
));
532 case XK_ISO_Left_Tab
:
552 item_list_fill_stdin(void)
559 Bool has_entry
= False
;
561 item_list_init(&globalconf
.items
);
563 if((line_len
= getline(&buf
, &len
, stdin
)) != -1)
569 buf
[line_len
- 1] = '\0';
570 newitem
= p_new(item_t
, 1);
571 newitem
->data
= a_strdup(buf
);
572 newitem
->match
= True
;
573 item_list_append(&globalconf
.items
, newitem
);
575 while((line_len
= getline(&buf
, &len
, stdin
)) != -1);
584 main(int argc
, char **argv
)
588 int opt
, ret
, x
, y
, i
, screen
= 0;
589 char *configfile
= NULL
, *cmd
;
591 const char *shell
= getenv("SHELL");
592 Area geometry
= { 0, 0, 0, 0, NULL
, NULL
};
596 static struct option long_options
[] =
598 {"help", 0, NULL
, 'h'},
599 {"version", 0, NULL
, 'v'},
600 {"exec", 0, NULL
, 'e'},
604 if(!(disp
= XOpenDisplay(NULL
)))
605 eprint("unable to open display");
607 globalconf
.display
= disp
;
609 while((opt
= getopt_long(argc
, argv
, "vhf:b:x:y:n:c:e:",
610 long_options
, NULL
)) != -1)
614 eprint_version(PROGNAME
);
617 exit_help(EXIT_SUCCESS
);
620 configfile
= a_strdup(optarg
);
623 globalconf
.exec
= a_strdup(optarg
);
627 if(argc
- optind
>= 1)
628 globalconf
.prompt
= a_strdup(argv
[optind
]);
630 /* Get the numlock mask */
631 globalconf
.numlockmask
= get_numlockmask(disp
);
633 si
= screensinfo_new(disp
);
634 if(si
->xinerama_is_active
)
636 if(XQueryPointer(disp
, RootWindow(disp
, DefaultScreen(disp
)),
637 &dummy
, &dummy
, &x
, &y
, &i
, &i
, &ui
))
639 screen
= screen_get_bycoord(si
, 0, x
, y
);
641 geometry
.x
= si
->geometry
[screen
].x
;
642 geometry
.y
= si
->geometry
[screen
].y
;
643 geometry
.width
= si
->geometry
[screen
].width
;
648 screen
= DefaultScreen(disp
);
650 geometry
.width
= DisplayWidth(disp
, DefaultScreen(disp
));
653 if((ret
= config_parse(screen
, configfile
, globalconf
.prompt
, &geometry
)))
656 /* Init the geometry */
658 geometry
.height
= 1.5 * MAX(globalconf
.styles
.normal
.font
->height
,
659 globalconf
.styles
.focus
.font
->height
);
661 screensinfo_delete(&si
);
663 /* Create the window */
664 globalconf
.sw
= simplewindow_new(disp
, DefaultScreen(disp
),
665 geometry
.x
, geometry
.y
, geometry
.width
, geometry
.height
, 0);
667 XStoreName(disp
, globalconf
.sw
->window
, PROGNAME
);
668 XMapRaised(disp
, globalconf
.sw
->window
);
670 /* Create the drawing context */
671 globalconf
.ctx
= draw_context_new(disp
, DefaultScreen(disp
),
672 geometry
.width
, geometry
.height
,
673 globalconf
.sw
->drawable
);
676 if(!item_list_fill_stdin())
677 item_list_fill_file(NULL
);
681 for(opt
= 1000; opt
; opt
--)
683 if(XGrabKeyboard(disp
, DefaultRootWindow(disp
), True
,
684 GrabModeAsync
, GrabModeAsync
, CurrentTime
) == GrabSuccess
)
689 eprint("cannot grab keyboard");
695 XNextEvent(disp
, &ev
);
702 handle_kpress(&ev
.xkey
);
705 if(!ev
.xexpose
.count
)
706 simplewindow_refresh_drawable(globalconf
.sw
, DefaultScreen(disp
));
716 printf("%s\n", globalconf
.text
);
720 shell
= a_strdup("/bin/sh");
721 len
= a_strlen(globalconf
.exec
) + a_strlen(globalconf
.text
) + 1;
722 cmd
= p_new(char, len
);
723 a_strcpy(cmd
, len
, globalconf
.exec
);
724 a_strcat(cmd
, len
, globalconf
.text
);
725 execl(shell
, shell
, "-c", cmd
, NULL
);
729 draw_context_delete(globalconf
.ctx
);
730 simplewindow_delete(globalconf
.sw
);
736 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80