/* * This file is part of YAD. * * YAD 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 3 of the License, or * (at your option) any later version. * * YAD 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 YAD. If not, see <http://www.gnu.org/licenses/>. * * Copyright (C) 2008-2023, Victor Ananjevsky <victor@sanana.kiev.ua> */ #include "yad.h" static GtkWidget *icon_view; static GtkListStore *store; enum { COL_FILENAME = 0, COL_NAME, COL_TOOLTIP, COL_PIXBUF, COL_COMMAND, COL_TERM, NUM_COLS }; enum { TYPE_APP, TYPE_LINK }; typedef struct { gchar *name; gchar *comment; GdkPixbuf *pixbuf; gchar *command; gboolean in_term; } DEntry; static GdkPixbuf * scale_pixbuf (GdkPixbuf *pb) { GdkPixbuf *res; if (!pb) return NULL; if (options.common_data.icon_size > 0) { guint width, height; width = gdk_pixbuf_get_width (pb); height = gdk_pixbuf_get_height (pb); if (options.common_data.icon_size != width || options.common_data.icon_size != height) res = gdk_pixbuf_scale_simple (pb, options.common_data.icon_size, options.common_data.icon_size, GDK_INTERP_BILINEAR); else res = g_object_ref (pb); } else res = g_object_ref (pb); return res; } static void select_cb (GObject * obj, gpointer data) { static gboolean first_time = TRUE; if (!options.icons_data.compact) { GList *sel = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (icon_view)); if (sel) gtk_icon_view_item_activated (GTK_ICON_VIEW (icon_view), (GtkTreePath *) sel->data); g_list_foreach (sel, (GFunc) gtk_tree_path_free, NULL); g_list_free (sel); } else { GtkTreeIter iter; GtkTreeSelection *sel = (GtkTreeSelection *) obj; if (first_time) { /* don't activate item when dialog is appear and clear the selection */ first_time = FALSE; gtk_tree_selection_unselect_all (sel); return; } if (gtk_tree_selection_get_selected (sel, NULL, &iter)) { GtkTreeModel *model; GtkTreePath *path; model = gtk_tree_view_get_model (GTK_TREE_VIEW (icon_view)); path = gtk_tree_model_get_path (model, &iter); gtk_tree_view_row_activated (GTK_TREE_VIEW (icon_view), path, (GtkTreeViewColumn *) data); } } } static void activate_cb (GtkWidget * view, GtkTreePath * path, gpointer data) { GtkTreeIter iter; GtkTreeModel *model; gchar *cmd; gboolean in_term; if (!path) return; if (!options.icons_data.compact) model = gtk_icon_view_get_model (GTK_ICON_VIEW (view)); else model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, COL_COMMAND, &cmd, COL_TERM, &in_term, -1); if (cmd && cmd[0]) { if (in_term) { gchar *tcmd = g_strdup_printf (options.icons_data.term, cmd); run_command_async (tcmd); g_free (tcmd); } else run_command_async (cmd); } } static gboolean handle_stdin (GIOChannel * channel, GIOCondition condition, gpointer data) { static GtkTreeIter iter; static gint column_count = 1; static gint row_count = 0; static gboolean first_time = TRUE; GtkTreeModel *model; if (!options.icons_data.compact) model = gtk_icon_view_get_model (GTK_ICON_VIEW (icon_view)); else model = gtk_tree_view_get_model (GTK_TREE_VIEW (icon_view)); if (first_time) { first_time = FALSE; gtk_list_store_append (GTK_LIST_STORE (model), &iter); gtk_list_store_set (GTK_LIST_STORE (model), &iter, COL_FILENAME, "", -1); } if ((condition == G_IO_IN) || (condition == G_IO_IN + G_IO_HUP)) { GError *err = NULL; GString *string = g_string_new (NULL); while (channel->is_readable != TRUE) usleep (100); do { GdkPixbuf *spb = NULL; gint status; do { status = g_io_channel_read_line_string (channel, string, NULL, &err); while (gtk_events_pending ()) gtk_main_iteration (); } while (status == G_IO_STATUS_AGAIN); strip_new_line (string->str); if (status != G_IO_STATUS_NORMAL) { if (err) { g_printerr ("yad_icons_handle_stdin(): %s\n", err->message); g_error_free (err); err = NULL; } /* stop handling */ g_io_channel_shutdown (channel, TRUE, NULL); return FALSE; } /* clear list if ^L received */ if (string->str[0] == '\014') { gtk_list_store_clear (GTK_LIST_STORE (model)); row_count = 0; column_count = 1; continue; } if (column_count == NUM_COLS) { /* We're starting a new row */ column_count = 1; row_count++; gtk_list_store_append (GTK_LIST_STORE (model), &iter); gtk_list_store_set (GTK_LIST_STORE (model), &iter, COL_FILENAME, "", -1); } switch (column_count) { case COL_NAME: case COL_COMMAND: gtk_list_store_set (GTK_LIST_STORE (model), &iter, column_count, string->str, -1); break; case COL_TOOLTIP: { gchar *buf = g_markup_escape_text (string->str, -1); gtk_list_store_set (GTK_LIST_STORE (model), &iter, column_count, buf, -1); g_free (buf); break; } case COL_PIXBUF: if (options.icons_data.compact) { if (*string->str) spb = get_pixbuf (string->str, YAD_SMALL_ICON, TRUE); } else { GdkPixbuf *pb = get_pixbuf (string->str, YAD_BIG_ICON, FALSE); if (pb) { spb = scale_pixbuf (pb); g_object_unref (pb); } } gtk_list_store_set (GTK_LIST_STORE (model), &iter, column_count, spb, -1); if (spb) g_object_unref (spb); break; case COL_TERM: gtk_list_store_set (GTK_LIST_STORE (model), &iter, column_count, get_bool_val (string->str), -1); break; } column_count++; } while (g_io_channel_get_buffer_condition (channel) == G_IO_IN); g_string_free (string, TRUE); } if ((condition != G_IO_IN) && (condition != G_IO_IN + G_IO_HUP)) { g_io_channel_shutdown (channel, TRUE, NULL); return FALSE; } return TRUE; } static DEntry * parse_desktop_file (gchar * filename) { DEntry *ent; GKeyFile *kf; GError *err = NULL; ent = g_new0 (DEntry, 1); kf = g_key_file_new (); if (g_key_file_load_from_file (kf, filename, 0, &err)) { gchar *icon; if (g_key_file_has_group (kf, "Desktop Entry")) { gint i, type; gchar *val; /* get type */ val = g_key_file_get_string (kf, "Desktop Entry", "Type", NULL); if (g_ascii_strcasecmp (val, "Link") == 0) type = TYPE_LINK; else type = TYPE_APP; g_free (val); /* get name */ if (options.icons_data.generic) ent->name = g_key_file_get_locale_string (kf, "Desktop Entry", "GenericName", NULL, NULL); if (!ent->name) ent->name = g_key_file_get_locale_string (kf, "Desktop Entry", "Name", NULL, NULL); /* use filename as a fallback */ if (!ent->name) { gchar *ext_pos, *nm = g_path_get_basename (filename); ext_pos = g_strrstr (nm, ".desktop"); if (ext_pos) *ext_pos = '\000'; ent->name = nm; } /* get tooltip */ val = g_key_file_get_locale_string (kf, "Desktop Entry", "Comment", NULL, NULL); if (val) { ent->comment = g_markup_escape_text (val, -1); g_free (val); } else ent->comment = g_strdup (ent->name); /* parse command or url */ if (type == TYPE_APP) { ent->command = g_key_file_get_string (kf, "Desktop Entry", "Exec", NULL); if (ent->command) { /* remove possible arguments patterns */ for (i = strlen (ent->command); i > 0; i--) { if (ent->command[i] == '%') { ent->command[i] = '\0'; break; } } ent->in_term = g_key_file_get_boolean (kf, "Desktop Entry", "Terminal", NULL); } } else { gchar *url = g_key_file_get_string (kf, "Desktop Entry", "URL", NULL); if (url) { #ifndef STANDALONE ent->command = g_strdup_printf (g_settings_get_string (settings, "open-command"), url); #else ent->command = g_strdup_printf (OPEN_CMD, url); #endif g_free (url); } } /* add icon */ icon = g_key_file_get_string (kf, "Desktop Entry", "Icon", NULL); if (icon) { if (options.icons_data.compact) ent->pixbuf = get_pixbuf (icon, YAD_SMALL_ICON, TRUE); else { GdkPixbuf *pb = get_pixbuf (icon, YAD_BIG_ICON, FALSE); ent->pixbuf = scale_pixbuf (pb); if (pb) g_object_unref (pb); } g_free (icon); } } } else g_printerr (_("Unable to parse file %s: %s\n"), filename, err->message); g_key_file_free (kf); return ent; } static void read_dir () { GDir *dir; const gchar *filename; GError *err = NULL; dir = g_dir_open (options.icons_data.directory, 0, &err); if (!dir) { g_printerr (_("Unable to open directory %s: %s\n"), options.icons_data.directory, err->message); return; } gtk_list_store_clear (store); while ((filename = g_dir_read_name (dir)) != NULL) { DEntry *ent; GtkTreeIter iter; gchar *fullname; if (!g_str_has_suffix (filename, ".desktop")) continue; fullname = g_build_filename (options.icons_data.directory, filename, NULL); ent = parse_desktop_file (fullname); g_free (fullname); if (ent->name) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COL_FILENAME, filename, COL_NAME, ent->name, COL_TOOLTIP, ent->comment ? ent->comment : "", COL_PIXBUF, ent->pixbuf, COL_COMMAND, ent->command ? ent->command : "", COL_TERM, ent->in_term, -1); } /* free desktop entry */ g_free (ent->name); g_free (ent->comment); g_free (ent->command); if (ent->pixbuf) g_object_unref (ent->pixbuf); g_free (ent); } g_dir_close (dir); } static void dir_changed_cb (GFileMonitor *mon, GFile *file, GFile *ofile, GFileMonitorEvent ev, gpointer data) { if (ev == G_FILE_MONITOR_EVENT_DELETED || ev == G_FILE_MONITOR_EVENT_CREATED) read_dir (); } GtkWidget * icons_create_widget (GtkWidget * dlg) { GtkWidget *w; w = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (w), GTK_SHADOW_ETCHED_IN); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (w), options.data.hscroll_policy, options.data.vscroll_policy); store = gtk_list_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_BOOLEAN); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), options.icons_data.sort_by_name ? COL_NAME : COL_FILENAME, options.icons_data.descend ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING); if (!options.icons_data.compact) { icon_view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (store)); gtk_widget_set_name (icon_view, "yad-icons-full"); gtk_icon_view_set_text_column (GTK_ICON_VIEW (icon_view), COL_NAME); gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (icon_view), COL_PIXBUF); gtk_icon_view_set_tooltip_column (GTK_ICON_VIEW (icon_view), COL_TOOLTIP); gtk_icon_view_set_item_width (GTK_ICON_VIEW (icon_view), options.icons_data.width); if (options.icons_data.single_click) g_signal_connect (G_OBJECT (icon_view), "selection-changed", G_CALLBACK (select_cb), NULL); } else { GtkCellRenderer *r; GtkTreeViewColumn *col; icon_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); gtk_widget_set_name (icon_view, "yad-icons-compact"); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (icon_view), FALSE); col = gtk_tree_view_column_new (); r = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (col, r, FALSE); gtk_tree_view_column_set_attributes (col, r, "pixbuf", COL_PIXBUF, NULL); r = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (col, r, FALSE); gtk_tree_view_column_set_attributes (col, r, "text", COL_NAME, NULL); gtk_tree_view_column_set_resizable (col, TRUE); gtk_tree_view_column_set_expand (col, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (icon_view), col); gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (icon_view), COL_TOOLTIP); if (options.icons_data.single_click) { GtkTreeSelection *sel; sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (icon_view)); g_signal_connect (G_OBJECT (sel), "changed", G_CALLBACK (select_cb), col); } } /* handle directory */ if (options.icons_data.directory) read_dir (); else if (options.common_data.listen) { /* read from stdin */ GIOChannel *channel; channel = g_io_channel_unix_new (0); if (channel) { g_io_channel_set_encoding (channel, NULL, NULL); g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL); g_io_add_watch (channel, G_IO_IN | G_IO_HUP, handle_stdin, NULL); } } if (!options.icons_data.compact) g_signal_connect (G_OBJECT (icon_view), "item-activated", G_CALLBACK (activate_cb), NULL); else g_signal_connect (G_OBJECT (icon_view), "row-activated", G_CALLBACK (activate_cb), NULL); /* start file monitor */ if (options.icons_data.monitor && options.icons_data.directory) { GFile *file = g_file_new_for_path (options.icons_data.directory); if (file) { GFileMonitor *mon = g_file_monitor_directory (file, 0, NULL, NULL); g_signal_connect (G_OBJECT (mon), "changed", G_CALLBACK (dir_changed_cb), NULL); g_object_unref (file); } } gtk_container_add (GTK_CONTAINER (w), icon_view); return w; }