/* mb-applet-menu-launcher - a small application launcher Copyright 2002 Matthew Allum This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ /* TODO o fix crash on menu's bigger than display. o add logout / lock buttons on root menu. - add use_gpe defines for this ! */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef USE_DNOTIFY #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_LIBSN #define SN_API_NOT_YET_FROZEN 1 #include #endif #ifndef MB_HAVE_PNG #include #endif typedef struct _app { MBTrayApp *tray_app; MBMenu *mbmenu; MBPixbuf *pb; MBPixbufImage *img_tray; MBPixbufImage *img_tray_scaled; MBPixbufImage *img_tray_active; MBPixbufImage *img_tray_active_scaled; Atom mbtheme_atom; Atom mbcommand_atom; char *theme_name; Bool button_is_down; #ifdef USE_LIBSN SnDisplay *sn_display; #endif } AppData; AppData *app_data; MBMenuMenu *root; Bool Update_Pending = False; jmp_buf Jbuf; MBMenuMenu *active[10]; int MenuWasActive = False; Bool WantDebianMenus = False; volatile Bool WantReload = False; static void reap_children (int signum); static void fork_exec (char *cmd); static void usage (); static int parse_menu_file (char *data); static void build_menu (void); static void catch_sigsegv (int sig); static void load_icon(void); #ifdef USE_LIBSN static void sn_exec(char* name, char* bin_name, char *desc); #endif #ifdef USE_DNOTIFY static void reload_menu(int signum, siginfo_t *siginfo, void *data); #endif #ifdef MB_HAVE_PNG #define TRAY_IMG "mbmenu.png" #define TRAY_IMG_ACTIVE "mbmenu-active.png" #else #define TRAY_IMG "mbmenu.xpm" #define TRAY_IMG_ACTIVE "mbmenu-active.xpm" #endif #define SINGLE_INSTANCE 1 #define STARTUP_NOTIFY 2 #define UP 0 #define DOWN 1 #define MENUDIR "/usr/lib/menu" #define MBMAX(x,y) ((x>y)?(x):(y)) #define NEW(OBJ) ((OBJ *)(malloc(sizeof(OBJ)))) #define IS_WHITESPACE(c) ((c) == ' ' || (c) == '\\' \ || (c) == '\t' || (c)=='\n' || (c)=='?') #ifdef DEBUG #define DBG(txt, args... ) fprintf(stderr, "DEBUG: " txt , ##args ) #else #define DBG(txt, args... ) /* nothing */ #endif static void usage() { printf("usage: mbmenu [options ....]\n" "Where options are\n" " -display Display to connect to\n" "\nAlso set MB_USE_DEB_MENUS env var to enable parsing /usr/lib/menu" "\n" ); exit(1); } static void catch_sigsegv(int sig) { DBG("ouch\n"); signal(SIGSEGV, SIG_DFL); longjmp(Jbuf, 1); } static void reap_children(int signum) { pid_t pid; do { pid = waitpid(-1, NULL, WNOHANG); } while (pid > 0); } #ifdef USE_LIBSN static void sn_exec(char* name, char* bin_name, char *desc) { SnLauncherContext *context; pid_t child_pid = 0; context = sn_launcher_context_new (app_data->sn_display, mb_tray_app_xscreen(app_data->tray_app)); if (name) sn_launcher_context_set_name (context, name); if (desc) sn_launcher_context_set_description (context, desc); if (bin_name) sn_launcher_context_set_binary_name (context, bin_name); sn_launcher_context_initiate (context, "mbmenu launch", bin_name, CurrentTime); switch ((child_pid = fork ())) { case -1: fprintf (stderr, "Fork failed\n" ); break; case 0: sn_launcher_context_setup_child_process (context); mb_exec(bin_name); fprintf (stderr, "mb-applet-menu-launcher: Failed to exec %s \n", bin_name); _exit (1); break; } } #endif static void fork_exec(char *cmd) { pid_t pid; switch (pid = fork()) { case 0: mb_exec(cmd); /* execlp("/bin/sh", "sh", "-c", cmd, NULL); */ fprintf(stderr, "mb-applet-menu-launcher: exec of '%s' failed, cleaning up child\n", cmd); exit(1); case -1: fprintf(stderr, "mb-applet-menu-launcher: can't fork '%s'\n", cmd); break; } } #ifdef USE_LIBSN static void menu_clicked_sn_cb(MBMenuItem *item) { sn_exec(item->title, item->info, item->info); } static void menu_clicked_si_cb(MBMenuItem *item) { Window win_found; win_found = mb_single_instance_get_window(mb_tray_app_xdisplay(app_data->tray_app), item->info); if (win_found != None) { mb_util_window_activate(mb_tray_app_xdisplay(app_data->tray_app), win_found); } else menu_clicked_sn_cb(item); } #endif static void menu_clicked_cb(MBMenuItem *item) { fork_exec(item->info); } static int parse_menu_file(char *data) { char *p, *key, *val; char *tmp_title; char *tmp_section; char *tmp_cmd; char *tmp_icon; char *tmp_needs; signal(SIGSEGV, catch_sigsegv); if (setjmp(Jbuf)) return 0; /* catch possible parse segfualt */ tmp_title = NULL; tmp_section = NULL; tmp_cmd = NULL; tmp_icon = NULL; tmp_needs = NULL; p = data; if (*p != '?') return 0; /* check is an actual menu entry */ while(*(++p) != ':'); *p = ' '; /* skip to first entry */ while(*p != '\0') { if ((!IS_WHITESPACE(*p)) || (IS_WHITESPACE(*p) && *(p+1) != '\0' && (!IS_WHITESPACE(*(p+1))))) { /* process key=pair */ char *lc = " \t\n\\"; char *sc = "\""; char *tc = lc; if (IS_WHITESPACE(*p)) p++; key = p; while(*p != '=') p++; *p = '\0'; DBG("\tkey %s ", key); if (*(++p) == '"') { p++; tc = sc; } /* skip "'s */ val = p; while(index(tc,*p) == NULL) { if (*p == '\\' && *(p+1) == '"') p++; /* skip \" */ p++; } *p = '\0'; DBG("value %s \n", val); if(!strcmp(key,"title")) { tmp_title = val; } else if(!strcmp(key,"section")) { tmp_section = val; } else if(!strcmp(key,"command")) { tmp_cmd = val; } else if(!strcmp(key,"icon")) { tmp_icon = val; } else if(!strcmp(key,"icon16")) { tmp_icon = val; } else if(!strcmp(key,"icon32")) { tmp_icon = val; } else if(!strcmp(key,"icon48")) { tmp_icon = val; } else if(!strcmp(key,"needs")) { tmp_needs = val; } } p++; if (tmp_section && (*p == '?' || *p == '\0')) { if ( (!strcmp(tmp_needs,"x11")) || (!strcmp(tmp_needs,"X11")) || (!strcmp(tmp_needs,"text")) || tmp_needs == NULL ) { MBMenuMenu *m = NULL; char *tmpstr = (char *)malloc(sizeof(char)*(strlen(tmp_section)+ strlen(tmp_title)+12)); sprintf(tmpstr, "Other/%s", tmp_section); m = mb_menu_add_path(app_data->mbmenu, tmpstr, NULL, 0); mb_menu_add_item_to_menu(app_data->mbmenu, m, tmp_title, tmp_icon, tmp_cmd, menu_clicked_cb, NULL, 0); } } } /* reset all */ tmp_title = NULL; tmp_section = NULL; tmp_cmd = NULL; tmp_icon = NULL; /* new menu entry */ if ( *p == '?') { DBG("new entry, igonoring package(foo) data\n"); while(*(++p) != ':'); *p = ' '; } signal(SIGSEGV, SIG_DFL); return 1; } #ifdef USE_DNOTIFY static void really_reload_menu(int signum) { DBG("Really Reload menu callback\n"); WantReload = True; } static void reload_menu(int signum, siginfo_t *siginfo, void *data) { DBG("Reload menu callback\n"); /* To avoid the barrage of signals dnotify likes to send when * a file is updated we set an alarm. * */ signal (SIGALRM, really_reload_menu); alarm(3); } #endif static void build_menu(void) { #define APP_PATHS_N 4 struct menu_lookup_t { char *match_str; MBMenuMenu *item; }; int i = 0, j = 0; DIR *dp; struct dirent *dir_entry; struct stat stat_info; #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif char orig_wd[MAXPATHLEN]; char dirs[2][256] = { MENUDIR }; FILE *fp; char *buf; int len; MBMenuMenu *menu_panel; char *tmp_path = NULL, *tmp_path2 = NULL ; char vfolder_path_root[512]; char vfolder_path[512]; MBDotDesktop *dd; MBDotDesktopFolders *ddfolders; MBDotDesktopFolderEntry *ddentry; char *root_match_str; /* List of paths to check for .desktop files */ char app_paths[APP_PATHS_N][256]; Bool got_root_items = False; Bool had_action_sepearator = False; struct menu_lookup_t *menu_lookup = NULL; snprintf( vfolder_path_root, 512, "%s/.matchbox/vfolders/Root.directory", getenv("HOME")); snprintf( vfolder_path, 512, "%s/.matchbox/vfolders", getenv("HOME")); if (stat(vfolder_path_root, &stat_info)) { snprintf(vfolder_path_root, 512, PKGDATADIR "/vfolders/Root.directory"); snprintf(vfolder_path, 512, PKGDATADIR "/vfolders" ); } dd = mb_dotdesktop_new_from_file(vfolder_path_root); if (!dd) { fprintf( stderr, "mb-applet-menu-launcher: cant open " PKGDATADIR "/vfolders/Root.desktop\n" " Did you install matchbox-common ?" ); exit(1); } root_match_str = mb_dotdesktop_get(dd, "Match"); /* Now grab the vfolders */ ddfolders = mb_dot_desktop_folders_new(vfolder_path); menu_lookup = malloc(sizeof(struct menu_lookup_t)*mb_dot_desktop_folders_get_cnt(ddfolders)); mb_dot_desktop_folders_enumerate(ddfolders, ddentry) { char *icon_path = NULL; char *folder_name = NULL; /* Check Name is valid for entry */ if ((folder_name = mb_dot_desktop_folder_entry_get_name(ddentry)) == NULL) continue; if (mb_dot_desktop_folder_entry_get_icon(ddentry)) icon_path = mb_dot_desktop_icon_get_full_path (app_data->theme_name, 16, mb_dot_desktop_folder_entry_get_icon(ddentry) ); menu_lookup[i].item = mb_menu_add_path(app_data->mbmenu, folder_name, icon_path, MBMENU_NO_SORT ); menu_lookup[i].match_str = mb_dot_desktop_folder_entry_get_match(ddentry); i++; } menu_panel = mb_menu_add_path(app_data->mbmenu, "Utilities/Panel" , NULL, MBMENU_NO_SORT ); tmp_path = mb_dot_desktop_icon_get_full_path (app_data->theme_name, 16, "mbfolder.png" ); tmp_path2 = mb_dot_desktop_icon_get_full_path (app_data->theme_name, 16, "mbnoapp.png" ); mb_menu_set_default_icons(app_data->mbmenu, tmp_path, tmp_path2); if (tmp_path) free(tmp_path); if (tmp_path2) free(tmp_path2); if (getcwd(orig_wd, MAXPATHLEN) == (char *)NULL) { fprintf(stderr, "mb-applet-menu-launcher: cant get current directory\n"); exit(0); } snprintf(app_paths[0], 256, "%s/applications", DATADIR); snprintf(app_paths[1], 256, "/usr/share/applications"); snprintf(app_paths[2], 256, "/usr/local/share/applications"); snprintf(app_paths[3], 256, "%s/.applications", getenv("HOME")); for (j = 0; j < APP_PATHS_N; j++) { /* Dont reread the prefix path if matches */ if (j > 0 && !strcmp(app_paths[0], app_paths[j])) continue; if ((dp = opendir(app_paths[j])) == NULL) { fprintf(stderr, "mb-applet-menu-launcher: failed to open %s\n", app_paths[j]); continue; } chdir(app_paths[j]); while((dir_entry = readdir(dp)) != NULL) { if (strcmp(dir_entry->d_name+strlen(dir_entry->d_name)-8,".desktop")) continue; lstat(dir_entry->d_name, &stat_info); if (!(S_ISDIR(stat_info.st_mode))) { MBDotDesktop *dd; int flags = 0; dd = mb_dotdesktop_new_from_file(dir_entry->d_name); if (dd) { char *png_path = NULL; MBMenuActivateCB activate_callback = NULL; if (mb_dotdesktop_get(dd, "Icon") && mb_dotdesktop_get(dd, "Name") && mb_dotdesktop_get(dd, "Exec")) { MBMenuMenu *m = NULL, *fallback = NULL; char *category; png_path = mb_dot_desktop_icon_get_full_path(app_data->theme_name, 16, mb_dotdesktop_get(dd, "Icon")); category = mb_dotdesktop_get(dd, "Categories"); if (png_path && category && strstr(category, "Action")) { m = app_data->mbmenu->rootmenu; if (!had_action_sepearator) { mb_menu_add_seperator_to_menu(app_data->mbmenu, app_data->mbmenu->rootmenu, MBMENU_NO_SORT); had_action_sepearator = True; } flags = MBMENU_NO_SORT; } else { if (root_match_str) { if (!strcmp("fallback", root_match_str)) { fallback = app_data->mbmenu->rootmenu; } else if (category && strstr(category, root_match_str)) { m = app_data->mbmenu->rootmenu; } } if (m == NULL && category != NULL) { for (i=0; imbmenu->rootmenu) { if (got_root_items == False) { mb_menu_add_seperator_to_menu(app_data->mbmenu, app_data->mbmenu->rootmenu, MBMENU_PREPEND); got_root_items = True; } flags = MBMENU_PREPEND; } mb_menu_add_item_to_menu(app_data->mbmenu, m, mb_dotdesktop_get(dd, "Name"), png_path, mb_dotdesktop_get_exec(dd), activate_callback, (void *)app_data, flags); /* mb_menu_add_seperator_to_menu(app_data-> mbmenu, m); */ free(png_path); } } else fprintf(stderr, "mb-applet-menu-launcher: %s has no icon, png or name\n", dir_entry->d_name); mb_dotdesktop_free(dd); } else fprintf(stderr, "mb-applet-menu-launcher: failed to parse %s :( \n", dir_entry->d_name); } } closedir(dp); } /* Now parse old Debian / Familiar Menu entrys */ chdir(orig_wd); if (WantDebianMenus) { strcpy(dirs[1], (char *)getenv("HOME")); strcat(dirs[1], "/.menu"); for(i=0; i<2; i++) { if ((dp = opendir(dirs[i])) == NULL) { fprintf(stderr, "mb-applet-menu-launcher: failed to open %s\n", dirs[i]); continue; } chdir(dirs[i]); while((dir_entry = readdir(dp)) != NULL) { lstat(dir_entry->d_name, &stat_info); if (!(S_ISDIR(stat_info.st_mode))) { DBG("file %s \n", dir_entry->d_name); fp = fopen(dir_entry->d_name, "r"); buf = malloc(sizeof(char) * (stat_info.st_size + 1)); len = fread(buf, 1, stat_info.st_size, fp); if (len >= 0) buf[len] = '\0'; if (!(parse_menu_file(buf))) fprintf(stderr, "mb-applet-menu-launcher: had problems parsing %s. Ignoring. \n", dir_entry->d_name); DBG("done\n\n"); fclose(fp); free(buf); } } closedir(dp); } chdir(orig_wd); } } static void menu_get_popup_pos (MBTrayApp *app, int *x, int *y) { int abs_x, abs_y, menu_h, menu_w; mb_tray_app_get_absolute_coords (app, &abs_x, &abs_y); mb_menu_get_root_menu_size(app_data->mbmenu, &menu_w, &menu_h); if (mb_tray_app_tray_is_vertical (app)) { /* XXX need to figure out menu size before its mapped so we can figure out offset for east panel */ *y = abs_y + mb_tray_app_height(app); if (abs_x > (DisplayWidth(mb_tray_app_xdisplay(app), mb_tray_app_xscreen(app)) /2)) *x = abs_x - menu_w - 2; else *x = abs_x + mb_tray_app_width(app) + 2; } else { *x = abs_x; if (abs_y > (DisplayHeight(mb_tray_app_xdisplay(app), mb_tray_app_xscreen(app)) /2)) *y = abs_y - 2; else *y = abs_y + mb_tray_app_height(app) + menu_h; } } void button_callback (MBTrayApp *app, int x, int y, Bool is_released ) { int abs_x, abs_y; static Bool next_cancels; #ifdef USE_DNOTIFY sigset_t block_sigset; #endif app_data->button_is_down = True; if (is_released) app_data->button_is_down = False; if (is_released && !next_cancels) { #ifdef USE_DNOTIFY /* block any reloads while active */ sigemptyset(&block_sigset); sigaddset(&block_sigset, SIGRTMIN); sigprocmask(SIG_BLOCK, &block_sigset, NULL); #endif menu_get_popup_pos (app, &abs_x, &abs_y); mb_menu_activate (app_data->mbmenu, abs_x, abs_y); } else if (mb_menu_is_active(app_data->mbmenu)) next_cancels = True; else next_cancels = False; mb_tray_app_repaint (app); } void paint_callback (MBTrayApp *app, Drawable drw ) { MBPixbufImage *img_bg = NULL; MBPixbufImage *img_button = NULL; img_button = ( app_data->button_is_down ? app_data->img_tray_active_scaled : app_data->img_tray_scaled ); img_bg = mb_tray_app_get_background (app, app_data->pb); mb_pixbuf_img_copy_composite (app_data->pb, img_bg, img_button, 0, 0, mb_pixbuf_img_get_width(app_data->img_tray_scaled), mb_pixbuf_img_get_height(app_data->img_tray_scaled), 0, 0 ); mb_pixbuf_img_render_to_drawable (app_data->pb, img_bg, drw, 0, 0); mb_pixbuf_img_free( app_data->pb, img_bg ); } void resize_callback (MBTrayApp *app, int w, int h) { if (app_data->img_tray_scaled) mb_pixbuf_img_free(app_data->pb, app_data->img_tray_scaled); app_data->img_tray_scaled = mb_pixbuf_img_scale(app_data->pb, app_data->img_tray, w, h); if (app_data->img_tray_active_scaled) mb_pixbuf_img_free(app_data->pb, app_data->img_tray_active_scaled); app_data->img_tray_active_scaled = mb_pixbuf_img_scale(app_data->pb, app_data->img_tray_active, w, h); } void theme_callback (MBTrayApp *app, char *theme_name) { if (!theme_name) return; if (app_data->theme_name) free(app_data->theme_name); app_data->theme_name = strdup(theme_name); if (app_data->mbmenu != NULL) { mb_menu_free(app_data->mbmenu); build_menu(); load_icon(); resize_callback (app, mb_tray_app_width(app), mb_tray_app_width(app) ); mb_tray_app_repaint (app_data->tray_app); } } void xevent_callback (MBTrayApp *app, XEvent *ev) { #ifdef USE_DNOTIFY sigset_t block_sigset; #endif mb_menu_handle_xevent (app_data->mbmenu, ev); #ifdef USE_DNOTIFY if (!mb_menu_is_active(app_data->mbmenu)) { /* Unblock any dnotify signals */ sigemptyset(&block_sigset); sigaddset(&block_sigset, SIGRTMIN); sigprocmask(SIG_UNBLOCK, &block_sigset, NULL); } #endif #define MB_CMD_SHOW_EXT_MENU 6 if (ev->type == ClientMessage) { if (ev->xclient.message_type == app_data->mbcommand_atom && ev->xclient.data.l[0] == MB_CMD_SHOW_EXT_MENU ) { #ifdef USE_DNOTIFY sigemptyset(&block_sigset); sigaddset(&block_sigset, SIGRTMIN); #endif if (!mb_menu_is_active(app_data->mbmenu)) { int abs_x, abs_y; #ifdef USE_DNOTIFY sigprocmask(SIG_BLOCK, &block_sigset, NULL); #endif menu_get_popup_pos (app, &abs_x, &abs_y); mb_menu_activate(app_data->mbmenu, abs_x, abs_y); } else { mb_menu_deactivate(app_data->mbmenu); #ifdef USE_DNOTIFY sigprocmask(SIG_UNBLOCK, &block_sigset, NULL); #endif } } } if (WantReload && !mb_menu_is_active(app_data->mbmenu)) { DBG("reloading menu\n"); mb_menu_free(app_data->mbmenu); build_menu(); WantReload = False; } } void load_icon(void) { char *icon_path = NULL; if (app_data->img_tray) mb_pixbuf_img_free(app_data->pb, app_data->img_tray); if (app_data->img_tray_active) mb_pixbuf_img_free(app_data->pb, app_data->img_tray_active); icon_path = mb_dot_desktop_icon_get_full_path (app_data->theme_name, 16, TRAY_IMG ); if (icon_path == NULL || !(app_data->img_tray = mb_pixbuf_img_new_from_file(app_data->pb, icon_path))) { fprintf(stderr, "mb-applet-menu-launcher: failed to load panel icon\n"); exit(1); } free(icon_path); icon_path = mb_dot_desktop_icon_get_full_path (app_data->theme_name, 16, TRAY_IMG_ACTIVE ); if (icon_path == NULL || !(app_data->img_tray_active = mb_pixbuf_img_new_from_file(app_data->pb, icon_path))) { int x, y; app_data->img_tray_active = mb_pixbuf_img_clone(app_data->pb, app_data->img_tray); for (x=0; ximg_tray_active); x++) for (y=0; yimg_tray_active); y++) { int aa; unsigned char r,g,b,a; mb_pixbuf_img_get_pixel(app_data->pb, app_data->img_tray_active, x, y, &r, &g, &b, &a); aa = (int)a; aa -= 0x80; if (aa < 0) aa = 0; mb_pixbuf_img_set_pixel_alpha(app_data->img_tray_active, x, y, aa); } } if (icon_path) free(icon_path); } int main( int argc, char *argv[]) { MBTrayApp *app = NULL; struct sigaction act; #ifdef USE_DNOTIFY int fd; #endif app_data = NEW(AppData); memset(app_data, 0, sizeof(AppData)); app = mb_tray_app_new ( "App Launcher", resize_callback, paint_callback, &argc, &argv ); if (app == NULL) usage(); if (argc > 1 && strstr(argv[1], "-h")) usage(); if (getenv("MB_USE_DEB_MENUS")) WantDebianMenus = True; app_data->tray_app = app; app_data->mbtheme_atom = XInternAtom(mb_tray_app_xdisplay(app), "_MB_THEME", False); app_data->mbcommand_atom = XInternAtom(mb_tray_app_xdisplay(app), "_MB_COMMAND", False); app_data->mbmenu = mb_menu_new(mb_tray_app_xdisplay(app), mb_tray_app_xscreen(app)); #ifdef USE_LIBSN app_data->sn_display = sn_display_new (mb_tray_app_xdisplay(app), NULL, NULL); #endif app_data->pb = mb_pixbuf_new(mb_tray_app_xdisplay(app), mb_tray_app_xscreen(app)); XSelectInput (mb_tray_app_xdisplay(app), mb_tray_app_xrootwin(app), PropertyChangeMask|SubstructureNotifyMask); mb_tray_app_set_button_callback (app, button_callback ); mb_tray_app_set_xevent_callback (app, xevent_callback ); mb_tray_app_set_theme_change_callback (app, theme_callback ); /* Make the tray app end up on right of mb panel */ mb_tray_app_request_offset (app, -1); load_icon(); build_menu(); /* Set up signals */ act.sa_flags = 0; sigemptyset(&act.sa_mask); act.sa_handler = reap_children; sigaction(SIGCHLD, &act, NULL); #ifdef USE_DNOTIFY #define DD_DIR DATADIR "/applications" act.sa_sigaction = reload_menu; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; sigaction(SIGRTMIN, &act, NULL); fd = open(DD_DIR, O_RDONLY); fcntl(fd, F_SETSIG, SIGRTMIN); fcntl(fd, F_NOTIFY, DN_MODIFY|DN_CREATE|DN_DELETE|DN_MULTISHOT); #endif mb_tray_app_set_icon(app, app_data->pb, app_data->img_tray); mb_tray_app_main (app); return 1; }