diff --git a/.gitignore b/.gitignore index 0a23147..15f9f8a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,6 @@ make_deb_32.sh make_deb.sh make_release.sh gtk2-media-player-v0.7.1-linux.zip -GTK2-template \ No newline at end of file +GTK2-template +tools/kino_9.1_amd64.deb +tools \ No newline at end of file diff --git a/Kino b/Kino deleted file mode 100755 index 37ebfc3..0000000 Binary files a/Kino and /dev/null differ diff --git a/install.sh b/install.sh deleted file mode 100755 index 0e7e57b..0000000 --- a/install.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -# Simple installation script for Kino - -if [ "$EUID" -ne 0 ]; then - echo "Please run as root (use sudo)" - exit 1 -fi - -INSTALL_DIR="/usr/local/lib/Kino" -BIN_LINK="/usr/bin/kino" -BIN_LINK_LOCAL="/usr/local/bin/kino" -DESKTOP_FILE="/usr/share/applications/kino.desktop" - -echo "Installing to $INSTALL_DIR..." - -# Create directory -mkdir -p "$INSTALL_DIR" - -# Copy binary and metadata using 'install' to handle running processes correctly -install -m 755 Kino "$INSTALL_DIR/" -install -m 644 assets/icon.png "$INSTALL_DIR/" - -# For directory copy, we use cp but remove destination first to ensure clean update -rm -rf "$INSTALL_DIR/lib" -cp -r lib "$INSTALL_DIR/" - -# Create wrapper scripts -cat < "$BIN_LINK" -#!/bin/bash -cd "$INSTALL_DIR" -./Kino "\$@" -EOF - -cp "$BIN_LINK" "$BIN_LINK_LOCAL" -chmod +x "$BIN_LINK" "$BIN_LINK_LOCAL" - -# Install desktop file -cp assets/kino.desktop "$DESKTOP_FILE" -# Update path in desktop file if needed - -# Update MIME database -echo "Updating MIME database..." -update-desktop-database - -echo "Installation complete!" -echo "Installed version timestamp: $(date -r "$INSTALL_DIR/Kino")" -echo "You can now run 'kino' or find it in your application menu." diff --git a/lib/libmpv.so.2 b/lib/libmpv.so.2 deleted file mode 100644 index 6dc8ecb..0000000 Binary files a/lib/libmpv.so.2 and /dev/null differ diff --git a/setup.sh b/setup.sh deleted file mode 100644 index d9a59b8..0000000 --- a/setup.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash - -# Universal Setup Script for GTK2 MPV Player -# Identifies the Linux distribution and installs required build dependencies. - -set -e - -PRINT_INFO() { echo -e "\e[34m[INFO]\e[0m $1"; } -PRINT_SUCCESS() { echo -e "\e[32m[SUCCESS]\e[0m $1"; } -PRINT_ERROR() { echo -e "\e[31m[ERROR]\e[0m $1"; } -PRINT_WARNING() { echo -e "\e[33m[WARNING]\e[0m $1"; } - -OS_TYPE="" -if [ -f /etc/os-release ]; then - . /etc/os-release - OS_TYPE=$ID -else - PRINT_ERROR "Could not detect operating system type. /etc/os-release missing." - exit 1 -fi - -PRINT_INFO "Detected operating system: $OS_TYPE" - -install_debian_deps() { - PRINT_INFO "Installing dependencies for Debian/Ubuntu/Mint..." - sudo apt-get update - sudo apt-get install -y build-essential pkg-config libgtk2.0-dev libmpv-dev -} - -install_fedora_deps() { - PRINT_INFO "Installing dependencies for Fedora/RHEL/CentOS..." - sudo dnf groupinstall -y "Development Tools" - sudo dnf install -y pkg-config gtk2-devel mpv-devel -} - -install_arch_deps() { - PRINT_INFO "Installing dependencies for Arch/Manjaro..." - sudo pacman -S --needed base-devel pkg-config gtk2 mpv -} - -install_opensuse_deps() { - PRINT_INFO "Installing dependencies for openSUSE..." - sudo zypper install -t pattern devel_C_C++ - sudo zypper install -y pkg-config gtk2-devel mpv-devel -} - -install_slackware_deps() { - PRINT_INFO "Checking dependencies for Slackware..." - PRINT_WARNING "Slackware dependency management varies. Please ensure the following are installed:" - echo " - gtk+2" - echo " - mpv (with client library support)" - echo " - pkg-config" - echo "" - PRINT_INFO "If you have slackpkg+ or similar, you can try searching for 'gtk+2' and 'mpv'." -} - -case "$OS_TYPE" in - ubuntu|debian|linuxmint|pop|kali|raspbian) - install_debian_deps - ;; - fedora|centos|rhel|almalinux|rocky) - install_fedora_deps - ;; - arch|manjaro) - install_arch_deps - ;; - opensuse*|suse) - install_opensuse_deps - ;; - slackware) - install_slackware_deps - ;; - *) - PRINT_WARNING "Unsupported or unknown distribution: $OS_TYPE" - PRINT_INFO "Please manually install the development files for: gtk2 and mpv." - exit 1 - ;; -esac - -PRINT_SUCCESS "Dependency installation step complete." -PRINT_INFO "You can now build the player using 'make'." diff --git a/src/dialogs.c b/src/dialogs.c index 96fcabb..6463303 100644 --- a/src/dialogs.c +++ b/src/dialogs.c @@ -1,4 +1,5 @@ #include "dialogs.h" +#include #include #include #include @@ -293,6 +294,33 @@ char* dialogs_save_screenshot(GtkWindow *parent) return result; } +void dialogs_show_keybinds(GtkWindow *parent) +{ + GtkWidget *dialog = gtk_dialog_new_with_buttons("Keyboard Shortcuts", + parent, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); + + gtk_window_set_default_size(GTK_WINDOW(dialog), 350, -1); + GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + + GtkWidget *label = gtk_label_new( + "Space\t\t\tPlay/Pause\n" + "F / F11\t\t\tFullscreen\n" + "Left / Right\tSeek Backward/Forward (5s)\n" + "Up / Down\t\tVolume Up/Down\n" + "M\t\t\t\tMute\n" + "Page Up/Down\tPrevious/Next Chapter\n" + "Esc\t\t\t\tExit Fullscreen" + ); + gtk_label_set_use_markup(GTK_LABEL(label), TRUE); + gtk_container_set_border_width(GTK_CONTAINER(label), 20); + gtk_box_pack_start(GTK_BOX(content_area), label, TRUE, TRUE, 0); + + gtk_widget_show_all(dialog); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + void dialogs_show_about(GtkWindow *parent) { GtkWidget *dialog = gtk_dialog_new_with_buttons("About Kino", @@ -323,10 +351,10 @@ void dialogs_show_about(GtkWindow *parent) } GtkWidget *title_label = gtk_label_new(NULL); - gtk_label_set_markup(GTK_LABEL(title_label), "Kino"); + gtk_label_set_markup(GTK_LABEL(title_label), "Kino v9.1"); gtk_box_pack_start(GTK_BOX(about_vbox), title_label, FALSE, FALSE, 0); - GtkWidget *version_label = gtk_label_new("Version 9.0"); + GtkWidget *version_label = gtk_label_new("Version 9.1"); gtk_box_pack_start(GTK_BOX(about_vbox), version_label, FALSE, FALSE, 0); GtkWidget *comment_label = gtk_label_new("A lightweight GTK2 media player frontend (Kino) using libmpv."); @@ -557,23 +585,22 @@ static void on_install_script_clicked(GtkButton *button, gpointer user_data) gtk_widget_destroy(chooser); } -void dialogs_show_script_manager(GtkWindow *parent, Player *player) +static void on_script_manager_widget_destroy(GtkWidget *widget, gpointer data) +{ + (void)widget; + ScriptManagerData *sm_data = (ScriptManagerData*)data; + if (sm_data->list_store) g_object_unref(sm_data->list_store); + g_free(sm_data); +} + +GtkWidget* dialogs_create_script_manager_widget(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); + g_signal_connect(vbox, "destroy", G_CALLBACK(on_script_manager_widget_destroy), data); GtkWidget *label = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(label), "Manage MPV Scripts\nEnabled scripts are loaded automatically on startup."); @@ -586,10 +613,10 @@ void dialogs_show_script_manager(GtkWindow *parent, Player *player) 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, /* Status text */ G_TYPE_STRING, /* Name */ G_TYPE_STRING, /* Internal Path */ - G_TYPE_BOOLEAN /* Enabled (for toggle) */ + G_TYPE_BOOLEAN /* Enabled */ ); GtkWidget *tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(data->list_store)); @@ -631,22 +658,39 @@ void dialogs_show_script_manager(GtkWindow *parent, Player *player) gtk_container_add(GTK_CONTAINER(btn_folder), icon_dir); gtk_widget_set_tooltip_text(btn_folder, "Open Scripts Folder"); + /* Setup xdg-open command */ const char *cfg_dir = g_get_user_config_dir(); char *s_dir = g_build_filename(cfg_dir, "gtk2-media-player", "scripts", NULL); + /* We don't want to free cmd every time, but here we can just attach it to the button lifecycle */ char *cmd = g_strdup_printf("xdg-open '%s'", s_dir); g_signal_connect_swapped(btn_folder, "clicked", G_CALLBACK(system), cmd); + /* Ensure cmd is freed when button is destroyed */ + g_object_set_data_full(G_OBJECT(btn_folder), "folder-cmd", cmd, g_free); + gtk_box_pack_start(GTK_BOX(hbox), btn_folder, FALSE, FALSE, 0); refresh_scripts_list(data); + g_free(s_dir); + + return vbox; +} + +void dialogs_show_script_manager(GtkWindow *parent, 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); + gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 400); + + GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + GtkWidget *sm_widget = dialogs_create_script_manager_widget(player); + gtk_box_pack_start(GTK_BOX(content_area), sm_widget, TRUE, TRUE, 0); 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); } @@ -658,6 +702,33 @@ static char* get_config_path(void) return g_build_filename(config_dir, "gtk2-media-player", "config", NULL); } +/* Color Conversion Helpers */ +static char* gdk_color_to_hex(GdkColor *color) +{ + return g_strdup_printf("#%02X%02X%02X", + color->red >> 8, + color->green >> 8, + color->blue >> 8); +} + +static void hex_to_gdk_color(const char *hex, GdkColor *color) +{ + if (!hex || hex[0] != '#') { + color->red = color->green = color->blue = 0; + return; + } + + unsigned int r, g, b; + if (sscanf(hex + 1, "%02x%02x%02x", &r, &g, &b) == 3) { + color->red = (r << 8) | r; + color->green = (g << 8) | g; + color->blue = (b << 8) | b; + } else { + color->red = color->green = color->blue = 0; + } +} + + Preferences* preferences_new(void) { Preferences *prefs = g_new0(Preferences, 1); @@ -674,12 +745,32 @@ Preferences* preferences_new(void) prefs->enable_osd = TRUE; prefs->enable_chapters = TRUE; prefs->enable_volume_boost = FALSE; + prefs->mouse_wheel_seeks = FALSE; prefs->last_dir = NULL; prefs->screenshot_directory = g_strdup(g_get_home_dir()); prefs->screenshot_format = g_strdup("png"); const char *config_dir = g_get_user_config_dir(); prefs->mpv_config_path = g_build_filename(config_dir, "gtk2-media-player", "mpv.conf", NULL); + + /* Subtitle defaults */ + prefs->sub_delay = 0.0; + prefs->sub_pos = 100; + prefs->sub_scale = 1.0; + prefs->sub_font = g_strdup("sans-serif"); + prefs->sub_font_size = 55; + prefs->sub_color = g_strdup("#FFFFFF"); + prefs->sub_color_opacity = 100; + prefs->sub_border_color = g_strdup("#000000"); + prefs->sub_border_size = 3.0; + prefs->sub_border_opacity = 100; + prefs->sub_border_enabled = TRUE; + prefs->sub_shadow_color = g_strdup("#000000"); + prefs->sub_shadow_offset = 0.0; + prefs->sub_shadow_opacity = 100; + prefs->sub_shadow_enabled = TRUE; + prefs->sub_bold = FALSE; + return prefs; } @@ -690,6 +781,10 @@ void preferences_free(Preferences *prefs) g_free(prefs->screenshot_format); g_free(prefs->mpv_config_path); g_free(prefs->last_dir); + g_free(prefs->sub_font); + g_free(prefs->sub_color); + g_free(prefs->sub_border_color); + g_free(prefs->sub_shadow_color); g_free(prefs); } @@ -729,6 +824,9 @@ void preferences_load(Preferences *prefs) load_bool_pref(keyfile, "enable_osd", &prefs->enable_osd); load_bool_pref(keyfile, "enable_chapters", &prefs->enable_chapters); load_bool_pref(keyfile, "enable_volume_boost", &prefs->enable_volume_boost); + load_bool_pref(keyfile, "mouse_wheel_seeks", &prefs->mouse_wheel_seeks); + load_bool_pref(keyfile, "sub_border_enabled", &prefs->sub_border_enabled); + load_bool_pref(keyfile, "sub_shadow_enabled", &prefs->sub_shadow_enabled); char *screenshot_dir = g_key_file_get_string(keyfile, "General", "screenshot_directory", &error); if (!error && screenshot_dir) { @@ -765,6 +863,49 @@ void preferences_load(Preferences *prefs) g_clear_error(&error); g_free(mpv_cfg); } + + /* Subtitle settings */ + prefs->sub_delay = g_key_file_get_double(keyfile, "Subtitles", "sub_delay", &error); + if (error) { prefs->sub_delay = 0.0; g_clear_error(&error); } + + prefs->sub_pos = g_key_file_get_integer(keyfile, "Subtitles", "sub_pos", &error); + if (error) { prefs->sub_pos = 100; g_clear_error(&error); } + + prefs->sub_scale = g_key_file_get_double(keyfile, "Subtitles", "sub_scale", &error); + if (error) { prefs->sub_scale = 1.0; g_clear_error(&error); } + + char *sub_font = g_key_file_get_string(keyfile, "Subtitles", "sub_font", &error); + if (!error && sub_font) { + g_free(prefs->sub_font); + prefs->sub_font = sub_font; + } else { + g_clear_error(&error); + g_free(sub_font); + } + + prefs->sub_font_size = g_key_file_get_integer(keyfile, "Subtitles", "sub_font_size", &error); + if (error) { prefs->sub_font_size = 55; g_clear_error(&error); } + + prefs->sub_color_opacity = g_key_file_get_integer(keyfile, "Subtitles", "sub_color_opacity", &error); + if (error) { prefs->sub_color_opacity = 100; g_clear_error(&error); } + + prefs->sub_border_size = g_key_file_get_double(keyfile, "Subtitles", "sub_border_size", &error); + if (error) { prefs->sub_border_size = 3.0; g_clear_error(&error); } + + prefs->sub_shadow_offset = g_key_file_get_double(keyfile, "Subtitles", "sub_shadow_offset", &error); + if (error) { prefs->sub_shadow_offset = 0.0; g_clear_error(&error); } + + load_bool_pref(keyfile, "sub_bold", &prefs->sub_bold); + load_bool_pref(keyfile, "sub_italic", &prefs->sub_italic); + + char *s_color = g_key_file_get_string(keyfile, "Subtitles", "sub_color", &error); + if (!error && s_color) { g_free(prefs->sub_color); prefs->sub_color = s_color; } else { g_clear_error(&error); g_free(s_color); } + + char *bd_color = g_key_file_get_string(keyfile, "Subtitles", "sub_border_color", &error); + if (!error && bd_color) { g_free(prefs->sub_border_color); prefs->sub_border_color = bd_color; } else { g_clear_error(&error); g_free(bd_color); } + + char *sh_color = g_key_file_get_string(keyfile, "Subtitles", "sub_shadow_color", &error); + if (!error && sh_color) { g_free(prefs->sub_shadow_color); prefs->sub_shadow_color = sh_color; } else { g_clear_error(&error); g_free(sh_color); } } g_key_file_free(keyfile); @@ -798,6 +939,9 @@ void preferences_save(Preferences *prefs) {"enable_osd", prefs->enable_osd}, {"enable_chapters", prefs->enable_chapters}, {"enable_volume_boost", prefs->enable_volume_boost}, + {"mouse_wheel_seeks", prefs->mouse_wheel_seeks}, + {"sub_border_enabled", prefs->sub_border_enabled}, + {"sub_shadow_enabled", prefs->sub_shadow_enabled}, {NULL, FALSE} }; @@ -819,6 +963,23 @@ void preferences_save(Preferences *prefs) g_key_file_set_string(keyfile, "General", "mpv_config_path", prefs->mpv_config_path); } + /* Subtitles */ + g_key_file_set_double(keyfile, "Subtitles", "sub_delay", prefs->sub_delay); + g_key_file_set_integer(keyfile, "Subtitles", "sub_pos", prefs->sub_pos); + g_key_file_set_double(keyfile, "Subtitles", "sub_scale", prefs->sub_scale); + if (prefs->sub_font) { + g_key_file_set_string(keyfile, "Subtitles", "sub_font", prefs->sub_font); + } + g_key_file_set_integer(keyfile, "Subtitles", "sub_font_size", prefs->sub_font_size); + g_key_file_set_string(keyfile, "Subtitles", "sub_color", prefs->sub_color); + g_key_file_set_integer(keyfile, "Subtitles", "sub_color_opacity", prefs->sub_color_opacity); + g_key_file_set_string(keyfile, "Subtitles", "sub_border_color", prefs->sub_border_color); + g_key_file_set_double(keyfile, "Subtitles", "sub_border_size", prefs->sub_border_size); + g_key_file_set_string(keyfile, "Subtitles", "sub_shadow_color", prefs->sub_shadow_color); + g_key_file_set_double(keyfile, "Subtitles", "sub_shadow_offset", prefs->sub_shadow_offset); + g_key_file_set_boolean(keyfile, "Subtitles", "sub_bold", prefs->sub_bold); + g_key_file_set_boolean(keyfile, "Subtitles", "sub_italic", prefs->sub_italic); + gsize length; char *data = g_key_file_to_data(keyfile, &length, NULL); if (data) { @@ -872,7 +1033,43 @@ static GtkWidget* create_pref_check(GtkWidget *box, const char *label, gboolean return check; } -gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginManager *pm) +static void on_sub_font_browse_clicked(GtkButton *btn, gpointer data) +{ + GtkWindow *parent = GTK_WINDOW(data); + GtkWidget *entry = g_object_get_data(G_OBJECT(btn), "entry"); + GtkWidget *font_dialog = gtk_font_selection_dialog_new("Select Subtitle Font"); + gtk_window_set_transient_for(GTK_WINDOW(font_dialog), parent); + + /* Set current font if possible */ + const char* current = gtk_entry_get_text(GTK_ENTRY(entry)); + if (current && strlen(current) > 0) { + gtk_font_selection_dialog_set_font_name(GTK_FONT_SELECTION_DIALOG(font_dialog), current); + } + + if (gtk_dialog_run(GTK_DIALOG(font_dialog)) == GTK_RESPONSE_OK) { + char *fontname = gtk_font_selection_dialog_get_font_name(GTK_FONT_SELECTION_DIALOG(font_dialog)); + + /* MPV sub-font property expects only the font family name, not the full Pango description. + * For example, "Arial Bold 12" should be just "Arial". */ + PangoFontDescription *desc = pango_font_description_from_string(fontname); + if (desc) { + const char *family = pango_font_description_get_family(desc); + if (family) { + gtk_entry_set_text(GTK_ENTRY(entry), family); + } else { + gtk_entry_set_text(GTK_ENTRY(entry), fontname); + } + pango_font_description_free(desc); + } else { + gtk_entry_set_text(GTK_ENTRY(entry), fontname); + } + + g_free(fontname); + } + gtk_widget_destroy(font_dialog); +} + +gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginManager *pm, int initial_tab, PreferencesApplyCallback apply_cb, gpointer user_data) { if (!prefs) return FALSE; @@ -881,6 +1078,7 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM parent, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_APPLY, GTK_RESPONSE_APPLY, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); @@ -907,33 +1105,130 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM gtk_notebook_append_page(GTK_NOTEBOOK(notebook), interface_vbox, gtk_label_new("Interface")); - /* 2. Config File Tab */ - GtkWidget *config_vbox = gtk_vbox_new(FALSE, 10); - gtk_container_set_border_width(GTK_CONTAINER(config_vbox), 10); + /* 2. Subtitles Tab */ + GtkWidget *sub_vbox = gtk_vbox_new(FALSE, 10); + gtk_container_set_border_width(GTK_CONTAINER(sub_vbox), 10); - GtkWidget *config_info_label = gtk_label_new(NULL); - gtk_label_set_markup(GTK_LABEL(config_info_label), "mpv Configuration File\n" - "Specify an external mpv.conf file to load custom settings, shaders, and advanced options.\n" - "Note: Standard MPV syntax is supported."); - gtk_label_set_line_wrap(GTK_LABEL(config_info_label), TRUE); - gtk_box_pack_start(GTK_BOX(config_vbox), config_info_label, FALSE, FALSE, 5); + /* Delay */ + GtkWidget *delay_hbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(delay_hbox), gtk_label_new("Subtitle Delay (seconds):"), FALSE, FALSE, 0); + GtkAdjustment *delay_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_delay, -100.0, 100.0, 0.1, 1.0, 0)); + GtkWidget *delay_spin = gtk_spin_button_new(delay_adj, 0.1, 1); + gtk_box_pack_start(GTK_BOX(delay_hbox), delay_spin, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(sub_vbox), delay_hbox, FALSE, FALSE, 0); - GtkWidget *cfg_hbox = gtk_hbox_new(FALSE, 10); - GtkWidget *cfg_entry = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(cfg_entry), prefs->mpv_config_path ? prefs->mpv_config_path : ""); - gtk_box_pack_start(GTK_BOX(cfg_hbox), cfg_entry, TRUE, TRUE, 0); + /* Position */ + GtkWidget *subpos_hbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(subpos_hbox), gtk_label_new("Vertical Position (0-100):"), FALSE, FALSE, 0); + GtkAdjustment *subpos_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_pos, 0, 100, 1, 10, 0)); + GtkWidget *subpos_spin = gtk_spin_button_new(subpos_adj, 1, 0); + gtk_box_pack_start(GTK_BOX(subpos_hbox), subpos_spin, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(sub_vbox), subpos_hbox, FALSE, FALSE, 0); - GtkWidget *cfg_browse_btn = gtk_button_new_with_label("Browse..."); - g_object_set_data(G_OBJECT(cfg_browse_btn), "entry", cfg_entry); - g_signal_connect(cfg_browse_btn, "clicked", G_CALLBACK(on_cfg_browse_clicked), dialog); - gtk_box_pack_start(GTK_BOX(cfg_hbox), cfg_browse_btn, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(config_vbox), cfg_hbox, FALSE, FALSE, 5); + /* Scale */ + GtkWidget *scale_hbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(scale_hbox), gtk_label_new("Font Scale:"), FALSE, FALSE, 0); + GtkAdjustment *scale_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_scale, 0.1, 10.0, 0.1, 1.0, 0)); + GtkWidget *scale_spin = gtk_spin_button_new(scale_adj, 0.1, 1); + gtk_box_pack_start(GTK_BOX(scale_hbox), scale_spin, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(sub_vbox), scale_hbox, FALSE, FALSE, 0); - GtkWidget *cfg_reset_btn = gtk_button_new_with_label("Reset to Default"); - g_signal_connect(cfg_reset_btn, "clicked", G_CALLBACK(on_cfg_reset_clicked), cfg_entry); - gtk_box_pack_start(GTK_BOX(config_vbox), cfg_reset_btn, FALSE, FALSE, 0); + /* Font family */ + GtkWidget *font_hbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(font_hbox), gtk_label_new("Font Family:"), FALSE, FALSE, 0); + GtkWidget *sub_font_entry = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(sub_font_entry), prefs->sub_font ? prefs->sub_font : "sans-serif"); + gtk_box_pack_start(GTK_BOX(font_hbox), sub_font_entry, TRUE, TRUE, 0); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), config_vbox, gtk_label_new("Config File")); + GtkWidget *sub_font_btn = gtk_button_new_with_label("Browse..."); + g_object_set_data(G_OBJECT(sub_font_btn), "entry", sub_font_entry); + g_signal_connect(sub_font_btn, "clicked", G_CALLBACK(on_sub_font_browse_clicked), dialog); + gtk_box_pack_start(GTK_BOX(font_hbox), sub_font_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(sub_vbox), font_hbox, FALSE, FALSE, 0); + + /* Font Size & Styles */ + GtkWidget *font_style_hbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(font_style_hbox), gtk_label_new("Font Size (px):"), FALSE, FALSE, 0); + GtkAdjustment *sub_size_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_font_size, 1, 300, 1, 10, 0)); + GtkWidget *sub_font_size_spin = gtk_spin_button_new(sub_size_adj, 1, 0); + gtk_box_pack_start(GTK_BOX(font_style_hbox), sub_font_size_spin, FALSE, FALSE, 0); + + GtkWidget *sub_bold_check = gtk_check_button_new_with_label("Bold"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sub_bold_check), prefs->sub_bold); + gtk_box_pack_start(GTK_BOX(font_style_hbox), sub_bold_check, FALSE, FALSE, 0); + + GtkWidget *sub_italic_check = gtk_check_button_new_with_label("Italic"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sub_italic_check), prefs->sub_italic); + gtk_box_pack_start(GTK_BOX(font_style_hbox), sub_italic_check, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(sub_vbox), font_style_hbox, FALSE, FALSE, 0); + + /* Primary Color & Opacity */ + GtkWidget *color_hbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(color_hbox), gtk_label_new("Font Color (Primary):"), FALSE, FALSE, 0); + GtkWidget *sub_color_btn = gtk_color_button_new(); + GdkColor sc; hex_to_gdk_color(prefs->sub_color, &sc); + gtk_color_button_set_color(GTK_COLOR_BUTTON(sub_color_btn), &sc); + gtk_box_pack_start(GTK_BOX(color_hbox), sub_color_btn, FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(color_hbox), gtk_label_new("Opacity (%):"), FALSE, FALSE, 0); + GtkAdjustment *color_alpha_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_color_opacity, 0, 100, 1, 10, 0)); + GtkWidget *sub_color_opacity_spin = gtk_spin_button_new(color_alpha_adj, 1, 0); + gtk_box_pack_start(GTK_BOX(color_hbox), sub_color_opacity_spin, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(sub_vbox), color_hbox, FALSE, FALSE, 0); + + /* Border Row */ + GtkWidget *border_row_hbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(border_row_hbox), gtk_label_new("Border:"), FALSE, FALSE, 0); + GtkWidget *sub_border_color_btn = gtk_color_button_new(); + GdkColor bdc; hex_to_gdk_color(prefs->sub_border_color, &bdc); + gtk_color_button_set_color(GTK_COLOR_BUTTON(sub_border_color_btn), &bdc); + gtk_box_pack_start(GTK_BOX(border_row_hbox), sub_border_color_btn, FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(border_row_hbox), gtk_label_new("Opacity (%):"), FALSE, FALSE, 0); + GtkAdjustment *border_op_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_border_opacity, 0, 100, 1, 10, 0)); + GtkWidget *sub_border_opacity_spin = gtk_spin_button_new(border_op_adj, 1, 0); + gtk_box_pack_start(GTK_BOX(border_row_hbox), sub_border_opacity_spin, FALSE, FALSE, 0); + + GtkWidget *border_check = gtk_check_button_new_with_label("Enabled"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(border_check), prefs->sub_border_enabled); + gtk_box_pack_start(GTK_BOX(border_row_hbox), border_check, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(sub_vbox), border_row_hbox, FALSE, FALSE, 0); + + /* Border Size */ + GtkWidget *border_size_hbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(border_size_hbox), gtk_label_new("Border Size:"), FALSE, FALSE, 0); + GtkAdjustment *border_size_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_border_size, 0, 10, 0.5, 1, 0)); + GtkWidget *sub_border_size_spin = gtk_spin_button_new(border_size_adj, 0.5, 1); + gtk_box_pack_start(GTK_BOX(border_size_hbox), sub_border_size_spin, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(sub_vbox), border_size_hbox, FALSE, FALSE, 0); + + /* Shadow Row */ + GtkWidget *shadow_row_hbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(shadow_row_hbox), gtk_label_new("Shadow:"), FALSE, FALSE, 0); + GtkWidget *sub_shadow_color_btn = gtk_color_button_new(); + GdkColor shc; hex_to_gdk_color(prefs->sub_shadow_color, &shc); + gtk_color_button_set_color(GTK_COLOR_BUTTON(sub_shadow_color_btn), &shc); + gtk_box_pack_start(GTK_BOX(shadow_row_hbox), sub_shadow_color_btn, FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(shadow_row_hbox), gtk_label_new("Opacity (%):"), FALSE, FALSE, 0); + GtkAdjustment *shadow_op_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_shadow_opacity, 0, 100, 1, 10, 0)); + GtkWidget *sub_shadow_opacity_spin = gtk_spin_button_new(shadow_op_adj, 1, 0); + gtk_box_pack_start(GTK_BOX(shadow_row_hbox), sub_shadow_opacity_spin, FALSE, FALSE, 0); + + GtkWidget *shadow_check = gtk_check_button_new_with_label("Enabled"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shadow_check), prefs->sub_shadow_enabled); + gtk_box_pack_start(GTK_BOX(shadow_row_hbox), shadow_check, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(sub_vbox), shadow_row_hbox, FALSE, FALSE, 0); + + /* Shadow Offset */ + GtkWidget *shadow_off_hbox = gtk_hbox_new(FALSE, 10); + gtk_box_pack_start(GTK_BOX(shadow_off_hbox), gtk_label_new("Shadow Offset:"), FALSE, FALSE, 0); + GtkAdjustment *shadow_offset_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_shadow_offset, 0, 10, 0.5, 1, 0)); + GtkWidget *sub_shadow_offset_spin = gtk_spin_button_new(shadow_offset_adj, 0.5, 1); + gtk_box_pack_start(GTK_BOX(shadow_off_hbox), sub_shadow_offset_spin, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(sub_vbox), shadow_off_hbox, FALSE, FALSE, 0); + + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), sub_vbox, gtk_label_new("Subtitles")); /* 3. Miscellaneous Tab */ GtkWidget *misc_vbox = gtk_vbox_new(FALSE, 10); @@ -947,6 +1242,7 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM gtk_box_pack_start(GTK_BOX(misc_vbox), vol_hbox, FALSE, FALSE, 0); GtkWidget *vol_boost_check = create_pref_check(misc_vbox, "Enable volume boost (up to 150%)", prefs->enable_volume_boost); + GtkWidget *mouse_wheel_check = create_pref_check(misc_vbox, "Mouse wheel seeks through video instead of adjusting volume", prefs->mouse_wheel_seeks); GtkWidget *position_check = create_pref_check(misc_vbox, "Remember playback position", prefs->remember_position); GtkWidget *dir_check = create_pref_check(misc_vbox, "Remember last folder in file chooser", prefs->remember_last_dir); GtkWidget *mpris_check = create_pref_check(misc_vbox, "Enable MPRIS support (D-Bus)", prefs->use_mpris); @@ -980,11 +1276,50 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM GtkWidget *plugins_vbox = plugin_manager_create_config_widget(pm); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), plugins_vbox, gtk_label_new("Plugins")); + /* 5. Scripts Tab */ + GtkWidget *scripts_vbox = dialogs_create_script_manager_widget(plugin_manager_get_player(pm)); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scripts_vbox, gtk_label_new("Scripts")); + + /* 6. Config File Tab */ + GtkWidget *config_vbox = gtk_vbox_new(FALSE, 10); + gtk_container_set_border_width(GTK_CONTAINER(config_vbox), 10); + + GtkWidget *config_info_label = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(config_info_label), "mpv Configuration File\n" + "Specify an external mpv.conf file to load custom settings, shaders, and advanced options.\n" + "Note: Standard MPV syntax is supported."); + gtk_label_set_line_wrap(GTK_LABEL(config_info_label), TRUE); + gtk_box_pack_start(GTK_BOX(config_vbox), config_info_label, FALSE, FALSE, 5); + + GtkWidget *cfg_hbox = gtk_hbox_new(FALSE, 10); + GtkWidget *cfg_entry = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(cfg_entry), prefs->mpv_config_path ? prefs->mpv_config_path : ""); + gtk_box_pack_start(GTK_BOX(cfg_hbox), cfg_entry, TRUE, TRUE, 0); + + GtkWidget *cfg_browse_btn = gtk_button_new_with_label("Browse..."); + g_object_set_data(G_OBJECT(cfg_browse_btn), "entry", cfg_entry); + g_signal_connect(cfg_browse_btn, "clicked", G_CALLBACK(on_cfg_browse_clicked), dialog); + gtk_box_pack_start(GTK_BOX(cfg_hbox), cfg_browse_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(config_vbox), cfg_hbox, FALSE, FALSE, 5); + + GtkWidget *cfg_reset_btn = gtk_button_new_with_label("Reset to Default"); + g_signal_connect(cfg_reset_btn, "clicked", G_CALLBACK(on_cfg_reset_clicked), cfg_entry); + gtk_box_pack_start(GTK_BOX(config_vbox), cfg_reset_btn, FALSE, FALSE, 0); + + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), config_vbox, gtk_label_new("Config File")); + + if (initial_tab >= 0 && initial_tab < gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook))) { + gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), initial_tab); + } + gtk_widget_show_all(dialog); - gboolean result = FALSE; - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { - prefs->default_volume = gtk_spin_button_get_value(GTK_SPIN_BUTTON(vol_spin)); + int response; + gboolean changed = FALSE; + do { + response = gtk_dialog_run(GTK_DIALOG(dialog)); + if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY) { + prefs->default_volume = gtk_spin_button_get_value(GTK_SPIN_BUTTON(vol_spin)); prefs->remember_position = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(position_check)); prefs->show_playlist = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(playlist_check)); prefs->auto_resize = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(resize_check)); @@ -997,7 +1332,39 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM prefs->enable_osd = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(osd_check)); prefs->enable_chapters = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chapters_check)); prefs->enable_volume_boost = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vol_boost_check)); + prefs->mouse_wheel_seeks = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mouse_wheel_check)); + /* Subtitles */ + prefs->sub_delay = gtk_spin_button_get_value(GTK_SPIN_BUTTON(delay_spin)); + prefs->sub_pos = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(subpos_spin)); + prefs->sub_scale = gtk_spin_button_get_value(GTK_SPIN_BUTTON(scale_spin)); + g_free(prefs->sub_font); + prefs->sub_font = g_strdup(gtk_entry_get_text(GTK_ENTRY(sub_font_entry))); + + prefs->sub_font_size = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(sub_font_size_spin)); + prefs->sub_bold = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sub_bold_check)); + prefs->sub_italic = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sub_italic_check)); + prefs->sub_border_enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(border_check)); + prefs->sub_shadow_enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(shadow_check)); + + GdkColor c; + gtk_color_button_get_color(GTK_COLOR_BUTTON(sub_color_btn), &c); + g_free(prefs->sub_color); + prefs->sub_color = gdk_color_to_hex(&c); + prefs->sub_color_opacity = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(sub_color_opacity_spin)); + + gtk_color_button_get_color(GTK_COLOR_BUTTON(sub_border_color_btn), &c); + g_free(prefs->sub_border_color); + prefs->sub_border_color = gdk_color_to_hex(&c); + prefs->sub_border_size = gtk_spin_button_get_value(GTK_SPIN_BUTTON(sub_border_size_spin)); + prefs->sub_border_opacity = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(sub_border_opacity_spin)); + + gtk_color_button_get_color(GTK_COLOR_BUTTON(sub_shadow_color_btn), &c); + g_free(prefs->sub_shadow_color); + prefs->sub_shadow_color = gdk_color_to_hex(&c); + prefs->sub_shadow_offset = gtk_spin_button_get_value(GTK_SPIN_BUTTON(sub_shadow_offset_spin)); + prefs->sub_shadow_opacity = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(sub_shadow_opacity_spin)); + g_free(prefs->screenshot_directory); prefs->screenshot_directory = g_strdup(gtk_entry_get_text(GTK_ENTRY(ss_entry))); @@ -1008,11 +1375,13 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM prefs->mpv_config_path = g_strdup(gtk_entry_get_text(GTK_ENTRY(cfg_entry))); preferences_save(prefs); - result = TRUE; + if (apply_cb) apply_cb(user_data); + changed = TRUE; } - - gtk_widget_destroy(dialog); - return result; +} while (response == GTK_RESPONSE_APPLY); + +gtk_widget_destroy(dialog); +return (response == GTK_RESPONSE_OK || (response == GTK_RESPONSE_CANCEL && changed)); } double dialogs_goto_time(GtkWindow *parent, double current_time, double duration) diff --git a/src/dialogs.h b/src/dialogs.h index 7b03092..dadb114 100644 --- a/src/dialogs.h +++ b/src/dialogs.h @@ -23,17 +23,37 @@ typedef struct { gboolean enable_osd; gboolean enable_chapters; gboolean enable_volume_boost; + gboolean mouse_wheel_seeks; char *last_dir; char *screenshot_directory; char *screenshot_format; char *mpv_config_path; + /* Subtitle settings */ + double sub_delay; + int sub_pos; + double sub_scale; + char *sub_font; + int sub_font_size; + char *sub_color; + int sub_color_opacity; + char *sub_border_color; + double sub_border_size; + int sub_border_opacity; + gboolean sub_border_enabled; + char *sub_shadow_color; + double sub_shadow_offset; + int sub_shadow_opacity; + gboolean sub_shadow_enabled; + gboolean sub_bold; + gboolean sub_italic; } Preferences; Preferences* preferences_new(void); void preferences_free(Preferences *prefs); void preferences_load(Preferences *prefs); void preferences_save(Preferences *prefs); -gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, struct PluginManager *pm); +typedef void (*PreferencesApplyCallback)(gpointer data); +gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, struct PluginManager *pm, int initial_tab, PreferencesApplyCallback apply_cb, gpointer user_data); /* File dialogs */ GSList* dialogs_open_files(GtkWindow *parent, Preferences *prefs); @@ -43,8 +63,9 @@ char* dialogs_open_subtitle(GtkWindow *parent, Preferences *prefs); char* dialogs_save_playlist(GtkWindow *parent, Preferences *prefs); char* dialogs_save_screenshot(GtkWindow *parent); -/* About dialog */ +/* About and Keybinds dialogs */ void dialogs_show_about(GtkWindow *parent); +void dialogs_show_keybinds(GtkWindow *parent); /* Go to time dialog */ double dialogs_goto_time(GtkWindow *parent, double current_time, double duration); @@ -54,5 +75,6 @@ double dialogs_select_speed(GtkWindow *parent, double current_speed); /* Script Manager dialog */ void dialogs_show_script_manager(GtkWindow *parent, Player *player); +GtkWidget* dialogs_create_script_manager_widget(Player *player); #endif /* DIALOGS_H */ diff --git a/src/main.c b/src/main.c index f973ea5..f55ac50 100644 --- a/src/main.c +++ b/src/main.c @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) /* Basic command line parsing */ for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { - printf("Media Player v0.7\n"); + printf("Kino v9.1\n"); printf("Usage: %s [options] [files...]\n\n", argv[0]); printf("Options:\n"); printf(" -h, --help Show this help message\n"); @@ -99,7 +99,7 @@ int main(int argc, char *argv[]) return 0; } if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) { - printf("Kino v9.0\n"); + printf("Kino v9.1\n"); return 0; } } diff --git a/src/mpv_loader.c b/src/mpv_loader.c index a36b6bc..d90d05c 100644 --- a/src/mpv_loader.c +++ b/src/mpv_loader.c @@ -37,7 +37,7 @@ int load_mpv_library(void) }; for (size_t i = 0; i < sizeof(lib_names) / sizeof(lib_names[0]); i++) { - lib_handle = dlopen(lib_names[i], RTLD_LAZY); + lib_handle = dlopen(lib_names[i], RTLD_NOW | RTLD_GLOBAL); if (lib_handle) { printf("Loaded mpv library from: %s\n", lib_names[i]); break; diff --git a/src/player.c b/src/player.c index 94a101d..ab6cdc9 100644 --- a/src/player.c +++ b/src/player.c @@ -569,6 +569,143 @@ void player_set_subtitle_track(Player *player, int track) check_error(mpv_set_property_string(player->mpv, "sid", sid_str)); } +void player_set_subtitle_delay(Player *player, double delay) +{ + if (!player || !player->mpv) return; + char val[32]; + snprintf(val, sizeof(val), "%f", delay); + mpv_set_property_string(player->mpv, "sub-delay", val); +} + +void player_set_subtitle_pos(Player *player, int pos) +{ + if (!player || !player->mpv) return; + char val[16]; + snprintf(val, sizeof(val), "%d", pos); + mpv_set_property_string(player->mpv, "sub-pos", val); +} + +void player_set_subtitle_scale(Player *player, double scale) +{ + if (!player || !player->mpv) return; + char val[32]; + snprintf(val, sizeof(val), "%f", scale); + mpv_set_property_string(player->mpv, "sub-scale", val); +} + +void player_set_subtitle_font(Player *player, const char *font) +{ + if (!player || !player->mpv) return; + + if (font && strlen(font) > 0) { + mpv_set_property_string(player->mpv, "sub-font", font); + } + + /* ALWAYS Force override subtitles to use our selected style/colors/box */ + mpv_set_property_string(player->mpv, "sub-ass-override", "force"); + mpv_set_property_string(player->mpv, "sub-style-override", "force"); +} + +static char* compose_color_with_alpha(const char *hex_color, int opacity) +{ + if (!hex_color || hex_color[0] != '#') return g_strdup("#ffffffff"); + + /* Ensure opacity is within 0-100 */ + if (opacity < 0) opacity = 0; + if (opacity > 100) opacity = 100; + + if (opacity == 100) { + /* Standard #RRGGBB is most compatible when opaque */ + return g_strdup(hex_color); + } + + int alpha = (opacity * 255) / 100; + unsigned int r, g, b; + if (sscanf(hex_color + 1, "%02x%02x%02x", &r, &g, &b) == 3) { + /* MPV expects #AARRGGBB for colors with alpha */ + return g_strdup_printf("#%02x%02x%02x%02x", alpha, r, g, b); + } + + return g_strdup("#ffffffff"); +} + +void player_set_subtitle_font_size(Player *player, int size) +{ + if (!player || !player->mpv) return; + char val[16]; + snprintf(val, sizeof(val), "%d", size); + mpv_set_property_string(player->mpv, "sub-font-size", val); +} + +void player_set_subtitle_color(Player *player, const char *hex_color, int opacity) +{ + if (!player || !player->mpv) return; + char *full_color = compose_color_with_alpha(hex_color, opacity); + mpv_set_property_string(player->mpv, "sub-color", full_color); + g_free(full_color); +} + +void player_set_subtitle_border_color(Player *player, const char *hex_color, int opacity) +{ + if (!player || !player->mpv) return; + char *full_color = compose_color_with_alpha(hex_color, opacity); + mpv_set_property_string(player->mpv, "sub-border-color", full_color); + g_free(full_color); +} + +void player_set_subtitle_border_size(Player *player, double size) +{ + if (!player || !player->mpv) return; + char val[16]; + snprintf(val, sizeof(val), "%.1f", size); + mpv_set_property_string(player->mpv, "sub-border-size", val); +} +void player_set_subtitle_border_enabled(Player *player, gboolean enabled) +{ + if (!player || !player->mpv) return; + /* In mpv, setting sub-border-size to 0 disables the border */ + if (!enabled) { + mpv_set_property_string(player->mpv, "sub-border-size", "0"); + } +} + +void player_set_subtitle_shadow_color(Player *player, const char *hex_color, int opacity) +{ + if (!player || !player->mpv) return; + char *full_color = compose_color_with_alpha(hex_color, opacity); + mpv_set_property_string(player->mpv, "sub-shadow-color", full_color); + g_free(full_color); +} + +void player_set_subtitle_shadow_offset(Player *player, double offset) +{ + if (!player || !player->mpv) return; + char val[16]; + snprintf(val, sizeof(val), "%.1f", offset); + mpv_set_property_string(player->mpv, "sub-shadow-offset", val); +} + +void player_set_subtitle_shadow_enabled(Player *player, gboolean enabled) +{ + if (!player || !player->mpv) return; + /* In mpv, setting sub-shadow-offset to 0 effectively disables the shadow */ + if (!enabled) { + mpv_set_property_string(player->mpv, "sub-shadow-offset", "0"); + } +} + +void player_set_subtitle_bold(Player *player, gboolean bold) +{ + if (!player || !player->mpv) return; + mpv_set_property_string(player->mpv, "sub-bold", bold ? "yes" : "no"); +} + +void player_set_subtitle_italic(Player *player, gboolean italic) +{ + if (!player || !player->mpv) return; + mpv_set_property_string(player->mpv, "sub-italic", italic ? "yes" : "no"); +} + void player_load_subtitle(Player *player, const char *path) { if (!player || !player->mpv || !path) return; diff --git a/src/player.h b/src/player.h index e21329e..f4d4785 100644 --- a/src/player.h +++ b/src/player.h @@ -60,6 +60,20 @@ int player_get_current_subtitle_track(Player *player); void player_set_audio_track(Player *player, int track); void player_set_subtitle_track(Player *player, int track); void player_load_subtitle(Player *player, const char *path); +void player_set_subtitle_delay(Player *player, double delay); +void player_set_subtitle_pos(Player *player, int pos); +void player_set_subtitle_scale(Player *player, double scale); +void player_set_subtitle_font(Player *player, const char *font); +void player_set_subtitle_font_size(Player *player, int size); +void player_set_subtitle_color(Player *player, const char *hex_color, int opacity); +void player_set_subtitle_border_color(Player *player, const char *hex_color, int opacity); +void player_set_subtitle_border_size(Player *player, double size); +void player_set_subtitle_shadow_color(Player *player, const char *hex_color, int opacity); +void player_set_subtitle_shadow_offset(Player *player, double offset); +void player_set_subtitle_bold(Player *player, gboolean bold); +void player_set_subtitle_italic(Player *player, gboolean italic); +void player_set_subtitle_border_enabled(Player *player, gboolean enabled); +void player_set_subtitle_shadow_enabled(Player *player, gboolean enabled); /* Audio/Video info */ char** player_get_audio_track_list(Player *player, int *count); diff --git a/src/plugin_manager.c b/src/plugin_manager.c index 69772e3..88a37d2 100644 --- a/src/plugin_manager.c +++ b/src/plugin_manager.c @@ -153,6 +153,12 @@ void plugin_manager_destroy(PluginManager *pm) g_pm = NULL; } +Player* plugin_manager_get_player(PluginManager *pm) +{ + if (!pm || !pm->ui) return NULL; + return ui_get_player(pm->ui); +} + void plugin_manager_set_player_callbacks(PluginManager *pm, void (*play)(void), void (*pause)(void), @@ -617,15 +623,7 @@ GtkWidget* plugin_manager_get_menu(PluginManager *pm) GtkWidget *install_item = gtk_menu_item_new_with_label("Install Plugin..."); 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); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), install_item); /* Tools / Plugins menu doesn't need script management anymore as it's in Preferences */ /* Manage plugins - Removed as per user request (moved to preferences) */ /* diff --git a/src/plugin_manager.h b/src/plugin_manager.h index dcee0b2..0b31dcd 100644 --- a/src/plugin_manager.h +++ b/src/plugin_manager.h @@ -11,6 +11,7 @@ #include "plugin.h" typedef struct PluginManager PluginManager; +typedef struct Player Player; /** * Loaded plugin instance @@ -49,6 +50,11 @@ PluginManager* plugin_manager_new(AppUI *ui); */ void plugin_manager_destroy(PluginManager *pm); +/** + * Get the player instance associated with this manager + */ +Player* plugin_manager_get_player(PluginManager *pm); + /** * Set player control callbacks * Must be called before loading plugins diff --git a/src/ui.c b/src/ui.c index 68094f0..93e3623 100644 --- a/src/ui.c +++ b/src/ui.c @@ -103,6 +103,9 @@ struct AppUI { /* Plugin manager */ PluginManager *plugin_manager; + + /* Subtitle dragging */ + gboolean sub_dragging; }; /* Forward declarations */ @@ -118,6 +121,7 @@ static void on_drag_data_received(GtkWidget *widget, GdkDragContext *context, gi static gboolean on_video_area_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer data); static gboolean on_video_area_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data); static gboolean on_video_area_motion(GtkWidget *widget, GdkEventMotion *event, gpointer data); +static gboolean on_video_area_button_release(GtkWidget *widget, GdkEventButton *event, gpointer data); static gboolean auto_hide_timer_cb(gpointer data); static void add_path_to_playlist(AppUI *ui, const char *path, gboolean clear); static void on_recent_item_activated(GtkRecentChooser *chooser, gpointer data); @@ -146,10 +150,13 @@ static void on_subtitle_disable(GtkMenuItem *item, gpointer data); static void on_view_fullscreen(GtkMenuItem *item, gpointer data); static void on_view_playlist(GtkMenuItem *item, gpointer data); static void on_view_always_on_top(GtkMenuItem *item, gpointer data); +static void on_always_on_top_toggled(GtkCheckMenuItem *item, gpointer data); static void on_view_screenshot(GtkMenuItem *item, gpointer data); static void on_view_preferences(GtkMenuItem *item, gpointer data); +static void on_subtitle_settings_clicked(GtkMenuItem *item, gpointer data); static void on_aspect_ratio_selected(GtkMenuItem *item, gpointer data); static void on_audio_mute(GtkMenuItem *item, gpointer data); +static void on_help_keybinds(GtkMenuItem *item, gpointer data); static void on_help_about(GtkMenuItem *item, gpointer data); /* Plugin API wrapper forward declarations */ @@ -252,6 +259,7 @@ AppUI* ui_new(void) g_signal_connect(ui->video_area, "realize", G_CALLBACK(on_video_area_realize), ui); g_signal_connect(ui->video_container, "scroll-event", G_CALLBACK(on_video_area_scroll), ui); g_signal_connect(ui->video_container, "button-press-event", G_CALLBACK(on_video_area_button_press), ui); + g_signal_connect(ui->video_container, "button-release-event", G_CALLBACK(on_video_area_button_release), ui); g_signal_connect(ui->video_container, "motion-notify-event", G_CALLBACK(on_video_area_motion), ui); /* Enable drag and drop on container as well */ @@ -324,6 +332,12 @@ AppUI* ui_new(void) if (ui->prefs->use_mpris) { mpris_init(ui); } + + /* Apply initial subtitle preferences */ + player_set_subtitle_delay(ui->player, ui->prefs->sub_delay); + player_set_subtitle_pos(ui->player, ui->prefs->sub_pos); + player_set_subtitle_scale(ui->player, ui->prefs->sub_scale); + player_set_subtitle_font(ui->player, ui->prefs->sub_font); return ui; } @@ -352,6 +366,56 @@ void ui_destroy(AppUI *ui) g_free(ui); } +void ui_apply_subtitle_preferences(AppUI *ui) +{ + if (!ui || !ui->player || !ui->prefs) return; + + player_set_subtitle_delay(ui->player, ui->prefs->sub_delay); + player_set_subtitle_pos(ui->player, ui->prefs->sub_pos); + player_set_subtitle_scale(ui->player, ui->prefs->sub_scale); + player_set_subtitle_font(ui->player, ui->prefs->sub_font); + player_set_subtitle_font_size(ui->player, ui->prefs->sub_font_size); + player_set_subtitle_color(ui->player, ui->prefs->sub_color, ui->prefs->sub_color_opacity); + player_set_subtitle_border_color(ui->player, ui->prefs->sub_border_color, ui->prefs->sub_border_opacity); + player_set_subtitle_border_size(ui->player, ui->prefs->sub_border_size); + player_set_subtitle_border_enabled(ui->player, ui->prefs->sub_border_enabled); + player_set_subtitle_shadow_color(ui->player, ui->prefs->sub_shadow_color, ui->prefs->sub_shadow_opacity); + player_set_subtitle_shadow_offset(ui->player, ui->prefs->sub_shadow_offset); + player_set_subtitle_shadow_enabled(ui->player, ui->prefs->sub_shadow_enabled); + player_set_subtitle_bold(ui->player, ui->prefs->sub_bold); + player_set_subtitle_italic(ui->player, ui->prefs->sub_italic); +} + +static void ui_apply_preferences_internal(gpointer data) +{ + AppUI *ui = (AppUI *)data; + if (!ui || !ui->player || !ui->prefs) return; + + /* Apply subtitle settings */ + ui_apply_subtitle_preferences(ui); + + /* Volume settings */ + player_set_volume(ui->player, ui->prefs->default_volume); + player_set_volume_boost(ui->player, ui->prefs->enable_volume_boost); + + /* Mouse cursor hiding in windowed mode */ + if (!ui->is_fullscreen) { + if (ui->prefs->hide_cursor_windowed) { + if (ui->auto_hide_timer_id == 0) { + ui->auto_hide_timer_id = g_timeout_add(3000, auto_hide_timer_cb, ui); + } + } else { + if (ui->auto_hide_timer_id > 0) { + g_source_remove(ui->auto_hide_timer_id); + ui->auto_hide_timer_id = 0; + } + /* Restore cursor */ + GdkWindow *window = gtk_widget_get_window(ui->video_area); + if (window) gdk_window_set_cursor(window, NULL); + } + } +} + void ui_set_preferences(AppUI *ui, Preferences *prefs) { if (!ui || !prefs) return; @@ -370,6 +434,8 @@ void ui_set_preferences(AppUI *ui, Preferences *prefs) if (ui->prefs->screenshot_directory) { player_set_option_string(ui->player, "screenshot-directory", ui->prefs->screenshot_directory); } + + ui_apply_subtitle_preferences(ui); } if (ui->controls) { @@ -821,6 +887,8 @@ static void on_player_event(Player *player, mpv_event *event, gpointer user_data case MPV_EVENT_START_FILE: ui->last_mpris_pos = 0; mpris_update_playback_status(ui); + /* Re-apply subtitle preferences for each new file */ + ui_apply_subtitle_preferences(ui); break; case MPV_EVENT_FILE_LOADED: { @@ -1041,12 +1109,20 @@ static gboolean on_video_area_scroll(GtkWidget *widget, GdkEventScroll *event, g { (void)widget; AppUI *ui = (AppUI *)data; - double vol = player_get_volume(ui->player); - if (event->direction == GDK_SCROLL_UP) { - player_set_volume(ui->player, vol + 5); - } else if (event->direction == GDK_SCROLL_DOWN) { - player_set_volume(ui->player, vol - 5); + if (ui->prefs->mouse_wheel_seeks) { + if (event->direction == GDK_SCROLL_UP) { + player_seek(ui->player, 5); + } else if (event->direction == GDK_SCROLL_DOWN) { + player_seek(ui->player, -5); + } + } else { + double vol = player_get_volume(ui->player); + if (event->direction == GDK_SCROLL_UP) { + player_set_volume(ui->player, vol + 5); + } else if (event->direction == GDK_SCROLL_DOWN) { + player_set_volume(ui->player, vol - 5); + } } return TRUE; @@ -1063,11 +1139,39 @@ static gboolean on_video_area_button_press(GtkWidget *widget, GdkEventButton *ev } else if (event->type == GDK_BUTTON_PRESS && event->button == 3) { on_playback_play_pause(NULL, ui); return TRUE; + } else if (event->type == GDK_BUTTON_PRESS && event->button == 1 && (event->state & GDK_CONTROL_MASK)) { + ui->sub_dragging = TRUE; + /* Update position immediately */ + GtkAllocation alloc; + gtk_widget_get_allocation(ui->video_area, &alloc); + if (alloc.height > 0) { + int pos = (int)((event->y / alloc.height) * 100); + if (pos < 0) pos = 0; + if (pos > 100) pos = 100; + player_set_subtitle_pos(ui->player, pos); + + /* Show feedback */ + char msg[32]; + snprintf(msg, sizeof(msg), "Subtitle Position: %d%%", pos); + const char *cmd[] = {"show-text", msg, "1000", NULL}; + mpv_command_async(player_get_mpv_handle(ui->player), 0, cmd); + } + return TRUE; } return FALSE; } +static gboolean on_video_area_button_release(GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + (void)widget; + AppUI *ui = (AppUI *)data; + if (event->button == 1) { + ui->sub_dragging = FALSE; + } + return FALSE; +} + static gboolean auto_hide_timer_cb(gpointer data) { AppUI *ui = (AppUI *)data; @@ -1098,9 +1202,29 @@ static gboolean auto_hide_timer_cb(gpointer data) static gboolean on_video_area_motion(GtkWidget *widget, GdkEventMotion *event, gpointer data) { (void)widget; - (void)event; AppUI *ui = (AppUI *)data; + if (ui->sub_dragging) { + GtkAllocation alloc; + gtk_widget_get_allocation(ui->video_area, &alloc); + if (alloc.height > 0) { + int pos = (int)((event->y / alloc.height) * 100); + if (pos < 0) pos = 0; + if (pos > 100) pos = 100; + player_set_subtitle_pos(ui->player, pos); + + /* Update UI preferences state so it survives restart/dialog */ + if (ui->prefs) ui->prefs->sub_pos = pos; + + /* Show feedback */ + char msg[32]; + snprintf(msg, sizeof(msg), "Subtitle Position: %d%%", pos); + const char *cmd[] = {"show-text", msg, "500", NULL}; + mpv_command_async(player_get_mpv_handle(ui->player), 0, cmd); + } + return TRUE; + } + if (ui->is_fullscreen || ui->prefs->hide_cursor_windowed) { /* Show controls */ if (ui->is_fullscreen && ui->fs_window) { @@ -1200,6 +1324,13 @@ void update_track_menus(AppUI *ui) /* Update Subtitle Menu */ clear_menu(ui->subtitle_menu); + GtkWidget *sub_settings_item = gtk_menu_item_new_with_label("Subtitle Settings..."); + g_signal_connect(sub_settings_item, "activate", G_CALLBACK(on_subtitle_settings_clicked), ui); + gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), sub_settings_item); + + GtkWidget *sub_sep = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), sub_sep); + int sub_count = 0; char **sub_tracks = player_get_subtitle_track_list(ui->player, &sub_count); @@ -1398,6 +1529,13 @@ static void create_menubar(AppUI *ui) GtkWidget *subtitle_item = gtk_menu_item_new_with_mnemonic("_Subtitles"); gtk_menu_item_set_submenu(GTK_MENU_ITEM(subtitle_item), subtitle_root_menu); + GtkWidget *sub_settings_item = gtk_menu_item_new_with_label("Subtitle Settings..."); + g_signal_connect(sub_settings_item, "activate", G_CALLBACK(on_subtitle_settings_clicked), ui); + gtk_menu_shell_append(GTK_MENU_SHELL(subtitle_root_menu), sub_settings_item); + + GtkWidget *sub_sep = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(subtitle_root_menu), sub_sep); + GtkWidget *sub_tracks_item = gtk_menu_item_new_with_mnemonic("_Select Track"); ui->subtitle_menu = gtk_menu_new(); gtk_menu_item_set_submenu(GTK_MENU_ITEM(sub_tracks_item), ui->subtitle_menu); @@ -1501,6 +1639,10 @@ static void create_menubar(AppUI *ui) gtk_menu_item_set_submenu(GTK_MENU_ITEM(help_item), help_menu); gtk_menu_item_set_right_justified(GTK_MENU_ITEM(help_item), FALSE); + GtkWidget *key_item = gtk_menu_item_new_with_label("Keyboard Shortcuts"); + g_signal_connect(key_item, "activate", G_CALLBACK(on_help_keybinds), ui); + gtk_menu_shell_append(GTK_MENU_SHELL(help_menu), key_item); + GtkWidget *about_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL); g_signal_connect(about_item, "activate", G_CALLBACK(on_help_about), ui); gtk_menu_shell_append(GTK_MENU_SHELL(help_menu), about_item); @@ -1792,123 +1934,6 @@ static void on_view_screenshot(GtkMenuItem *item, gpointer data) player_screenshot(ui->player); } -static void on_view_preferences(GtkMenuItem *item, gpointer data) -{ - (void)item; - AppUI *ui = (AppUI *)data; - if (dialogs_show_preferences(GTK_WINDOW(ui->window), ui->prefs, ui->plugin_manager)) { - /* Apply changes immediately */ - if (!ui->is_fullscreen) { - if (ui->prefs->hide_cursor_windowed) { - if (ui->auto_hide_timer_id == 0) { - ui->auto_hide_timer_id = g_timeout_add(3000, auto_hide_timer_cb, ui); - } - } else { - if (ui->auto_hide_timer_id > 0) { - g_source_remove(ui->auto_hide_timer_id); - ui->auto_hide_timer_id = 0; - } - /* Restore cursor and controls */ - GdkWindow *window = gtk_widget_get_window(ui->video_area); - if (window) gdk_window_set_cursor(window, NULL); - gtk_widget_show(controls_get_widget(ui->controls)); - } - } - - /* Apply Focus Rect setting (GTK2 specific hack) */ - if (ui->prefs->hide_focus_rect) { - gtk_rc_parse_string("style \"no-focus-rect\" { GtkWidget::focus-line-width = 0 GtkWidget::focus-padding = 0 } widget \"*\" style \"no-focus-rect\""); - } else { - gtk_rc_parse_string("style \"default-focus-rect\" { GtkWidget::focus-line-width = 1 GtkWidget::focus-padding = 1 } widget \"*\" style \"default-focus-rect\""); - } - gtk_widget_reset_rc_styles(ui->window); - gtk_widget_queue_draw(ui->window); - - /* Apply OSD setting */ - player_set_option_string(ui->player, "osd-level", ui->prefs->enable_osd ? "1" : "0"); - player_set_option_string(ui->player, "osd-bar", ui->prefs->enable_osd ? "yes" : "no"); - - /* Apply mpv.conf if changed - parse and apply each option */ - if (ui->prefs->mpv_config_path && g_file_test(ui->prefs->mpv_config_path, G_FILE_TEST_EXISTS)) { - /* First, reset common properties to defaults to avoid "bleeding" from previous configs */ - player_set_option_string(ui->player, "brightness", "0"); - player_set_option_string(ui->player, "contrast", "0"); - player_set_option_string(ui->player, "saturation", "0"); - player_set_option_string(ui->player, "gamma", "0"); - player_set_option_string(ui->player, "hue", "0"); - - player_set_option_string(ui->player, "osd-color", "#FFFFFFFF"); - player_set_option_string(ui->player, "osd-border-color", "#FF000000"); - player_set_option_string(ui->player, "osd-shadow-color", "#80000000"); - player_set_option_string(ui->player, "osd-font-size", "55"); - player_set_option_string(ui->player, "osd-border-size", "3"); - player_set_option_string(ui->player, "osd-shadow-offset", "0"); - - FILE *fp = fopen(ui->prefs->mpv_config_path, "r"); - if (fp) { - char line[512]; - int applied = 0; - while (fgets(line, sizeof(line), fp)) { - /* Strip newline */ - char *nl = strchr(line, '\n'); - if (nl) *nl = '\0'; - - /* Skip comments and empty lines */ - char *p = line; - while (*p == ' ' || *p == '\t') p++; - if (*p == '#' || *p == '\0') continue; - - /* Find '=' separator */ - char *eq = strchr(p, '='); - if (eq) { - *eq = '\0'; - char *key = p; - char *value = eq + 1; - - /* Trim trailing spaces from key */ - char *k_end = key + strlen(key) - 1; - while (k_end > key && (*k_end == ' ' || *k_end == '\t')) *k_end-- = '\0'; - - /* Trim leading spaces from value */ - while (*value == ' ' || *value == '\t') value++; - - /* Remove surrounding quotes if present */ - size_t vlen = strlen(value); - if (vlen >= 2 && ((value[0] == '\'' && value[vlen-1] == '\'') || - (value[0] == '"' && value[vlen-1] == '"'))) { - value[vlen-1] = '\0'; - value++; - } - - player_set_option_string(ui->player, key, value); - applied++; - } - } - fclose(fp); - - /* Show feedback with count */ - char msg[64]; - snprintf(msg, sizeof(msg), "Applied %d config options", applied); - const char *msg_cmd[] = {"show-text", msg, "2000", NULL}; - mpv_command_async(player_get_mpv_handle(ui->player), 0, msg_cmd); - } - } - - /* Apply Screenshot Format */ - if (ui->prefs->screenshot_format) { - player_set_option_string(ui->player, "screenshot-format", ui->prefs->screenshot_format); - } - - /* Apply Screenshot Dir */ - if (ui->prefs->screenshot_directory) { - player_set_option_string(ui->player, "screenshot-directory", ui->prefs->screenshot_directory); - } - - /* Show feedback */ - const char *saved_cmd[] = {"show-text", "Preferences saved", "1500", NULL}; - mpv_command_async(player_get_mpv_handle(ui->player), 0, saved_cmd); - } -} static void on_aspect_ratio_selected(GtkMenuItem *item, gpointer data) { @@ -1917,6 +1942,28 @@ static void on_aspect_ratio_selected(GtkMenuItem *item, gpointer data) player_set_aspect_ratio(ui->player, aspect); } +static void on_view_preferences(GtkMenuItem *item, gpointer data) +{ + (void)item; + AppUI *ui = (AppUI *)data; + dialogs_show_preferences(GTK_WINDOW(ui->window), ui->prefs, ui->plugin_manager, -1, ui_apply_preferences_internal, ui); +} + +static void on_subtitle_settings_clicked(GtkMenuItem *item, gpointer data) +{ + (void)item; + AppUI *ui = (AppUI *)data; + /* Open preferences directly on the Subtitles tab (index 1) */ + dialogs_show_preferences(GTK_WINDOW(ui->window), ui->prefs, ui->plugin_manager, 1, ui_apply_preferences_internal, ui); +} + +static void on_help_keybinds(GtkMenuItem *item, gpointer data) +{ + (void)item; + AppUI *ui = (AppUI *)data; + dialogs_show_keybinds(GTK_WINDOW(ui->window)); +} + static void on_help_about(GtkMenuItem *item, gpointer data) { (void)item; diff --git a/tools/create_portable_maximum_compatability.sh b/tools/create_portable_maximum_compatability.sh deleted file mode 100755 index c922fa2..0000000 --- a/tools/create_portable_maximum_compatability.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/bash - -# Script to create a portable bundle for GTK2 Media Player -# Gathering all shared library dependencies into a single folder. - -# Ensure we are in the project root -cd "$(dirname "$0")/.." - -APP_NAME="gtk2-mpv-player" -BUNDLE_DIR="${APP_NAME}-portable" - -echo "Creating portable bundle in ${BUNDLE_DIR}..." - -# 1. Clean and Build -make clean -make - -if [ ! -f "$APP_NAME" ]; then - echo "Build failed. Exiting." - exit 1 -fi - -# 2. Create directory structure -rm -rf "$BUNDLE_DIR" -mkdir -p "$BUNDLE_DIR/bin" -mkdir -p "$BUNDLE_DIR/lib" - -# 3. Copy executable -cp "$APP_NAME" "$BUNDLE_DIR/bin/" - -# 4. Gather dependencies -echo "Gathering dependencies (Lean Edition)..." - -# Aggressively exclude everything not strictly required for basic playback. -# Pruning: Encoders (x265, rav1e, svt), Math (lapack, blas), TTS (flite), -# Niche codecs (codec2, sphinx), and heavy rendering (placebo, caca). -EXCLUDES="libc\.so|libm\.so|libdl\.so|librt\.so|libpthread\.so|libutil\.so|libnsl\.so|libresolv\.so|libgcc_s\.so|libX11\.so|libXext\.so|libXrender\.so|libXinerama\.so|libXi\.so|libXrandr\.so|libXcursor\.so|libXcomposite\.so|libXdamage\.so|libXfixes\.so|libXau\.so|libXdmcp\.so|libxcb.*|libz\.so|libGL.*|libEGL\.so|libdrm\.so|libwayland.*|libasound\.so|libpulse.*|libdbus-1\.so|libudev\.so|libsystemd\.so|libgpg-error\.so|libgcrypt\.so|liblzma\.so|libbz2\.so|libexpat\.so|libuuid\.so|libfontconfig\.so|libfreetype\.so|libcap\.so|libflite.*|liblapack.*|libgfortran.*|libcodec2.*|libsphinx.*|libpocketsphinx.*|librav1e.*|libSvtAv1Enc.*|libblas.*|libdav1d.*|libcrypto\.so|libssl\.so|libgnutls\.so|libidn2\.so|libtasn1\.so|libunistring\.so|libgmp\.so|libhogweed\.so|libnettle\.so|libp11-kit\.so|libxml2\.so|libdb-.*\.so|libarchive\.so|libzstd\.so|liblzma\.so|libbz2\.so|libpcre.*|libselinux.*|libmount\.so|libblkid\.so|libffi\.so|libreadline\.so|libtinfo\.so|libncurses.*|libplacebo.*|libcaca.*|libopenmpt.*|libvulkan.*|libOpenCL.*|libvpl.*|libmysofa.*|librsvg.*|libx265.*" - -TEMP_LIBS=$(mktemp) - -# Initial list from the binary and libmpv -ldd "$APP_NAME" | grep "=> /" | awk '{print $3}' >> "$TEMP_LIBS" -if [ -d "lib" ]; then - cp -nv lib/*.so* "$BUNDLE_DIR/lib/" 2>/dev/null - find lib -name "*.so*" -exec ldd {} \; | grep "=> /" | awk '{print $3}' >> "$TEMP_LIBS" -fi - -# Function to filter and copy libraries -copy_libs() { - sort -u "$1" | while read -r LIB; do - if [ -f "$LIB" ]; then - LIB_BASE=$(basename "$LIB") - if ! echo "$LIB_BASE" | grep -qE "$EXCLUDES"; then - cp -nv "$LIB" "$BUNDLE_DIR/lib/" 2>/dev/null - fi - fi - done -} - -# First pass -copy_libs "$TEMP_LIBS" - -# Second pass: check dependencies of the copied libraries -TEMP_LIBS_2=$(mktemp) -find "$BUNDLE_DIR/lib" -name "*.so*" -exec ldd {} \; | grep "=> /" | awk '{print $3}' >> "$TEMP_LIBS_2" -copy_libs "$TEMP_LIBS_2" - -rm "$TEMP_LIBS" "$TEMP_LIBS_2" - - - -# 5. Strip symbols to save space -echo "Stripping symbols..." -strip --strip-unneeded "$BUNDLE_DIR/bin/$APP_NAME" -find "$BUNDLE_DIR/lib" -name "*.so*" -exec strip --strip-unneeded {} \; 2>/dev/null - - - - -# 5. Create launch script -cat > "$BUNDLE_DIR/launch.sh" < /" | awk '{print $3}' >> "$TEMP_LIBS" - -# Add libmpv (required) -if [ -f "lib/libmpv.so.2" ]; then - cp -v "lib/libmpv.so.2" "$BUNDLE_DIR/lib/" -fi - -# Copy filtered libs -sort -u "$TEMP_LIBS" | while read -r LIB; do - LIB_BASE=$(basename "$LIB") - if ! echo "$LIB_BASE" | grep -qE "$EXCLUDES"; then - if [ -f "$LIB" ]; then - cp -nv "$LIB" "$BUNDLE_DIR/lib/" 2>/dev/null - fi - fi -done - -rm "$TEMP_LIBS" - -# 5. Strip symbols to save space -echo "Stripping symbols..." -strip --strip-unneeded "$BUNDLE_DIR/bin/$APP_NAME" -find "$BUNDLE_DIR/lib" -name "*.so*" -exec strip --strip-unneeded {} \; 2>/dev/null - -# 6. Create launch script -cat > "$BUNDLE_DIR/launch.sh" <