From 6675b568d92d680fc1261d9a09eff2f824402061 Mon Sep 17 00:00:00 2001 From: Jon Nettleton Date: Thu, 7 May 2009 21:29:44 -0400 Subject: [PATCH 1/3] Update gnome-shell for renamed Mutter Adjust references to the mutter binary and plugin locations to account for the rework of Mutter to install as 'mutter' instead of 'metacity' http://bugzilla.gnome.org/show_bug.cgi?id=581814 --- configure.ac | 16 +++++++++------- src/Makefile.am | 20 ++++++++++---------- src/gnome-shell.in | 4 ++-- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/configure.ac b/configure.ac index 503369076..b01e6aa3f 100644 --- a/configure.ac +++ b/configure.ac @@ -40,7 +40,7 @@ fi AM_CONDITIONAL(BUILD_RECORDER, $build_recorder) -PKG_CHECK_MODULES(MUTTER_PLUGIN, gtk+-2.0 dbus-glib-1 metacity-plugins gjs-gi-1.0 xscrnsaver libgnome-menu $recorder_modules gconf-2.0 gdk-x11-2.0 clutter-x11-0.9 clutter-glx-0.9) +PKG_CHECK_MODULES(MUTTER_PLUGIN, gtk+-2.0 dbus-glib-1 mutter-plugins gjs-gi-1.0 xscrnsaver libgnome-menu $recorder_modules gconf-2.0 gdk-x11-2.0 clutter-x11-0.9 clutter-glx-0.9) PKG_CHECK_MODULES(TIDY, clutter-0.9) PKG_CHECK_MODULES(BIG, clutter-0.9 gtk+-2.0 librsvg-2.0) PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0) @@ -51,11 +51,13 @@ PKG_CHECK_MODULES(TASKPANEL, libwnck-1.0 dbus-glib-1) # it becomes stable. PKG_CHECK_MODULES(LIBGNOMEUI, libgnomeui-2.0) -META_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix metacity-plugins`/bin +MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin # FIXME: metacity-plugins.pc should point directly to its .gir file -META_LIB_DIR=`$PKG_CONFIG --variable=libdir metacity-plugins` -AC_SUBST(META_BIN_DIR) -AC_SUBST(META_LIB_DIR) +MUTTER_LIB_DIR=`$PKG_CONFIG --variable=libdir mutter-plugins` +MUTTER_PLUGIN_DIR=`$PKG_CONFIG --variable=plugindir mutter-plugins` +AC_SUBST(MUTTER_BIN_DIR) +AC_SUBST(MUTTER_LIB_DIR) +AC_SUBST(MUTTER_PLUGIN_DIR) GJS_JS_DIR=`$PKG_CONFIG --variable=jsdir gjs-1.0` GJS_JS_NATIVE_DIR=`$PKG_CONFIG --variable=jsnativedir gjs-1.0` @@ -92,8 +94,8 @@ if test "x$GCC" = "xyes"; then fi changequote([,])dnl -AC_PATH_PROG(metacity, [metacity]) -AC_SUBST(metacity) +AC_PATH_PROG(mutter, [mutter]) +AC_SUBST(mutter) AC_OUTPUT([ Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 9fd4d659c..e0aab00a7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,7 +8,7 @@ noinst_LTLIBRARIES = bin_SCRIPTS = gnome-shell gnome-shell: gnome-shell.in - sed -e "s|@META_BIN_DIR[@]|$(META_BIN_DIR)|" \ + sed -e "s|@MUTTER_BIN_DIR[@]|$(MUTTER_BIN_DIR)|" \ -e "s|@GJS_JS_DIR[@]|$(GJS_JS_DIR)|" \ -e "s|@GJS_JS_NATIVE_DIR[@]|$(GJS_JS_NATIVE_DIR)|" \ -e "s|@libexecdir[@]|$(libexecdir)|" \ @@ -33,7 +33,7 @@ gnome_shell_cflags = \ -DGNOME_SHELL_PKGLIBDIR=\"$(pkglibdir)\" \ -DJSDIR=\"$(pkgdatadir)/js\" -plugindir = $(libdir)/metacity/plugins/clutter +plugindir = $(MUTTER_PLUGIN_DIR) plugin_LTLIBRARIES = libgnome-shell.la shell_built_sources = \ @@ -139,16 +139,16 @@ libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags) typelibdir = $(pkglibdir) typelib_DATA = Shell-0.1.typelib Tidy-1.0.typelib Big-1.0.typelib -Shell-0.1.gir: $(metacity) $(G_IR_SCANNER) Big-1.0.gir libgnome-shell.la Makefile +Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) Big-1.0.gir libgnome-shell.la Makefile $(G_IR_SCANNER) \ --namespace=Shell \ --nsversion=0.1 \ - --add-include-path=$(META_LIB_DIR)/metacity/ \ + --add-include-path=$(MUTTER_LIB_DIR)/mutter/ \ --include=Clutter-0.9 \ --include=Meta-2.27 \ --add-include-path=$(builddir) \ --include=Big-1.0 \ - --program=metacity \ + --program=mutter \ --program-arg=--mutter-plugins=$$(pwd)/libgnome-shell.la \ $(addprefix $(srcdir)/,$(libgnome_shell_la_gir_sources)) \ $(libgnome_shell_la_CPPFLAGS) \ @@ -158,15 +158,15 @@ CLEANFILES += Shell-0.1.gir # The dependency on libgnome-shell.la here is because g-ir-compiler opens it # (not the fake library, since we've already done the rewriting) Shell-0.1.typelib: libgnome-shell.la Shell-0.1.gir Big-1.0.gir - LD_LIBRARY_PATH=$${LD_LIBRARY_PATH:+$$LD_LIBRARY_PATH:}. g-ir-compiler --includedir=$(builddir) --includedir=$(META_LIB_DIR)/metacity/ Shell-0.1.gir -o $@ + LD_LIBRARY_PATH=$${LD_LIBRARY_PATH:+$$LD_LIBRARY_PATH:}. g-ir-compiler --includedir=$(builddir) --includedir=$(MUTTER_LIB_DIR)/mutter/ Shell-0.1.gir -o $@ CLEANFILES += Shell-0.1.typelib -Tidy-1.0.gir: $(metacity) $(G_IR_SCANNER) libgnome-shell.la libtidy-1.0.la Makefile +Tidy-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libtidy-1.0.la Makefile $(G_IR_SCANNER) \ --namespace=Tidy \ --nsversion=1.0 \ --include=Clutter-0.9 \ - --program=metacity \ + --program=mutter \ --program-arg=--mutter-plugins=$$(pwd)/libgnome-shell.la \ $(addprefix $(srcdir)/,$(tidy_source_h)) \ $(addprefix $(srcdir)/,$(tidy_source_c)) \ @@ -179,13 +179,13 @@ Tidy-1.0.typelib: libtidy-1.0.la Tidy-1.0.gir LD_LIBRARY_PATH=$${LD_LIBRARY_PATH:+$$LD_LIBRARY_PATH:}. g-ir-compiler Tidy-1.0.gir -o $@ CLEANFILES += Tidy-1.0.typelib -Big-1.0.gir: $(metacity) $(G_IR_SCANNER) libgnome-shell.la libbig-1.0.la Makefile +Big-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libbig-1.0.la Makefile $(G_IR_SCANNER) \ --namespace=Big \ --nsversion=1.0 \ --include=Clutter-0.9 \ --include=GdkPixbuf-2.0 \ - --program=metacity \ + --program=mutter \ --program-arg=--mutter-plugins=$$(pwd)/libgnome-shell.la \ $(addprefix $(srcdir)/,$(big_source_h)) \ $(addprefix $(srcdir)/,$(big_source_c)) \ diff --git a/src/gnome-shell.in b/src/gnome-shell.in index 1cb2008ab..96c14ec09 100755 --- a/src/gnome-shell.in +++ b/src/gnome-shell.in @@ -134,7 +134,7 @@ def start_shell(): # Set up environment env = dict(os.environ) env.update({'GNOME_SHELL_JS' : '@GJS_JS_DIR@:@GJS_JS_NATIVE_DIR@:' + js_dir, - 'PATH' : '@META_BIN_DIR@:' + os.environ.get('PATH', '') + ':' + taskpanel_dir, + 'PATH' : '@MUTTER_BIN_DIR@:' + os.environ.get('PATH', '') + ':' + taskpanel_dir, 'GNOME_DISABLE_CRASH_DIALOG' : '1'}) if running_from_source_tree: @@ -187,7 +187,7 @@ def start_shell(): else: args = [] - args.extend(['metacity', '--mutter-plugins=' + plugin, '--replace']) + args.extend(['mutter', '--mutter-plugins=' + plugin, '--replace']) if options.sync: args.append('--sync') return subprocess.Popen(args, env=env) From 4314c6e57f3c00badca177610e100efcc577c89e Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 16 Jun 2009 11:55:14 -0400 Subject: [PATCH 2/3] Close memory leaks in ShellAppMonitor, ShellAppSystem, ShellTextureCache Unref'ing the GdkPixbuf is particularly critical. Some smaller fixes also included. --- src/shell-app-monitor.c | 4 +--- src/shell-app-system.c | 11 +++++++--- src/shell-global.c | 3 +-- src/shell-texture-cache.c | 46 ++++++++++++++++++++++----------------- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/shell-app-monitor.c b/src/shell-app-monitor.c index 95c7a264e..7a7cd58d7 100644 --- a/src/shell-app-monitor.c +++ b/src/shell-app-monitor.c @@ -812,13 +812,11 @@ restore_from_file (ShellAppMonitor *monitor) /* FIXME: do something if conversion fails! */ /* like: errno = NULL; ... if (errno) { g_free (line); goto out; } */ } - else if (error) + else { g_free (line); goto out; } - else /* End of file */ - goto out; } /* Line is about an app. * If no activity was provided yet, just skip */ diff --git a/src/shell-app-system.c b/src/shell-app-system.c index 0605b71ee..739d7dc7b 100644 --- a/src/shell-app-system.c +++ b/src/shell-app-system.c @@ -145,13 +145,13 @@ reread_directories (ShellAppSystem *self, GSList **cache, GMenuTree *tree) shell_entry->icon = g_strdup (gmenu_tree_directory_get_icon (dir)); *cache = g_slist_prepend (*cache, shell_entry); - - gmenu_tree_item_unref (dir); } break; default: break; } + + gmenu_tree_item_unref (item); } *cache = g_slist_reverse (*cache); @@ -260,13 +260,18 @@ shell_app_system_get_applications_for_menu (ShellAppSystem *monitor, { char *path; GMenuTreeDirectory *menu_entry; + GSList *apps; path = g_strdup_printf ("/%s", menu); menu_entry = gmenu_tree_get_directory_from_path (monitor->priv->apps_tree, path); g_free (path); g_assert (menu_entry != NULL); - return gather_entries_recurse (monitor, NULL, menu_entry); + apps = gather_entries_recurse (monitor, NULL, menu_entry); + + gmenu_tree_item_unref (menu_entry); + + return apps; } /** diff --git a/src/shell-global.c b/src/shell-global.c index f6e2af969..950ac8c33 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -421,7 +421,6 @@ shell_get_categories_for_desktop_file(const char *desktop_file_name) const char * const *search_dirs; char **categories = NULL; GSList *categories_list = NULL; - char *full_path = NULL; GError *error = NULL; gsize len; int i; @@ -429,7 +428,7 @@ shell_get_categories_for_desktop_file(const char *desktop_file_name) key_file = g_key_file_new (); search_dirs = get_applications_search_path(); - g_key_file_load_from_dirs (key_file, desktop_file_name, (const char **)search_dirs, &full_path, 0, &error); + g_key_file_load_from_dirs (key_file, desktop_file_name, (const char **)search_dirs, NULL, 0, &error); if (error != NULL) { diff --git a/src/shell-texture-cache.c b/src/shell-texture-cache.c index c5930877f..5af4cb0a0 100644 --- a/src/shell-texture-cache.c +++ b/src/shell-texture-cache.c @@ -188,6 +188,22 @@ typedef struct { int height; } Dimensions; +static void +icon_lookup_data_destroy (gpointer p) +{ + AsyncIconLookupData *data = p; + + if (data->icon) + { + g_object_unref (data->icon); + gtk_icon_info_free (data->icon_info); + } + else if (data->uri) + g_free (data->uri); + + g_free (data); +} + /** * on_image_size_prepared: * @@ -321,7 +337,7 @@ load_pixbuf_thread (GSimpleAsyncResult *result, AsyncIconLookupData *data; GError *error = NULL; - data = g_simple_async_result_get_op_res_gpointer (result); + data = g_object_get_data (result, "load_icon_pixbuf_async"); if (data->uri) pixbuf = impl_load_pixbuf_file (data->uri, data->width, data->height, &error); @@ -336,27 +352,10 @@ load_pixbuf_thread (GSimpleAsyncResult *result, return; } - g_simple_async_result_set_op_res_gpointer (result, g_object_ref (pixbuf), g_object_unref); } -static void -icon_lookup_data_destroy (gpointer p) -{ - AsyncIconLookupData *data = p; - - if (data->icon) - { - g_object_unref (data->icon); - gtk_icon_info_free (data->icon_info); - } - else if (data->uri) - g_free (data->uri); - - g_free (data); -} - /** * load_icon_pixbuf_async: * @@ -383,8 +382,10 @@ load_icon_pixbuf_async (ShellTextureCache *cache, result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_icon_pixbuf_async); - g_simple_async_result_set_op_res_gpointer (result, data, icon_lookup_data_destroy); + g_object_set_data_full (result, "load_icon_pixbuf_async", data, icon_lookup_data_destroy); g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable); + + g_object_unref (result); } static void @@ -407,8 +408,10 @@ load_uri_pixbuf_async (ShellTextureCache *cache, result = g_simple_async_result_new (G_OBJECT (cache), callback, user_data, load_uri_pixbuf_async); - g_simple_async_result_set_op_res_gpointer (result, data, icon_lookup_data_destroy); + g_object_set_data_full (result, "load_uri_pixbuf_async", data, icon_lookup_data_destroy); g_simple_async_result_run_in_thread (result, load_pixbuf_thread, G_PRIORITY_DEFAULT, cancellable); + + g_object_unref (result); } static GdkPixbuf * @@ -464,6 +467,8 @@ on_pixbuf_loaded (GObject *source, texdata = pixbuf_to_cogl_handle (pixbuf); + g_object_unref (pixbuf); + if (data->icon) { gpointer orig_key, value; @@ -619,6 +624,7 @@ shell_texture_cache_load_uri_sync (ShellTextureCache *cache, texture = CLUTTER_TEXTURE (clutter_texture_new ()); texdata = pixbuf_to_cogl_handle (pixbuf); + g_object_unref (pixbuf); clutter_texture_set_cogl_texture (texture, texdata); return CLUTTER_ACTOR (texture); From a3d35af444445b92b3fe2475ebef4e282b8c34d1 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Tue, 16 Jun 2009 12:20:12 -0400 Subject: [PATCH 3/3] Split appDisplay and docDisplay into "model" and "view" parts This lets us share the recent-app-tracking, recent-file-tracking, and icon-drawing code between the overlay and the sidebar, without the sidebar having to poke into AppDisplayItem and DocDisplayItem's guts. --- configure.ac | 1 + js/Makefile.am | 2 +- js/misc/Makefile.am | 5 + js/misc/appInfo.js | 133 +++++++++++++++++++++++++++ js/misc/docInfo.js | 112 +++++++++++++++++++++++ js/ui/appDisplay.js | 72 ++------------- js/ui/docDisplay.js | 114 ++++------------------- js/ui/widget.js | 218 +++++++++++++++++++++++++------------------- 8 files changed, 405 insertions(+), 252 deletions(-) create mode 100644 js/misc/Makefile.am create mode 100644 js/misc/appInfo.js create mode 100644 js/misc/docInfo.js diff --git a/configure.ac b/configure.ac index b01e6aa3f..1e448b633 100644 --- a/configure.ac +++ b/configure.ac @@ -101,6 +101,7 @@ AC_OUTPUT([ Makefile data/Makefile js/Makefile + js/misc/Makefile js/ui/Makefile src/Makefile ]) diff --git a/js/Makefile.am b/js/Makefile.am index aac7812e6..43d6f51cb 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -1 +1 @@ -SUBDIRS = ui +SUBDIRS = misc ui diff --git a/js/misc/Makefile.am b/js/misc/Makefile.am new file mode 100644 index 000000000..434dc3a37 --- /dev/null +++ b/js/misc/Makefile.am @@ -0,0 +1,5 @@ +jsmiscdir = $(pkgdatadir)/js/misc + +dist_jsmisc_DATA = \ + appInfo.js \ + docInfo.js diff --git a/js/misc/appInfo.js b/js/misc/appInfo.js new file mode 100644 index 000000000..c7bf9c7a4 --- /dev/null +++ b/js/misc/appInfo.js @@ -0,0 +1,133 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const Clutter = imports.gi.Clutter; +const Gio = imports.gi.Gio; +const Gtk = imports.gi.Gtk; +const Shell = imports.gi.Shell; + +const Main = imports.ui.main; + +// TODO - move this into GConf once we're not a plugin anymore +// but have taken over metacity +// This list is taken from GNOME Online popular applications +// http://online.gnome.org/applications +// but with nautilus removed (since it should already be running) +// and evince, totem, and gnome-file-roller removed (since they're +// usually started by opening documents, not by opening the app +// directly) +const DEFAULT_APPLICATIONS = [ + 'mozilla-firefox.desktop', + 'gnome-terminal.desktop', + 'evolution.desktop', + 'gedit.desktop', + 'mozilla-thunderbird.desktop', + 'rhythmbox.desktop', + 'epiphany.desktop', + 'xchat.desktop', + 'openoffice.org-1.9-writer.desktop', + 'emacs.desktop', + 'gnome-system-monitor.desktop', + 'openoffice.org-1.9-calc.desktop', + 'eclipse.desktop', + 'openoffice.org-1.9-impress.desktop', + 'vncviewer.desktop' +]; + +function AppInfo(appId) { + this._init(appId); +} + +AppInfo.prototype = { + _init : function(appId) { + this.appId = appId; + this._gAppInfo = Gio.DesktopAppInfo.new(appId); + if (!this._gAppInfo) + throw new Error('Unknown appId ' + appId); + + this.name = this._gAppInfo.get_name(); + this.description = this._gAppInfo.get_description(); + + this._gicon = this._gAppInfo.get_icon(); + }, + + getIcon : function(size) { + if (this._gicon) + return Shell.TextureCache.get_default().load_gicon(this._gicon, size); + else + return new Clutter.Texture({ width: size, height: size }); + }, + + getIconPath : function(size) { + if (this._gicon) { + let iconTheme = Gtk.IconTheme.get_default(); + let previewIconInfo = iconTheme.lookup_by_gicon(this._gicon, size, 0); + if (previewIconInfo) + return previewIconInfo.get_filename(); + } + return null; + }, + + launch : function() { + this._gAppInfo.launch([], Main.createAppLaunchContext()); + } +}; + +var _infos = {}; + +// getAppInfo: +// @appId: an appId +// +// Gets an #AppInfo for @appId. This is preferable to calling +// new AppInfo() directly, because it caches #AppInfos. +// +// Return value: the new or cached #AppInfo, or %null if @appId +// doesn't point to a valid .desktop file +function getAppInfo(appId) { + let info = _infos[appId]; + if (info === undefined) { + try { + info = _infos[appId] = new AppInfo(appId); + } catch (e) { + info = _infos[appId] = null; + } + } + return info; +} + +// getMostUsedApps: +// @count: maximum number of apps to retrieve +// +// Gets a list of #AppInfos for the @count most-frequently-used +// applications +// +// Return value: the list of #AppInfo +function getMostUsedApps(count) { + let appMonitor = new Shell.AppMonitor(); + + // Ask for more apps than we need, since the list of recently used + // apps might contain an app we don't have a desktop file for + let apps = appMonitor.get_most_used_apps (0, Math.round(count * 1.5)); + let matches = [], alreadyAdded = {}; + + for (let i = 0; i < apps.length && matches.length <= count; i++) { + let appId = apps[i] + ".desktop"; + let appInfo = getAppInfo(appId); + if (appInfo) { + matches.push(appInfo); + alreadyAdded[appId] = true; + } + } + + // Fill the list with default applications it's not full yet + for (let i = 0; i < DEFAULT_APPLICATIONS.length && matches.length <= count; i++) { + let appId = DEFAULT_APPLICATIONS[i]; + if (alreadyAdded[appId]) + continue; + + let appInfo = getAppInfo(appId); + if (appInfo) + matches.push(appInfo); + } + + return matches; +} diff --git a/js/misc/docInfo.js b/js/misc/docInfo.js new file mode 100644 index 000000000..dfae4a72b --- /dev/null +++ b/js/misc/docInfo.js @@ -0,0 +1,112 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const Clutter = imports.gi.Clutter; +const Gio = imports.gi.Gio; +const Gtk = imports.gi.Gtk; +const Shell = imports.gi.Shell; + +const Main = imports.ui.main; + +const THUMBNAIL_ICON_MARGIN = 2; + +function DocInfo(recentInfo) { + this._init(recentInfo); +} + +DocInfo.prototype = { + _init : function(recentInfo) { + this._recentInfo = recentInfo; + this.name = recentInfo.get_display_name(); + this.uri = recentInfo.get_uri(); + this.mimeType = recentInfo.get_mime_type(); + + this._iconPixbuf = Shell.get_thumbnail(this.uri, this.mimeType); + }, + + getIcon : function(size) { + let icon = new Clutter.Texture(); + + if (this._iconPixbuf) { + // We calculate the width and height of the texture so as + // to preserve the aspect ratio of the thumbnail. Because + // the images generated based on thumbnails don't have an + // internal padding like system icons do, we create a + // slightly smaller texture and then create a group around + // it for padding purposes + + let scalingFactor = (size - THUMBNAIL_ICON_MARGIN * 2) / Math.max(this._iconPixbuf.get_width(), this._iconPixbuf.get_height()); + icon.set_width(Math.ceil(this._iconPixbuf.get_width() * scalingFactor)); + icon.set_height(Math.ceil(this._iconPixbuf.get_height() * scalingFactor)); + Shell.clutter_texture_set_from_pixbuf(icon, this._iconPixbuf); + + let group = new Clutter.Group({ width: size, + height: size }); + group.add_actor(icon); + icon.set_position(THUMBNAIL_ICON_MARGIN, THUMBNAIL_ICON_MARGIN); + return group; + } else { + Shell.clutter_texture_set_from_pixbuf(icon, this._recentInfo.get_icon(size)); + return icon; + } + }, + + launch : function() { + // While using Gio.app_info_launch_default_for_uri() would be + // shorter in terms of lines of code, we are not doing so + // because that would duplicate the work of retrieving the + // mime type. + + let appInfo = Gio.app_info_get_default_for_type(this.mimeType, true); + + if (appInfo != null) { + appInfo.launch_uris([this.uri], Main.createAppLaunchContext()); + } else { + log("Failed to get default application info for mime type " + mimeType + + ". Will try to use the last application that registered the document."); + let appName = this._recentInfo.last_application(); + let [success, appExec, count, time] = this._recentInfo.get_application_info(appName); + if (success) { + log("Will open a document with the following command: " + appExec); + // TODO: Change this once better support for creating + // GAppInfo is added to GtkRecentInfo, as right now + // this relies on the fact that the file uri is + // already a part of appExec, so we don't supply any + // files to appInfo.launch(). + + // The 'command line' passed to + // create_from_command_line is allowed to contain + // '%' macros that are expanded to file + // name / icon name, etc, so we need to escape % as %% + appExec = appExec.replace(/%/g, "%%"); + + let appInfo = Gio.app_info_create_from_commandline(appExec, null, 0, null); + + // The point of passing an app launch context to + // launch() is mostly to get startup notification and + // associated benefits like the app appearing on the + // right desktop; but it doesn't really work for now + // because with the way we create the appInfo we + // aren't reading the application's desktop file, and + // thus don't find the StartupNotify=true in it. So, + // despite passing the app launch context, no startup + // notification occurs. + appInfo.launch([], Main.createAppLaunchContext()); + } else { + log("Failed to get application info for " + this.uri); + } + } + }, + + exists : function() { + return this._recentInfo.exists(); + }, + + lastVisited : function() { + // We actually used get_modified() instead of get_visited() + // here, as GtkRecentInfo doesn't updated get_visited() + // correctly. See + // http://bugzilla.gnome.org/show_bug.cgi?id=567094 + + return this._recentInfo.get_modified(); + } +}; diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index d0ba6bb99..ab53772fe 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -9,8 +9,8 @@ const Shell = imports.gi.Shell; const Lang = imports.lang; const Signals = imports.signals; +const AppInfo = imports.misc.appInfo; const GenericDisplay = imports.ui.genericDisplay; -const Main = imports.ui.main; const ENTERED_MENU_COLOR = new Clutter.Color(); ENTERED_MENU_COLOR.from_pixel(0x00ff0022); @@ -48,7 +48,7 @@ const MAX_ITEMS = 30; /* This class represents a single display item containing information about an application. * - * appInfo - GAppInfo object containing information about the application + * appInfo - AppInfo object containing information about the application * availableWidth - total width available for the item */ function AppDisplayItem(appInfo, availableWidth) { @@ -62,36 +62,15 @@ AppDisplayItem.prototype = { GenericDisplay.GenericDisplayItem.prototype._init.call(this, availableWidth); this._appInfo = appInfo; - let name = appInfo.get_name(); - - let description = appInfo.get_description(); - - let iconTheme = Gtk.IconTheme.get_default(); - - let gicon = appInfo.get_icon(); - let texCache = Shell.TextureCache.get_default(); - let icon; - if (gicon == null) - icon = new Clutter.Texture({ width: GenericDisplay.ITEM_DISPLAY_ICON_SIZE, - height: GenericDisplay.ITEM_DISPLAY_ICON_SIZE - }); - else - icon = texCache.load_gicon(gicon, GenericDisplay.ITEM_DISPLAY_ICON_SIZE); - this._setItemInfo(name, description, icon); - }, - - //// Public methods //// - - // Returns the application info associated with this display item. - getAppInfo : function () { - return this._appInfo; + this._setItemInfo(appInfo.name, appInfo.description, + appInfo.getIcon(GenericDisplay.ITEM_DISPLAY_ICON_SIZE)); }, //// Public method overrides //// // Opens an application represented by this display item. launch : function() { - this._appInfo.launch([], Main.createAppLaunchContext()); + this._appInfo.launch(); }, //// Protected method overrides //// @@ -101,15 +80,7 @@ AppDisplayItem.prototype = { if (!this._showPreview || this._previewIcon) return; - let previewIconPath = null; - - if (this._gicon != null) { - let iconTheme = Gtk.IconTheme.get_default(); - let previewIconInfo = iconTheme.lookup_by_gicon(this._gicon, GenericDisplay.PREVIEW_ICON_SIZE, Gtk.IconLookupFlags.NO_SVG); - if (previewIconInfo) - previewIconPath = previewIconInfo.get_filename(); - } - + let previewIconPath = this._appInfo.getIconPath(GenericDisplay.PREVIEW_ICON_SIZE); if (previewIconPath) { try { this._previewIcon = new Clutter.Texture({ width: GenericDisplay.PREVIEW_ICON_SIZE, height: GenericDisplay.PREVIEW_ICON_SIZE}); @@ -346,7 +317,7 @@ AppDisplay.prototype = { }, _addApp: function(appId) { - let appInfo = Gio.DesktopAppInfo.new(appId); + let appInfo = AppInfo.getAppInfo(appId); if (appInfo != null) { this._allItems[appId] = appInfo; // [] is returned if we could not get the categories or the list of categories was empty @@ -393,31 +364,8 @@ AppDisplay.prototype = { // Sets the list of the displayed items based on the most used apps. _setDefaultList : function() { - // Ask or more app than we need, since the list of recently used apps - // might contain an app we don't have a desktop file for - var apps = this._appMonitor.get_most_used_apps (0, Math.round(MAX_ITEMS * 1.5)); - this._matchedItems = []; - for (let i = 0; i < apps.length; i++) { - if (this._matchedItems.length > MAX_ITEMS) - break; - let appId = apps[i] + ".desktop"; - let appInfo = this._allItems[appId]; - if (appInfo) { - this._matchedItems.push(appId); - } - } - - // Fill the list with default applications it's not full yet - for (let i = 0; i < DEFAULT_APPLICATIONS.length; i++) { - if (this._matchedItems.length > MAX_ITEMS) - break; - let appId = DEFAULT_APPLICATIONS[i]; - let appInfo = this._allItems[appId]; - // Don't add if not available or already present in the list - if (appInfo && (this._matchedItems.indexOf(appId) == -1)) { - this._matchedItems.push(appId); - } - } + let matchedInfos = AppInfo.getMostUsedApps(MAX_ITEMS); + this._matchedItems = matchedInfos.map(function(info) { return info.appId; }); }, // Compares items associated with the item ids based on the alphabetical order @@ -486,7 +434,7 @@ AppDisplay.prototype = { return false; }, - // Creates an AppDisplayItem based on itemInfo, which is expected be a GAppInfo object. + // Creates an AppDisplayItem based on itemInfo, which is expected be an AppInfo object. _createDisplayItem: function(itemInfo) { return new AppDisplayItem(itemInfo, this._columnWidth); } diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js index 070fe62eb..2ea28208c 100644 --- a/js/ui/docDisplay.js +++ b/js/ui/docDisplay.js @@ -7,14 +7,13 @@ const Lang = imports.lang; const Shell = imports.gi.Shell; const Signals = imports.signals; +const DocInfo = imports.misc.docInfo; const GenericDisplay = imports.ui.genericDisplay; const Main = imports.ui.main; -const ITEM_DISPLAY_ICON_MARGIN = 2; - /* This class represents a single display item containing information about a document. * - * docInfo - GtkRecentInfo object containing information about the document + * docInfo - DocInfo object containing information about the document * availableWidth - total width available for the item */ function DocDisplayItem(docInfo, availableWidth) { @@ -28,107 +27,34 @@ DocDisplayItem.prototype = { GenericDisplay.GenericDisplayItem.prototype._init.call(this, availableWidth); this._docInfo = docInfo; - let name = docInfo.get_display_name(); - - // we can possibly display tags in the space for description in the future - let description = ""; - - let icon = new Clutter.Texture(); - this._iconPixbuf = Shell.get_thumbnail(docInfo.get_uri(), docInfo.get_mime_type()); - if (this._iconPixbuf) { - // We calculate the width and height of the texture so as to preserve the aspect ratio of the thumbnail. - // Because the images generated based on thumbnails don't have an internal padding like system icons do, - // we create a slightly smaller texture and then use extra margin when positioning it. - let scalingFactor = (GenericDisplay.ITEM_DISPLAY_ICON_SIZE - ITEM_DISPLAY_ICON_MARGIN * 2) / Math.max(this._iconPixbuf.get_width(), this._iconPixbuf.get_height()); - icon.set_width(Math.ceil(this._iconPixbuf.get_width() * scalingFactor)); - icon.set_height(Math.ceil(this._iconPixbuf.get_height() * scalingFactor)); - Shell.clutter_texture_set_from_pixbuf(icon, this._iconPixbuf); - icon.x = GenericDisplay.ITEM_DISPLAY_PADDING + ITEM_DISPLAY_ICON_MARGIN; - icon.y = GenericDisplay.ITEM_DISPLAY_PADDING + ITEM_DISPLAY_ICON_MARGIN; - } else { - Shell.clutter_texture_set_from_pixbuf(icon, docInfo.get_icon(GenericDisplay.ITEM_DISPLAY_ICON_SIZE)); - icon.x = GenericDisplay.ITEM_DISPLAY_PADDING; - icon.y = GenericDisplay.ITEM_DISPLAY_PADDING; - } - - this._setItemInfo(name, description, icon); + this._setItemInfo(docInfo.name, "", + docInfo.getIcon(GenericDisplay.ITEM_DISPLAY_ICON_SIZE)); }, //// Public methods //// - // Returns the document info associated with this display item. - getDocInfo : function() { - return this._docInfo; - }, - //// Public method overrides //// // Opens a document represented by this display item. launch : function() { - // While using Gio.app_info_launch_default_for_uri() would be shorter - // in terms of lines of code, we are not doing so because that would - // duplicate the work of retrieving the mime type. - let mimeType = this._docInfo.get_mime_type(); - let appInfo = Gio.app_info_get_default_for_type(mimeType, true); - - if (appInfo != null) { - appInfo.launch_uris([this._docInfo.get_uri()], Main.createAppLaunchContext()); - } else { - log("Failed to get default application info for mime type " + mimeType + - ". Will try to use the last application that registered the document."); - let appName = this._docInfo.last_application(); - let [success, appExec, count, time] = this._docInfo.get_application_info(appName); - if (success) { - log("Will open a document with the following command: " + appExec); - // TODO: Change this once better support for creating GAppInfo is added to - // GtkRecentInfo, as right now this relies on the fact that the file uri is - // already a part of appExec, so we don't supply any files to appInfo.launch(). - - // The 'command line' passed to create_from_command_line is allowed to contain - // '%' macros that are expanded to file name / icon name, etc, - // so we need to escape % as %% - appExec = appExec.replace(/%/g, "%%"); - - let appInfo = Gio.app_info_create_from_commandline(appExec, null, 0, null); - - // The point of passing an app launch context to launch() is mostly to get - // startup notification and associated benefits like the app appearing - // on the right desktop; but it doesn't really work for now because with - // the way we create the appInfo we aren't reading the application's desktop - // file, and thus don't find the StartupNotify=true in it. So, despite passing - // the app launch context, no startup notification occurs. - appInfo.launch([], Main.createAppLaunchContext()); - } else { - log("Failed to get application info for " + this._docInfo.get_uri()); - } - } + this._docInfo.launch(); }, //// Protected method overrides //// // Ensures the preview icon is created. _ensurePreviewIconCreated : function() { - if (this._previewIcon) - return; - - this._previewIcon = new Clutter.Texture(); - if (this._iconPixbuf) { - let scalingFactor = (GenericDisplay.PREVIEW_ICON_SIZE / Math.max(this._iconPixbuf.get_width(), this._iconPixbuf.get_height())); - this._previewIcon.set_width(Math.ceil(this._iconPixbuf.get_width() * scalingFactor)); - this._previewIcon.set_height(Math.ceil(this._iconPixbuf.get_height() * scalingFactor)); - Shell.clutter_texture_set_from_pixbuf(this._previewIcon, this._iconPixbuf); - } else { - Shell.clutter_texture_set_from_pixbuf(this._previewIcon, this._docInfo.get_icon(GenericDisplay.PREVIEW_ICON_SIZE)); - } + if (!this._previewIcon) + this._previewIcon = this._docInfo.getIcon(GenericDisplay.PREVIEW_ICON_SIZE); }, // Creates and returns a large preview icon, but only if this._docInfo is an image file // and we were able to generate a pixbuf from it successfully. _createLargePreviewIcon : function(availableWidth, availableHeight) { - if (this._docInfo.get_mime_type() == null || this._docInfo.get_mime_type().indexOf("image/") != 0) + if (this._docInfo.mimeType == null || this._docInfo.mimeType.indexOf("image/") != 0) return null; - return Shell.TextureCache.get_default().load_uri_sync(this._docInfo.get_uri(), availableWidth, availableHeight); + return Shell.TextureCache.get_default().load_uri_sync(this._docInfo.uri, availableWidth, availableHeight); } }; @@ -170,10 +96,11 @@ DocDisplay.prototype = { this._allItems = {}; let docs = this._recentManager.get_items(); for (let i = 0; i < docs.length; i++) { - let docInfo = docs[i]; - let docId = docInfo.get_uri(); + let recentInfo = docs[i]; + let docInfo = new DocInfo.DocInfo(recentInfo); + // we use GtkRecentInfo URI as an item Id - this._allItems[docId] = docInfo; + this._allItems[docInfo.uri] = docInfo; } this._docsStale = false; }, @@ -217,15 +144,8 @@ DocDisplay.prototype = { _compareItems : function(itemIdA, itemIdB) { let docA = this._allItems[itemIdA]; let docB = this._allItems[itemIdB]; - // We actually used get_modified() instead of get_visited() here, as GtkRecentInfo - // doesn't updated get_visited() correctly. - // See http://bugzilla.gnome.org/show_bug.cgi?id=567094 - if (docA.get_modified() > docB.get_modified()) - return -1; - else if (docA.get_modified() < docB.get_modified()) - return 1; - else - return 0; + + return docB.lastVisited() - docA.lastVisited(); }, // Checks if the item info can be a match for the search string by checking @@ -238,7 +158,7 @@ DocDisplay.prototype = { if (search == null || search == '') return true; - let name = itemInfo.get_display_name().toLowerCase(); + let name = itemInfo.name.toLowerCase(); if (name.indexOf(search) >= 0) return true; // TODO: we can also check doc URIs, so that @@ -246,7 +166,7 @@ DocDisplay.prototype = { return false; }, - // Creates a DocDisplayItem based on itemInfo, which is expected be a GtkRecentInfo object. + // Creates a DocDisplayItem based on itemInfo, which is expected to be a DocInfo object. _createDisplayItem: function(itemInfo) { return new DocDisplayItem(itemInfo, this._columnWidth); } diff --git a/js/ui/widget.js b/js/ui/widget.js index 2e38bf02f..a49db3117 100644 --- a/js/ui/widget.js +++ b/js/ui/widget.js @@ -6,11 +6,13 @@ const Gio = imports.gi.Gio; const Gtk = imports.gi.Gtk; const Mainloop = imports.mainloop; const Lang = imports.lang; +const Pango = imports.gi.Pango; const Shell = imports.gi.Shell; const Signals = imports.signals; -const AppDisplay = imports.ui.appDisplay; +const AppInfo = imports.misc.appInfo; const DocDisplay = imports.ui.docDisplay; +const DocInfo = imports.misc.docInfo; const COLLAPSED_WIDTH = 24; const EXPANDED_WIDTH = 200; @@ -156,17 +158,118 @@ ClockWidget.prototype = { }; +const ITEM_ICON_SIZE = 48; +const ITEM_PADDING = 1; +const ITEM_SPACING = 4; + const ITEM_BG_COLOR = new Clutter.Color(); ITEM_BG_COLOR.from_pixel(0x00000000); const ITEM_NAME_COLOR = new Clutter.Color(); ITEM_NAME_COLOR.from_pixel(0x000000ff); -const ITEM_DESCRIPTION_COLOR = new Clutter.Color(); -ITEM_DESCRIPTION_COLOR.from_pixel(0x404040ff); -function hackUpDisplayItemColors(item) { - item._bg.background_color = ITEM_BG_COLOR; - item._name.color = ITEM_NAME_COLOR; - item._description.color = ITEM_DESCRIPTION_COLOR; +function LauncherWidget() { + this._init(); +} + +LauncherWidget.prototype = { + __proto__ : Widget.prototype, + + addItem : function(info) { + let item = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL, + width: EXPANDED_WIDTH, + height: ITEM_ICON_SIZE, + padding: ITEM_PADDING, + spacing: ITEM_SPACING, + reactive: true }); + item._info = info; + item.append(info.getIcon(ITEM_ICON_SIZE), Big.BoxPackFlags.NONE); + item.append(new Clutter.Text({ color: ITEM_NAME_COLOR, + font_name: "Sans 14px", + ellipsize: Pango.EllipsizeMode.END, + text: info.name }), + Big.BoxPackFlags.NONE); + + this.actor.append(item, Big.BoxPackFlags.NONE); + item.connect('button-press-event', Lang.bind(this, this._buttonPress)); + item.connect('button-release-event', Lang.bind(this, this._buttonRelease)); + item.connect('leave-event', Lang.bind(this, this._leave)); + item.connect('enter-event', Lang.bind(this, this._enter)); + + if (!this.collapsedActor) + return; + + item = new Big.Box({ width: COLLAPSED_WIDTH, + height: COLLAPSED_WIDTH, + padding: ITEM_PADDING, + reactive: true }); + item._info = info; + item.append(info.getIcon(COLLAPSED_WIDTH - 2 * ITEM_PADDING), + Big.BoxPackFlags.NONE); + + this.collapsedActor.append(item, Big.BoxPackFlags.NONE); + item.connect('button-press-event', Lang.bind(this, this._buttonPress)); + item.connect('button-release-event', Lang.bind(this, this._buttonRelease)); + item.connect('leave-event', Lang.bind(this, this._leave)); + item.connect('enter-event', Lang.bind(this, this._enter)); + }, + + clear : function() { + let children, i; + + children = this.actor.get_children(); + for (i = 0; i < children.length; i++) + children[i].destroy(); + + if (this.collapsedActor) { + children = this.collapsedActor.get_children(); + for (i = 0; i < children.length; i++) + children[i].destroy(); + } + }, + + _buttonPress : function(item) { + Clutter.grab_pointer(item); + item._buttonDown = true; + item._inItem = true; + this._updateItemState(item); + return true; + }, + + _leave : function(item, evt) { + if (evt.get_source() == item && item._buttonDown) { + item._inItem = false; + this._updateItemState(item); + } + return false; + }, + + _enter : function(item, evt) { + if (evt.get_source() == item && item._buttonDown) { + item._inItem = true; + this._updateItemState(item); + } + return false; + }, + + _buttonRelease : function(item) { + Clutter.ungrab_pointer(item); + item._buttonDown = false; + this._updateItemState(item); + + if (item._inItem) { + item._info.launch(); + this.activated(); + } + return true; + }, + + _updateItemState : function(item) { + if (item._buttonDown && item._inItem) { + item.padding_top = item.padding_left = 2 * ITEM_PADDING; + item.padding_bottom = item.padding_right = 0; + } else + item.padding = ITEM_PADDING; + } }; function AppsWidget() { @@ -174,44 +277,16 @@ function AppsWidget() { } AppsWidget.prototype = { - __proto__ : Widget.prototype, + __proto__ : LauncherWidget.prototype, _init : function() { this.title = "Applications"; this.actor = new Big.Box({ spacing: 2 }); this.collapsedActor = new Big.Box({ spacing: 2}); - let added = 0; - for (let i = 0; i < AppDisplay.DEFAULT_APPLICATIONS.length && added < 5; i++) { - let id = AppDisplay.DEFAULT_APPLICATIONS[i]; - let appInfo = Gio.DesktopAppInfo.new(id); - if (!appInfo) - continue; - - let box = new Big.Box({ padding: 2, - corner_radius: 2 }); - let appDisplayItem = new AppDisplay.AppDisplayItem( - appInfo, EXPANDED_WIDTH); - hackUpDisplayItemColors(appDisplayItem); - box.append(appDisplayItem.actor, Big.BoxPackFlags.NONE); - this.actor.append(box, Big.BoxPackFlags.NONE); - appDisplayItem.connect('select', Lang.bind(this, this._itemActivated)); - - // Cheaty cheat cheat - let icon = new Clutter.Clone({ source: appDisplayItem._icon, - width: COLLAPSED_WIDTH, - height: COLLAPSED_WIDTH, - reactive: true }); - this.collapsedActor.append(icon, Big.BoxPackFlags.NONE); - icon.connect('button-release-event', Lang.bind(this, function() { this._itemActivated(appDisplayItem); })); - - added++; - } - }, - - _itemActivated: function(item) { - item.launch(); - this.activated(); + let apps = AppInfo.getMostUsedApps(5); + for (let i = 0; i < apps.length; i++) + this.addItem(apps[i]); } }; @@ -220,7 +295,7 @@ function DocsWidget() { } DocsWidget.prototype = { - __proto__ : Widget.prototype, + __proto__ : LauncherWidget.prototype, _init : function() { this.title = "Recent Docs"; @@ -232,62 +307,21 @@ DocsWidget.prototype = { }, _recentChanged: function() { - let i, docId; + let i; - this._allItems = {}; + this.clear(); + + let items = []; let docs = this._recentManager.get_items(); for (i = 0; i < docs.length; i++) { - let docInfo = docs[i]; - let docId = docInfo.get_uri(); - // we use GtkRecentInfo URI as an item Id - this._allItems[docId] = docInfo; + let docInfo = new DocInfo.DocInfo (docs[i]); + + if (docInfo.exists()) + items.push(docInfo); } - this._matchedItems = []; - let docIdsToRemove = []; - for (docId in this._allItems) { - // this._allItems[docId].exists() checks if the resource still exists - if (this._allItems[docId].exists()) - this._matchedItems.push(docId); - else - docIdsToRemove.push(docId); - } - - for (docId in docIdsToRemove) { - delete this._allItems[docId]; - } - - this._matchedItems.sort(Lang.bind(this, function (a,b) { return this._compareItems(a,b); })); - - let children = this.actor.get_children(); - for (let c = 0; c < children.length; c++) - this.actor.remove_actor(children[c]); - - for (i = 0; i < Math.min(this._matchedItems.length, 5); i++) { - let box = new Big.Box({ padding: 2, - corner_radius: 2 }); - let docDisplayItem = new DocDisplay.DocDisplayItem( - this._allItems[this._matchedItems[i]], EXPANDED_WIDTH); - hackUpDisplayItemColors(docDisplayItem); - box.append(docDisplayItem.actor, Big.BoxPackFlags.NONE); - this.actor.append(box, Big.BoxPackFlags.NONE); - docDisplayItem.connect('select', Lang.bind(this, this._itemActivated)); - } - }, - - _compareItems : function(itemIdA, itemIdB) { - let docA = this._allItems[itemIdA]; - let docB = this._allItems[itemIdB]; - if (docA.get_modified() > docB.get_modified()) - return -1; - else if (docA.get_modified() < docB.get_modified()) - return 1; - else - return 0; - }, - - _itemActivated: function(item) { - item.launch(); - this.activated(); + items.sort(function (a,b) { return b.lastVisited() - a.lastVisited(); }); + for (i = 0; i < Math.min(items.length, 5); i++) + this.addItem(items[i]); } };