diff --git a/.gitignore b/.gitignore index 4a6a8c0..6f6492f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/src/dialogs.c b/src/dialogs.c index 13c3429..e55b81d 100644 --- a/src/dialogs.c +++ b/src/dialogs.c @@ -2,6 +2,10 @@ #include #include #include +#include +#include +#include +#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), "Manage MPV Scripts\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), "Note: Running scripts cannot be fully unloaded without a restart."); + 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) diff --git a/src/dialogs.h b/src/dialogs.h index 75d0608..d741299 100644 --- a/src/dialogs.h +++ b/src/dialogs.h @@ -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 */ diff --git a/src/player.c b/src/player.c index f9b67ed..48056a5 100644 --- a/src/player.c +++ b/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) { diff --git a/src/player.h b/src/player.h index eda3059..95b592b 100644 --- a/src/player.h +++ b/src/player.h @@ -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 */ diff --git a/src/plugin_manager.c b/src/plugin_manager.c index 7af4860..69772e3 100644 --- a/src/plugin_manager.c +++ b/src/plugin_manager.c @@ -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); diff --git a/src/ui.c b/src/ui.c index d01d244..c3a2324 100644 --- a/src/ui.c +++ b/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); +} + diff --git a/src/ui.h b/src/ui.h index fd3a5ed..5b4b66a 100644 --- a/src/ui.h +++ b/src/ui.h @@ -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 */