/*
* compiz-deskmenu 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 of the License, or
* (at your option) any later version.
*
* compiz-deskmenu 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Copyright 2008 Christopher Williams
*/
/*
Roadmap:
Necessary:
TODO: Add a viewport # indicator for the window list for accesiblity reasons difficulty: hard
TODO: Add configuration for menu icon size difficulty: easy
TODO: Add toggle of tearables difficulty: easy
TODO: Add a sane icon dialog difficulty: medium-hard
For fun, might not implement:
TODO: Add ability to call up menus from the menu.xml file by name, if this is really, really needed or requested
*/
#include
#include
#include
#include
#define HAVE_WNCK 1
#if HAVE_WNCK
#include "deskmenu-wnck.h"
#endif
#include "deskmenu-utils.h"
#include "deskmenu-menu.h"
#include "deskmenu-glue.h"
G_DEFINE_TYPE(Deskmenu, deskmenu, G_TYPE_OBJECT) //this is calling deskmenu_class_init
static void
start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attr_names,
const gchar **attr_values,
gpointer user_data,
GError **error);
static void
text (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error);
static void
end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error);
static GMarkupParser parser = {
start_element,
end_element,
text,
NULL,
NULL
};
static GHashTable *item_hash;
static GHashTable *element_hash;
static void
deskmenu_free_item (DeskmenuObject *dm_object);
GQuark
deskmenu_error_quark (void)
{
static GQuark quark = 0;
if (!quark)
quark = g_quark_from_static_string ("deskmenu_error");
return quark;
}
static void
quit (GtkWidget *widget,
gpointer data)
{
gtk_main_quit ();
}
//This is how menu command is launched
static void
launcher_activated (GtkWidget *widget,
gchar *command)
{
GError *error = NULL;
if (!gdk_spawn_command_line_on_screen (gdk_screen_get_default (), parse_expand_tilde(command), &error))
{
deskmenu_widget_error(error);
}
}
//This is how a recent document is opened
static void
recent_activated (GtkRecentChooser *chooser,
gchar *command)
{
GError *error = NULL;
gchar *full_command, *file;
file = gtk_recent_chooser_get_current_uri (chooser);
full_command = get_full_command(command, file);
if (!gdk_spawn_command_line_on_screen (gdk_screen_get_default (), parse_expand_tilde(full_command), &error))
{
deskmenu_widget_error(error);
}
}
/* prototype code for pipemenu */
static void
pipe_menu_recreate (GtkWidget *item,
gchar *command)
{
gchar *stdout;
gchar *cache_entries = g_object_get_data (G_OBJECT(item), "cached");
GtkWidget *submenu;
submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM(item));
if (submenu)
{
if (strcmp (cache_entries, "yes") != 0)
{
gtk_widget_destroy (submenu);
submenu = NULL;
}
}
if (!submenu)
{
submenu = gtk_menu_new();
if (g_spawn_command_line_sync (parse_expand_tilde(command), &stdout, NULL, NULL, NULL))
{
GError *error = NULL;
DeskmenuObject *dm_object = g_object_get_data (G_OBJECT(item), "menu");
dm_object->current_menu = submenu;
dm_object->make_from_pipe = TRUE;
if (gtk_menu_get_tearoff_state (GTK_MENU(dm_object->menu)))
{
gtk_menu_shell_append (GTK_MENU_SHELL (dm_object->current_menu),
gtk_tearoff_menu_item_new());
}
GMarkupParseContext *context = g_markup_parse_context_new (&parser,
0, dm_object, NULL);
g_markup_parse_context_parse (context, stdout, strlen(stdout), &error);
g_markup_parse_context_free (context);
if (error)
{
g_print(error->message); //spit out the message manually
if (dm_object->current_item)
{
//force reset
deskmenu_free_item(dm_object);
}
g_error_free (error);
}
g_free(stdout);
dm_object->make_from_pipe = FALSE;
}
else
{
GtkWidget *empty_item = gtk_menu_item_new_with_label ("Unable to get output");
gtk_widget_set_sensitive (empty_item, FALSE);
gtk_menu_shell_append (GTK_MENU_SHELL (submenu), empty_item);
}
gtk_widget_show_all(submenu);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
}
}
static void
launcher_name_exec_update (GtkWidget *label)
{
gchar *exec, *stdout;
exec = g_object_get_data (G_OBJECT (label), "exec");
if (g_spawn_command_line_sync (exec, &stdout, NULL, NULL, NULL))
gtk_label_set_text (GTK_LABEL (label), g_strstrip(stdout));
else
gtk_label_set_text (GTK_LABEL (label), "execution error");
g_free (stdout);
}
static
GtkWidget *make_recent_documents_list (gboolean images, gchar *command, int limit, int age, gchar *sort_type)
{
GtkWidget *widget = gtk_recent_chooser_menu_new ();
if (images)
{
gtk_recent_chooser_set_show_icons (GTK_RECENT_CHOOSER(widget), TRUE);
}
else
{
gtk_recent_chooser_set_show_icons (GTK_RECENT_CHOOSER(widget), FALSE);
}
g_signal_connect (G_OBJECT (widget), "item-activated", G_CALLBACK (recent_activated), g_strdup (command));
if (age)
{
GtkRecentFilter *filter = gtk_recent_filter_new ();
gtk_recent_filter_add_pattern (filter, "*");
gtk_recent_filter_add_age (filter, age);
gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER(widget), filter);
}
if (sort_type) {
if (strcmp (sort_type, "most used") == 0)
{
gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER(widget), GTK_RECENT_SORT_MRU);
}
else
{
gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER(widget), GTK_RECENT_SORT_LRU);
}
}
gtk_recent_chooser_set_limit (GTK_RECENT_CHOOSER(widget), limit);
return widget;
}
static void
deskmenu_construct_item (DeskmenuObject *dm_object)
{
DeskmenuItem *item = dm_object->current_item;
GtkWidget *menu_item, *submenu;
gchar *name, *icon, *command, *vpicon;
gboolean images;
gint w, h;
//constructs the items in menu
switch (item->type)
{
case DESKMENU_ITEM_LAUNCHER:
if (item->name_exec)
{
GtkWidget *label;
GHook *hook;
name = g_strstrip (item->name->str);
menu_item = gtk_image_menu_item_new ();
label = gtk_label_new_with_mnemonic (NULL);
gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
g_object_set_data (G_OBJECT (label), "exec", g_strdup (name));
gtk_container_add (GTK_CONTAINER (menu_item), label);
hook = g_hook_alloc (dm_object->show_hooks);
hook->data = (gpointer) label;
hook->func = (GHookFunc *) launcher_name_exec_update;
g_hook_append (dm_object->show_hooks, hook);
}
else
{
if (item->name)
name = g_strstrip (item->name->str);
else
name = "";
menu_item = gtk_image_menu_item_new_with_mnemonic (name);
}
if (item->icon)
{
icon = g_strstrip (item->icon->str);
if (item->icon_file) {
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM
(menu_item), gtk_image_new_from_pixbuf (gdk_pixbuf_new_from_file_at_size (parse_expand_tilde(icon), w, h, NULL)));
}
else {
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU));
}
}
if (item->command_pipe)
{
command = g_strstrip (item->command->str);
if (item->cache_output)
{
g_object_set_data(G_OBJECT(menu_item), "cached", g_strdup("yes"));
}
else
{
g_object_set_data(G_OBJECT(menu_item), "cached", g_strdup("no"));
}
g_object_set_data(G_OBJECT(menu_item), "menu", dm_object);
g_signal_connect (G_OBJECT (menu_item), "activate",
G_CALLBACK (pipe_menu_recreate), g_strdup(command));
submenu = gtk_menu_new();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
}
else
{
if (item->command)
{
command = g_strstrip (item->command->str);
g_signal_connect (G_OBJECT (menu_item), "activate",
G_CALLBACK (launcher_activated), g_strdup (command));
}
}
gtk_menu_shell_append (GTK_MENU_SHELL (dm_object->current_menu),
menu_item);
break;
#if HAVE_WNCK
case DESKMENU_ITEM_WINDOWLIST:
menu_item = gtk_image_menu_item_new_with_mnemonic ("_Windows");
images = FALSE;
gboolean this_vp = FALSE;
gboolean mini_only = FALSE;
if (item->icon)
{
images = TRUE;
icon = g_strstrip (item->icon->str);
if (item->icon_file) {
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM
(menu_item), gtk_image_new_from_pixbuf (gdk_pixbuf_new_from_file_at_size (parse_expand_tilde(icon), w, h, NULL)));
}
else {
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU));
}
}
if (item->thisvp
&& strcmp (g_strstrip (item->thisvp->str), "true") == 0)
this_vp = TRUE;
if (item->mini_only
&& strcmp (g_strstrip (item->mini_only->str), "true") == 0)
mini_only = TRUE;
g_object_set_data(G_OBJECT(menu_item), "windowlist", deskmenu_windowlist_initialize (images, this_vp, mini_only));
g_signal_connect (G_OBJECT (menu_item), "activate",
G_CALLBACK (refresh_windowlist_item), NULL);
submenu = gtk_menu_new();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
gtk_menu_shell_append (GTK_MENU_SHELL (dm_object->current_menu),
menu_item);
break;
case DESKMENU_ITEM_VIEWPORTLIST:
menu_item = gtk_image_menu_item_new_with_mnemonic ("_Viewports");
gboolean wrap, file;
wrap = FALSE;
images = FALSE;
file = FALSE;
if (item->wrap
&& strcmp (g_strstrip (item->wrap->str), "true") == 0)
wrap = TRUE;
if (item->icon)
{
images = TRUE;
icon = g_strstrip (item->icon->str);
if (item->icon_file) {
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM
(menu_item), gtk_image_new_from_pixbuf (gdk_pixbuf_new_from_file_at_size (parse_expand_tilde(icon), w, h, NULL)));
}
else {
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU));
}
}
if (item->vpicon)
{
vpicon = g_strstrip (parse_expand_tilde(item->vpicon->str));
if (item->vpicon_file) {
file = TRUE;
}
}
g_object_set_data(G_OBJECT(menu_item), "vplist", deskmenu_vplist_initialize (wrap, images, file, vpicon));
g_signal_connect (G_OBJECT (menu_item), "activate",
G_CALLBACK (refresh_viewportlist_item), NULL);
submenu = gtk_menu_new();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
gtk_menu_shell_append (GTK_MENU_SHELL (dm_object->current_menu),
menu_item);
break;
#endif
case DESKMENU_ITEM_RELOAD:
menu_item = gtk_image_menu_item_new_with_mnemonic ("Reload");
if (item->icon)
{
icon = g_strstrip (item->icon->str);
if (item->icon_file) {
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM
(menu_item), gtk_image_new_from_pixbuf (gdk_pixbuf_new_from_file_at_size (parse_expand_tilde(icon), w, h, NULL)));
}
else {
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU));
}
}
g_signal_connect (G_OBJECT (menu_item), "activate",
G_CALLBACK (quit), NULL);
gtk_menu_shell_append (GTK_MENU_SHELL (dm_object->current_menu),
menu_item);
break;
case DESKMENU_ITEM_DOCUMENTS:
menu_item = gtk_image_menu_item_new_with_mnemonic ("Recent Doc_uments");
gint limit, age;
gchar *sort_type;
images = FALSE;
if (item->icon)
{
images = TRUE;
icon = g_strstrip (item->icon->str);
if (item->icon_file) {
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM
(menu_item), gtk_image_new_from_pixbuf (gdk_pixbuf_new_from_file_at_size (parse_expand_tilde(icon), w, h, NULL)));
}
else {
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU));
}
}
if (item->age)
{
age = atoi(g_strstrip (item->age->str));
}
if (item->sort_type) {
sort_type = g_strstrip (item->sort_type->str);
}
if (item->quantity)
{
limit = atoi(g_strstrip (item->quantity->str));
}
else
{
limit = -1;
}
if (item->command)
{
command = g_strstrip (item->command->str);
}
else
{
command = g_strdup ("xdg-open");
}
GtkWidget *docs = make_recent_documents_list(images, command, limit, age, sort_type);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item),
docs);
gtk_menu_shell_append (GTK_MENU_SHELL (dm_object->current_menu),
menu_item);
break;
default:
break;
}
}
static void
deskmenu_free_item (DeskmenuObject *dm_object) {
/* free data used to make it */
if (dm_object->current_item->name)
g_string_free (dm_object->current_item->name, TRUE);
if (dm_object->current_item->icon)
g_string_free (dm_object->current_item->icon, TRUE);
if (dm_object->current_item->command)
g_string_free (dm_object->current_item->command, TRUE);
if (dm_object->current_item->wrap)
g_string_free (dm_object->current_item->wrap, TRUE);
if (dm_object->current_item->vpicon)
g_string_free (dm_object->current_item->vpicon, TRUE);
if (dm_object->current_item->mini_only)
g_string_free (dm_object->current_item->mini_only, TRUE);
if (dm_object->current_item->thisvp)
g_string_free (dm_object->current_item->thisvp, TRUE);
if (dm_object->current_item->sort_type)
g_string_free (dm_object->current_item->sort_type, TRUE);
if (dm_object->current_item->quantity)
g_string_free (dm_object->current_item->quantity, TRUE);
if (dm_object->current_item->age)
g_string_free (dm_object->current_item->age, TRUE);
dm_object->current_item = NULL;
}
/* The handler functions. */
static void
start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attr_names,
const gchar **attr_values,
gpointer user_data,
GError **error)
{
DeskmenuObject *dm_object = user_data;
DeskmenuElementType element_type;
const gchar **ncursor = attr_names, **vcursor = attr_values;
GtkWidget *item, *menu;
gint w, h;
element_type = GPOINTER_TO_INT (g_hash_table_lookup
(element_hash, element_name));
if ((dm_object->menu && !dm_object->current_menu)
|| (!dm_object->menu && element_type != DESKMENU_ELEMENT_MENU))
{
gint line_num, char_num;
g_markup_parse_context_get_position (context, &line_num, &char_num);
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
"Error on line %d char %d: Element '%s' declared outside of "
"toplevel menu element", line_num, char_num, element_name);
return;
}
switch (element_type)
{
case DESKMENU_ELEMENT_MENU:
if (dm_object->current_item != NULL)
{
gint line_num, char_num;
g_markup_parse_context_get_position (context, &line_num,
&char_num);
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
"Error on line %d char %d: Element 'menu' cannot be nested "
"inside of an item element", line_num, char_num);
return;
}
if (!dm_object->menu)
{
/*if (strcmp (*ncursor, "size") == 0) {
deskmenu->w = g_strdup (*vcursor);
deskmenu->h = g_strdup (*vcursor);
}
else {
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, deskmenu->w, deskmenu->h);
}*/
dm_object->menu = gtk_menu_new ();
g_object_set_data (G_OBJECT (dm_object->menu), "parent menu",
NULL);
dm_object->current_menu = dm_object->menu;
}
else
{
gchar *name = NULL;
gchar *icon = NULL;
gboolean name_exec = FALSE;
gboolean icon_file = FALSE;
while (*ncursor)
{
if (strcmp (*ncursor, "name") == 0)
name = g_strdup (*vcursor);
else if (strcmp (*ncursor, "icon") == 0)
icon = g_strdup (*vcursor);
else if ((strcmp (*ncursor, "mode") == 0)
&& (strcmp (*vcursor, "exec") == 0))
name_exec = TRUE;
else if ((strcmp (*ncursor, "mode1") == 0)
&& (strcmp (*vcursor, "file") == 0))
icon_file = TRUE;
else
g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
"Unknown attribute: %s", *ncursor);
ncursor++;
vcursor++;
}
if (name_exec)
{
GtkWidget *label;
GHook *hook;
item = gtk_image_menu_item_new ();
label = gtk_label_new_with_mnemonic (NULL);
gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
g_object_set_data (G_OBJECT (label), "exec", g_strdup (name));
gtk_container_add (GTK_CONTAINER (item), label);
hook = g_hook_alloc (dm_object->show_hooks);
hook->data = (gpointer) label;
hook->func = (GHookFunc *) launcher_name_exec_update;
g_hook_append (dm_object->show_hooks, hook);
}
else
{
if (name)
item = gtk_image_menu_item_new_with_mnemonic (name);
else
item = gtk_image_menu_item_new_with_mnemonic ("");
}
if (icon)
{
if (icon_file) {
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM
(item), gtk_image_new_from_pixbuf (gdk_pixbuf_new_from_file_at_size (parse_expand_tilde(icon), w, h, NULL)));
}
else {
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU));
}
}
gtk_menu_shell_append (GTK_MENU_SHELL (dm_object->current_menu), item);
menu = gtk_menu_new ();
g_object_set_data (G_OBJECT (menu), "parent menu",
dm_object->current_menu);
dm_object->current_menu = menu;
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
dm_object->current_menu);
if (!dm_object->make_from_pipe)
{
GtkWidget *pin = gtk_tearoff_menu_item_new();
gtk_menu_shell_append (GTK_MENU_SHELL (dm_object->current_menu),
pin); //add a pin menu item
dm_object->pin_items = g_slist_prepend (dm_object->pin_items, pin);
}
else
{
if (gtk_menu_get_tearoff_state (GTK_MENU(dm_object->menu)))
{
GtkWidget *pin = gtk_tearoff_menu_item_new();
gtk_menu_shell_append (GTK_MENU_SHELL (dm_object->current_menu),
pin); //add a pin menu item
}
}
g_free (name);
g_free (icon);
}
break;
case DESKMENU_ELEMENT_SEPARATOR:
if (dm_object->current_item != NULL)
{
gint line_num, char_num;
g_markup_parse_context_get_position (context, &line_num,
&char_num);
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
"Error on line %d char %d: Element 'menu' cannot be nested "
"inside of an item element", line_num, char_num);
return;
}
else {
gchar *name = NULL;
gchar *icon = NULL;
gboolean name_exec = FALSE;
gboolean icon_file = FALSE;
gboolean decorate = FALSE;
gint w, h;
item = gtk_separator_menu_item_new();
while (*ncursor)
{
if (strcmp (*ncursor, "name") == 0) {
name = g_strdup (*vcursor);
if (!decorate)
{
decorate = TRUE;
}
}
else if (strcmp (*ncursor, "icon") == 0) {
icon = g_strdup (*vcursor);
if (!decorate)
{
decorate = TRUE;
}
}
else if ((strcmp (*ncursor, "mode") == 0)
&& (strcmp (*vcursor, "exec") == 0))
name_exec = TRUE;
else if ((strcmp (*ncursor, "mode1") == 0)
&& (strcmp (*vcursor, "file") == 0))
icon_file = TRUE;
else
g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
"Unknown attribute: %s", *ncursor);
ncursor++;
vcursor++;
}
if (decorate)
{
GtkHBox *box = gtk_hbox_new (FALSE, 3);
gtk_container_add (GTK_CONTAINER(item), GTK_WIDGET(box));
if (name_exec)
{
GtkWidget *label;
GHook *hook;
label = gtk_label_new_with_mnemonic (NULL);
g_object_set_data (G_OBJECT (label), "exec", g_strdup (name));
gtk_box_pack_end (GTK_BOX(box), label,
TRUE,
FALSE,
0);
hook = g_hook_alloc (dm_object->show_hooks);
hook->data = (gpointer) label;
hook->func = (GHookFunc *) launcher_name_exec_update;
g_hook_append (dm_object->show_hooks, hook);
}
else
{
gtk_box_pack_end (GTK_BOX(box), gtk_label_new_with_mnemonic (name),
TRUE,
FALSE,
0);
}
if (icon)
{
GtkWidget *image;
if (icon_file) {
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
image = gtk_image_new_from_pixbuf (gdk_pixbuf_new_from_file_at_size (parse_expand_tilde(icon), w, h, NULL));
}
else {
image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU);
}
gtk_box_pack_start (GTK_BOX(box), image,
FALSE,
FALSE,
0);
}
gtk_widget_set_state (item, GTK_STATE_PRELIGHT); /*derive colors from menu hover*/
g_free (name);
g_free (icon);
}
gtk_menu_shell_append (GTK_MENU_SHELL (dm_object->current_menu), item);
}
break;
case DESKMENU_ELEMENT_ITEM:
if (dm_object->current_item != NULL)
{
gint line_num, char_num;
g_markup_parse_context_get_position (context, &line_num,
&char_num);
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
"Error on line %d char %d: Element 'item' cannot be nested "
"inside of another item element", line_num, char_num);
return;
}
dm_object->current_item = g_slice_new0 (DeskmenuItem);
while (*ncursor)
{
if (strcmp (*ncursor, "type") == 0)
dm_object->current_item->type = GPOINTER_TO_INT
(g_hash_table_lookup (item_hash, *vcursor));
else
g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
"Unknown attribute: %s", *ncursor);
ncursor++;
vcursor++;
}
break;
case DESKMENU_ELEMENT_NAME:
while (*ncursor)
{
if ((strcmp (*ncursor, "mode") == 0)
&& (strcmp (*vcursor, "exec") == 0))
dm_object->current_item->name_exec = TRUE;
ncursor++;
vcursor++;
} /* no break here to let it fall through */
case DESKMENU_ELEMENT_ICON:
while (*ncursor)
{
if ((strcmp (*ncursor, "mode1") == 0)
&& (strcmp (*vcursor, "file") == 0))
dm_object->current_item->icon_file = TRUE;
ncursor++;
vcursor++;
} /* no break here to let it fall through */
case DESKMENU_ELEMENT_VPICON:
while (*ncursor)
{
if ((strcmp (*ncursor, "mode1") == 0)
&& (strcmp (*vcursor, "file") == 0))
dm_object->current_item->vpicon_file = TRUE;
ncursor++;
vcursor++;
} /* no break here to let it fall through */
case DESKMENU_ELEMENT_COMMAND:
while (*ncursor)
{
if ((strcmp (*ncursor, "mode2") == 0)
&& (strcmp (*vcursor, "pipe") == 0))
dm_object->current_item->command_pipe = TRUE;
if (dm_object->current_item->command_pipe == TRUE
&& (strcmp (*ncursor, "cache") == 0)
&& (strcmp (*vcursor, "true") == 0))
dm_object->current_item->cache_output = TRUE;
ncursor++;
vcursor++;
} /* no break here to let it fall through */
case DESKMENU_ELEMENT_WRAP:
if (dm_object->current_item)
dm_object->current_item->current_element = element_type;
break;
case DESKMENU_ELEMENT_THISVP:
if (dm_object->current_item)
dm_object->current_item->current_element = element_type;
break;
case DESKMENU_ELEMENT_MINIONLY:
if (dm_object->current_item)
dm_object->current_item->current_element = element_type;
break;
case DESKMENU_ELEMENT_QUANTITY:
if (dm_object->current_item)
dm_object->current_item->current_element = element_type;
break;
case DESKMENU_ELEMENT_SORT:
if (dm_object->current_item)
dm_object->current_item->current_element = element_type;
break;
case DESKMENU_ELEMENT_AGE:
if (dm_object->current_item)
dm_object->current_item->current_element = element_type;
break;
default:
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
"Unknown element: %s", element_name);
break;
}
}
//dealing with empty attributes
static void
text (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error)
{
DeskmenuObject *dm_object = user_data;
DeskmenuItem *item = dm_object->current_item;
if (!(item && item->current_element))
return;
switch (item->current_element)
{
case DESKMENU_ELEMENT_NAME:
if (!item->name)
item->name = g_string_new_len (text, text_len);
else
g_string_append_len (item->name, text, text_len);
break;
case DESKMENU_ELEMENT_ICON:
if (!item->icon)
item->icon = g_string_new_len (text, text_len);
else
g_string_append_len (item->icon, text, text_len);
break;
case DESKMENU_ELEMENT_VPICON:
if (!item->vpicon)
item->vpicon = g_string_new_len (text, text_len);
else
g_string_append_len (item->vpicon, text, text_len);
break;
case DESKMENU_ELEMENT_COMMAND:
if (!item->command)
item->command = g_string_new_len (text, text_len);
else
g_string_append_len (item->command, text, text_len);
break;
case DESKMENU_ELEMENT_WRAP:
if (!item->wrap)
item->wrap = g_string_new_len (text, text_len);
else
g_string_append_len (item->wrap, text, text_len);
break;
case DESKMENU_ELEMENT_THISVP:
if (!item->thisvp)
item->thisvp = g_string_new_len (text, text_len);
else
g_string_append_len (item->thisvp, text, text_len);
break;
case DESKMENU_ELEMENT_MINIONLY:
if (!item->mini_only)
item->mini_only = g_string_new_len (text, text_len);
else
g_string_append_len (item->mini_only, text, text_len);
break;
case DESKMENU_ELEMENT_AGE:
if (!item->age)
item->age = g_string_new_len (text, text_len);
else
g_string_append_len (item->age, text, text_len);
break;
case DESKMENU_ELEMENT_QUANTITY:
if (!item->quantity)
item->quantity = g_string_new_len (text, text_len);
else
g_string_append_len (item->quantity, text, text_len);
break;
case DESKMENU_ELEMENT_SORT:
if (!item->sort_type)
item->sort_type = g_string_new_len (text, text_len);
else
g_string_append_len (item->sort_type, text, text_len);
break;
default:
break;
}
}
static void
end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error)
{
DeskmenuElementType element_type;
DeskmenuObject *dm_object = user_data;
GtkWidget *parent;
element_type = GPOINTER_TO_INT (g_hash_table_lookup
(element_hash, element_name));
switch (element_type)
{
case DESKMENU_ELEMENT_MENU:
g_return_if_fail (dm_object->current_item == NULL);
parent = g_object_get_data (G_OBJECT (dm_object->current_menu),
"parent menu");
dm_object->current_menu = parent;
break;
/* separator building is now dealt with in the beginning */
case DESKMENU_ELEMENT_ITEM:
g_return_if_fail (dm_object->current_item != NULL);
/* finally make the item ^_^ */
deskmenu_construct_item (dm_object);
deskmenu_free_item(dm_object);
break;
default:
break;
}
}
/* Class init */
static void
deskmenu_class_init (DeskmenuClass *deskmenu_class)
{
dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (deskmenu_class),
&dbus_glib_deskmenu_object_info);
}
/* Instance init, matches up words to types,
note how there's no handler for pipe since it's
replaced in its own chunk */
static void
deskmenu_init (Deskmenu *deskmenu)
{
deskmenu->pinnable = FALSE;
deskmenu->file_cache = g_hash_table_new (g_str_hash, g_str_equal);
}
static void
set_up_item_hash (void) {
item_hash= g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_insert (item_hash, "launcher",
GINT_TO_POINTER (DESKMENU_ITEM_LAUNCHER));
#if HAVE_WNCK
g_hash_table_insert (item_hash, "windowlist",
GINT_TO_POINTER (DESKMENU_ITEM_WINDOWLIST));
g_hash_table_insert (item_hash, "viewportlist",
GINT_TO_POINTER (DESKMENU_ITEM_VIEWPORTLIST));
#endif
g_hash_table_insert (item_hash, "documents",
GINT_TO_POINTER (DESKMENU_ITEM_DOCUMENTS));
g_hash_table_insert (item_hash, "reload",
GINT_TO_POINTER (DESKMENU_ITEM_RELOAD));
}
static void
set_up_element_hash (void) {
element_hash = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_insert (element_hash, "menu",
GINT_TO_POINTER (DESKMENU_ELEMENT_MENU));
g_hash_table_insert (element_hash, "separator",
GINT_TO_POINTER (DESKMENU_ELEMENT_SEPARATOR));
g_hash_table_insert (element_hash, "item",
GINT_TO_POINTER (DESKMENU_ELEMENT_ITEM));
g_hash_table_insert (element_hash, "name",
GINT_TO_POINTER (DESKMENU_ELEMENT_NAME));
g_hash_table_insert (element_hash, "icon",
GINT_TO_POINTER (DESKMENU_ELEMENT_ICON));
g_hash_table_insert (element_hash, "vpicon",
GINT_TO_POINTER (DESKMENU_ELEMENT_VPICON));
g_hash_table_insert (element_hash, "command",
GINT_TO_POINTER (DESKMENU_ELEMENT_COMMAND));
g_hash_table_insert (element_hash, "thisvp",
GINT_TO_POINTER (DESKMENU_ELEMENT_THISVP));
g_hash_table_insert (element_hash, "minionly",
GINT_TO_POINTER (DESKMENU_ELEMENT_MINIONLY));
g_hash_table_insert (element_hash, "wrap",
GINT_TO_POINTER (DESKMENU_ELEMENT_WRAP));
g_hash_table_insert (element_hash, "sort",
GINT_TO_POINTER (DESKMENU_ELEMENT_SORT));
g_hash_table_insert (element_hash, "quantity",
GINT_TO_POINTER (DESKMENU_ELEMENT_QUANTITY));
g_hash_table_insert (element_hash, "age",
GINT_TO_POINTER (DESKMENU_ELEMENT_AGE));
}
static DeskmenuObject
*deskmenu_object_init (void) {
DeskmenuObject *dm_object = g_slice_new0 (DeskmenuObject);
dm_object->menu = NULL;
dm_object->current_menu = NULL;
dm_object->current_item = NULL;
dm_object->make_from_pipe = FALSE;
dm_object->pin_items = NULL;
dm_object->show_hooks = g_slice_new0 (GHookList);
g_hook_list_init (dm_object->show_hooks, sizeof (GHook));
return dm_object;
}
static DeskmenuObject
*deskmenu_parse_file (gchar *filename)
{
GError *error = NULL;
DeskmenuObject *dm_object = deskmenu_object_init();
GMarkupParseContext *context = g_markup_parse_context_new (&parser,
0, dm_object, NULL);
gchar *text;
gsize length;
g_file_get_contents (filename, &text, &length, NULL); //cache already handled file existence check
if (!g_markup_parse_context_parse (context, text, strlen(text), &error)
|| !g_markup_parse_context_end_parse (context, &error))
{
g_printerr ("Parse failed with message: %s \n", error->message);
g_error_free (error);
exit (1);
}
g_free(text); //free the joined array
g_markup_parse_context_free (context); //free the parser
gtk_widget_show_all (dm_object->menu);
return dm_object;
}
static DeskmenuObject
*check_file_cache (Deskmenu *deskmenu, gchar *filename) {
DeskmenuObject *dm_object;
gchar *user_default = g_build_path (G_DIR_SEPARATOR_S, g_get_user_config_dir (),
"compiz",
"boxmenu",
"menu.xml",
NULL);
//TODO: add a size column to cache for possible autorefresh
g_print("Checking cache...\n");
if (strlen(filename) == 0)
filename = g_build_path (G_DIR_SEPARATOR_S,
g_get_user_config_dir (),
"compiz",
"boxmenu",
"menu.xml",
NULL);
if (strcmp(filename, user_default) == 0) {
g_print("Looking up default menu...\n");
/*
set default filename to be [configdir]/compiz/boxmenu/menu.xml
*/
gboolean success = FALSE;
if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
g_print("Getting default system menu...\n");
const gchar* const *cursor = g_get_system_config_dirs ();
gchar *path = NULL;
while (*cursor)
{
g_free (path);
path = g_strdup (*cursor);
filename = g_build_path (G_DIR_SEPARATOR_S,
path,
"compiz",
"boxmenu",
"menu.xml",
NULL);
if (g_file_test(filename, G_FILE_TEST_EXISTS))
{
if (g_hash_table_lookup(deskmenu->file_cache, filename) == NULL)
{
g_hash_table_insert (deskmenu->file_cache, g_strdup(filename), deskmenu_parse_file(filename));
g_print("Prepared default system menu!\n");
}
else
{
g_print("Retrieving default system menu!\n");
}
g_free (path);
success = TRUE;
break;
}
cursor++;
}
}
else
{
if (g_hash_table_lookup(deskmenu->file_cache, user_default) == NULL)
{
if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
g_print("Preparing default menu!\n");
g_hash_table_insert (deskmenu->file_cache, g_strdup(filename), deskmenu_parse_file(filename));
success = TRUE;
}
}
else
{
g_print("Retrieving cached default user menu...\n");
success = TRUE;
}
}
if (!success)
{
g_printerr ("Couldn't find a menu file...\n");
exit (1);
}
}
else {
if (g_hash_table_lookup(deskmenu->file_cache, filename) == NULL) {
if (g_file_test(filename, G_FILE_TEST_EXISTS))
{
g_print("Preparing new non-default menu...\n");
g_hash_table_insert (deskmenu->file_cache, g_strdup(filename), deskmenu_parse_file(filename));
}
else {
if (g_hash_table_lookup(deskmenu->file_cache, user_default) != NULL)
{
g_print("Couldn't find specified file, loading default...\n");
filename = user_default;
}
else
{
g_printerr ("Couldn't find a menu file...\n");
exit (1);
}
}
}
}
dm_object = g_hash_table_lookup (deskmenu->file_cache, filename);
g_printf("Done loading %s!\n", filename);
return dm_object;
}
#if HAVE_WNCK
gboolean
deskmenu_vplist (Deskmenu *deskmenu,
gboolean toggle_wrap,
gboolean toggle_images,
gboolean toggle_file,
gchar *viewport_icon) {
DeskmenuVplist *vplist = deskmenu_vplist_initialize(toggle_wrap, toggle_images, toggle_file, g_strstrip(viewport_icon));
deskmenu_vplist_new (vplist);
gtk_menu_popup (GTK_MENU (vplist->menu),
NULL, NULL, NULL, NULL,
0, 0);
return TRUE;
}
gboolean
deskmenu_windowlist (Deskmenu *deskmenu,
gboolean images,
gboolean thisvp,
gboolean mini_only) {
DeskmenuWindowlist *windowlist = deskmenu_windowlist_initialize (images, thisvp, mini_only);
deskmenu_windowlist_new(windowlist);
gtk_menu_popup (GTK_MENU (windowlist->menu),
NULL, NULL, NULL, NULL,
0, 0);
return TRUE;
}
#endif
gboolean
deskmenu_documentlist (Deskmenu *deskmenu,
gboolean images,
gchar *command,
int limit,
int age,
gchar *sort_type) {
GtkWidget *menu = make_recent_documents_list (images, g_strdup(command), limit, age, g_strstrip(sort_type));
gtk_menu_popup (GTK_MENU (menu),
NULL, NULL, NULL, NULL,
0, 0);
return TRUE;
}
/* The show method */
static void
deskmenu_show (DeskmenuObject *dm_object,
Deskmenu *deskmenu,
GError **error)
{
GSList *list = NULL, *iterator = NULL;
list = dm_object->pin_items;
g_hook_list_invoke (dm_object->show_hooks, FALSE);
if (deskmenu->pinnable)
{
gtk_menu_set_tearoff_state (GTK_MENU (dm_object->menu), TRUE);
for (iterator = list; iterator; iterator = iterator->next) {
gtk_widget_show (iterator->data);
gtk_widget_set_no_show_all (iterator->data, FALSE);
}
}
else {
gtk_menu_popup (GTK_MENU (dm_object->menu),
NULL, NULL, NULL, NULL,
0, 0);
for (iterator = list; iterator; iterator = iterator->next) {
gtk_widget_hide (iterator->data);
gtk_widget_set_no_show_all (iterator->data, TRUE);
}
}
}
gboolean
deskmenu_pin (Deskmenu *deskmenu,
gboolean pin)
{
deskmenu->pinnable = pin;
return TRUE;
}
gboolean
deskmenu_reload (Deskmenu *deskmenu,
GError **error)
{
gtk_main_quit ();
return TRUE;
}
/* The dbus method for binary client */
gboolean
deskmenu_control (Deskmenu *deskmenu, gchar *filename, gchar *workingd, GError **error)
{
chdir (workingd);
DeskmenuObject *dm_object = check_file_cache (deskmenu, g_strstrip(filename));
deskmenu_show(dm_object, deskmenu, error);
return TRUE;
}
//precache backend, currently needs GUI
static void
deskmenu_precache (Deskmenu *deskmenu, gchar *filename)
{
GError *error = NULL;
GKeyFile *config = g_key_file_new ();
int i;
(void *)check_file_cache(deskmenu, ""); //always cache default menu
g_print("Attempting to precache files in config...");
if (!filename)
{
filename = g_build_path (G_DIR_SEPARATOR_S,
g_get_user_config_dir (),
"compiz",
"boxmenu",
"precache.ini",
NULL);
}
if (!g_key_file_load_from_file (config, filename, G_KEY_FILE_NONE, NULL))
{
g_print("Configuration not found, will not precache files...");
}
else
{
g_print("Configuration found! Starting precache...");
gchar **files = g_key_file_get_keys (config, "Files", NULL, &error);
gchar *feed;
while (files[i])
{
feed = g_key_file_get_string (config, "Files", files[i], &error);
(void *)check_file_cache(deskmenu, parse_expand_tilde(feed));
i++;
}
g_strfreev(files);
g_free(feed);
}
g_key_file_free (config);
}
int
main (int argc,
char **argv)
{
DBusGConnection *connection;
GError *error = NULL;
GObject *deskmenu;
g_type_init ();
connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
if (connection == NULL)
{
g_printerr ("Failed to open connection to bus: %s", error->message);
g_error_free (error);
exit (1);
}
g_print ("Starting the daemon...\n");
GOptionContext *context;
gchar *file = NULL;
GOptionEntry entries[] =
{
{ "config", 'c', 0, G_OPTION_ARG_FILENAME, &file,
"Use FILE instead of the default daemon configuration", "FILE" },
{ NULL, 0, 0, 0, NULL, NULL, NULL }
};
context = g_option_context_new (NULL);
g_option_context_add_main_entries (context, entries, NULL);
error = NULL;
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("option parsing failed: %s", error->message);
g_error_free (error);
return 1;
}
g_set_prgname ("Compiz Boxmenu");
g_option_context_free (context);
#if HAVE_WNCK
wnck_set_client_type (WNCK_CLIENT_TYPE_PAGER);
#endif
gtk_init (&argc, &argv);
deskmenu = g_object_new (DESKMENU_TYPE, NULL);
dbus_g_connection_register_g_object (connection, DESKMENU_PATH_DBUS, deskmenu);
if (!dbus_bus_request_name (dbus_g_connection_get_connection (connection),
DESKMENU_SERVICE_DBUS,
DBUS_NAME_FLAG_REPLACE_EXISTING,
NULL))
return 1;
set_up_element_hash();
set_up_item_hash();
deskmenu_precache(DESKMENU(deskmenu), file);
gtk_main ();
return 0;
}