From 3b3a6b7a756e0f162d212249750524b7ce045cea Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Wed, 29 Sep 2021 14:21:33 +0200 Subject: [PATCH 1/7] Remove `/python sourcepath` I feel like this mostly is confusing people. Also don't see much value for it. To me it looks like a regular workflow is like: ``` /plugin install ~/src/profanity-plugins/my.py ``` The whole thing with sourcepath, install (which also loads without having that described anywhere), load etc is confusing. Also each plugin file that is present in `.local/share/profanity/plugins` will then be auto loaded. Which means after installation. --- src/command/cmd_ac.c | 18 ----------- src/command/cmd_defs.c | 8 +---- src/command/cmd_funcs.c | 64 +++------------------------------------- src/config/preferences.c | 4 --- src/config/preferences.h | 1 - 5 files changed, 5 insertions(+), 90 deletions(-) diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index e821eb8b..39ab24bc 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -242,7 +242,6 @@ static Autocomplete console_ac; static Autocomplete console_msg_ac; static Autocomplete autoping_ac; static Autocomplete plugins_ac; -static Autocomplete plugins_sourcepath_ac; static Autocomplete plugins_load_ac; static Autocomplete plugins_unload_ac; static Autocomplete plugins_reload_ac; @@ -940,11 +939,6 @@ cmd_ac_init(void) autocomplete_add(plugins_ac, "unload"); autocomplete_add(plugins_ac, "reload"); autocomplete_add(plugins_ac, "python_version"); - autocomplete_add(plugins_ac, "sourcepath"); - - plugins_sourcepath_ac = autocomplete_new(); - autocomplete_add(plugins_sourcepath_ac, "set"); - autocomplete_add(plugins_sourcepath_ac, "clear"); filepath_ac = autocomplete_new(); @@ -1355,7 +1349,6 @@ cmd_ac_reset(ProfWin* window) autocomplete_reset(console_msg_ac); autocomplete_reset(autoping_ac); autocomplete_reset(plugins_ac); - autocomplete_reset(plugins_sourcepath_ac); autocomplete_reset(blocked_ac); autocomplete_reset(tray_ac); autocomplete_reset(presence_ac); @@ -2638,21 +2631,10 @@ _plugins_autocomplete(ProfWin* window, const char* const input, gboolean previou { char* result = NULL; - if (strncmp(input, "/plugins sourcepath set ", 24) == 0) { - return cmd_ac_complete_filepath(input, "/plugins sourcepath set", previous); - } - if (strncmp(input, "/plugins install ", 17) == 0) { return cmd_ac_complete_filepath(input, "/plugins install", previous); } - if (strncmp(input, "/plugins sourcepath ", 20) == 0) { - result = autocomplete_param_with_ac(input, "/plugins sourcepath", plugins_sourcepath_ac, TRUE, previous); - if (result) { - return result; - } - } - if (strncmp(input, "/plugins load ", 14) == 0) { if (plugins_load_ac == NULL) { plugins_load_ac = autocomplete_new(); diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index d86e62a3..898608af 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -2136,7 +2136,6 @@ static struct cmd_t command_defs[] = { { "/plugins", parse_args, 0, 3, NULL, CMD_SUBFUNCS( - { "sourcepath", cmd_plugins_sourcepath }, { "install", cmd_plugins_install }, { "uninstall", cmd_plugins_uninstall }, { "update", cmd_plugins_update }, @@ -2148,8 +2147,6 @@ static struct cmd_t command_defs[] = { CMD_NOTAGS CMD_SYN( "/plugins", - "/plugins sourcepath set ", - "/plugins sourcepath clear", "/plugins install []", "/plugins uninstall []", "/plugins update []", @@ -2160,9 +2157,7 @@ static struct cmd_t command_defs[] = { CMD_DESC( "Manage plugins. Passing no arguments lists currently loaded plugins.") CMD_ARGS( - { "sourcepath set ", "Set the default path to install plugins from, will be used if no arg is passed to /plugins install." }, - { "sourcepath clear", "Clear the default plugins source path." }, - { "install []", "Install a plugin, or all plugins found in a directory (recursive). Passing no argument will use the sourcepath if one is set." }, + { "install []", "Install a plugin, or all plugins found in a directory (recursive)." }, { "uninstall []", "Uninstall a plugin." }, { "update []", "Updates an installed plugin" }, { "load []", "Load a plugin that already exists in the plugin directory, passing no argument loads all found plugins." }, @@ -2170,7 +2165,6 @@ static struct cmd_t command_defs[] = { { "reload []", "Reload a plugin, passing no argument will reload all plugins." }, { "python_version", "Show the Python interpreter version." }) CMD_EXAMPLES( - "/plugins sourcepath set /home/meee/projects/profanity-plugins", "/plugins install", "/plugins install /home/steveharris/Downloads/metal.py", "/plugins update /home/steveharris/Downloads/metal.py", diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index ad1b3331..b970c73b 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -6924,64 +6924,14 @@ cmd_receipts(ProfWin* window, const char* const command, gchar** args) return TRUE; } -gboolean -cmd_plugins_sourcepath(ProfWin* window, const char* const command, gchar** args) -{ - if (args[1] == NULL) { - char* sourcepath = prefs_get_string(PREF_PLUGINS_SOURCEPATH); - if (sourcepath) { - cons_show("Current plugins sourcepath: %s", sourcepath); - g_free(sourcepath); - } else { - cons_show("Plugins sourcepath not currently set."); - } - return TRUE; - } - - if (g_strcmp0(args[1], "clear") == 0) { - prefs_set_string(PREF_PLUGINS_SOURCEPATH, NULL); - cons_show("Plugins sourcepath cleared."); - return TRUE; - } - - if (g_strcmp0(args[1], "set") == 0) { - if (args[2] == NULL) { - cons_bad_cmd_usage(command); - return TRUE; - } - - char* path = get_expanded_path(args[2]); - - if (!is_dir(path)) { - cons_show("Plugins sourcepath must be a directory."); - free(path); - return TRUE; - } - - cons_show("Setting plugins sourcepath: %s", path); - prefs_set_string(PREF_PLUGINS_SOURCEPATH, path); - free(path); - return TRUE; - } - - cons_bad_cmd_usage(command); - return TRUE; -} - gboolean cmd_plugins_install(ProfWin* window, const char* const command, gchar** args) { char* path; if (args[1] == NULL) { - char* sourcepath = prefs_get_string(PREF_PLUGINS_SOURCEPATH); - if (sourcepath) { - path = strdup(sourcepath); - g_free(sourcepath); - } else { - cons_show("Either a path must be provided or the sourcepath property must be set, see /help plugins"); - return TRUE; - } + cons_show("Please provide a path to the plugin file or directory, see /help plugins"); + return TRUE; } else { path = get_expanded_path(args[1]); } @@ -7046,14 +6996,8 @@ cmd_plugins_update(ProfWin* window, const char* const command, gchar** args) char* path; if (args[1] == NULL) { - char* sourcepath = prefs_get_string(PREF_PLUGINS_SOURCEPATH); - if (sourcepath) { - path = strdup(sourcepath); - g_free(sourcepath); - } else { - cons_show("Either a path must be provided or the sourcepath property must be set, see /help plugins"); - return TRUE; - } + cons_show("Please provide a path to the plugin file or directory, see /help plugins"); + return TRUE; } else { path = get_expanded_path(args[1]); } diff --git a/src/config/preferences.c b/src/config/preferences.c index df23efe6..0d913a36 100644 --- a/src/config/preferences.c +++ b/src/config/preferences.c @@ -1928,8 +1928,6 @@ _get_group(preference_t pref) case PREF_BOOKMARK_INVITE: case PREF_ROOM_LIST_CACHE: return PREF_GROUP_MUC; - case PREF_PLUGINS_SOURCEPATH: - return PREF_GROUP_PLUGINS; case PREF_OMEMO_LOG: case PREF_OMEMO_POLICY: case PREF_OMEMO_TRUST_MODE: @@ -2163,8 +2161,6 @@ _get_key(preference_t pref) return "color.occupants.nick"; case PREF_BOOKMARK_INVITE: return "bookmark.invite"; - case PREF_PLUGINS_SOURCEPATH: - return "sourcepath"; case PREF_ROOM_LIST_CACHE: return "rooms.cache"; case PREF_STATUSBAR_SHOW_NAME: diff --git a/src/config/preferences.h b/src/config/preferences.h index f7474c1f..30bd434a 100644 --- a/src/config/preferences.h +++ b/src/config/preferences.h @@ -157,7 +157,6 @@ typedef enum { PREF_ROSTER_COLOR_NICK, PREF_OCCUPANTS_COLOR_NICK, PREF_BOOKMARK_INVITE, - PREF_PLUGINS_SOURCEPATH, PREF_ROOM_LIST_CACHE, PREF_STATUSBAR_SHOW_NAME, PREF_STATUSBAR_SHOW_NUMBER, From ba7b6c2e96be57c24e11c64f4d6e5c97f025516b Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Wed, 29 Sep 2021 17:31:24 +0200 Subject: [PATCH 2/7] Clean sourcepath from profrc See 3b3a6b7a756e0f162d212249750524b7ce045cea for sourcepath removal. --- src/config/preferences.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/config/preferences.c b/src/config/preferences.c index 0d913a36..114455fd 100644 --- a/src/config/preferences.c +++ b/src/config/preferences.c @@ -214,6 +214,11 @@ _prefs_load(void) } } + // 0.12 started to remove `sourcepath` + if (g_key_file_has_key(prefs, PREF_GROUP_PLUGINS, "sourcepath", NULL)) { + g_key_file_remove_key(prefs, PREF_GROUP_PLUGINS, "sourcepath", NULL); + } + _save_prefs(); boolean_choice_ac = autocomplete_new(); From 7486e22b77d2d5467af2b21d3e38ba66a5bddf69 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Wed, 29 Sep 2021 14:48:33 +0200 Subject: [PATCH 3/7] Look for plugins to install in global location Two options to install plugins. Mention the whole path: `/plugins install ~/src/profanity-plugins/my.py` Mention only the plugin name: `/plugins install my.py` The latter will look in `/usr/local/share/profanity/plugins/` for the file and copy it over to `~/.local/share/profanity/plugins`. At first I was thinking about loading the plugins from the global location. But users most likely don't want to have all plugins activated that an admin installs on a system. Regards https://github.com/profanity-im/profanity/issues/945 --- configure.ac | 28 ++++++++++++++++++---------- src/command/cmd_funcs.c | 22 ++++++++++++++++++++-- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index 3151cdc8..2061a3df 100644 --- a/configure.ac +++ b/configure.ac @@ -96,6 +96,9 @@ elif test "x$enable_python_plugins" != xno; then fi fi AS_IF([test "x$PLATFORM" = xosx], [rm -f Python.framework]) + # set path to look for global python plugins + GLOBAL_PYTHON_PLUGINS_PATH='${pkgdatadir}/plugins' + AC_SUBST(GLOBAL_PYTHON_PLUGINS_PATH) else AM_CONDITIONAL([BUILD_PYTHON_API], [false]) fi @@ -119,6 +122,9 @@ else [AC_MSG_ERROR([dl library needed to run C plugins])], [AM_CONDITIONAL([BUILD_C_API], [false])]) ])]) + # set path to look for global c plugins + GLOBAL_C_PLUGINS_PATH='${pkglibdir}/plugins' + AC_SUBST(GLOBAL_C_PLUGINS_PATH) else AM_CONDITIONAL([BUILD_C_API], [false]) fi @@ -360,7 +366,7 @@ AS_IF([test "x$PLATFORM" = xosx], [AM_CFLAGS="$AM_CFLAGS -Qunused-arguments"]) AM_LDFLAGS="$AM_LDFLAGS -export-dynamic" AM_CPPFLAGS="$AM_CPPFLAGS $glib_CFLAGS $gio_CFLAGS $curl_CFLAGS $libnotify_CFLAGS $PYTHON_CPPFLAGS ${GTK_CFLAGS} ${SQLITE_CFLAGS}" -AM_CPPFLAGS="$AM_CPPFLAGS -DTHEMES_PATH=\"\\\"$THEMES_PATH\\\"\" -DICONS_PATH=\"\\\"$ICONS_PATH\\\"\"" +AM_CPPFLAGS="$AM_CPPFLAGS -DTHEMES_PATH=\"\\\"$THEMES_PATH\\\"\" -DICONS_PATH=\"\\\"$ICONS_PATH\\\"\" -DGLOBAL_PYTHON_PLUGINS_PATH=\"\\\"$GLOBAL_PYTHON_PLUGINS_PATH\\\"\" -DGLOBAL_C_PLUGINS_PATH=\"\\\"$GLOBAL_C_PLUGINS_PATH\\\"\"" LIBS="$glib_LIBS $gio_LIBS $curl_LIBS $libnotify_LIBS $PYTHON_LIBS $PYTHON_EXTRA_LIBS $PYTHON_LDFLAGS ${GTK_LIBS} ${SQLITE_LIBS} $LIBS" AC_SUBST(AM_LDFLAGS) @@ -374,14 +380,16 @@ AC_CONFIG_FILES([Makefile]) AC_OUTPUT echo "" -echo "PLATFORM : $host_os" -echo "PACKAGE_STATUS : $PACKAGE_STATUS" -echo "AM_CFLAGS : $AM_CFLAGS" -echo "AM_CPPFLAGS : $AM_CPPFLAGS" -echo "AM_LDFLAGS : $AM_LDFLAGS" -echo "LIBS : $LIBS" -echo "Install themes : $THEMES_INSTALL" -echo "Themes path : $THEMES_PATH" -echo "Icons path : $ICONS_PATH" +echo "PLATFORM : $host_os" +echo "PACKAGE_STATUS : $PACKAGE_STATUS" +echo "AM_CFLAGS : $AM_CFLAGS" +echo "AM_CPPFLAGS : $AM_CPPFLAGS" +echo "AM_LDFLAGS : $AM_LDFLAGS" +echo "LIBS : $LIBS" +echo "Install themes : $THEMES_INSTALL" +echo "Themes path : $THEMES_PATH" +echo "Icons path : $ICONS_PATH" +echo "Global Python plugins path : $GLOBAL_PYTHON_PLUGINS_PATH" +echo "Global C plugins path : $GLOBAL_C_PLUGINS_PATH" echo "" echo "Now you can run \`make' to build profanity" diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index b970c73b..302de659 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -6927,13 +6927,31 @@ cmd_receipts(ProfWin* window, const char* const command, gchar** args) gboolean cmd_plugins_install(ProfWin* window, const char* const command, gchar** args) { - char* path; + char* path = NULL; if (args[1] == NULL) { cons_show("Please provide a path to the plugin file or directory, see /help plugins"); return TRUE; - } else { + } + + // take whole path or build it in case it's just the plugin name + if (strchr(args[1], '/')) { path = get_expanded_path(args[1]); + } else { + if (g_str_has_suffix(args[1], ".py")) { + path = g_strdup_printf("%s/%s", GLOBAL_PYTHON_PLUGINS_PATH, args[1]); + } else if (g_str_has_suffix(args[1], ".so")) { + path = g_strdup_printf("%s/%s", GLOBAL_C_PLUGINS_PATH, args[1]); + } else { + cons_show("Plugins must have one of the following extensions: '.py' '.so'"); + return TRUE; + } + } + + if (access(path, R_OK) != 0) { + cons_show("Cannot access: %s", path); + free(path); + return TRUE; } if (is_regular_file(path)) { From 25820235fe3460966eade8ea13734e1e2d2f1ed6 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Wed, 29 Sep 2021 15:26:17 +0200 Subject: [PATCH 4/7] List globally available plugins Packagers can package https://github.com/profanity-im/profanity-plugins or another collection of plugins to `/usr/local/share/profanity/plugins` (python) and `/usr/local/lib64/profanity` (c). `/plugins` will list these globally available plugins now along with the ones thare are installed (`~/.local/share/profanity/plugins`) and loaded. Regards https://github.com/profanity-im/profanity/issues/945 --- src/command/cmd_defs.c | 2 +- src/command/cmd_funcs.c | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index 898608af..37a2db89 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -2155,7 +2155,7 @@ static struct cmd_t command_defs[] = { "/plugins reload []", "/plugins python_version") CMD_DESC( - "Manage plugins. Passing no arguments lists currently loaded plugins.") + "Manage plugins. Passing no arguments lists currently loaded plugins and global plugins which are available for local installation. Global directory for Python plugins is " GLOBAL_PYTHON_PLUGINS_PATH " and for C Plugins is " GLOBAL_C_PLUGINS_PATH ".") CMD_ARGS( { "install []", "Install a plugin, or all plugins found in a directory (recursive)." }, { "uninstall []", "Uninstall a plugin." }, diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 302de659..ab48c3fb 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -7172,6 +7172,42 @@ cmd_plugins_python_version(ProfWin* window, const char* const command, gchar** a gboolean cmd_plugins(ProfWin* window, const char* const command, gchar** args) { + GDir* global_pyp_dir = NULL; + GDir* global_cp_dir = NULL; + + if (access(GLOBAL_PYTHON_PLUGINS_PATH, R_OK) == 0) { + GError* error = NULL; + global_pyp_dir = g_dir_open(GLOBAL_PYTHON_PLUGINS_PATH, 0, &error); + if (error) { + log_warning("Error when trying to open global plugins path: %s", GLOBAL_PYTHON_PLUGINS_PATH); + g_error_free(error); + return TRUE; + } + } + if (access(GLOBAL_C_PLUGINS_PATH, R_OK) == 0) { + GError* error = NULL; + global_cp_dir = g_dir_open(GLOBAL_C_PLUGINS_PATH, 0, &error); + if (error) { + log_warning("Error when trying to open global plugins path: %s", GLOBAL_C_PLUGINS_PATH); + g_error_free(error); + return TRUE; + } + } + if (global_pyp_dir) { + const gchar *filename; + cons_show("The following Python plugins are available globally and can be installed:"); + while ((filename = g_dir_read_name(global_pyp_dir))) { + cons_show(" %s", filename); + } + } + if (global_cp_dir) { + const gchar *filename; + cons_show("The following C plugins are available globally and can be installed:"); + while ((filename = g_dir_read_name(global_cp_dir))) { + cons_show(" %s", filename); + } + } + GList* plugins = plugins_loaded_list(); if (plugins == NULL) { cons_show("No plugins installed."); From 18c02e5c4d626ef0d7966ac7467db3552ff0b975 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Wed, 29 Sep 2021 15:30:11 +0200 Subject: [PATCH 5/7] Remove Ruby comment There most likely won't ever be Ruby plugins. Regards https://github.com/profanity-im/profanity/issues/779 --- configure.ac | 2 -- 1 file changed, 2 deletions(-) diff --git a/configure.ac b/configure.ac index 2061a3df..ce79743d 100644 --- a/configure.ac +++ b/configure.ac @@ -7,8 +7,6 @@ AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([src/main.c]) AC_CONFIG_HEADERS([src/config.h]) AM_INIT_AUTOMAKE([foreign subdir-objects]) -# TODO Introduce with Ruby plugins, see https://github.com/profanity-im/profanity/issues/779 -#AX_PREFIX_CONFIG_H([src/prof_config.h], [PROF], [src/config.h]) ### Checks for programs. AC_PROG_CC From b0e0012c22f5ffac4da91cef05743916e9b43ac7 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Wed, 29 Sep 2021 15:36:14 +0200 Subject: [PATCH 6/7] Fix `/plugins update ~/dir` If `~/dir` exists profanity exits for me. Whole code for updating plugins from a dir isn't even implemented. Even though some messgages suggest otherwise. Remove this and only allow updating of one file. --- src/command/cmd_funcs.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index ab48c3fb..8325ab44 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -7014,7 +7014,7 @@ cmd_plugins_update(ProfWin* window, const char* const command, gchar** args) char* path; if (args[1] == NULL) { - cons_show("Please provide a path to the plugin file or directory, see /help plugins"); + cons_show("Please provide a path to the plugin file, see /help plugins"); return TRUE; } else { path = get_expanded_path(args[1]); @@ -7054,13 +7054,8 @@ cmd_plugins_update(ProfWin* window, const char* const command, gchar** args) return TRUE; } - if (is_dir(path)) { - free(path); - return FALSE; - } - free(path); - cons_show("Argument must be a file or directory."); + cons_show("Argument must be a file."); return TRUE; } From 312063636718b6480c16af3fb0ef57ad3d8ae907 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Wed, 29 Sep 2021 17:25:18 +0200 Subject: [PATCH 7/7] Add more help about how to use plugins `/plugins install` installs a plugin to `.local/share/profanity/plugins`. And also loads it. When a plugin is loaded it will automatically be added to the `profrc` file like this: ``` [plugins] load=my.py; ``` On the next start Profanity will try to load this plugin again unless `/plugin unload my.py` is called. --- src/command/cmd_defs.c | 4 ++-- src/command/cmd_funcs.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index 37a2db89..1b8c3112 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -2157,10 +2157,10 @@ static struct cmd_t command_defs[] = { CMD_DESC( "Manage plugins. Passing no arguments lists currently loaded plugins and global plugins which are available for local installation. Global directory for Python plugins is " GLOBAL_PYTHON_PLUGINS_PATH " and for C Plugins is " GLOBAL_C_PLUGINS_PATH ".") CMD_ARGS( - { "install []", "Install a plugin, or all plugins found in a directory (recursive)." }, + { "install []", "Install a plugin, or all plugins found in a directory (recursive). And loads it/them." }, { "uninstall []", "Uninstall a plugin." }, { "update []", "Updates an installed plugin" }, - { "load []", "Load a plugin that already exists in the plugin directory, passing no argument loads all found plugins." }, + { "load []", "Load a plugin that already exists in the plugin directory, passing no argument loads all found plugins. It will be loaded upon next start too unless unloaded." }, { "unload []", "Unload a loaded plugin, passing no argument will unload all plugins." }, { "reload []", "Reload a plugin, passing no argument will reload all plugins." }, { "python_version", "Show the Python interpreter version." }) diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 8325ab44..2a206d5b 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -6965,7 +6965,7 @@ cmd_plugins_install(ProfWin* window, const char* const command, gchar** args) gchar* plugin_name = g_path_get_basename(path); gboolean result = plugins_install(plugin_name, path, error_message); if (result) { - cons_show("Plugin installed: %s", plugin_name); + cons_show("Plugin installed and loaded: %s", plugin_name); } else { cons_show("Failed to install plugin: %s. %s", plugin_name, error_message->str); } @@ -6978,7 +6978,7 @@ cmd_plugins_install(ProfWin* window, const char* const command, gchar** args) if (result->installed || result->failed) { if (result->installed) { cons_show(""); - cons_show("Installed plugins:"); + cons_show("Installed and loaded plugins:"); GSList* curr = result->installed; while (curr) { cons_show(" %s", curr->data);