863 lines
27 KiB
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);
|
||
|
}
|