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.
30 #include <sys/types.h>
35 #include <X11/extensions/Xinerama.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"
43 #define PROGNAME "awesome-menu"
45 #define CLEANMASK(mask) (mask & ~(globalconf.numlockmask | LockMask))
54 static status_t status
= RUN
;
56 /** Import awesome config file format */
57 extern cfg_opt_t awesome_opts
[];
59 typedef struct item_t item_t
;
68 item_delete(item_t
**item
)
70 p_delete(&(*item
)->data
);
74 DO_SLIST(item_t
, item
, item_delete
);
76 /** awesome-run global configuration structure */
83 /** The draw contet */
87 /** Draw shadow_offset under text */
89 /** Foreground color */
91 /** Background color */
93 /** List background */
94 XColor fg_focus
, bg_focus
;
96 unsigned int numlockmask
;
102 item_t
*item_selected
;
103 /** What to do with the result text */
109 static AwesomeMenuConf globalconf
;
111 static void __attribute__ ((noreturn
))
112 exit_help(int exit_code
)
114 FILE *outfile
= (exit_code
== EXIT_SUCCESS
) ? stdout
: stderr
;
115 fprintf(outfile
, "Usage: %s [-e command] <message>\n",
121 config_parse(const char *confpatharg
, const char *menu_title
, Area
*geometry
)
124 char *confpath
, *opt
;
125 cfg_t
*cfg
, *cfg_menu
= NULL
, *cfg_screen
, *cfg_general
,
126 *cfg_colors
, *cfg_menu_colors
= NULL
;
129 confpath
= config_file();
131 confpath
= a_strdup(confpatharg
);
133 cfg
= cfg_init(awesome_opts
, CFGF_NONE
);
135 switch((ret
= cfg_parse(cfg
, confpath
)))
138 perror("awesome-message: parsing configuration file failed");
140 case CFG_PARSE_ERROR
:
141 cfg_error(cfg
, "awesome: parsing configuration file %s failed.\n", confpath
);
148 if(menu_title
&& !(cfg_menu
= cfg_gettsec(cfg
, "menu", menu_title
)))
149 warn("no definition for menu %s in configuration file: using default\n", menu_title
);
151 /* get global screen section */
152 if(!(cfg_screen
= cfg_getsec(cfg
, "screen")))
153 eprint("parsing configuration file failed, no screen section found\n");
155 /* get colors and general section */
157 cfg_menu_colors
= cfg_getsec(cfg_menu
, "colors");
158 cfg_general
= cfg_getsec(cfg_screen
, "general");
159 cfg_colors
= cfg_getsec(cfg_screen
, "colors");
162 if(!cfg_menu_colors
|| !(opt
= cfg_getstr(cfg_menu_colors
, "normal_fg")))
163 opt
= cfg_getstr(cfg_colors
, "normal_fg");
165 draw_color_new(globalconf
.display
, DefaultScreen(globalconf
.display
),
166 opt
, &globalconf
.fg
);
168 if(!cfg_menu_colors
|| !(opt
= cfg_getstr(cfg_menu_colors
, "normal_bg")))
169 opt
= cfg_getstr(cfg_colors
, "normal_bg");
171 draw_color_new(globalconf
.display
, DefaultScreen(globalconf
.display
),
172 opt
, &globalconf
.bg
);
174 if(!cfg_menu_colors
|| !(opt
= cfg_getstr(cfg_menu_colors
, "focus_fg")))
175 opt
= cfg_getstr(cfg_colors
, "focus_fg");
177 draw_color_new(globalconf
.display
, DefaultScreen(globalconf
.display
),
178 opt
, &globalconf
.fg_focus
);
180 if(!cfg_menu_colors
|| !(opt
= cfg_getstr(cfg_menu_colors
, "focus_bg")))
181 opt
= cfg_getstr(cfg_colors
, "focus_bg");
183 draw_color_new(globalconf
.display
, DefaultScreen(globalconf
.display
),
184 opt
, &globalconf
.bg_focus
);
187 if(!cfg_menu
|| !(opt
= cfg_getstr(cfg_menu
, "font")))
188 opt
= cfg_getstr(cfg_general
, "font");
190 globalconf
.font
= XftFontOpenName(globalconf
.display
, DefaultScreen(globalconf
.display
),
193 globalconf
.shadow_offset
= cfg_getint(cfg_general
, "text_shadow_offset");
197 if((i
= cfg_getint(cfg_menu
, "x")) != (int) 0xffffffff)
199 if((i
= cfg_getint(cfg_menu
, "y")) != (int) 0xffffffff)
201 if((i
= cfg_getint(cfg_menu
, "width")) > 0)
203 if((i
= cfg_getint(cfg_menu
, "height")) > 0)
204 geometry
->height
= i
;
213 get_last_word(char *text
)
217 if((last_word
= strrchr(text
, ' ')))
226 item_list_fill_file(const char *directory
)
228 char cwd
[PATH_MAX
], *home
, *user
, *filename
;
231 struct dirent
*dirinfo
;
233 ssize_t len
, lenfile
;
234 struct passwd
*passwd
= NULL
;
237 item_list_wipe(&globalconf
.items
);
240 a_strcpy(cwd
, sizeof(cwd
), "./");
241 else if(a_strlen(directory
) > 1 && directory
[0] == '~')
243 if(directory
[1] == '/')
245 if((home
= getenv("HOME")))
246 a_strcpy(cwd
, sizeof(cwd
), home
);
247 a_strcat(cwd
, sizeof(cwd
), directory
+ 1);
251 if(!(file
= strchr(directory
, '/')))
252 file
= directory
+ a_strlen(directory
);
253 len
= (file
- directory
) + 1;
254 user
= p_new(char, len
);
255 a_strncpy(user
, len
, directory
+ 1, (file
- directory
) - 1);
256 if((passwd
= getpwnam(user
)))
258 a_strcpy(cwd
, sizeof(cwd
), passwd
->pw_dir
);
259 a_strcat(cwd
, sizeof(cwd
), file
);
270 a_strcpy(cwd
, sizeof(cwd
), directory
);
272 if(!(dir
= opendir(cwd
)))
275 while((dirinfo
= readdir(dir
)))
277 item
= p_new(item_t
, 1);
279 /* + 1 for \0 + 1 for / if directory */
280 len
= a_strlen(directory
) + a_strlen(dirinfo
->d_name
) + 2;
282 item
->data
= p_new(char, len
);
283 if(a_strlen(directory
))
284 a_strcpy(item
->data
, len
, directory
);
285 a_strcat(item
->data
, len
, dirinfo
->d_name
);
287 lenfile
= a_strlen(cwd
) + a_strlen(dirinfo
->d_name
) + 2;
289 filename
= p_new(char, lenfile
);
290 a_strcpy(filename
, lenfile
, cwd
);
291 a_strcat(filename
, lenfile
, dirinfo
->d_name
);
293 if(!stat(filename
, &st
) && S_ISDIR(st
.st_mode
))
294 a_strcat(item
->data
, len
, "/");
298 item_list_push(&globalconf
.items
, item
);
307 complete(Bool reverse
)
312 item_t
*(*item_iter
)(item_t
**, item_t
*) = item_list_next_cycle
;
315 item_iter
= item_list_prev_cycle
;
317 if(globalconf
.item_selected
)
318 item
= item_iter(&globalconf
.items
, globalconf
.item_selected
);
320 item
= globalconf
.items
;
322 for(; item
&& loop
; item
= item_iter(&globalconf
.items
, item
))
326 word
= get_last_word(globalconf
.text
);
327 a_strcpy(word
, sizeof(globalconf
.text
) - (word
- globalconf
.text
), item
->data
);
328 globalconf
.item_selected
= item
;
332 * Since loop is 2, it will be 1 at first iter, and then 0 if we
333 * get back before matching an item (i.e. no items match) to the
334 * first elem: so it will break the loop, otherwise it loops for
337 if(item
== globalconf
.items
)
343 compute_match(const char *word
)
345 ssize_t len
= a_strlen(word
);
348 /* reset the selected item to NULL */
349 globalconf
.item_selected
= NULL
;
353 if(word
[len
- 1] == '/'
354 || word
[len
- 1] == ' ')
355 item_list_fill_file(word
);
357 for(item
= globalconf
.items
; item
; item
= item
->next
)
358 if(!a_strncmp(word
, item
->data
, a_strlen(word
)))
365 if(a_strlen(globalconf
.text
))
366 item_list_fill_file(NULL
);
367 for(item
= globalconf
.items
; item
; item
= item
->next
)
376 draw_item(item_t
*item
, Area geometry
)
378 if(item
== globalconf
.item_selected
)
379 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
380 MARGIN
/ 2, globalconf
.font
, item
->data
,
381 globalconf
.shadow_offset
,
382 globalconf
.fg_focus
, globalconf
.bg_focus
);
384 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
385 MARGIN
/ 2, globalconf
.font
, item
->data
,
386 globalconf
.shadow_offset
,
387 globalconf
.fg
, globalconf
.bg
);
394 Area geometry
= { 0, 0, 0, 0, NULL
, NULL
};
395 Bool selected_item_is_drawn
= False
;
396 int len
, prompt_len
, x_of_previous_item
;
398 geometry
.width
= globalconf
.sw
->geometry
.width
;
399 geometry
.height
= globalconf
.sw
->geometry
.height
;
401 if(a_strlen(globalconf
.prompt
))
403 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
404 MARGIN
, globalconf
.font
, globalconf
.prompt
,
405 globalconf
.shadow_offset
,
406 globalconf
.fg_focus
, globalconf
.bg_focus
);
408 len
= MARGIN
* 2 + draw_textwidth(globalconf
.display
, globalconf
.font
, globalconf
.prompt
);
410 geometry
.width
-= len
;
413 draw_text(globalconf
.ctx
, geometry
, AlignLeft
,
414 MARGIN
, globalconf
.font
, globalconf
.text
,
415 globalconf
.shadow_offset
,
416 globalconf
.fg
, globalconf
.bg
);
418 len
= MARGIN
* 2 + MAX(draw_textwidth(globalconf
.display
, globalconf
.font
, globalconf
.text
),
419 geometry
.width
/ 20);
421 geometry
.width
-= len
;
422 prompt_len
= geometry
.x
;
424 for(item
= globalconf
.items
; item
&& geometry
.width
> 0; item
= item
->next
)
427 len
= MARGIN
+ draw_textwidth(globalconf
.display
, globalconf
.font
, item
->data
);
428 if(item
== globalconf
.item_selected
)
430 if(len
> geometry
.width
)
433 selected_item_is_drawn
= True
;
435 draw_item(item
, geometry
);
437 geometry
.width
-= len
;
440 /* we have an item selected but not drawn, so redraw in the other side */
441 if(globalconf
.item_selected
&& !selected_item_is_drawn
)
443 geometry
.x
= globalconf
.sw
->geometry
.width
;
445 for(item
= globalconf
.item_selected
; item
; item
= item_list_prev(&globalconf
.items
, item
))
448 x_of_previous_item
= geometry
.x
;
449 geometry
.width
= MARGIN
+ draw_textwidth(globalconf
.display
, globalconf
.font
, item
->data
);
450 geometry
.x
-= geometry
.width
;
452 if(geometry
.x
< prompt_len
)
455 draw_item(item
, geometry
);
460 geometry
.x
= prompt_len
;
461 geometry
.width
= x_of_previous_item
- prompt_len
;
462 draw_rectangle(globalconf
.ctx
, geometry
, True
, globalconf
.bg
);
465 else if(geometry
.width
)
466 draw_rectangle(globalconf
.ctx
, geometry
, True
, globalconf
.bg
);
468 simplewindow_refresh_drawable(globalconf
.sw
, DefaultScreen(globalconf
.display
));
469 XSync(globalconf
.display
, False
);
473 handle_kpress(XKeyEvent
*e
)
480 len
= a_strlen(globalconf
.text
);
481 num
= XLookupString(e
, buf
, sizeof(buf
), &ksym
, 0);
482 if(e
->state
& ControlMask
)
512 while(i
>= 0 && globalconf
.text
[i
] == ' ')
513 globalconf
.text
[i
--] = '\0';
514 while(i
>= 0 && globalconf
.text
[i
] != ' ')
515 globalconf
.text
[i
--] = '\0';
516 compute_match(get_last_word(globalconf
.text
));
521 else if(CLEANMASK(e
->state
) & Mod1Mask
)
537 if(num
&& !iscntrl((int) buf
[0]))
539 if(buf
[0] != '/' || globalconf
.text
[len
- 1] != '/')
542 a_strncat(globalconf
.text
, sizeof(globalconf
.text
), buf
, num
);
544 compute_match(get_last_word(globalconf
.text
));
551 globalconf
.text
[--len
] = '\0';
552 compute_match(get_last_word(globalconf
.text
));
556 case XK_ISO_Left_Tab
:
576 item_list_fill_stdin(void)
580 Bool has_entry
= False
;
582 item_list_init(&globalconf
.items
);
584 if(fgets(buf
, sizeof(buf
), stdin
))
590 buf
[a_strlen(buf
) - 1] = '\0';
591 newitem
= p_new(item_t
, 1);
592 newitem
->data
= a_strdup(buf
);
593 newitem
->match
= True
;
594 item_list_append(&globalconf
.items
, newitem
);
596 while(fgets(buf
, sizeof(buf
), stdin
));
602 main(int argc
, char **argv
)
607 char *configfile
= NULL
, *cmd
;
609 const char *shell
= getenv("SHELL");
610 Area 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 if((ret
= config_parse(configfile
, globalconf
.prompt
, &geometry
)))
648 /* Get the numlock mask */
649 globalconf
.numlockmask
= get_numlockmask(disp
);
651 /* Init the geometry */
653 geometry
.height
= globalconf
.font
->height
* 1.5;
655 /* XXX this must be replace with a common/ infra */
656 if(XineramaIsActive(disp
))
658 XineramaScreenInfo
*si
;
659 int xinerama_screen_number
, i
, x
, y
;
663 XQueryPointer(disp
, RootWindow(disp
, DefaultScreen(disp
)),
664 &dummy
, &dummy
, &x
, &y
, &i
, &i
, &ui
);
666 si
= XineramaQueryScreens(disp
, &xinerama_screen_number
);
668 /* XXX use screen_get_bycoord() !!! */
669 for(i
= 0; i
< xinerama_screen_number
; i
++)
670 if((x
< 0 || (x
>= si
[i
].x_org
&& x
< si
[i
].x_org
+ si
[i
].width
))
671 && (y
< 0 || (y
>= si
[i
].y_org
&& y
< si
[i
].y_org
+ si
[i
].height
)))
675 geometry
.x
= si
[i
].x_org
;
677 geometry
.y
= si
[i
].y_org
;
679 geometry
.width
= si
[i
].width
;
683 else if(!geometry
.width
)
684 geometry
.width
= DisplayWidth(disp
, DefaultScreen(disp
));
686 /* Create the window */
687 globalconf
.sw
= simplewindow_new(disp
, DefaultScreen(disp
),
688 geometry
.x
, geometry
.y
, geometry
.width
, geometry
.height
, 0);
690 XStoreName(disp
, globalconf
.sw
->window
, PROGNAME
);
691 XMapRaised(disp
, globalconf
.sw
->window
);
693 /* Create the drawing context */
694 globalconf
.ctx
= draw_context_new(disp
, DefaultScreen(disp
),
695 geometry
.width
, geometry
.height
,
696 globalconf
.sw
->drawable
);
699 if(!item_list_fill_stdin())
700 item_list_fill_file(NULL
);
704 for(opt
= 1000; opt
; opt
--)
706 if(XGrabKeyboard(disp
, DefaultRootWindow(disp
), True
,
707 GrabModeAsync
, GrabModeAsync
, CurrentTime
) == GrabSuccess
)
712 eprint("cannot grab keyboard");
718 XNextEvent(disp
, &ev
);
725 handle_kpress(&ev
.xkey
);
728 if(!ev
.xexpose
.count
)
729 simplewindow_refresh_drawable(globalconf
.sw
, DefaultScreen(disp
));
739 printf("%s\n", globalconf
.text
);
743 shell
= a_strdup("/bin/sh");
744 len
= a_strlen(globalconf
.exec
) + a_strlen(globalconf
.text
) + 1;
745 cmd
= p_new(char, len
);
746 a_strcpy(cmd
, len
, globalconf
.exec
);
747 a_strcat(cmd
, len
, globalconf
.text
);
748 execl(shell
, shell
, "-c", cmd
, NULL
);
752 draw_context_delete(globalconf
.ctx
);
753 simplewindow_delete(globalconf
.sw
);
759 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80