gem-graph-client/libide/gui/ide-application.c

863 lines
27 KiB
C

/* ide-application.c
*
* Copyright 2014-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-application"
#include "config.h"
#ifdef __linux__
# include <sys/prctl.h>
#endif
#include <glib/gi18n.h>
#include <handy.h>
#include <libpeas/peas-autocleanups.h>
#include <libide-themes.h>
#include "ide-language-defaults.h"
#include "ide-application.h"
#include "ide-application-addin.h"
#include "ide-application-private.h"
#include "ide-gui-global.h"
#include "ide-primary-workspace.h"
#include "ide-worker.h"
G_DEFINE_FINAL_TYPE (IdeApplication, ide_application, DZL_TYPE_APPLICATION)
#define IS_UI_PROCESS(app) ((app)->type == NULL)
typedef struct
{
IdeApplication *self;
GFile **files;
gint n_files;
const gchar *hint;
} OpenData;
static void
ide_application_add_platform_data (GApplication *app,
GVariantBuilder *builder)
{
IdeApplication *self = (IdeApplication *)app;
g_assert (IDE_IS_APPLICATION (self));
g_assert (self->argv != NULL);
G_APPLICATION_CLASS (ide_application_parent_class)->add_platform_data (app, builder);
g_variant_builder_add (builder,
"{sv}",
"gnome-builder-version",
g_variant_new_string (IDE_VERSION_S));
g_variant_builder_add (builder,
"{sv}",
"argv",
g_variant_new_strv ((const gchar * const *)self->argv, -1));
}
static gint
ide_application_command_line (GApplication *app,
GApplicationCommandLine *cmdline)
{
IdeApplication *self = (IdeApplication *)app;
g_assert (IDE_IS_APPLICATION (self));
g_assert (G_IS_APPLICATION_COMMAND_LINE (cmdline));
/* Allow plugins to handle command-line */
_ide_application_command_line (self, cmdline);
return G_APPLICATION_CLASS (ide_application_parent_class)->command_line (app, cmdline);
}
static gboolean
ide_application_local_command_line (GApplication *app,
gchar ***arguments,
gint *exit_status)
{
IdeApplication *self = (IdeApplication *)app;
g_assert (IDE_IS_APPLICATION (self));
g_assert (arguments != NULL);
g_assert (exit_status != NULL);
g_assert (self->argv == NULL);
/* Save these for later, to use by cmdline addins */
self->argv = g_strdupv (*arguments);
return G_APPLICATION_CLASS (ide_application_parent_class)->local_command_line (app, arguments, exit_status);
}
static void
ide_application_register_keybindings (IdeApplication *self)
{
g_autoptr(GSettings) settings = NULL;
g_autofree gchar *name = NULL;
g_assert (IDE_IS_APPLICATION (self));
settings = g_settings_new ("org.gnome.builder.editor");
name = g_settings_get_string (settings, "keybindings");
self->keybindings = ide_keybindings_new (name);
g_settings_bind (settings, "keybindings", self->keybindings, "mode", G_SETTINGS_BIND_GET);
}
static int
keybinding_key_snooper (GtkWidget *grab_widget,
GdkEventKey *key,
gpointer func_data)
{
IdeApplication *self = func_data;
g_assert (IDE_IS_APPLICATION (self));
/* We need to hijack <Ctrl>period because ibus is messing it up. However,
* we only get a release event since it gets hijacked from the compositor
* so we never see the event. Instead, we catch the release and then change
* the focus to what we want (clearlying the state created in the compositor
* ibus bits).
*/
if (key->type == GDK_KEY_RELEASE &&
key->keyval == GDK_KEY_period &&
(key->state & GDK_CONTROL_MASK) != 0)
{
if (IDE_IS_WORKSPACE (grab_widget))
{
DzlShortcutManager *shortcuts = dzl_shortcut_manager_get_default ();
g_clear_object (&key->window);
key->window = g_object_ref (gtk_widget_get_window (grab_widget));
key->type = GDK_KEY_PRESS;
dzl_shortcut_manager_handle_event (shortcuts, key, grab_widget);
return TRUE;
}
}
return FALSE;
}
static void
ide_application_startup (GApplication *app)
{
IdeApplication *self = (IdeApplication *)app;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_APPLICATION (self));
/*
* We require a desktop session that provides a properly working
* D-Bus environment. Bail if for some reason that is not the case.
*/
if (g_getenv ("DBUS_SESSION_BUS_ADDRESS") == NULL)
g_warning ("%s",
_("GNOME Builder requires a session with D-Bus which was not found. Please set DBUS_SESSION_BUS_ADDRESS. Some features may not be available."));
G_APPLICATION_CLASS (ide_application_parent_class)->startup (app);
if (IS_UI_PROCESS (self))
{
g_autofree gchar *style_path = NULL;
GtkSourceStyleSchemeManager *styles;
/* Setup key snoopers */
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
gtk_key_snooper_install (keybinding_key_snooper, self);
G_GNUC_END_IGNORE_DEPRECATIONS
/* Setup access to private icons dir */
gtk_icon_theme_prepend_search_path (gtk_icon_theme_get_default (), PACKAGE_ICONDIR);
/* Add custom style locations for gtksourceview schemes */
styles = gtk_source_style_scheme_manager_get_default ();
style_path = g_build_filename (g_get_home_dir (), ".local", "share", "gtksourceview-4", "styles", NULL);
gtk_source_style_scheme_manager_append_search_path (styles, style_path);
gtk_source_style_scheme_manager_append_search_path (styles, PACKAGE_DATADIR"/gtksourceview-4/styles/");
hdy_init ();
/* Load color settings (Night Light, Dark Mode, etc) */
_ide_application_init_color (self);
}
/* And now we can load the rest of our plugins for startup. */
_ide_application_load_plugins (self);
if (IS_UI_PROCESS (self))
{
/* Make sure our shorcuts are registered */
_ide_application_init_shortcuts (self);
/* Load keybindings from plugins and what not */
ide_application_register_keybindings (self);
/* Load language defaults into gsettings */
ide_language_defaults_init_async (NULL, NULL, NULL);
}
}
static void
ide_application_shutdown (GApplication *app)
{
IdeApplication *self = (IdeApplication *)app;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_APPLICATION (self));
_ide_application_unload_addins (self);
g_clear_pointer (&self->plugin_settings, g_hash_table_unref);
g_clear_object (&self->addins);
g_clear_object (&self->settings);
g_clear_object (&self->keybindings);
G_APPLICATION_CLASS (ide_application_parent_class)->shutdown (app);
}
static void
ide_application_activate_worker (IdeApplication *self)
{
g_autoptr(GDBusConnection) connection = NULL;
g_autoptr(GError) error = NULL;
PeasPluginInfo *plugin_info;
PeasExtension *extension;
PeasEngine *engine;
IDE_ENTRY;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_APPLICATION (self));
g_assert (ide_str_equal0 (self->type, "worker"));
g_assert (self->dbus_address != NULL);
g_assert (self->plugin != NULL);
#ifdef __linux__
prctl (PR_SET_PDEATHSIG, SIGKILL);
#endif
IDE_TRACE_MSG ("Connecting to %s", self->dbus_address);
connection = g_dbus_connection_new_for_address_sync (self->dbus_address,
(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING),
NULL, NULL, &error);
if (error != NULL)
{
g_error ("D-Bus failure: %s", error->message);
IDE_EXIT;
}
engine = peas_engine_get_default ();
if (!(plugin_info = peas_engine_get_plugin_info (engine, self->plugin)))
{
g_error ("No such plugin \"%s\"", self->plugin);
IDE_EXIT;
}
if (!(extension = peas_engine_create_extension (engine, plugin_info, IDE_TYPE_WORKER, NULL)))
{
g_error ("Failed to create \"%s\" worker", self->plugin);
IDE_EXIT;
}
ide_worker_register_service (IDE_WORKER (extension), connection);
g_application_hold (G_APPLICATION (self));
g_dbus_connection_start_message_processing (connection);
IDE_EXIT;
}
static void
ide_application_activate_cb (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
g_assert (PEAS_IS_EXTENSION_SET (set));
g_assert (plugin_info != NULL);
g_assert (IDE_IS_APPLICATION_ADDIN (exten));
g_assert (IDE_IS_APPLICATION (user_data));
ide_application_addin_activate (IDE_APPLICATION_ADDIN (exten), user_data);
}
static void
ide_application_activate (GApplication *app)
{
IdeApplication *self = (IdeApplication *)app;
GtkWindow *window;
IDE_ENTRY;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_APPLICATION (self));
if (ide_str_equal0 (self->type, "worker"))
{
ide_application_activate_worker (self);
IDE_EXIT;
}
if ((window = gtk_application_get_active_window (GTK_APPLICATION (self))))
ide_gtk_window_present (window);
if (self->addins != NULL)
peas_extension_set_foreach (self->addins,
ide_application_activate_cb,
self);
IDE_EXIT;
}
static void
ide_application_open_cb (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
IdeApplicationAddin *app_addin = (IdeApplicationAddin*) exten;
OpenData *data = user_data;
g_assert (PEAS_IS_EXTENSION_SET (set));
g_assert (plugin_info != NULL);
g_assert (IDE_IS_APPLICATION_ADDIN (app_addin));
g_assert (data != NULL);
g_assert (IDE_IS_APPLICATION (data->self));
g_assert (data->files != NULL);
ide_application_addin_open (app_addin, data->self, data->files, data->n_files, data->hint);
}
static void
ide_application_open (GApplication *app,
GFile **files,
gint n_files,
const gchar *hint)
{
IdeApplication *self = (IdeApplication*)app;
OpenData data;
IDE_ENTRY;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_APPLICATION (self));
g_assert (files);
g_assert (n_files > 0);
g_assert (hint);
data.self = self;
data.files = files;
data.n_files = n_files;
data.hint = hint;
if (self->addins != NULL)
peas_extension_set_foreach (self->addins, ide_application_open_cb, &data);
IDE_EXIT;
}
static void
ide_application_dispose (GObject *object)
{
IdeApplication *self = (IdeApplication *)object;
/* We don't necessarily get startup/shutdown called when we are
* the remote process, so ensure they get cleared here rather than
* in ::shutdown.
*/
g_clear_pointer (&self->started_at, g_date_time_unref);
g_clear_pointer (&self->workbenches, g_ptr_array_unref);
g_clear_pointer (&self->plugin_settings, g_hash_table_unref);
g_clear_pointer (&self->plugin_gresources, g_hash_table_unref);
g_clear_pointer (&self->argv, g_strfreev);
g_clear_pointer (&self->plugin, g_free);
g_clear_pointer (&self->type, g_free);
g_clear_pointer (&self->dbus_address, g_free);
g_clear_object (&self->addins);
g_clear_object (&self->settings);
g_clear_object (&self->network_monitor);
g_clear_object (&self->worker_manager);
G_OBJECT_CLASS (ide_application_parent_class)->dispose (object);
}
static void
ide_application_class_init (IdeApplicationClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
object_class->dispose = ide_application_dispose;
app_class->activate = ide_application_activate;
app_class->open = ide_application_open;
app_class->add_platform_data = ide_application_add_platform_data;
app_class->command_line = ide_application_command_line;
app_class->local_command_line = ide_application_local_command_line;
app_class->startup = ide_application_startup;
app_class->shutdown = ide_application_shutdown;
}
static void
ide_application_init (IdeApplication *self)
{
self->started_at = g_date_time_new_now_local ();
self->workspace_type = IDE_TYPE_PRIMARY_WORKSPACE;
self->workbenches = g_ptr_array_new_with_free_func (g_object_unref);
self->settings = g_settings_new ("org.gnome.builder");
self->plugin_gresources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify)g_resource_unref);
g_application_set_default (G_APPLICATION (self));
gtk_window_set_default_icon_name (ide_get_application_id ());
ide_themes_init ();
/* Ensure our core data is loaded early. */
dzl_application_add_resources (DZL_APPLICATION (self), "resource:///org/gnome/libide-sourceview/");
dzl_application_add_resources (DZL_APPLICATION (self), "resource:///org/gnome/libide-gui/");
dzl_application_add_resources (DZL_APPLICATION (self), "resource:///org/gnome/libide-terminal/");
/* Make sure our GAction are available */
_ide_application_init_actions (self);
}
IdeApplication *
_ide_application_new (gboolean standalone,
const gchar *type,
const gchar *plugin,
const gchar *dbus_address)
{
GApplicationFlags flags = G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN;
IdeApplication *self;
if (standalone || ide_str_equal0 (type, "worker"))
flags |= G_APPLICATION_NON_UNIQUE;
self = g_object_new (IDE_TYPE_APPLICATION,
"application-id", ide_get_application_id (),
"flags", flags,
"resource-base-path", "/org/gnome/builder",
NULL);
self->type = g_strdup (type);
self->plugin = g_strdup (plugin);
self->dbus_address = g_strdup (dbus_address);
/* Load plugins indicating they support startup features */
_ide_application_load_plugins_for_startup (self);
/* Now that early plugins are loaded, we can activate app addins. We'll
* load additional plugins later after post-early stage startup
*/
_ide_application_load_addins (self);
/* Register command-line options, possibly from plugins. */
_ide_application_add_option_entries (self);
return g_steal_pointer (&self);
}
static void
ide_application_add_workbench_cb (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
g_assert (PEAS_IS_EXTENSION_SET (set));
g_assert (plugin_info != NULL);
g_assert (IDE_IS_APPLICATION_ADDIN (exten));
g_assert (IDE_IS_WORKBENCH (user_data));
ide_application_addin_workbench_added (IDE_APPLICATION_ADDIN (exten), user_data);
}
void
ide_application_add_workbench (IdeApplication *self,
IdeWorkbench *workbench)
{
g_return_if_fail (IDE_IS_APPLICATION (self));
g_return_if_fail (IDE_IS_WORKBENCH (workbench));
g_ptr_array_add (self->workbenches, g_object_ref (workbench));
peas_extension_set_foreach (self->addins,
ide_application_add_workbench_cb,
workbench);
}
static void
ide_application_remove_workbench_cb (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
g_assert (PEAS_IS_EXTENSION_SET (set));
g_assert (plugin_info != NULL);
g_assert (IDE_IS_APPLICATION_ADDIN (exten));
g_assert (IDE_IS_WORKBENCH (user_data));
ide_application_addin_workbench_removed (IDE_APPLICATION_ADDIN (exten), user_data);
}
void
ide_application_remove_workbench (IdeApplication *self,
IdeWorkbench *workbench)
{
g_return_if_fail (IDE_IS_APPLICATION (self));
g_return_if_fail (IDE_IS_WORKBENCH (workbench));
peas_extension_set_foreach (self->addins,
ide_application_remove_workbench_cb,
workbench);
g_ptr_array_remove (self->workbenches, workbench);
}
/**
* ide_application_foreach_workbench:
* @self: an #IdeApplication
* @callback: (scope call): a #GFunc callback
* @user_data: user data for @callback
*
* Calls @callback for each of the registered workbenches.
*
* Since: 3.32
*/
void
ide_application_foreach_workbench (IdeApplication *self,
GFunc callback,
gpointer user_data)
{
g_return_if_fail (IDE_IS_APPLICATION (self));
g_return_if_fail (callback != NULL);
for (guint i = self->workbenches->len; i > 0; i--)
{
IdeWorkbench *workbench = g_ptr_array_index (self->workbenches, i - 1);
callback (workbench, user_data);
}
}
/**
* ide_application_set_workspace_type:
* @self: a #IdeApplication
*
* Sets the #GType of an #IdeWorkspace that should be used when creating the
* next workspace upon handling files from command-line arguments. This is
* reset after the files are opened and is generally only useful from
* #IdeApplicationAddin's who need to alter the default workspace.
*
* Since: 3.32
*/
void
ide_application_set_workspace_type (IdeApplication *self,
GType workspace_type)
{
g_return_if_fail (IDE_IS_APPLICATION (self));
g_return_if_fail (g_type_is_a (workspace_type, IDE_TYPE_WORKSPACE));
self->workspace_type = workspace_type;
}
static void
ide_application_network_changed_cb (IdeApplication *self,
gboolean available,
GNetworkMonitor *monitor)
{
g_assert (IDE_IS_APPLICATION (self));
g_assert (G_IS_NETWORK_MONITOR (monitor));
self->has_network = !!available;
}
/**
* ide_application_has_network:
* @self: (nullable): a #IdeApplication
*
* This is a helper that uses an internal #GNetworkMonitor to track if we
* have access to the network. It works around some issues we've seen in
* the wild that make determining if we have network access difficult.
*
* Returns: %TRUE if we think there is network access.
*
* Since: 3.32
*/
gboolean
ide_application_has_network (IdeApplication *self)
{
g_return_val_if_fail (!self || IDE_IS_APPLICATION (self), FALSE);
if (self == NULL)
self = IDE_APPLICATION_DEFAULT;
if (self->network_monitor == NULL)
{
self->network_monitor = g_object_ref (g_network_monitor_get_default ());
g_signal_connect_object (self->network_monitor,
"network-changed",
G_CALLBACK (ide_application_network_changed_cb),
self,
G_CONNECT_SWAPPED);
self->has_network = g_network_monitor_get_network_available (self->network_monitor);
/*
* FIXME: Ignore the network portal initially for now.
*
* See https://gitlab.gnome.org/GNOME/glib/merge_requests/227 for more
* information about when this is fixed.
*
* See Also: https://gitlab.gnome.org/GNOME/glib/-/issues/1718
*/
if (!self->has_network && ide_is_flatpak ())
self->has_network = TRUE;
}
return self->has_network;
}
/**
* ide_application_get_started_at:
* @self: a #IdeApplication
*
* Gets the time the application was started.
*
* Returns: (transfer none): a #GDateTime
*
* Since: 3.32
*/
GDateTime *
ide_application_get_started_at (IdeApplication *self)
{
g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
return self->started_at;
}
static void
ide_application_get_worker_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeWorkerManager *worker_manager = (IdeWorkerManager *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
GDBusProxy *proxy;
g_assert (IDE_IS_WORKER_MANAGER (worker_manager));
if (!(proxy = ide_worker_manager_get_worker_finish (worker_manager, result, &error)))
ide_task_return_error (task, g_steal_pointer (&error));
else
ide_task_return_pointer (task, g_steal_pointer (&proxy), g_object_unref);
}
/**
* ide_application_get_worker_async:
* @self: an #IdeApplication
* @plugin_name: The name of the plugin.
* @cancellable: (allow-none): a #GCancellable or %NULL.
* @callback: a #GAsyncReadyCallback or %NULL.
* @user_data: user data for @callback.
*
* Asynchronously requests a #GDBusProxy to a service provided in a worker
* process. The worker should be an #IdeWorker implemented by the plugin named
* @plugin_name. The #IdeWorker is responsible for created both the service
* registered on the bus and the proxy to it.
*
* The #IdeApplication is responsible for spawning a subprocess for the worker.
*
* @callback should call ide_application_get_worker_finish() with the result
* provided to retrieve the result.
*
* Since: 3.32
*/
void
ide_application_get_worker_async (IdeApplication *self,
const gchar *plugin_name,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(IdeTask) task = NULL;
g_return_if_fail (IDE_IS_APPLICATION (self));
g_return_if_fail (plugin_name != NULL);
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
if (self->worker_manager == NULL)
self->worker_manager = ide_worker_manager_new ();
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_application_get_worker_async);
ide_worker_manager_get_worker_async (self->worker_manager,
plugin_name,
cancellable,
ide_application_get_worker_cb,
g_steal_pointer (&task));
}
/**
* ide_application_get_worker_finish:
* @self: an #IdeApplication.
* @result: a #GAsyncResult
* @error: a location for a #GError, or %NULL.
*
* Completes an asynchronous request to get a proxy to a worker process.
*
* Returns: (transfer full): a #GDBusProxy or %NULL.
*
* Since: 3.32
*/
GDBusProxy *
ide_application_get_worker_finish (IdeApplication *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
g_return_val_if_fail (IDE_IS_TASK (result), NULL);
return ide_task_propagate_pointer (IDE_TASK (result), error);
}
/**
* ide_application_find_workbench_for_file:
* @self: a #IdeApplication
* @file: a #GFile
*
* Looks for the workbench that is the closest match to @file.
*
* If no workbench is the root of @file, then %NULL is returned.
*
* Returns: (transfer none) (nullable): an #IdeWorkbench or %NULL
*
* Since: 3.32
*/
IdeWorkbench *
ide_application_find_workbench_for_file (IdeApplication *self,
GFile *file)
{
g_autofree gchar *suffix = NULL;
IdeWorkbench *match = NULL;
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
for (guint i = 0; i < self->workbenches->len; i++)
{
IdeWorkbench *workbench = g_ptr_array_index (self->workbenches, 0);
IdeContext *context = ide_workbench_get_context (workbench);
g_autoptr(GFile) workdir = ide_context_ref_workdir (context);
if (!ide_workbench_has_project (workbench))
continue;
if (g_file_has_prefix (file, workdir))
{
g_autofree gchar *relative = g_file_get_relative_path (workdir, file);
if (!suffix || strlen (relative) < strlen (suffix))
{
match = workbench;
g_free (suffix);
suffix = g_steal_pointer (&relative);
}
}
}
/* TODO: If a file is installed, but was installed by a workspace that
* we have open, we want to switch to that file instead of the
* installed version. For example, something installed to
* /app/include/libpeas-1.0/libpeas/peas-engine.h should really open
* libpeas/peas-engine.h from the project. This will require querying
* the pipeline/build-system for installed files to reverse-map the
* filename.
*/
return match;
}
void
ide_application_set_command_line_handled (IdeApplication *self,
GApplicationCommandLine *cmdline,
gboolean handled)
{
g_return_if_fail (IDE_IS_APPLICATION (self));
g_return_if_fail (G_IS_APPLICATION_COMMAND_LINE (cmdline));
return g_object_set_data (G_OBJECT (cmdline), "COMMAND_LINE_HANDLED", GINT_TO_POINTER (!!handled));
}
gboolean
ide_application_get_command_line_handled (IdeApplication *self,
GApplicationCommandLine *cmdline)
{
g_return_val_if_fail (IDE_IS_APPLICATION (self), FALSE);
g_return_val_if_fail (G_IS_APPLICATION_COMMAND_LINE (cmdline), FALSE);
return !!g_object_get_data (G_OBJECT (cmdline), "COMMAND_LINE_HANDLED");
}
/**
* ide_application_find_addin_by_module_name:
* @self: a #IdeApplication
* @module_name: the name of the plugin module
*
* Finds a loaded #IdeApplicationAddin within @self that was part of
* the plugin matching @module_name.
*
* Returns: (transfer none) (type IdeApplicationAddin) (nullable): an
* #IdeApplicationAddin or %NULL.
*
* Since: 3.34
*/
gpointer
ide_application_find_addin_by_module_name (IdeApplication *self,
const gchar *module_name)
{
PeasEngine *engine;
PeasPluginInfo *plugin_info;
if (self == NULL)
self = IDE_APPLICATION_DEFAULT;
g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
g_return_val_if_fail (module_name != NULL, NULL);
if (self->addins == NULL)
return NULL;
engine = peas_engine_get_default ();
plugin_info = peas_engine_get_plugin_info (engine, module_name);
if (plugin_info == NULL)
return NULL;
return peas_extension_set_get_extension (self->addins, plugin_info);
}