diff --git a/pixmaps/gnome-commander-settings-symbolic.svg b/pixmaps/gnome-commander-settings-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..f9c124a6c99f2b4e34bd9f4b5b786c83c3886973
--- /dev/null
+++ b/pixmaps/gnome-commander-settings-symbolic.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/pixmaps/meson.build b/pixmaps/meson.build
index 62d40dee48b58fb9e0f90cb35a4f59aca05f31f7..750f0db6f1e14170a16bfea76ccd7f0bf83ad1e5 100644
--- a/pixmaps/meson.build
+++ b/pixmaps/meson.build
@@ -41,6 +41,7 @@ icons_scalable = [
'gnome-commander-down-symbolic.svg',
'gnome-commander-bookmark-outline-symbolic.svg',
'gnome-commander-info-outline-symbolic.svg',
+ 'gnome-commander-settings-symbolic.svg',
]
install_data(icons_scalable,
diff --git a/src/config.rs.in b/src/config.rs.in
index bbe5e3466a4010c4bb707a7aa0fdeeaf0c1ed149..db4c594d775c41df4bb30bf3fe0cea5bded0cae3 100644
--- a/src/config.rs.in
+++ b/src/config.rs.in
@@ -21,6 +21,7 @@ pub const PACKAGE: &str = @PACKAGE@;
pub const PREFIX: &str = @PREFIX@;
pub const DATADIR: &str = @DATADIR@;
pub const PIXMAPS_DIR: &str = @PIXMAPS_DIR@;
+pub const PLUGIN_DIR: &str = @PLUGIN_DIR@;
pub const PACKAGE_NAME: &str = @PACKAGE_NAME@;
pub const PACKAGE_VERSION: &str = @PACKAGE_VERSION@;
diff --git a/src/file_list_popup.rs b/src/file_list_popup.rs
index bf83bb446a7273c5b93956d7316359b05573caf6..442ddb1333d207f46d596f17fb683304ed2bb266 100644
--- a/src/file_list_popup.rs
+++ b/src/file_list_popup.rs
@@ -28,7 +28,7 @@ use crate::{
file_descriptor::FileDescriptorExt,
},
main_win::MainWindow,
- plugin_manager::{plugin_manager_get_active_plugins, wrap_plugin_menu},
+ plugin_manager::wrap_plugin_menu,
utils::MenuBuilderExt,
};
use gettextrs::gettext;
@@ -234,7 +234,7 @@ pub fn file_popup_menu(main_win: &MainWindow, file_list: &FileList) -> Option() {
if let Some(menu) = file_actions.create_popup_menu_items(&main_win.state()) {
let menu = wrap_plugin_menu(&action_group_name, &menu);
diff --git a/src/gmodule.rs b/src/gmodule.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d13a69590e05394eb547046492def57f05332a8b
--- /dev/null
+++ b/src/gmodule.rs
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2025 Andrey Kutejko
+ *
+ * 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 .
+ *
+ * For more details see the file COPYING.
+ */
+
+use gtk::glib::{
+ self,
+ ffi::gpointer,
+ translate::{from_glib_full, ToGlibPtr},
+};
+use std::path::Path;
+
+pub mod ffi {
+ use glib::ffi::{gboolean, gpointer, GError, GQuark};
+ use std::ffi::{c_char, c_uint};
+
+ pub type GModuleFlags = c_uint;
+ pub const G_MODULE_BIND_LAZY: GModuleFlags = 1;
+ pub const G_MODULE_BIND_LOCAL: GModuleFlags = 2;
+ pub const G_MODULE_BIND_MASK: GModuleFlags = 3;
+
+ #[repr(C)]
+ #[derive(Debug, Copy, Clone)]
+ pub struct GModule {
+ _unused: [u8; 0],
+ }
+
+ pub type GModuleError = c_uint;
+ pub const G_MODULE_ERROR_FAILED: GModuleError = 0;
+ pub const G_MODULE_ERROR_CHECK_FAILED: GModuleError = 1;
+
+ extern "C" {
+ pub fn g_module_supported() -> gboolean;
+
+ pub fn g_module_open_full(
+ file_name: *const c_char,
+ flags: GModuleFlags,
+ error: *mut *mut GError,
+ ) -> *mut GModule;
+
+ pub fn g_module_close(module: *mut GModule) -> gboolean;
+
+ pub fn g_module_make_resident(module: *mut GModule);
+
+ pub fn g_module_error_quark() -> GQuark;
+
+ pub fn g_module_symbol(
+ module: *mut GModule,
+ symbol_name: *const c_char,
+ symbol: *mut gpointer,
+ ) -> gboolean;
+
+ pub fn g_module_name(module: *mut GModule) -> *const c_char;
+ }
+}
+
+#[repr(C)]
+pub struct GModule(*mut ffi::GModule);
+
+impl GModule {
+ pub fn open(file_name: &Path, flags: GModuleFlags) -> Result {
+ let mut f: ffi::GModuleFlags = 0;
+ if flags.lazy {
+ f |= ffi::G_MODULE_BIND_LAZY;
+ }
+ if flags.local {
+ f |= ffi::G_MODULE_BIND_LOCAL;
+ }
+
+ let mut error = std::ptr::null_mut();
+ let module = unsafe { ffi::g_module_open_full(file_name.to_glib_none().0, f, &mut error) };
+
+ if module.is_null() {
+ return Err(unsafe { from_glib_full(error) });
+ }
+
+ Ok(Self(module))
+ }
+
+ pub fn symbol(&self, symbol_name: &str) -> Option {
+ let mut symbol = std::ptr::null_mut();
+ let result =
+ unsafe { ffi::g_module_symbol(self.0, symbol_name.to_glib_none().0, &mut symbol) != 0 };
+ if result {
+ Some(symbol)
+ } else {
+ None
+ }
+ }
+
+ pub fn leak(mut self) -> *mut ffi::GModule {
+ std::mem::replace(&mut self.0, std::ptr::null_mut())
+ }
+}
+
+impl Drop for GModule {
+ fn drop(&mut self) {
+ if !self.0.is_null() {
+ let status = unsafe { ffi::g_module_close(self.0) };
+ if status == 0 {
+ eprintln!("Failed to close a GModule.");
+ }
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct GModuleFlags {
+ pub lazy: bool,
+ pub local: bool,
+}
diff --git a/src/gnome-cmd-about-plugin.h b/src/gnome-cmd-about-plugin.h
index f42e8de8c511a3b2ea71b0c5550feb24cad1bc0a..eb264597e31fa1a670f679c4bf7ec2686fe59e43 100644
--- a/src/gnome-cmd-about-plugin.h
+++ b/src/gnome-cmd-about-plugin.h
@@ -67,6 +67,6 @@ struct GnomeCmdAboutPluginClass
GtkDialogClass parent_class;
};
-GType gnome_cmd_about_plugin_get_type (void) G_GNUC_CONST;
+extern "C" GType gnome_cmd_about_plugin_get_type (void) G_GNUC_CONST;
GtkWidget *gnome_cmd_about_plugin_new (PluginInfo *info);
diff --git a/src/gnome-cmd-application.cc b/src/gnome-cmd-application.cc
index ef70ef00117fd6d9a4368cd84cdf78e4bbee30bf..f74f811dbcf21c9e87e399fd8dddb346571707eb 100644
--- a/src/gnome-cmd-application.cc
+++ b/src/gnome-cmd-application.cc
@@ -31,7 +31,6 @@
#include "gnome-cmd-con.h"
#include "utils.h"
#include "imageloader.h"
-#include "plugin_manager.h"
#include "tags/gnome-cmd-tags.h"
@@ -132,13 +131,11 @@ static void gnome_cmd_application_activate(GApplication *application)
gcmd_owner.load_async();
gcmd_tags_init();
- plugin_manager_init ();
}
static void gnome_cmd_application_shutdown(GApplication *application)
{
- plugin_manager_shutdown ();
gcmd_tags_shutdown ();
IMAGE_free ();
diff --git a/src/gnome-cmd-data.cc b/src/gnome-cmd-data.cc
index 597ff49c172386ac9142af93a39539639ce9629e..43b0ab92f37a2dfe7033370524d1bb251f36023e 100644
--- a/src/gnome-cmd-data.cc
+++ b/src/gnome-cmd-data.cc
@@ -47,7 +47,6 @@ GnomeCmdData gnome_cmd_data;
struct GnomeCmdData::Private
{
GnomeCmdConList *con_list;
- GList *auto_load_plugins;
};
GSettingsSchemaSource* GnomeCmdData::GetGlobalSchemaSource()
@@ -89,7 +88,6 @@ struct _GcmdSettings
GSettings *programs;
GSettings *network;
GSettings *internalviewer;
- GSettings *plugins;
};
G_DEFINE_TYPE (GcmdSettings, gcmd_settings, G_TYPE_OBJECT)
@@ -114,7 +112,6 @@ static void gcmd_settings_dispose (GObject *object)
g_clear_object (&gs->programs);
g_clear_object (&gs->network);
g_clear_object (&gs->internalviewer);
- g_clear_object (&gs->plugins);
G_OBJECT_CLASS (gcmd_settings_parent_class)->dispose (object);
}
@@ -982,9 +979,6 @@ static void gcmd_settings_init (GcmdSettings *gs)
global_schema = g_settings_schema_source_lookup (global_schema_source, GCMD_PREF_INTERNAL_VIEWER, FALSE);
gs->internalviewer = g_settings_new_full (global_schema, nullptr, nullptr);
-
- global_schema = g_settings_schema_source_lookup (global_schema_source, GCMD_PREF_PLUGINS, FALSE);
- gs->plugins = g_settings_new_full (global_schema, nullptr, nullptr);
}
@@ -2160,16 +2154,6 @@ inline void GnomeCmdData::save_intviewer_defaults()
set_gsettings_enum_when_changed (options.gcmd_settings->internalviewer, GCMD_SETTINGS_IV_SEARCH_MODE, intviewer_defaults.search_mode);
}
-/**
- * This function saves all entries of the auto load plugin list in the associated GSettings string array.
- * @returns the return value of set_gsettings_string_array_from_glist().
- */
-inline gboolean GnomeCmdData::save_auto_load_plugins()
-{
- return set_gsettings_string_array_from_glist(options.gcmd_settings->plugins, GCMD_SETTINGS_PLUGINS_AUTOLOAD, priv->auto_load_plugins);
-}
-
-
/**
* Returns a GList with newly allocated char strings
*/
@@ -2220,18 +2204,6 @@ inline void GnomeCmdData::load_intviewer_defaults()
}
-/**
- * This function pushes the list of plugins to be automatically loaded into the
- * associated Glist.
- */
-inline void GnomeCmdData::load_auto_load_plugins()
-{
- g_list_free(priv->auto_load_plugins);
-
- priv->auto_load_plugins = get_list_from_gsettings_string_array (options.gcmd_settings->plugins, GCMD_SETTINGS_PLUGINS_AUTOLOAD);
-}
-
-
GnomeCmdData::GnomeCmdData(): search_defaults(profiles)
{
quick_connect = nullptr;
@@ -2581,7 +2553,6 @@ void GnomeCmdData::load()
g_free (quick_connect_uri);
load_intviewer_defaults();
- load_auto_load_plugins();
set_g_volume_monitor ();
}
@@ -2715,7 +2686,6 @@ void GnomeCmdData::save(GnomeCmdMainWin *main_win)
save_bookmarks ();
save_advrename_profiles ();
save_intviewer_defaults ();
- save_auto_load_plugins ();
g_settings_sync ();
}
@@ -2914,18 +2884,6 @@ gpointer gnome_cmd_data_get_con_list ()
}
-GList *gnome_cmd_data_get_auto_load_plugins ()
-{
- return gnome_cmd_data.priv->auto_load_plugins;
-}
-
-
-void gnome_cmd_data_set_auto_load_plugins (GList *plugins)
-{
- gnome_cmd_data.priv->auto_load_plugins = plugins;
-}
-
-
const gchar *gnome_cmd_data_get_symlink_prefix ()
{
char *symlink_prefix;
diff --git a/src/gnome-cmd-data.h b/src/gnome-cmd-data.h
index bca8683942e7f30e69ae543883a0bc665c03a64c..7a6fddebd7e493f9dc5415fd183933fb7189c660 100644
--- a/src/gnome-cmd-data.h
+++ b/src/gnome-cmd-data.h
@@ -206,9 +206,6 @@ GcmdSettings *gcmd_settings_new ();
#define GCMD_SETTINGS_IV_SEARCH_PATTERN_TEXT "search-pattern-text"
#define GCMD_SETTINGS_IV_SEARCH_PATTERN_HEX "search-pattern-hex"
-#define GCMD_PREF_PLUGINS "org.gnome.gnome-commander.plugins.general"
-#define GCMD_SETTINGS_PLUGINS_AUTOLOAD "autoload"
-
//gKeyFile constants
#define DEVICES_FILENAME "devices"
#define DEVICES_DEVICE "device"
diff --git a/src/gnome-cmd-main-win.cc b/src/gnome-cmd-main-win.cc
index 97750c37b039cc00090b6bea4e4ebad3ea107e0e..08352c0f63a52f2b075bd64ab66b4a1790353f9d 100644
--- a/src/gnome-cmd-main-win.cc
+++ b/src/gnome-cmd-main-win.cc
@@ -105,6 +105,8 @@ struct GnomeCmdMainWin::Private
bool state_saved;
GnomeCmdShortcuts *gcmd_shortcuts;
GObject *color_themes;
+
+ GObject *plugin_manager;
};
@@ -112,6 +114,7 @@ G_DEFINE_TYPE (GnomeCmdMainWin, gnome_cmd_main_win, GTK_TYPE_APPLICATION_WINDOW)
extern "C" GType gnome_cmd_color_themes_get_type();
+extern "C" GType plugin_manager_get_type ();
static gboolean set_equal_panes_idle (gpointer *user_data);
@@ -494,6 +497,8 @@ static void dispose (GObject *object)
g_clear_object (&main_win->priv->color_themes);
+ g_clear_object (&main_win->priv->plugin_manager);
+
G_OBJECT_CLASS (gnome_cmd_main_win_parent_class)->dispose (object);
}
@@ -553,6 +558,8 @@ static void gnome_cmd_main_win_init (GnomeCmdMainWin *mw)
"display", gdk_display_get_default(),
nullptr));
+ mw->priv->plugin_manager = G_OBJECT (g_object_new (plugin_manager_get_type (), nullptr));
+
gtk_window_set_title (GTK_WINDOW (mw),
gcmd_owner.is_root()
? _("GNOME Commander — ROOT PRIVILEGES")
@@ -619,6 +626,7 @@ static void gnome_cmd_main_win_init (GnomeCmdMainWin *mw)
g_signal_connect (gnome_cmd_con_list_get (), "list-changed", G_CALLBACK (on_con_list_list_changed), mw);
g_signal_connect (mw->priv->color_themes, "theme-changed", G_CALLBACK (on_update_view), mw);
+ g_signal_connect_swapped (mw->priv->plugin_manager, "plugins-changed", G_CALLBACK (gnome_cmd_main_win_plugins_updated), mw);
mw->focus_file_lists();
}
@@ -817,7 +825,7 @@ gboolean GnomeCmdMainWin::key_pressed(GnomeCmdKeyPress *event)
{
case GDK_KEY_P:
case GDK_KEY_p:
- plugin_manager_show (*this);
+ g_action_group_activate_action (*this, "plugins-configure", nullptr);
break;
case GDK_KEY_f:
@@ -1262,7 +1270,17 @@ void gnome_cmd_main_win_update_bookmarks(GnomeCmdMainWin *main_win)
main_win->update_bookmarks();
}
+void gnome_cmd_main_win_plugins_updated (GnomeCmdMainWin *main_win)
+{
+ main_win->plugins_updated();
+}
+
GnomeCmdShortcuts *gnome_cmd_main_win_shortcuts(GnomeCmdMainWin *main_win)
{
return main_win->priv->gcmd_shortcuts;
}
+
+GObject *gnome_cmd_main_win_get_plugin_manager (GnomeCmdMainWin *main_win)
+{
+ return main_win->priv->plugin_manager;
+}
diff --git a/src/gnome-cmd-main-win.h b/src/gnome-cmd-main-win.h
index f8effd11106752ba4a799050588ed5d0704987c1..55f61ebabc2a0fdf11f5d1031bf71dc27a74c433 100644
--- a/src/gnome-cmd-main-win.h
+++ b/src/gnome-cmd-main-win.h
@@ -23,7 +23,6 @@
#include "gnome-cmd-file-selector.h"
#include "gnome-cmd-cmdline.h"
-#include "plugin_manager.h"
#include "dialogs/gnome-cmd-advrename-dialog.h"
#include "dialogs/gnome-cmd-search-dialog.h"
@@ -114,5 +113,10 @@ extern "C" void gnome_cmd_main_win_focus_file_lists(GnomeCmdMainWin *main_win);
extern "C" void gnome_cmd_main_win_update_bookmarks(GnomeCmdMainWin *main_win);
+extern "C" void gnome_cmd_main_win_plugins_updated (GnomeCmdMainWin *main_win);
+
struct GnomeCmdShortcuts;
extern "C" GnomeCmdShortcuts *gnome_cmd_main_win_shortcuts(GnomeCmdMainWin *main_win);
+
+extern "C" GObject *gnome_cmd_main_win_get_plugin_manager (GnomeCmdMainWin *main_win);
+
diff --git a/src/gnome-cmd-user-actions.cc b/src/gnome-cmd-user-actions.cc
index 4e2598c07c69da4c1c65bedb63f4b8fba87b3f58..524d5513a6cdd29c0f591d87d186d38f8d45919e 100644
--- a/src/gnome-cmd-user-actions.cc
+++ b/src/gnome-cmd-user-actions.cc
@@ -33,7 +33,6 @@
#include "gnome-cmd-main-win.h"
#include "gnome-cmd-user-actions.h"
#include "gnome-cmd-dir-indicator.h"
-#include "plugin_manager.h"
#include "cap.h"
#include "utils.h"
#include "dialogs/gnome-cmd-advrename-dialog.h"
@@ -1216,13 +1215,3 @@ void bookmarks_view (GSimpleAction *action, GVariant *parameter, gpointer user_d
gnome_cmd_dir_indicator_show_bookmarks (GNOME_CMD_DIR_INDICATOR (main_win->fs (ACTIVE)->dir_indicator));
}
-
-
-/************** Plugins Menu **************/
-
-void plugins_configure (GSimpleAction *action, GVariant *parameter, gpointer user_data)
-{
- auto main_win = static_cast(user_data);
-
- plugin_manager_show (GTK_WINDOW (main_win));
-}
diff --git a/src/libgcmd/configurable.rs b/src/libgcmd/configurable.rs
new file mode 100644
index 0000000000000000000000000000000000000000..508ef4c20f334be5c8613700fea8fe76aef7a1d7
--- /dev/null
+++ b/src/libgcmd/configurable.rs
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2025 Andrey Kutejko
+ *
+ * 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 .
+ *
+ * For more details see the file COPYING.
+ */
+
+use gtk::glib::{self, prelude::*, translate::*};
+
+pub mod ffi {
+ use gtk::{ffi::GtkWindow, glib::ffi::GType};
+
+ #[repr(C)]
+ pub struct GnomeCmdConfigurable {
+ _data: [u8; 0],
+ _marker: std::marker::PhantomData<(*mut u8, std::marker::PhantomPinned)>,
+ }
+
+ extern "C" {
+ pub fn gnome_cmd_configurable_get_type() -> GType;
+
+ pub fn gnome_cmd_configurable_configure(
+ cfgbl: *mut GnomeCmdConfigurable,
+ parent_window: *const GtkWindow,
+ );
+ }
+}
+
+glib::wrapper! {
+ pub struct Configurable(Interface);
+
+ match fn {
+ type_ => || ffi::gnome_cmd_configurable_get_type(),
+ }
+}
+
+pub trait ConfigurableExt: IsA + 'static {
+ fn configure(&self, parent_window: >k::Window) {
+ unsafe {
+ ffi::gnome_cmd_configurable_configure(
+ self.as_ref().to_glib_none().0,
+ parent_window.to_glib_none().0,
+ )
+ }
+ }
+}
+
+impl> ConfigurableExt for O {}
diff --git a/src/libgcmd/mod.rs b/src/libgcmd/mod.rs
index d870f880f8b72a79b12f8e942be30c63db830b7e..90ca16fc33b9967a8bb898acf84c1b35aded471d 100644
--- a/src/libgcmd/mod.rs
+++ b/src/libgcmd/mod.rs
@@ -17,6 +17,24 @@
* For more details see the file COPYING.
*/
+pub mod configurable;
pub mod file_actions;
pub mod file_descriptor;
pub mod state;
+
+use std::ffi::c_char;
+
+pub const GNOME_CMD_PLUGIN_SYSTEM_CURRENT_VERSION: u32 = 1;
+
+#[repr(C)]
+pub struct PluginInfo {
+ pub plugin_system_version: u32,
+ pub name: *const c_char,
+ pub version: *const c_char,
+ pub copyright: *const c_char,
+ pub comments: *const c_char,
+ pub authors: *const *const c_char,
+ pub documenters: *const *const c_char,
+ pub translator: *const c_char,
+ pub webpage: *const c_char,
+}
diff --git a/src/main.rs b/src/main.rs
index 8989adb63aad4f98e15614be05bab32ed0143348..44c54253a7cb17ed07dee10ad1cfc023e431e359 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -60,6 +60,8 @@ mod types;
mod user_actions;
mod utils;
+mod gmodule;
+
use application::GnomeCmdApplication;
use gtk::{glib, prelude::*};
use std::error::Error;
diff --git a/src/main_win.rs b/src/main_win.rs
index 15417a04706fe4823f280695dd6cc44738334233..a693ec041f1f96f8c8d58cd0ca76440eb81593fc 100644
--- a/src/main_win.rs
+++ b/src/main_win.rs
@@ -32,7 +32,7 @@ use crate::{
file_actions::{FileActions, FileActionsExt},
state::{State, StateExt},
},
- plugin_manager::{plugin_manager_get_active_plugins, wrap_plugin_menu},
+ plugin_manager::{wrap_plugin_menu, PluginManager},
shortcuts::Shortcuts,
types::FileSelectorID,
user_actions,
@@ -50,9 +50,8 @@ use gtk::{
};
pub mod ffi {
+ use super::*;
use crate::file_selector::ffi::GnomeCmdFileSelector;
- use crate::shortcuts::Shortcuts;
- use crate::types::FileSelectorID;
use gtk::glib::ffi::GType;
#[repr(C)]
@@ -79,6 +78,10 @@ pub mod ffi {
pub fn gnome_cmd_main_win_update_bookmarks(main_win: *mut GnomeCmdMainWin);
pub fn gnome_cmd_main_win_shortcuts(main_win: *mut GnomeCmdMainWin) -> *mut Shortcuts;
+
+ pub fn gnome_cmd_main_win_get_plugin_manager(
+ main_win: *mut GnomeCmdMainWin,
+ ) -> *mut ::GlibType;
}
#[derive(Copy, Clone)]
@@ -137,6 +140,14 @@ impl MainWindow {
state
}
+
+ pub fn plugin_manager(&self) -> PluginManager {
+ unsafe {
+ from_glib_none(ffi::gnome_cmd_main_win_get_plugin_manager(
+ self.to_glib_none().0,
+ ))
+ }
+ }
}
#[no_mangle]
@@ -145,7 +156,7 @@ pub extern "C" fn gnome_cmd_main_win_install_actions(mw_ptr: *mut ffi::GnomeCmdM
mw.add_action_entries(user_actions::USER_ACTIONS.iter().map(|a| a.action_entry()));
}
-fn main_menu() -> gio::Menu {
+fn main_menu(main_win: &MainWindow) -> gio::Menu {
let menu = gio::Menu::new();
menu.append_submenu(Some(&gettext("_File")), &{
@@ -356,7 +367,7 @@ fn main_menu() -> gio::Menu {
.section({
gio::Menu::new().item(gettext("_Configure Plugins…"), "win.plugins-configure")
})
- .section(create_plugins_menu())
+ .section(create_plugins_menu(main_win))
});
menu.append_submenu(Some(&gettext("_Settings")), &{
@@ -463,9 +474,9 @@ fn create_bookmarks_menu() -> gio::Menu {
menu
}
-fn create_plugins_menu() -> gio::Menu {
+fn create_plugins_menu(main_win: &MainWindow) -> gio::Menu {
let menu = gio::Menu::new();
- for (action_group_name, plugin) in plugin_manager_get_active_plugins() {
+ for (action_group_name, plugin) in main_win.plugin_manager().active_plugins() {
if let Some(file_actions) = plugin.downcast_ref::() {
if let Some(plugin_menu) = file_actions.create_main_menu() {
let plugin_menu = wrap_plugin_menu(&action_group_name, &plugin_menu);
@@ -482,7 +493,7 @@ pub extern "C" fn gnome_cmd_main_menu_new(
initial: gboolean,
) -> *mut GMenu {
let mw: MainWindow = unsafe { from_glib_none(mw_ptr) };
- let menu = main_menu();
+ let menu = main_menu(&mw);
if initial != 0 {
let shortcuts = extract_menu_shortcuts(menu.upcast_ref());
let shortcuts_controller = gtk::ShortcutController::for_model(&shortcuts);
diff --git a/src/meson.build b/src/meson.build
index 11414093df8aed1c95d0112b4ea5b84613cb7b1e..6f31d7f98cb986d42df52173ebd981652d43a7c9 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -44,7 +44,6 @@ sources_headers = files(
'gnome-cmd-xfer.h',
'history.h',
'imageloader.h',
- 'plugin_manager.h',
'utils.h',
'gnome-cmd-dialog.h',
'text-utils.h',
@@ -91,7 +90,6 @@ sources_sources = files(
'gnome-cmd-xfer.cc',
'history.cc',
'imageloader.cc',
- 'plugin_manager.cc',
'utils.cc',
'gnome-cmd-dialog.cc',
'text-utils.cc',
diff --git a/src/plugin_manager.cc b/src/plugin_manager.cc
deleted file mode 100644
index e529b7a547c3b3fdb0efb531d3b547b173c4345f..0000000000000000000000000000000000000000
--- a/src/plugin_manager.cc
+++ /dev/null
@@ -1,457 +0,0 @@
-/**
- * @file plugin_manager.cc
- * @copyright (C) 2001-2006 Marcus Bjurman\n
- * @copyright (C) 2007-2012 Piotr Eljasiak\n
- * @copyright (C) 2013-2024 Uwe Scholz\n
- *
- * @copyright 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 2 of the License, or
- * (at your option) any later version.
- *
- * @copyright 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.
- *
- * @copyright You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include
-#include
-
-#include "gnome-cmd-includes.h"
-#include "gnome-cmd-data.h"
-#include "plugin_manager.h"
-#include "utils.h"
-#include "imageloader.h"
-#include "gnome-cmd-main-win.h"
-#include "gnome-cmd-about-plugin.h"
-#include "dirlist.h"
-#include "gnome-cmd-dialog.h"
-#include "widget-factory.h"
-
-using namespace std;
-
-
-// The names of these functions shall never change
-#define MODULE_INIT_FUNC "create_plugin"
-#define MODULE_INFO_FUNC "get_plugin_info"
-
-
-static GList *plugins = nullptr;
-
-gchar* get_plugin_config_location();
-
-typedef GObject *(*PluginConstructorFunc)(void);
-
-
-static void load_plugin (PluginData *data)
-{
- GModule *module = g_module_open (data->fpath, G_MODULE_BIND_LAZY);
- PluginInfoFunc info_func;
- PluginConstructorFunc init_func;
- GObject *plugin;
-
- if (!module)
- {
- g_printerr ("ERROR: Failed to load the plugin '%s': %s\n", data->fname, g_module_error ());
- return;
- }
-
- // Try to get a reference to the "get_plugin_info" function
- if (!g_module_symbol (module, MODULE_INFO_FUNC, (gpointer *) &info_func))
- {
- g_printerr ("ERROR: The plugin-file '%s' has no function named '%s'.\n", data->fname, MODULE_INFO_FUNC);
- g_module_close (module);
- return;
- }
-
- // Try to get the plugin info
- data->info = info_func ();
- if (!data->info)
- {
- g_printerr ("ERROR: The plugin-file '%s' did not return valid plugin info:\n", data->fname);
- g_printerr (" The function '%s' returned NULL\n", MODULE_INFO_FUNC);
- g_module_close (module);
- return;
- }
-
- // Check that the plugin is compatible
- if (data->info->plugin_system_version != GNOME_CMD_PLUGIN_SYSTEM_CURRENT_VERSION)
- {
- g_printerr ("ERROR: The plugin '%s' is not compatible with this version of %s:\n", data->info->name, PACKAGE);
- g_printerr (" Plugin system supported by the plugin is %d, it should be %d\n", data->info->plugin_system_version,
- GNOME_CMD_PLUGIN_SYSTEM_CURRENT_VERSION);
- return;
- }
-
- // Try to get a reference to the "create_plugin" function
- if (!g_module_symbol (module, MODULE_INIT_FUNC, (gpointer *) &init_func))
- {
- g_printerr ("ERROR: The plugin '%s' has no '%s' function\n", data->info->name, MODULE_INIT_FUNC);
- g_module_close (module);
- return;
- }
-
- // Try to initialize the plugin
- plugin = init_func ();
- if (!plugin)
- {
- g_printerr ("ERROR: The plugin '%s' could not be initialized:\n", data->info->name);
- g_printerr (" The '%s' function returned NULL\n", MODULE_INIT_FUNC);
- g_module_close (module);
- return;
- }
-
- // All is OK, everyone is happy
- data->plugin = plugin;
- data->module = module;
- data->loaded = TRUE;
-}
-
-
-static void activate_plugin (PluginData *data)
-{
- if (data->active)
- return;
-
- if (!data->loaded)
- load_plugin (data);
-
- if (!data->loaded)
- return;
-
- data->active = TRUE;
-}
-
-
-static void inactivate_plugin (PluginData *data)
-{
- if (!data->active)
- return;
-
- data->active = FALSE;
-}
-
-
-static void scan_plugins_in_dir (const gchar *dpath)
-{
- static int index = 0;
- auto gFileInfoList = sync_dir_list(dpath);
-
- if (g_list_length(gFileInfoList) == 0)
- return;
-
- for (auto gFileInfoListItem = gFileInfoList; gFileInfoListItem; gFileInfoListItem = gFileInfoListItem->next)
- {
- auto gFileInfo = (GFileInfo*) gFileInfoListItem->data;
- if (g_file_info_get_file_type(gFileInfo) != G_FILE_TYPE_REGULAR)
- continue;
- if (!g_str_has_suffix (g_file_info_get_name(gFileInfo), "." G_MODULE_SUFFIX))
- continue;
-
- // the direntry has the correct extension and is a regular file, let's accept it
- PluginData *data = g_new0 (PluginData, 1);
- data->fname = g_strdup (g_file_info_get_name(gFileInfo));
- data->fpath = g_build_filename (dpath, g_file_info_get_name(gFileInfo), nullptr);
- data->loaded = FALSE;
- data->active = FALSE;
- data->autoload = FALSE;
- data->action_group_name = g_strdup_printf ("plugin%d", index++);
- activate_plugin (data);
- if (!data->loaded)
- {
- g_free (data->fname);
- g_free (data->fpath);
- g_free (data->action_group_name);
- g_free (data);
- }
- else
- plugins = g_list_append (plugins, data);
- }
- g_list_free_full(gFileInfoList, g_object_unref);
-}
-
-
-void plugin_manager_init ()
-{
- if (plugins)
- {
- g_warning ("plugin_manager already initiated");
- return;
- }
-
- // find user plugins
- gchar *user_dir = get_plugin_config_location();
- if (!is_dir_existing(user_dir))
- {
- create_dir (user_dir);
- }
-
- scan_plugins_in_dir (user_dir);
-
- g_free (user_dir);
-
- // find system plugins
- scan_plugins_in_dir (PLUGIN_DIR);
-
- // activate plugins
- for (GList *l=gnome_cmd_data_get_auto_load_plugins (); l; l=l->next)
- {
- char *name = (gchar *) l->data;
-
- for (GList *l2 = plugins; l2; l2 = l2->next)
- {
- auto data = static_cast (l2->data);
- if (strcmp (name, data->fname) == 0)
- data->autoload = TRUE;
- }
- }
-
- // deactivate plugins that shouldn't be autoloaded
- for (GList *l=plugins; l; l=l->next)
- {
- auto data = static_cast (l->data);
- if (!data->autoload)
- inactivate_plugin (data);
- }
-
- main_win->plugins_updated();
-}
-
-
-void plugin_manager_shutdown ()
-{
- GList *out = nullptr;
-
- for (GList *l=plugins; l; l=l->next)
- {
- auto data = static_cast (l->data);
- if (data->active)
- out = g_list_append (out, data->fname);
- }
-
- gnome_cmd_data_set_auto_load_plugins (out);
-}
-
-
-GList *plugin_manager_get_all ()
-{
- return plugins;
-}
-
-
-static PluginData *get_selected_plugin (GtkTreeView *view)
-{
- GtkTreeModel *model;
- GtkTreeIter iter;
- PluginData *data;
-
- g_return_val_if_fail (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (view), &model, &iter), nullptr);
- gtk_tree_model_get (model, &iter, 4, &data, -1);
- return data;
-}
-
-
-static void on_plugin_selection_changed (GtkTreeSelection *selection, GtkWidget *dialog)
-{
- GtkWidget *toggle_button = lookup_widget (dialog, "toggle_button");
- GtkWidget *conf_button = lookup_widget (dialog, "conf_button");
- GtkWidget *about_button = lookup_widget (dialog, "about_button");
- GtkTreeModel *model;
- GtkTreeIter iter;
- PluginData *data;
-
- if (gtk_tree_selection_get_selected (selection, &model, &iter))
- {
- gtk_tree_model_get (model, &iter, 4, &data, -1);
- gtk_widget_set_sensitive (about_button, TRUE);
- gtk_button_set_label (GTK_BUTTON (toggle_button), data->active ? _("Disable") : _("Enable"));
- gtk_widget_set_sensitive (toggle_button, TRUE);
- gtk_widget_set_sensitive (conf_button, data->active);
- }
- else
- {
- gtk_widget_set_sensitive (toggle_button, FALSE);
- gtk_widget_set_sensitive (about_button, FALSE);
- gtk_widget_set_sensitive (conf_button, FALSE);
- }
-}
-
-
-static void update_plugin_list (GtkTreeView *view, GtkWidget *dialog)
-{
- GtkTreeModel *model = gtk_tree_view_get_model (view);
- GtkTreeIter iter;
- gboolean only_update = gtk_tree_model_get_iter_first (model, &iter);
- GtkListStore *store = GTK_LIST_STORE (model);
-
- GIcon *exec_icon = g_themed_icon_new ("system-run");
-
- for (GList *i=plugins; i; i=i->next)
- {
- auto data = static_cast (i->data);
-
- if (only_update)
- {
- gtk_list_store_set (store, &iter,
- 0, data->active ? exec_icon : nullptr,
- 1, (gchar *) data->info->name,
- 4, data,
- -1);
- gtk_tree_model_iter_next (model, &iter);
- }
- else
- {
- gtk_list_store_append (store, &iter);
- gtk_list_store_set (store, &iter,
- 0, data->active ? exec_icon : nullptr,
- 1, (gchar *) data->info->name,
- 2, (gchar *) data->info->version,
- 3, data->fname,
- 4, data,
- -1);
- }
- }
-
- on_plugin_selection_changed (gtk_tree_view_get_selection (view), dialog);
-}
-
-
-inline void do_toggle (GtkWidget *dialog)
-{
- GtkTreeView *view = GTK_TREE_VIEW (lookup_widget (dialog, "avail_view"));
- PluginData *data = get_selected_plugin (view);
-
- if (!data) return;
-
- if (data->active)
- inactivate_plugin (data);
- else
- activate_plugin (data);
-
- update_plugin_list (view, dialog);
- main_win->plugins_updated();
-}
-
-
-static void on_plugin_activated (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, GtkWidget *dialog)
-{
- do_toggle (dialog);
-}
-
-
-static void on_toggle (GtkButton *button, GtkWidget *dialog)
-{
- do_toggle (dialog);
-}
-
-
-static void on_configure (GtkButton *button, GtkWidget *dialog)
-{
- GtkTreeView *view = GTK_TREE_VIEW (lookup_widget (dialog, "avail_view"));
- PluginData *data = get_selected_plugin (view);
-
- g_return_if_fail (data != nullptr);
- g_return_if_fail (data->active);
-
- if (GNOME_CMD_IS_CONFIGURABLE (data->plugin))
- gnome_cmd_configurable_configure (GNOME_CMD_CONFIGURABLE (data->plugin), GTK_WINDOW (dialog));
-}
-
-
-static void on_about (GtkButton *button, GtkWidget *dialog)
-{
- GtkTreeView *view = GTK_TREE_VIEW (lookup_widget (dialog, "avail_view"));
- PluginData *data = get_selected_plugin (view);
-
- g_return_if_fail (data != nullptr);
-
- GtkWidget *about = gnome_cmd_about_plugin_new (data->info);
-
- gtk_window_set_transient_for (GTK_WINDOW (about), GTK_WINDOW (dialog));
- gtk_window_present (GTK_WINDOW (about));
-}
-
-
-static void on_close (GtkButton *button, GtkWidget *dialog)
-{
- gtk_window_destroy (GTK_WINDOW (dialog));
-}
-
-
-void plugin_manager_show (GtkWindow *parent_window)
-{
- GtkWidget *dialog, *hbox, *bbox, *button;
- GtkListStore *avail_store;
- GtkWidget *avail_view;
-
- dialog = gnome_cmd_dialog_new (parent_window, _("Available plugins"));
- g_object_ref (dialog);
-
- hbox = create_vbox (dialog, FALSE, 6);
- avail_store = gtk_list_store_new (5, G_TYPE_ICON, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
- avail_view = create_treeview (dialog, "avail_view", GTK_TREE_MODEL (avail_store), 20, G_CALLBACK (on_plugin_selection_changed), nullptr);
- gtk_widget_set_hexpand (GTK_WIDGET (avail_view), TRUE);
- gtk_widget_set_vexpand (GTK_WIDGET (avail_view), TRUE);
- create_treeview_column (avail_view, 0, 20, "");
- create_treeview_column (avail_view, 1, 200, _("Name"));
- create_treeview_column (avail_view, 2, 50, _("Version"));
- create_treeview_column (avail_view, 3, 50, _("File"));
-
- bbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
- GtkSizeGroup* bbox_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
-
- button = create_button (GTK_WIDGET (dialog), _("_Enable"), G_CALLBACK (on_toggle));
- g_object_set_data (G_OBJECT (dialog), "toggle_button", button);
- gtk_size_group_add_widget (bbox_size_group, button);
- gtk_box_append (GTK_BOX (bbox), button);
-
- button = create_button (GTK_WIDGET (dialog), _("_Configure"), G_CALLBACK (on_configure));
- g_object_set_data (G_OBJECT (dialog), "conf_button", button);
- gtk_widget_set_sensitive (button, FALSE);
- gtk_size_group_add_widget (bbox_size_group, button);
- gtk_box_append (GTK_BOX (bbox), button);
-
- button = create_button (GTK_WIDGET (dialog), _("_About"), G_CALLBACK (on_about));
- g_object_set_data (G_OBJECT (dialog), "about_button", button);
- gtk_widget_set_sensitive (button, FALSE);
- gtk_size_group_add_widget (bbox_size_group, button);
- gtk_box_append (GTK_BOX (bbox), button);
-
- gtk_box_append (GTK_BOX (hbox), avail_view);
- gtk_box_append (GTK_BOX (hbox), bbox);
-
- gnome_cmd_dialog_add_expanding_category (GNOME_CMD_DIALOG (dialog), hbox);
-
- avail_view = lookup_widget (avail_view, "avail_view");
- g_signal_connect (GTK_TREE_VIEW (avail_view), "row-activated", G_CALLBACK (on_plugin_activated), dialog);
-
- update_plugin_list (GTK_TREE_VIEW (avail_view), dialog);
-
- gnome_cmd_dialog_add_button (GNOME_CMD_DIALOG (dialog), _("_Close"), G_CALLBACK(on_close), dialog);
-
- gtk_widget_set_size_request (GTK_WIDGET (dialog), 500, 300);
- gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);
- gtk_window_present (GTK_WINDOW (dialog));
-}
-
-gchar* get_plugin_config_location()
-{
- gchar* returnString {nullptr};
-
- string userPluginConfigDir = get_package_config_dir();
- userPluginConfigDir += (char*) "/plugins";
-
- if (!is_dir_existing(userPluginConfigDir.c_str()))
- {
- create_dir (userPluginConfigDir.c_str());
- }
-
- returnString = g_strdup(userPluginConfigDir.c_str());
-
- return returnString;
-}
diff --git a/src/plugin_manager.h b/src/plugin_manager.h
deleted file mode 100644
index f47c32a6e34e9fee2a3559fdd63730f67085ad69..0000000000000000000000000000000000000000
--- a/src/plugin_manager.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * @file plugin_manager.h
- * @copyright (C) 2001-2006 Marcus Bjurman\n
- * @copyright (C) 2007-2012 Piotr Eljasiak\n
- * @copyright (C) 2013-2024 Uwe Scholz\n
- *
- * @copyright 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 2 of the License, or
- * (at your option) any later version.
- *
- * @copyright 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.
- *
- * @copyright You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#pragma once
-
-#include
-
-struct PluginData
-{
- gboolean active;
- gboolean loaded;
- gboolean autoload;
- gchar *fname;
- gchar *fpath;
- gchar *action_group_name;
- GObject *plugin;
- PluginInfo *info;
- GModule *module;
-};
-
-
-void plugin_manager_init ();
-void plugin_manager_shutdown ();
-extern "C" GList *plugin_manager_get_all ();
-void plugin_manager_show (GtkWindow *parent_window);
-
-extern "C" GMenu *gnome_cmd_wrap_plugin_menu (const gchar *plugin, GMenuModel *menu);
diff --git a/src/plugin_manager.rs b/src/plugin_manager.rs
index 90adf6e382c0fed9db8effa32d5dcb7c225fbbd8..16e194b85b301a1b8a0390f57c3189e49a784c2c 100644
--- a/src/plugin_manager.rs
+++ b/src/plugin_manager.rs
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 Andrey Kutejko
+ * Copyright 2024-2025 Andrey Kutejko
*
* 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
@@ -17,16 +17,311 @@
* For more details see the file COPYING.
*/
+use crate::{
+ config::{PACKAGE, PLUGIN_DIR},
+ gmodule::{self, GModule, GModuleFlags},
+ libgcmd::{
+ configurable::{Configurable, ConfigurableExt},
+ PluginInfo, GNOME_CMD_PLUGIN_SYSTEM_CURRENT_VERSION,
+ },
+ utils::{dialog_button_box, handle_escape_key, NO_BUTTONS},
+};
+use gettextrs::gettext;
use gtk::{
- gio::{self, ffi::GMenu},
+ gio,
glib::{
- ffi::{gboolean, GList},
+ ffi::{gpointer, GType},
gobject_ffi::GObject,
- translate::{from_glib_full, from_glib_none, ToGlibPtr},
+ translate::{from_glib_full, from_glib_none, FromGlib, IntoGlib},
},
prelude::*,
+ subclass::prelude::*,
};
-use std::ffi::{c_char, c_void};
+use std::{
+ fs, io,
+ path::{Path, PathBuf},
+};
+
+const GCMD_PREF_PLUGINS: &str = "org.gnome.gnome-commander.plugins.general";
+
+mod imp {
+ use super::*;
+ use glib::subclass::Signal;
+ use std::{cell::RefCell, sync::OnceLock};
+
+ #[derive(Default)]
+ pub struct PluginManager {
+ pub plugins: RefCell>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for PluginManager {
+ const NAME: &'static str = "GnomeCmdPluginManager";
+ type Type = super::PluginManager;
+ }
+
+ impl ObjectImpl for PluginManager {
+ fn constructed(&self) {
+ self.parent_constructed();
+ self.obj().init();
+ }
+
+ fn dispose(&self) {
+ self.obj().save();
+ }
+
+ fn signals() -> &'static [Signal] {
+ static SIGNALS: OnceLock> = OnceLock::new();
+ SIGNALS.get_or_init(|| vec![Signal::builder("plugins-changed").build()])
+ }
+ }
+}
+
+glib::wrapper! {
+ pub struct PluginManager(ObjectSubclass);
+}
+
+impl PluginManager {
+ fn init(&self) {
+ let user_plugin_dir = glib::user_config_dir().join(PACKAGE).join("plugins");
+ if let Err(error) = self.scan_plugins_in_dir(&user_plugin_dir) {
+ eprintln!(
+ "ERROR: Failed to load plugins from {}: {}",
+ user_plugin_dir.display(),
+ error
+ );
+ }
+ if let Err(error) = self.scan_plugins_in_dir(&Path::new(PLUGIN_DIR)) {
+ eprintln!(
+ "ERROR: Failed to load plugins from {}: {}",
+ PLUGIN_DIR, error
+ );
+ }
+ for name in gio::Settings::new(GCMD_PREF_PLUGINS).strv("autoload") {
+ for data in self.imp().plugins.borrow_mut().iter_mut() {
+ if data.file_name == name {
+ data.active = true;
+ }
+ }
+ }
+ }
+
+ fn save(&self) {
+ let plugins = self.imp().plugins.borrow();
+ let autoload: Vec<&str> = plugins
+ .iter()
+ .filter(|d| d.active)
+ .map(|d| d.file_name.as_str())
+ .collect();
+ if let Err(error) = gio::Settings::new(GCMD_PREF_PLUGINS).set_strv("autoload", autoload) {
+ eprintln!(
+ "ERROR: Failed to save autoload plugin list: {}: {}",
+ PLUGIN_DIR, error.message
+ );
+ }
+ }
+
+ fn scan_plugins_in_dir(&self, path: &Path) -> io::Result<()> {
+ for entry in fs::read_dir(path)? {
+ let entry = entry?;
+ let path = entry.path();
+ let Some(ext) = path.extension() else {
+ continue;
+ };
+ if ext != "so" && ext != "dylib" && ext != "dll" {
+ continue;
+ }
+ let metadata = entry.metadata()?;
+ if !metadata.is_file() {
+ continue;
+ }
+ match load_plugin(path) {
+ Ok(mut data) => {
+ data.action_group_name = format!("plugin{}", self.imp().plugins.borrow().len());
+ self.imp().plugins.borrow_mut().push(data);
+ }
+ Err(error) => {
+ eprintln!("ERROR: {}", error);
+ }
+ }
+ }
+ Ok(())
+ }
+
+ pub fn set_active(&self, plugin: &glib::Object, active: bool) {
+ let mut changed = false;
+ for plugin_data in self.imp().plugins.borrow_mut().iter_mut() {
+ if plugin_data.plugin == *plugin {
+ plugin_data.active = active;
+ changed = true;
+ }
+ }
+ if changed {
+ self.emit_by_name::<()>("plugins-changed", &[]);
+ }
+ }
+
+ pub fn active_plugins(&self) -> Vec<(String, glib::Object)> {
+ self.imp()
+ .plugins
+ .borrow()
+ .iter()
+ .filter(|d| d.active)
+ .map(|d| (d.action_group_name.clone(), d.plugin.clone()))
+ .collect()
+ }
+}
+
+pub struct PluginData {
+ pub active: bool,
+ pub file_name: String,
+ pub path: PathBuf,
+ pub action_group_name: String,
+ pub plugin: glib::Object,
+ pub info: PluginInfoOwned,
+ pub module: gmodule::GModule,
+}
+
+#[derive(Clone)]
+pub struct PluginInfoOwned {
+ pub plugin_system_version: u32,
+ pub name: Option,
+ pub version: Option,
+ pub copyright: Option,
+ pub comments: Option,
+ pub authors: Vec,
+ pub documenters: Vec,
+ pub translator: Option,
+ pub webpage: Option,
+}
+
+impl From<&PluginInfo> for PluginInfoOwned {
+ fn from(value: &PluginInfo) -> Self {
+ unsafe {
+ Self {
+ plugin_system_version: value.plugin_system_version,
+ name: from_glib_none(value.name),
+ version: from_glib_none(value.version),
+ copyright: from_glib_none(value.copyright),
+ comments: from_glib_none(value.comments),
+ authors: glib::StrV::from_glib_none(value.authors)
+ .iter()
+ .map(|s| s.to_string())
+ .collect(),
+ documenters: glib::StrV::from_glib_none(value.documenters)
+ .iter()
+ .map(|s| s.to_string())
+ .collect(),
+ translator: from_glib_none(value.translator),
+ webpage: from_glib_none(value.webpage),
+ }
+ }
+ }
+}
+
+const MODULE_INIT_FUNC: &str = "create_plugin";
+const MODULE_INFO_FUNC: &str = "get_plugin_info";
+
+pub type PluginInfoFunc = unsafe extern "C" fn() -> *const PluginInfo;
+pub type PluginConstructorFunc = unsafe extern "C" fn() -> *mut GObject;
+
+fn load_plugin(path: PathBuf) -> Result {
+ let module = GModule::open(
+ &path,
+ GModuleFlags {
+ lazy: true,
+ local: false,
+ },
+ )
+ .map_err(|error| {
+ format!(
+ "ERROR: Failed to load the plugin '{}': {}",
+ path.display(),
+ error.message()
+ )
+ })?;
+
+ let info_func = module
+ .symbol(MODULE_INFO_FUNC)
+ .ok_or_else(|| {
+ format!(
+ "The plugin file '{}' has no function named '{}'.",
+ path.display(),
+ MODULE_INFO_FUNC
+ )
+ })
+ .and_then(|ptr| {
+ if ptr.is_null() {
+ Err(format!(
+ "The plugin ile '{}' has blank function named '{}'.",
+ path.display(),
+ MODULE_INFO_FUNC
+ ))
+ } else {
+ Ok(unsafe { std::mem::transmute::(ptr) })
+ }
+ })?;
+
+ // Try to get the plugin info
+ let info = unsafe { info_func() };
+ if info.is_null() {
+ return Err(format!("The plugin-file '{}' did not return valid plugin info. The function '{}' returned NULL.", path.display(), MODULE_INFO_FUNC));
+ }
+
+ // Check that the plugin is compatible
+ let plugin_system_version = unsafe { (*info).plugin_system_version };
+ if plugin_system_version != GNOME_CMD_PLUGIN_SYSTEM_CURRENT_VERSION {
+ return Err(format!(
+ "The plugin '{}' is not compatible with this version of {}: Plugin system supported by the plugin is {}, it should be {}.", path.display(), PACKAGE, plugin_system_version, GNOME_CMD_PLUGIN_SYSTEM_CURRENT_VERSION));
+ }
+
+ let info = PluginInfoOwned::from(unsafe { &*info });
+
+ let init_func = module
+ .symbol(MODULE_INIT_FUNC)
+ .ok_or_else(|| {
+ format!(
+ "The plugin '{}' has no function named '{}'.",
+ path.display(),
+ MODULE_INIT_FUNC
+ )
+ })
+ .and_then(|ptr| {
+ if ptr.is_null() {
+ Err(format!(
+ "The plugin '{}' has blank function named '{}'.",
+ path.display(),
+ MODULE_INIT_FUNC
+ ))
+ } else {
+ Ok(unsafe { std::mem::transmute::(ptr) })
+ }
+ })?;
+
+ // Try to initialize the plugin
+ let plugin = unsafe { init_func() };
+ if plugin.is_null() {
+ return Err(format!(
+ "The plugin '{}' could not be initialized: The '{}' function returned NULL",
+ path.display(),
+ MODULE_INIT_FUNC
+ ));
+ }
+
+ Ok(PluginData {
+ file_name: path
+ .file_name()
+ .and_then(|s| s.to_str())
+ .unwrap_or_default()
+ .to_owned(),
+ path,
+ active: false,
+ action_group_name: String::new(),
+ module,
+ info,
+ plugin: unsafe { from_glib_full(plugin) },
+ })
+}
#[derive(Debug, glib::Variant)]
pub struct PluginActionVariant {
@@ -78,70 +373,193 @@ pub fn wrap_plugin_menu(plugin: &str, menu: &gio::MenuModel) -> gio::Menu {
new_menu
}
-#[no_mangle]
-pub extern "C" fn gnome_cmd_wrap_plugin_menu(
- plugin_ptr: *const c_char,
- menu_ptr: *mut GMenu,
-) -> *mut GMenu {
- if plugin_ptr.is_null() || menu_ptr.is_null() {
- return std::ptr::null_mut();
- }
+pub fn show_plugin_manager(plugin_manager: &PluginManager, parent_window: >k::Window) {
+ let dialog = gtk::Window::builder()
+ .transient_for(parent_window)
+ .title(gettext("Available plugins"))
+ .modal(true)
+ .width_request(500)
+ .height_request(300)
+ .resizable(true)
+ .build();
- let plugin: String = unsafe { from_glib_none(plugin_ptr) };
- let menu: gio::Menu = unsafe { from_glib_full(menu_ptr) };
+ let grid = gtk::Grid::builder()
+ .margin_top(12)
+ .margin_bottom(12)
+ .margin_start(12)
+ .margin_end(12)
+ .row_spacing(6)
+ .column_spacing(12)
+ .build();
+ dialog.set_child(Some(&grid));
- let new_menu = wrap_plugin_menu(&plugin, menu.upcast_ref());
- new_menu.to_glib_full()
-}
+ let view = gtk::ListBox::builder()
+ .selection_mode(gtk::SelectionMode::None)
+ .build();
+ view.add_css_class("rich-list");
-extern "C" {
- fn plugin_manager_get_all() -> *const GList;
-}
+ let sw = gtk::ScrolledWindow::builder()
+ .child(&view)
+ .hexpand(true)
+ .vexpand(true)
+ .has_frame(true)
+ .build();
+ grid.attach(&sw, 0, 0, 1, 1);
-#[repr(C)]
-#[derive(Clone)]
-struct PluginData {
- active: gboolean,
- loaded: gboolean,
- autoload: gboolean,
- fname: *const c_char,
- fpath: *const c_char,
- action_group_name: *const c_char,
- plugin: *const GObject,
- info: *const c_void, /* PluginInfo */
- module: *const c_void, /* GModule */
-}
+ let close_button = gtk::Button::builder()
+ .label(gettext("_Close"))
+ .use_underline(true)
+ .build();
+ close_button.connect_clicked(glib::clone!(
+ #[weak]
+ dialog,
+ move |_| dialog.close()
+ ));
+ grid.attach(&dialog_button_box(NO_BUTTONS, &[close_button]), 0, 1, 1, 1);
-pub fn plugin_manager_get_active_plugin(action_group_name: &str) -> Option {
- unsafe {
- let mut list = plugin_manager_get_all();
- while !list.is_null() {
- let plugin_data = (*list).data as *const PluginData;
- if (*plugin_data).active != 0 {
- let name: String = from_glib_none((*plugin_data).action_group_name);
- if name == action_group_name {
- return Some(from_glib_none((*plugin_data).plugin));
- }
+ handle_escape_key(
+ &dialog,
+ >k::CallbackAction::new(glib::clone!(
+ #[weak]
+ dialog,
+ #[upgrade_or]
+ glib::Propagation::Proceed,
+ move |_, _| {
+ dialog.close();
+ glib::Propagation::Proceed
}
- list = (*list).next;
- }
+ )),
+ );
+
+ for plugin_data in plugin_manager.imp().plugins.borrow().iter() {
+ view.append(&create_plugin_widget(plugin_manager, plugin_data));
}
- None
+
+ dialog.present();
}
-pub fn plugin_manager_get_active_plugins() -> Vec<(String, glib::Object)> {
- let mut result = Vec::new();
- unsafe {
- let mut list = plugin_manager_get_all();
- while !list.is_null() {
- let plugin_data = (*list).data as *const PluginData;
- if (*plugin_data).active != 0 {
- let name: String = from_glib_none((*plugin_data).action_group_name);
- let plugin: glib::Object = from_glib_none((*plugin_data).plugin);
- result.push((name, plugin));
+fn create_plugin_widget(plugin_manager: &PluginManager, plugin_data: &PluginData) -> gtk::Widget {
+ let info = plugin_data.info.clone();
+ let plugin = plugin_data.plugin.clone();
+
+ let grid = gtk::Grid::builder()
+ .hexpand(true)
+ .column_spacing(12)
+ .build();
+
+ let name = gtk::Label::builder()
+ .label(info.name.as_deref().unwrap_or(&plugin_data.file_name))
+ .halign(gtk::Align::Start)
+ .hexpand(true)
+ .build();
+ name.add_css_class("title");
+ grid.attach(&name, 0, 0, 1, 1);
+
+ let version = gtk::Label::builder()
+ .label(gettext("Version: {}").replace("{}", info.version.as_deref().unwrap_or("-")))
+ .halign(gtk::Align::Start)
+ .build();
+ version.add_css_class("dim-label");
+ grid.attach(&version, 0, 1, 1, 1);
+
+ let file = gtk::Label::builder()
+ .label(gettext("File: {}").replace("{}", &plugin_data.file_name))
+ .halign(gtk::Align::Start)
+ .build();
+ file.add_css_class("dim-label");
+ grid.attach(&file, 0, 2, 1, 1);
+
+ let about = gtk::Button::builder()
+ .child(>k::Image::from_icon_name(
+ "gnome-commander-info-outline-symbolic",
+ ))
+ .tooltip_text(gettext("About"))
+ .vexpand(true)
+ .valign(gtk::Align::Center)
+ .build();
+ about.add_css_class("flat");
+ grid.attach(&about, 1, 0, 1, 3);
+
+ let configure = gtk::Button::builder()
+ .child(>k::Image::from_icon_name(
+ "gnome-commander-settings-symbolic",
+ ))
+ .tooltip_text(gettext("Configure"))
+ .vexpand(true)
+ .valign(gtk::Align::Center)
+ .build();
+ configure.add_css_class("flat");
+ grid.attach(&configure, 2, 0, 1, 3);
+
+ let switch = gtk::Switch::builder()
+ .active(plugin_data.active)
+ .vexpand(true)
+ .valign(gtk::Align::Center)
+ .build();
+ grid.attach(&switch, 3, 0, 1, 3);
+
+ about.connect_clicked(move |btn| {
+ if let Some(window) = btn.root().and_downcast::() {
+ extern "C" {
+ fn gnome_cmd_about_plugin_get_type() -> GType;
}
- list = (*list).next;
+
+ let type_ = unsafe { glib::Type::from_glib(gnome_cmd_about_plugin_get_type()) };
+ let about_dialog = glib::object::Object::with_type(type_)
+ .downcast::()
+ .unwrap();
+ about_dialog.set_property("name", &info.name);
+ about_dialog.set_property("version", &info.version);
+ about_dialog.set_property("copyright", &info.copyright);
+ about_dialog.set_property("comments", &info.comments);
+ about_dialog.set_property(
+ "authors",
+ &info
+ .authors
+ .iter()
+ .map(|v| v.to_value())
+ .collect::(),
+ );
+ about_dialog.set_property(
+ "documenters",
+ &info
+ .documenters
+ .iter()
+ .map(|v| v.to_value())
+ .collect::(),
+ );
+ about_dialog.set_property("translator_credits", &info.translator);
+ about_dialog.set_property("webpage", &info.webpage);
+
+ about_dialog.set_transient_for(Some(&window));
+ about_dialog.present();
}
- }
- result
+ });
+
+ configure.connect_clicked(glib::clone!(
+ #[strong]
+ plugin,
+ move |btn| {
+ if let Some(window) = btn.root().and_downcast::() {
+ if let Some(cfg) = plugin.downcast_ref::() {
+ cfg.configure(&window);
+ }
+ }
+ }
+ ));
+
+ switch.connect_active_notify(glib::clone!(
+ #[strong]
+ plugin_manager,
+ #[strong]
+ plugin,
+ move |switch| plugin_manager.set_active(&plugin, switch.is_active())
+ ));
+
+ grid.upcast()
+}
+
+#[no_mangle]
+pub extern "C" fn plugin_manager_get_type() -> GType {
+ PluginManager::static_type().into_glib()
}
diff --git a/src/user_actions.rs b/src/user_actions.rs
index f95dfea53da9595982359df261b5b915114b9666..f063b438643825fce72bf530a4d5175912f87ccf 100644
--- a/src/user_actions.rs
+++ b/src/user_actions.rs
@@ -51,7 +51,7 @@ use crate::{
file_descriptor::FileDescriptorExt,
},
main_win::{ffi::*, MainWindow},
- plugin_manager::{plugin_manager_get_active_plugin, PluginActionVariant},
+ plugin_manager::{show_plugin_manager, PluginActionVariant},
spawn::{spawn_async, spawn_async_command, SpawnError},
types::FileSelectorID,
utils::{display_help, get_modifiers_state, ErrorMessage},
@@ -797,7 +797,14 @@ pub fn connections_close_current(
/************** Plugins Menu ***********/
-c_action!(plugins_configure);
+pub fn plugins_configure(
+ main_win: &MainWindow,
+ _action: &gio::SimpleAction,
+ _parameter: Option<&glib::Variant>,
+) {
+ let plugin_manager = main_win.plugin_manager();
+ show_plugin_manager(&plugin_manager, main_win.upcast_ref());
+}
pub fn plugin_action(
main_win: &MainWindow,
@@ -809,7 +816,12 @@ pub fn plugin_action(
return;
};
- if let Some(plugin) = plugin_manager_get_active_plugin(&plugin_action.plugin) {
+ if let Some(plugin) = main_win
+ .plugin_manager()
+ .active_plugins()
+ .into_iter()
+ .find_map(|(name, plugin)| (name == plugin_action.plugin).then_some(plugin))
+ {
if let Some(file_actions) = plugin.downcast_ref::() {
file_actions.execute(
&plugin_action.action,