Added Script Manager

This commit is contained in:
laki 2026-02-16 18:43:40 +00:00
parent 5c326da7fd
commit 0ef995ae7a
8 changed files with 293 additions and 0 deletions

6
.gitignore vendored
View File

@ -14,3 +14,9 @@ src/mpris.o
src/main.o
src/controls.o
src/ui.o
gtk2-media-player_0.7.1_amd64.deb
.gitignore
make_deb_32.sh
make_deb.sh
make_release.sh
gtk2-media-player-v0.7.1-linux.zip

View File

@ -2,6 +2,10 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include "ui.h"
#include "plugin_manager.h"
/* File filter helpers */
@ -392,6 +396,260 @@ void dialogs_show_about(GtkWindow *parent)
gtk_widget_destroy(dialog);
}
/* ============================================================================
* Script Manager Dialog
* ============================================================================ */
typedef struct {
Player *player;
GtkListStore *list_store;
GtkTreeView *tree_view;
GtkWidget *dialog;
} ScriptManagerData;
enum {
COL_SCRIPT_STATUS = 0,
COL_SCRIPT_NAME,
COL_SCRIPT_PATH,
COL_SCRIPT_ENABLED,
NUM_SCRIPT_COLS
};
static void refresh_scripts_list(ScriptManagerData *data)
{
gtk_list_store_clear(data->list_store);
const char *config_dir = g_get_user_config_dir();
char *scripts_dir = g_build_filename(config_dir, "gtk2-media-player", "scripts", NULL);
DIR *dir = opendir(scripts_dir);
if (dir) {
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') continue;
size_t len = strlen(entry->d_name);
gboolean is_script = FALSE;
gboolean is_enabled = TRUE;
char *display_name = g_strdup(entry->d_name);
if ((len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0) ||
(len > 3 && strcmp(entry->d_name + len - 3, ".js") == 0)) {
is_script = TRUE;
} else if (len > 9 && strcmp(entry->d_name + len - 9, ".disabled") == 0) {
is_script = TRUE;
is_enabled = FALSE;
/* Remove .disabled for display */
display_name[len - 9] = '\0';
}
if (is_script) {
GtkTreeIter iter;
gtk_list_store_append(data->list_store, &iter);
gtk_list_store_set(data->list_store, &iter,
COL_SCRIPT_STATUS, is_enabled ? "● Active" : "○ Disabled",
COL_SCRIPT_NAME, display_name,
COL_SCRIPT_PATH, entry->d_name,
COL_SCRIPT_ENABLED, is_enabled,
-1);
}
g_free(display_name);
}
closedir(dir);
}
g_free(scripts_dir);
}
static void on_script_toggled(GtkCellRendererToggle *cell, char *path_str, gpointer user_data)
{
(void)cell;
ScriptManagerData *data = (ScriptManagerData*)user_data;
GtkTreeModel *model = GTK_TREE_MODEL(data->list_store);
GtkTreeIter iter;
if (gtk_tree_model_get_iter_from_string(model, &iter, path_str)) {
char *filename;
gboolean enabled;
gtk_tree_model_get(model, &iter, COL_SCRIPT_PATH, &filename, COL_SCRIPT_ENABLED, &enabled, -1);
const char *config_dir = g_get_user_config_dir();
char *old_path = g_build_filename(config_dir, "gtk2-media-player", "scripts", filename, NULL);
char *new_path;
enabled = !enabled; /* Toggle state */
if (!enabled) {
new_path = g_strdup_printf("%s.disabled", old_path);
} else {
/* Remove .disabled suffix */
size_t len = strlen(old_path);
if (len > 9) {
new_path = g_strndup(old_path, len - 9);
} else {
new_path = g_strdup(old_path);
}
}
if (rename(old_path, new_path) == 0) {
if (enabled) {
player_load_script(data->player, new_path);
}
refresh_scripts_list(data);
}
g_free(old_path);
g_free(new_path);
g_free(filename);
}
}
static void on_install_script_clicked(GtkButton *button, gpointer user_data)
{
(void)button;
ScriptManagerData *data = (ScriptManagerData*)user_data;
GtkWidget *chooser = gtk_file_chooser_dialog_new("Install Script",
GTK_WINDOW(data->dialog),
GTK_FILE_CHOOSER_ACTION_OPEN,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
NULL);
GtkFileFilter *filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, "Scripts (*.lua, *.js)");
gtk_file_filter_add_pattern(filter, "*.lua");
gtk_file_filter_add_pattern(filter, "*.js");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
if (gtk_dialog_run(GTK_DIALOG(chooser)) == GTK_RESPONSE_ACCEPT) {
char *src_path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser));
char *basename = g_path_get_basename(src_path);
const char *config_dir = g_get_user_config_dir();
char *dest_dir = g_build_filename(config_dir, "gtk2-media-player", "scripts", NULL);
char *dest_path = g_build_filename(dest_dir, basename, NULL);
/* Copy file */
GFile *src = g_file_new_for_path(src_path);
GFile *dest = g_file_new_for_path(dest_path);
GError *error = NULL;
if (g_file_copy(src, dest, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error)) {
player_load_script(data->player, dest_path);
refresh_scripts_list(data);
} else {
GtkWidget *msg = gtk_message_dialog_new(GTK_WINDOW(data->dialog),
GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"Failed to install script: %s", error->message);
gtk_dialog_run(GTK_DIALOG(msg));
gtk_widget_destroy(msg);
g_error_free(error);
}
g_object_unref(src);
g_object_unref(dest);
g_free(src_path);
g_free(basename);
g_free(dest_dir);
g_free(dest_path);
}
gtk_widget_destroy(chooser);
}
void dialogs_show_script_manager(GtkWindow *parent, Player *player)
{
ScriptManagerData *data = g_new0(ScriptManagerData, 1);
data->player = player;
GtkWidget *dialog = gtk_dialog_new_with_buttons("Script Manager",
parent,
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
NULL);
data->dialog = dialog;
gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 400);
GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
GtkWidget *vbox = gtk_vbox_new(FALSE, 10);
gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
gtk_box_pack_start(GTK_BOX(content_area), vbox, TRUE, TRUE, 0);
GtkWidget *label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(label), "<b>Manage MPV Scripts</b>\nEnabled scripts are loaded automatically on startup.");
gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
/* Scrollable list */
GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN);
gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
data->list_store = gtk_list_store_new(NUM_SCRIPT_COLS,
G_TYPE_STRING, /* Status text (optional display) */
G_TYPE_STRING, /* Name */
G_TYPE_STRING, /* Internal Path */
G_TYPE_BOOLEAN /* Enabled (for toggle) */
);
GtkWidget *tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(data->list_store));
data->tree_view = GTK_TREE_VIEW(tree_view);
gtk_container_add(GTK_CONTAINER(sw), tree_view);
/* Enabled Toggle Column */
GtkCellRenderer *toggle_renderer = gtk_cell_renderer_toggle_new();
g_signal_connect(toggle_renderer, "toggled", G_CALLBACK(on_script_toggled), data);
GtkTreeViewColumn *col_enabled = gtk_tree_view_column_new_with_attributes("On/Off", toggle_renderer, "active", COL_SCRIPT_ENABLED, NULL);
gtk_tree_view_append_column(data->tree_view, col_enabled);
/* Name Column */
GtkCellRenderer *text_renderer = gtk_cell_renderer_text_new();
GtkTreeViewColumn *col_name = gtk_tree_view_column_new_with_attributes("Script Name", text_renderer, "text", COL_SCRIPT_NAME, NULL);
gtk_tree_view_append_column(data->tree_view, col_name);
/* Sorting */
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(data->list_store), COL_SCRIPT_NAME, GTK_SORT_ASCENDING);
/* Persistent Note */
GtkWidget *note_label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(note_label), "<small><i>Note: Running scripts cannot be fully unloaded without a restart.</i></small>");
gtk_label_set_line_wrap(GTK_LABEL(note_label), TRUE);
gtk_box_pack_start(GTK_BOX(vbox), note_label, FALSE, FALSE, 0);
/* Buttons */
GtkWidget *hbox = gtk_hbox_new(FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
GtkWidget *btn_install = gtk_button_new_with_label("Install Script");
GtkWidget *icon_add = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON);
gtk_button_set_image(GTK_BUTTON(btn_install), icon_add);
g_signal_connect(btn_install, "clicked", G_CALLBACK(on_install_script_clicked), data);
gtk_box_pack_start(GTK_BOX(hbox), btn_install, TRUE, TRUE, 0);
GtkWidget *btn_folder = gtk_button_new();
GtkWidget *icon_dir = gtk_image_new_from_stock(GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_BUTTON);
gtk_container_add(GTK_CONTAINER(btn_folder), icon_dir);
gtk_widget_set_tooltip_text(btn_folder, "Open Scripts Folder");
const char *cfg_dir = g_get_user_config_dir();
char *s_dir = g_build_filename(cfg_dir, "gtk2-media-player", "scripts", NULL);
char *cmd = g_strdup_printf("xdg-open '%s'", s_dir);
g_signal_connect_swapped(btn_folder, "clicked", G_CALLBACK(system), cmd);
gtk_box_pack_start(GTK_BOX(hbox), btn_folder, FALSE, FALSE, 0);
refresh_scripts_list(data);
gtk_widget_show_all(dialog);
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
g_object_unref(data->list_store);
g_free(cmd);
g_free(s_dir);
g_free(data);
}
/* Preferences */
static char* get_config_path(void)

View File

@ -50,4 +50,7 @@ double dialogs_goto_time(GtkWindow *parent, double current_time, double duration
/* Speed selection dialog */
double dialogs_select_speed(GtkWindow *parent, double current_speed);
/* Script Manager dialog */
void dialogs_show_script_manager(GtkWindow *parent, Player *player);
#endif /* DIALOGS_H */

View File

@ -122,6 +122,18 @@ static void load_scripts(mpv_handle *mpv)
g_free(scripts_dir);
}
void player_load_script(Player *player, const char *path)
{
if (!player || !player->mpv || !path) return;
printf("Loading script at runtime: %s\n", path);
const char *cmd[] = {"load-script", path, NULL};
int res = mpv_command(player->mpv, cmd);
if (res < 0) {
fprintf(stderr, "Failed to load script: %s\n", mpv_error_string(res));
}
}
int player_init(Player *player)
{

View File

@ -102,4 +102,7 @@ mpv_handle* player_get_mpv_handle(Player *player);
void player_observe_property(Player *player, const char *name, mpv_format format, guint64 reply_userdata);
void player_unobserve_property(Player *player, guint64 reply_userdata);
/* Script management */
void player_load_script(Player *player, const char *path);
#endif /* PLAYER_H */

View File

@ -619,6 +619,10 @@ GtkWidget* plugin_manager_get_menu(PluginManager *pm)
g_signal_connect(install_item, "activate", G_CALLBACK(on_install_plugin), pm);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), install_item);
GtkWidget *manage_scripts_item = gtk_menu_item_new_with_label("Manage Scripts...");
g_signal_connect_swapped(manage_scripts_item, "activate", G_CALLBACK(ui_show_script_manager), pm->ui);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), manage_scripts_item);
GtkWidget *scripts_item = gtk_menu_item_new_with_label("Open Scripts Folder...");
g_signal_connect_swapped(scripts_item, "activate", G_CALLBACK(ui_open_scripts_folder), pm->ui);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), scripts_item);

View File

@ -1881,3 +1881,9 @@ void ui_open_scripts_folder(AppUI *ui)
g_free(scripts_dir);
}
void ui_show_script_manager(AppUI *ui)
{
if (!ui) return;
dialogs_show_script_manager(GTK_WINDOW(ui->window), ui->player);
}

View File

@ -52,5 +52,6 @@ Player* ui_get_player(AppUI *ui); /* Helper to get player from UI */
/* Application control */
void ui_restart(AppUI *ui);
void ui_open_scripts_folder(AppUI *ui);
void ui_show_script_manager(AppUI *ui);
#endif /* UI_H */