static void on_transient_window_title_changed (MetaWindow *window, GParamSpec *spec, ShellWindowTracker *self) { ShellAppSystem *appsys; ShellApp *app; const char *id; /* Check if we now have a mapping using the window title */ id = get_app_id_from_title (window); if (id == NULL) return; appsys = shell_app_system_get_default (); app = shell_app_system_get_app (appsys, id); if (app == NULL) return; g_object_unref (app); /* We found an app, don't listen for further title changes */ g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_transient_window_title_changed), self); /* It's simplest to just treat this as a remove + add. */ disassociate_window (self, window); track_window (self, window); }
/** * get_app_from_window_wmclass: * * Looks only at the given window, and attempts to determine * an application based on WM_CLASS. If one can't be determined, * return %NULL. * * Return value: (transfer full): A newly-referenced #ShellApp, or %NULL */ static ShellApp * get_app_from_window_wmclass (MetaWindow *window) { ShellApp *app; ShellAppSystem *appsys; char *wmclass; char *with_desktop; appsys = shell_app_system_get_default (); wmclass = get_appid_from_window (window); if (!wmclass) return NULL; with_desktop = g_strjoin (NULL, wmclass, ".desktop", NULL); g_free (wmclass); app = shell_app_system_lookup_heuristic_basename (appsys, with_desktop); g_free (with_desktop); if (app == NULL) { const char *id = get_app_id_from_title (window); if (id != NULL) app = shell_app_system_get_app (appsys, id); } return app; }
/** * shell_window_tracker_get_app_from_pid: * @tracker: A #ShellAppSystem * @pid: A Unix process identifier * * Look up the application corresponding to a process. * * Returns: (transfer none): A #ShellApp, or %NULL if none */ ShellApp * shell_window_tracker_get_app_from_pid (ShellWindowTracker *tracker, int pid) { GSList *running = shell_app_system_get_running (shell_app_system_get_default()); GSList *iter; ShellApp *result = NULL; for (iter = running; iter; iter = iter->next) { ShellApp *app = iter->data; GSList *pids = shell_app_get_pids (app); GSList *pids_iter; for (pids_iter = pids; pids_iter; pids_iter = pids_iter->next) { int app_pid = GPOINTER_TO_INT (pids_iter->data); if (app_pid == pid) { result = app; break; } } g_slist_free (pids); if (result != NULL) break; } g_slist_free (running); return result; }
/** * get_app_for_window: * * Determines the application associated with a window, using * all available information such as the window's MetaGroup, * and what we know about other windows. */ static ShellApp * get_app_for_window (ShellWindowTracker *monitor, MetaWindow *window) { ShellApp *result; const char *startup_id; result = NULL; /* First, we check whether we already know about this window, * if so, just return that. */ if (meta_window_get_window_type (window) == META_WINDOW_NORMAL) { result = g_hash_table_lookup (monitor->window_to_app, window); if (result != NULL) { g_object_ref (result); return result; } } /* Check if the app's WM_CLASS specifies an app */ result = get_app_from_window_wmclass (window); if (result != NULL) return result; /* Now we check whether we have a match through startup-notification */ startup_id = meta_window_get_startup_id (window); if (startup_id) { GSList *iter, *sequences; sequences = shell_window_tracker_get_startup_sequences (monitor); for (iter = sequences; iter; iter = iter->next) { ShellStartupSequence *sequence = iter->data; const char *id = shell_startup_sequence_get_id (sequence); if (strcmp (id, startup_id) != 0) continue; result = shell_startup_sequence_get_app (sequence); if (result) break; } } /* If we didn't get a startup-notification match, see if we matched * any other windows in the group. */ if (result == NULL) result = get_app_from_window_group (monitor, window); /* Our last resort - we create a fake app from the window */ if (result == NULL) result = shell_app_system_get_app_for_window (shell_app_system_get_default (), window); return result; }
static void shell_app_usage_init (ShellAppUsage *self) { ShellGlobal *global; char *shell_userdata_dir, *path; GDBusConnection *session_bus; ShellWindowTracker *tracker; ShellAppSystem *app_system; global = shell_global_get (); self->app_usages_for_context = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_destroy); tracker = shell_window_tracker_get_default (); g_signal_connect (tracker, "notify::focus-app", G_CALLBACK (on_focus_app_changed), self); app_system = shell_app_system_get_default (); g_signal_connect (app_system, "app-state-changed", G_CALLBACK (on_app_state_changed), self); session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); self->session_proxy = g_dbus_proxy_new_sync (session_bus, G_DBUS_PROXY_FLAGS_NONE, NULL, /* interface info */ "org.gnome.SessionManager", "/org/gnome/SessionManager/Presence", "org.gnome.SessionManager", NULL, /* cancellable */ NULL /* error */); g_signal_connect (self->session_proxy, "g-signal", G_CALLBACK (session_proxy_signal), self); g_object_unref (session_bus); self->last_idle = 0; self->currently_idle = FALSE; self->enable_monitoring = FALSE; g_object_get (shell_global_get(), "userdatadir", &shell_userdata_dir, NULL), path = g_build_filename (shell_userdata_dir, DATA_FILENAME, NULL); g_free (shell_userdata_dir); self->configfile = g_file_new_for_path (path); g_free (path); restore_from_file (self); self->privacy_settings = g_settings_new(PRIVACY_SCHEMA); g_signal_connect (self->privacy_settings, "changed::" ENABLE_MONITORING_KEY, G_CALLBACK (on_enable_monitoring_key_changed), self); update_enable_monitoring (self); }
/** * shell_startup_sequence_get_app: * @sequence: A #ShellStartupSequence * * Returns: (transfer none): The application being launched, or %NULL if unknown. */ ShellApp * shell_startup_sequence_get_app (ShellStartupSequence *sequence) { const char *appid; ShellAppSystem *appsys; ShellApp *app; appid = sn_startup_sequence_get_application_id ((SnStartupSequence*)sequence); if (!appid) return NULL; appsys = shell_app_system_get_default (); app = shell_app_system_lookup_app_for_path (appsys, appid); return app; }
/** * shell_startup_sequence_get_app: * @sequence: A #ShellStartupSequence * * Returns: (transfer none): The application being launched, or %NULL if unknown. */ ShellApp * shell_startup_sequence_get_app (ShellStartupSequence *sequence) { const char *appid; char *basename; ShellAppSystem *appsys; ShellApp *app; appid = sn_startup_sequence_get_application_id ((SnStartupSequence*)sequence); if (!appid) return NULL; basename = g_path_get_basename (appid); appsys = shell_app_system_get_default (); app = shell_app_system_lookup_app (appsys, basename); g_free (basename); return app; }
/** * shell_startup_sequence_get_app: * @sequence: A #ShellStartupSequence * * Returns: (transfer full): The application being launched, or %NULL if unknown. */ ShellApp * shell_startup_sequence_get_app (ShellStartupSequence *sequence) { #ifdef HAVE_SN_STARTUP_SEQUENCE_GET_APPLICATION_ID const char *appid; ShellAppSystem *appsys; ShellApp *app; appid = sn_startup_sequence_get_application_id ((SnStartupSequence*)sequence); if (!appid) return NULL; appsys = shell_app_system_get_default (); app = shell_app_system_get_app_for_path (appsys, appid); return app; #else return NULL; #endif }
static ShellApp * get_app_from_gapplication_id (MetaWindow *window) { ShellApp *app; ShellAppSystem *appsys; const char *id; char *desktop_file; appsys = shell_app_system_get_default (); id = meta_window_get_gtk_application_id (window); if (!id) return FALSE; desktop_file = g_strconcat (id, ".desktop", NULL); app = shell_app_system_lookup_app (appsys, desktop_file); g_free (desktop_file); return app; }
/** * shell_app_usage_get_most_used: * @usage: the usage instance to request * @context: Activity identifier * * Get a list of most popular applications for a given context. * * Returns: (element-type ShellApp) (transfer full): List of applications */ GSList * shell_app_usage_get_most_used (ShellAppUsage *self, const char *context) { GSList *apps; GList *appids, *iter; GHashTable *usages; ShellAppSystem *appsys; SortAppsByUsageData data; usages = g_hash_table_lookup (self->app_usages_for_context, context); if (usages == NULL) return NULL; appsys = shell_app_system_get_default (); appids = g_hash_table_get_keys (usages); apps = NULL; for (iter = appids; iter; iter = iter->next) { const char *appid = iter->data; ShellApp *app; app = shell_app_system_lookup_app (appsys, appid); if (!app) continue; apps = g_slist_prepend (apps, g_object_ref (app)); } g_list_free (appids); data.usage = self; data.context_usages = usages; apps = g_slist_sort_with_data (apps, sort_apps_by_usage, &data); return apps; }
/* Save app data lists to file */ static gboolean idle_save_application_usage (gpointer data) { ShellAppUsage *self = SHELL_APP_USAGE (data); UsageIterator iter; const char *current_context; const char *context; const char *id; UsageData *usage; GFileOutputStream *output; GOutputStream *buffered_output; GDataOutputStream *data_output; GError *error = NULL; self->save_id = 0; /* Parent directory is already created by shell-global */ output = g_file_replace (self->configfile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error); if (!output) { g_debug ("Could not save applications usage data: %s", error->message); g_error_free (error); return FALSE; } buffered_output = g_buffered_output_stream_new (G_OUTPUT_STREAM (output)); g_object_unref (output); data_output = g_data_output_stream_new (G_OUTPUT_STREAM (buffered_output)); g_object_unref (buffered_output); if (!g_data_output_stream_put_string (data_output, "<?xml version=\"1.0\"?>\n<application-state>\n", NULL, &error)) goto out; usage_iterator_init (self, &iter); current_context = NULL; while (usage_iterator_next (self, &iter, &context, &id, &usage)) { ShellApp *app; app = shell_app_system_lookup_app (shell_app_system_get_default(), id); if (!app) continue; if (context != current_context) { if (current_context != NULL) { if (!g_data_output_stream_put_string (data_output, " </context>", NULL, &error)) goto out; } current_context = context; if (!g_data_output_stream_put_string (data_output, " <context", NULL, &error)) goto out; if (!write_attribute_string (data_output, "id", context, &error)) goto out; if (!g_data_output_stream_put_string (data_output, ">\n", NULL, &error)) goto out; } if (!g_data_output_stream_put_string (data_output, " <application", NULL, &error)) goto out; if (!write_attribute_string (data_output, "id", id, &error)) goto out; if (!write_attribute_uint (data_output, "open-window-count", shell_app_get_n_windows (app), &error)) goto out; if (!write_attribute_double (data_output, "score", usage->score, &error)) goto out; if (!write_attribute_uint (data_output, "last-seen", usage->last_seen, &error)) goto out; if (!g_data_output_stream_put_string (data_output, "/>\n", NULL, &error)) goto out; } if (current_context != NULL) { if (!g_data_output_stream_put_string (data_output, " </context>\n", NULL, &error)) goto out; } if (!g_data_output_stream_put_string (data_output, "</application-state>\n", NULL, &error)) goto out; out: if (!error) g_output_stream_close_async (G_OUTPUT_STREAM (data_output), 0, NULL, NULL, NULL); g_object_unref (data_output); if (error) { g_debug ("Could not save applications usage data: %s", error->message); g_error_free (error); } return FALSE; }
/** * shell_app_info_launch_full: * @timestamp: Event timestamp, or 0 for current event timestamp * @uris: List of uris to pass to application * @workspace: Start on this workspace, or -1 for default * @startup_id: (out): Returned startup notification ID, or %NULL if none * @error: A #GError */ gboolean shell_app_info_launch_full (ShellAppInfo *info, guint timestamp, GList *uris, int workspace, char **startup_id, GError **error) { ShellApp *shell_app; GDesktopAppInfo *gapp; GdkAppLaunchContext *context; gboolean ret; ShellGlobal *global; MetaScreen *screen; if (startup_id) *startup_id = NULL; if (info->type == SHELL_APP_INFO_TYPE_WINDOW) { /* We can't pass URIs into a window; shouldn't hit this * code path. If we do, fix the caller to disallow it. */ g_return_val_if_fail (uris == NULL, TRUE); meta_window_activate (info->window, timestamp); return TRUE; } else if (info->type == SHELL_APP_INFO_TYPE_ENTRY) { /* Can't use g_desktop_app_info_new, see bug 614879 */ const char *filename = gmenu_tree_entry_get_desktop_file_path ((GMenuTreeEntry *)info->entry); gapp = g_desktop_app_info_new_from_filename (filename); } else { char *filename = shell_app_info_get_desktop_file_path (info); gapp = g_desktop_app_info_new_from_filename (filename); g_free (filename); } if (!gapp) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Not found"); return FALSE; } global = shell_global_get (); screen = shell_global_get_screen (global); if (timestamp == 0) timestamp = clutter_get_current_event_time (); if (workspace < 0) workspace = meta_screen_get_active_workspace_index (screen); context = gdk_app_launch_context_new (); gdk_app_launch_context_set_timestamp (context, timestamp); gdk_app_launch_context_set_desktop (context, workspace); shell_app = shell_app_system_get_app (shell_app_system_get_default (), shell_app_info_get_id (info)); /* In the case where we know an app, we handle reaping the child internally, * in the window tracker. */ if (shell_app != NULL) ret = g_desktop_app_info_launch_uris_as_manager (gapp, uris, G_APP_LAUNCH_CONTEXT (context), G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, _gather_pid_callback, shell_app, error); else ret = g_desktop_app_info_launch_uris_as_manager (gapp, uris, G_APP_LAUNCH_CONTEXT (context), G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, error); g_object_unref (G_OBJECT (gapp)); return ret; }
/* * get_app_from_window_wmclass: * * Looks only at the given window, and attempts to determine * an application based on WM_CLASS. If one can't be determined, * return %NULL. * * Return value: (transfer full): A newly-referenced #ShellApp, or %NULL */ static ShellApp * get_app_from_window_wmclass (MetaWindow *window) { ShellApp *app; ShellAppSystem *appsys; const char *wm_class; const char *wm_instance; appsys = shell_app_system_get_default (); /* Notes on the heuristics used here: much of the complexity here comes from the desire to support Chrome apps. From https://bugzilla.gnome.org/show_bug.cgi?id=673657#c13 Currently chrome sets WM_CLASS as follows (the first string is the 'instance', the second one is the 'class': For the normal browser: WM_CLASS(STRING) = "chromium", "Chromium" For a bookmarked page (through 'Tools -> Create application shortcuts') WM_CLASS(STRING) = "wiki.gnome.org__GnomeShell_ApplicationBased", "Chromium" For an application from the chrome store (with a .desktop file created through right click, "Create shortcuts" from Chrome's apps overview) WM_CLASS(STRING) = "crx_blpcfgokakmgnkcojhhkbfbldkacnbeo", "Chromium" The .desktop file has a matching StartupWMClass, but the name differs, e.g. for the store app (youtube) there is .local/share/applications/chrome-blpcfgokakmgnkcojhhkbfbldkacnbeo-Default.desktop with StartupWMClass=crx_blpcfgokakmgnkcojhhkbfbldkacnbeo Note that chromium (but not google-chrome!) includes a StartupWMClass=chromium in their .desktop file, so we must match the instance first. Also note that in the good case (regular gtk+ app without hacks), instance and class are the same except for case and there is no StartupWMClass at all. */ /* first try a match from WM_CLASS (instance part) to StartupWMClass */ wm_instance = meta_window_get_wm_class_instance (window); app = shell_app_system_lookup_startup_wmclass (appsys, wm_instance); if (app != NULL) return g_object_ref (app); /* then try a match from WM_CLASS to StartupWMClass */ wm_class = meta_window_get_wm_class (window); app = shell_app_system_lookup_startup_wmclass (appsys, wm_class); if (app != NULL) return g_object_ref (app); /* then try a match from WM_CLASS (instance part) to .desktop */ app = shell_app_system_lookup_desktop_wmclass (appsys, wm_instance); if (app != NULL) return g_object_ref (app); /* finally, try a match from WM_CLASS to .desktop */ app = shell_app_system_lookup_desktop_wmclass (appsys, wm_class); if (app != NULL) return g_object_ref (app); return NULL; }