Added Script Manager
This commit is contained in:
parent
5c326da7fd
commit
0ef995ae7a
6
.gitignore
vendored
6
.gitignore
vendored
@ -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
|
||||
|
||||
258
src/dialogs.c
258
src/dialogs.c
@ -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)
|
||||
|
||||
@ -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 */
|
||||
|
||||
12
src/player.c
12
src/player.c
@ -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)
|
||||
|
||||
{
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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);
|
||||
|
||||
6
src/ui.c
6
src/ui.c
@ -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);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user