Added subtitle settings editor, moved around some settings and dialogs, etc

This commit is contained in:
laki 2026-03-22 17:54:50 +00:00
parent 20616c4fa6
commit 06f793f4f4
16 changed files with 783 additions and 492 deletions

4
.gitignore vendored
View File

@ -20,4 +20,6 @@ make_deb_32.sh
make_deb.sh make_deb.sh
make_release.sh make_release.sh
gtk2-media-player-v0.7.1-linux.zip gtk2-media-player-v0.7.1-linux.zip
GTK2-template GTK2-template
tools/kino_9.1_amd64.deb
tools

BIN
Kino

Binary file not shown.

View File

@ -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 <<EOF > "$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."

Binary file not shown.

View File

@ -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'."

View File

@ -1,4 +1,5 @@
#include "dialogs.h" #include "dialogs.h"
#include <pango/pango.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@ -293,6 +294,33 @@ char* dialogs_save_screenshot(GtkWindow *parent)
return result; 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(
"<b>Space</b>\t\t\tPlay/Pause\n"
"<b>F / F11</b>\t\t\tFullscreen\n"
"<b>Left / Right</b>\tSeek Backward/Forward (5s)\n"
"<b>Up / Down</b>\t\tVolume Up/Down\n"
"<b>M</b>\t\t\t\tMute\n"
"<b>Page Up/Down</b>\tPrevious/Next Chapter\n"
"<b>Esc</b>\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) void dialogs_show_about(GtkWindow *parent)
{ {
GtkWidget *dialog = gtk_dialog_new_with_buttons("About Kino", 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); GtkWidget *title_label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(title_label), "<span size='xx-large' weight='bold'>Kino</span>"); gtk_label_set_markup(GTK_LABEL(title_label), "<span size='x-large' weight='bold'>Kino v9.1</span>");
gtk_box_pack_start(GTK_BOX(about_vbox), title_label, FALSE, FALSE, 0); 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); 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."); 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); 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); ScriptManagerData *data = g_new0(ScriptManagerData, 1);
data->player = player; 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); GtkWidget *vbox = gtk_vbox_new(FALSE, 10);
gtk_container_set_border_width(GTK_CONTAINER(vbox), 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); 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_label_set_markup(GTK_LABEL(label), "<b>Manage MPV Scripts</b>\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); gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
data->list_store = gtk_list_store_new(NUM_SCRIPT_COLS, 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, /* Name */
G_TYPE_STRING, /* Internal Path */ 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)); 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_container_add(GTK_CONTAINER(btn_folder), icon_dir);
gtk_widget_set_tooltip_text(btn_folder, "Open Scripts Folder"); gtk_widget_set_tooltip_text(btn_folder, "Open Scripts Folder");
/* Setup xdg-open command */
const char *cfg_dir = g_get_user_config_dir(); const char *cfg_dir = g_get_user_config_dir();
char *s_dir = g_build_filename(cfg_dir, "gtk2-media-player", "scripts", NULL); 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); char *cmd = g_strdup_printf("xdg-open '%s'", s_dir);
g_signal_connect_swapped(btn_folder, "clicked", G_CALLBACK(system), cmd); 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); gtk_box_pack_start(GTK_BOX(hbox), btn_folder, FALSE, FALSE, 0);
refresh_scripts_list(data); 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_widget_show_all(dialog);
gtk_dialog_run(GTK_DIALOG(dialog)); gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(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); 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* preferences_new(void)
{ {
Preferences *prefs = g_new0(Preferences, 1); Preferences *prefs = g_new0(Preferences, 1);
@ -674,12 +745,32 @@ Preferences* preferences_new(void)
prefs->enable_osd = TRUE; prefs->enable_osd = TRUE;
prefs->enable_chapters = TRUE; prefs->enable_chapters = TRUE;
prefs->enable_volume_boost = FALSE; prefs->enable_volume_boost = FALSE;
prefs->mouse_wheel_seeks = FALSE;
prefs->last_dir = NULL; prefs->last_dir = NULL;
prefs->screenshot_directory = g_strdup(g_get_home_dir()); prefs->screenshot_directory = g_strdup(g_get_home_dir());
prefs->screenshot_format = g_strdup("png"); prefs->screenshot_format = g_strdup("png");
const char *config_dir = g_get_user_config_dir(); const char *config_dir = g_get_user_config_dir();
prefs->mpv_config_path = g_build_filename(config_dir, "gtk2-media-player", "mpv.conf", NULL); 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; return prefs;
} }
@ -690,6 +781,10 @@ void preferences_free(Preferences *prefs)
g_free(prefs->screenshot_format); g_free(prefs->screenshot_format);
g_free(prefs->mpv_config_path); g_free(prefs->mpv_config_path);
g_free(prefs->last_dir); 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); 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_osd", &prefs->enable_osd);
load_bool_pref(keyfile, "enable_chapters", &prefs->enable_chapters); load_bool_pref(keyfile, "enable_chapters", &prefs->enable_chapters);
load_bool_pref(keyfile, "enable_volume_boost", &prefs->enable_volume_boost); 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); char *screenshot_dir = g_key_file_get_string(keyfile, "General", "screenshot_directory", &error);
if (!error && screenshot_dir) { if (!error && screenshot_dir) {
@ -765,6 +863,49 @@ void preferences_load(Preferences *prefs)
g_clear_error(&error); g_clear_error(&error);
g_free(mpv_cfg); 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); g_key_file_free(keyfile);
@ -798,6 +939,9 @@ void preferences_save(Preferences *prefs)
{"enable_osd", prefs->enable_osd}, {"enable_osd", prefs->enable_osd},
{"enable_chapters", prefs->enable_chapters}, {"enable_chapters", prefs->enable_chapters},
{"enable_volume_boost", prefs->enable_volume_boost}, {"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} {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); 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; gsize length;
char *data = g_key_file_to_data(keyfile, &length, NULL); char *data = g_key_file_to_data(keyfile, &length, NULL);
if (data) { if (data) {
@ -872,7 +1033,43 @@ static GtkWidget* create_pref_check(GtkWidget *box, const char *label, gboolean
return check; 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; if (!prefs) return FALSE;
@ -881,6 +1078,7 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM
parent, parent,
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_APPLY, GTK_RESPONSE_APPLY,
GTK_STOCK_OK, GTK_RESPONSE_OK, GTK_STOCK_OK, GTK_RESPONSE_OK,
NULL); 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")); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), interface_vbox, gtk_label_new("Interface"));
/* 2. Config File Tab */ /* 2. Subtitles Tab */
GtkWidget *config_vbox = gtk_vbox_new(FALSE, 10); GtkWidget *sub_vbox = gtk_vbox_new(FALSE, 10);
gtk_container_set_border_width(GTK_CONTAINER(config_vbox), 10); gtk_container_set_border_width(GTK_CONTAINER(sub_vbox), 10);
GtkWidget *config_info_label = gtk_label_new(NULL); /* Delay */
gtk_label_set_markup(GTK_LABEL(config_info_label), "<b>mpv Configuration File</b>\n" GtkWidget *delay_hbox = gtk_hbox_new(FALSE, 10);
"Specify an external mpv.conf file to load custom settings, shaders, and advanced options.\n" gtk_box_pack_start(GTK_BOX(delay_hbox), gtk_label_new("Subtitle Delay (seconds):"), FALSE, FALSE, 0);
"<i>Note: Standard MPV syntax is supported.</i>"); GtkAdjustment *delay_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_delay, -100.0, 100.0, 0.1, 1.0, 0));
gtk_label_set_line_wrap(GTK_LABEL(config_info_label), TRUE); GtkWidget *delay_spin = gtk_spin_button_new(delay_adj, 0.1, 1);
gtk_box_pack_start(GTK_BOX(config_vbox), config_info_label, FALSE, FALSE, 5); 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); /* Position */
GtkWidget *cfg_entry = gtk_entry_new(); GtkWidget *subpos_hbox = gtk_hbox_new(FALSE, 10);
gtk_entry_set_text(GTK_ENTRY(cfg_entry), prefs->mpv_config_path ? prefs->mpv_config_path : ""); gtk_box_pack_start(GTK_BOX(subpos_hbox), gtk_label_new("Vertical Position (0-100):"), FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(cfg_hbox), cfg_entry, TRUE, TRUE, 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..."); /* Scale */
g_object_set_data(G_OBJECT(cfg_browse_btn), "entry", cfg_entry); GtkWidget *scale_hbox = gtk_hbox_new(FALSE, 10);
g_signal_connect(cfg_browse_btn, "clicked", G_CALLBACK(on_cfg_browse_clicked), dialog); gtk_box_pack_start(GTK_BOX(scale_hbox), gtk_label_new("Font Scale:"), FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(cfg_hbox), cfg_browse_btn, FALSE, FALSE, 0); GtkAdjustment *scale_adj = GTK_ADJUSTMENT(gtk_adjustment_new(prefs->sub_scale, 0.1, 10.0, 0.1, 1.0, 0));
gtk_box_pack_start(GTK_BOX(config_vbox), cfg_hbox, FALSE, FALSE, 5); 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"); /* Font family */
g_signal_connect(cfg_reset_btn, "clicked", G_CALLBACK(on_cfg_reset_clicked), cfg_entry); GtkWidget *font_hbox = gtk_hbox_new(FALSE, 10);
gtk_box_pack_start(GTK_BOX(config_vbox), cfg_reset_btn, FALSE, FALSE, 0); 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 */ /* 3. Miscellaneous Tab */
GtkWidget *misc_vbox = gtk_vbox_new(FALSE, 10); 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); 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 *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 *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 *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); 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); GtkWidget *plugins_vbox = plugin_manager_create_config_widget(pm);
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), plugins_vbox, gtk_label_new("Plugins")); 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), "<b>mpv Configuration File</b>\n"
"Specify an external mpv.conf file to load custom settings, shaders, and advanced options.\n"
"<i>Note: Standard MPV syntax is supported.</i>");
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); gtk_widget_show_all(dialog);
gboolean result = FALSE; int response;
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { gboolean changed = FALSE;
prefs->default_volume = gtk_spin_button_get_value(GTK_SPIN_BUTTON(vol_spin)); 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->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->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)); 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_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_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->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); g_free(prefs->screenshot_directory);
prefs->screenshot_directory = g_strdup(gtk_entry_get_text(GTK_ENTRY(ss_entry))); 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))); prefs->mpv_config_path = g_strdup(gtk_entry_get_text(GTK_ENTRY(cfg_entry)));
preferences_save(prefs); preferences_save(prefs);
result = TRUE; if (apply_cb) apply_cb(user_data);
changed = TRUE;
} }
} while (response == GTK_RESPONSE_APPLY);
gtk_widget_destroy(dialog);
return result; 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) double dialogs_goto_time(GtkWindow *parent, double current_time, double duration)

View File

@ -23,17 +23,37 @@ typedef struct {
gboolean enable_osd; gboolean enable_osd;
gboolean enable_chapters; gboolean enable_chapters;
gboolean enable_volume_boost; gboolean enable_volume_boost;
gboolean mouse_wheel_seeks;
char *last_dir; char *last_dir;
char *screenshot_directory; char *screenshot_directory;
char *screenshot_format; char *screenshot_format;
char *mpv_config_path; 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* preferences_new(void); Preferences* preferences_new(void);
void preferences_free(Preferences *prefs); void preferences_free(Preferences *prefs);
void preferences_load(Preferences *prefs); void preferences_load(Preferences *prefs);
void preferences_save(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 */ /* File dialogs */
GSList* dialogs_open_files(GtkWindow *parent, Preferences *prefs); 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_playlist(GtkWindow *parent, Preferences *prefs);
char* dialogs_save_screenshot(GtkWindow *parent); char* dialogs_save_screenshot(GtkWindow *parent);
/* About dialog */ /* About and Keybinds dialogs */
void dialogs_show_about(GtkWindow *parent); void dialogs_show_about(GtkWindow *parent);
void dialogs_show_keybinds(GtkWindow *parent);
/* Go to time dialog */ /* Go to time dialog */
double dialogs_goto_time(GtkWindow *parent, double current_time, double duration); 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 */ /* Script Manager dialog */
void dialogs_show_script_manager(GtkWindow *parent, Player *player); void dialogs_show_script_manager(GtkWindow *parent, Player *player);
GtkWidget* dialogs_create_script_manager_widget(Player *player);
#endif /* DIALOGS_H */ #endif /* DIALOGS_H */

View File

@ -83,7 +83,7 @@ int main(int argc, char *argv[])
/* Basic command line parsing */ /* Basic command line parsing */
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { 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("Usage: %s [options] [files...]\n\n", argv[0]);
printf("Options:\n"); printf("Options:\n");
printf(" -h, --help Show this help message\n"); printf(" -h, --help Show this help message\n");
@ -99,7 +99,7 @@ int main(int argc, char *argv[])
return 0; return 0;
} }
if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) { if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) {
printf("Kino v9.0\n"); printf("Kino v9.1\n");
return 0; return 0;
} }
} }

View File

@ -37,7 +37,7 @@ int load_mpv_library(void)
}; };
for (size_t i = 0; i < sizeof(lib_names) / sizeof(lib_names[0]); i++) { 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) { if (lib_handle) {
printf("Loaded mpv library from: %s\n", lib_names[i]); printf("Loaded mpv library from: %s\n", lib_names[i]);
break; break;

View File

@ -569,6 +569,143 @@ void player_set_subtitle_track(Player *player, int track)
check_error(mpv_set_property_string(player->mpv, "sid", sid_str)); 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) void player_load_subtitle(Player *player, const char *path)
{ {
if (!player || !player->mpv || !path) return; if (!player || !player->mpv || !path) return;

View File

@ -60,6 +60,20 @@ int player_get_current_subtitle_track(Player *player);
void player_set_audio_track(Player *player, int track); void player_set_audio_track(Player *player, int track);
void player_set_subtitle_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_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 */ /* Audio/Video info */
char** player_get_audio_track_list(Player *player, int *count); char** player_get_audio_track_list(Player *player, int *count);

View File

@ -153,6 +153,12 @@ void plugin_manager_destroy(PluginManager *pm)
g_pm = NULL; 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 plugin_manager_set_player_callbacks(PluginManager *pm,
void (*play)(void), void (*play)(void),
void (*pause)(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..."); GtkWidget *install_item = gtk_menu_item_new_with_label("Install Plugin...");
g_signal_connect(install_item, "activate", G_CALLBACK(on_install_plugin), pm); g_signal_connect(install_item, "activate", G_CALLBACK(on_install_plugin), pm);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), install_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 */
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);
/* Manage plugins - Removed as per user request (moved to preferences) */ /* Manage plugins - Removed as per user request (moved to preferences) */
/* /*

View File

@ -11,6 +11,7 @@
#include "plugin.h" #include "plugin.h"
typedef struct PluginManager PluginManager; typedef struct PluginManager PluginManager;
typedef struct Player Player;
/** /**
* Loaded plugin instance * Loaded plugin instance
@ -49,6 +50,11 @@ PluginManager* plugin_manager_new(AppUI *ui);
*/ */
void plugin_manager_destroy(PluginManager *pm); 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 * Set player control callbacks
* Must be called before loading plugins * Must be called before loading plugins

293
src/ui.c
View File

@ -103,6 +103,9 @@ struct AppUI {
/* Plugin manager */ /* Plugin manager */
PluginManager *plugin_manager; PluginManager *plugin_manager;
/* Subtitle dragging */
gboolean sub_dragging;
}; };
/* Forward declarations */ /* 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_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_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_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 gboolean auto_hide_timer_cb(gpointer data);
static void add_path_to_playlist(AppUI *ui, const char *path, gboolean clear); static void add_path_to_playlist(AppUI *ui, const char *path, gboolean clear);
static void on_recent_item_activated(GtkRecentChooser *chooser, gpointer data); 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_fullscreen(GtkMenuItem *item, gpointer data);
static void on_view_playlist(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_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_screenshot(GtkMenuItem *item, gpointer data);
static void on_view_preferences(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_aspect_ratio_selected(GtkMenuItem *item, gpointer data);
static void on_audio_mute(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); static void on_help_about(GtkMenuItem *item, gpointer data);
/* Plugin API wrapper forward declarations */ /* 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_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, "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-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); g_signal_connect(ui->video_container, "motion-notify-event", G_CALLBACK(on_video_area_motion), ui);
/* Enable drag and drop on container as well */ /* Enable drag and drop on container as well */
@ -324,6 +332,12 @@ AppUI* ui_new(void)
if (ui->prefs->use_mpris) { if (ui->prefs->use_mpris) {
mpris_init(ui); 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; return ui;
} }
@ -352,6 +366,56 @@ void ui_destroy(AppUI *ui)
g_free(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) void ui_set_preferences(AppUI *ui, Preferences *prefs)
{ {
if (!ui || !prefs) return; if (!ui || !prefs) return;
@ -370,6 +434,8 @@ void ui_set_preferences(AppUI *ui, Preferences *prefs)
if (ui->prefs->screenshot_directory) { if (ui->prefs->screenshot_directory) {
player_set_option_string(ui->player, "screenshot-directory", ui->prefs->screenshot_directory); player_set_option_string(ui->player, "screenshot-directory", ui->prefs->screenshot_directory);
} }
ui_apply_subtitle_preferences(ui);
} }
if (ui->controls) { 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: case MPV_EVENT_START_FILE:
ui->last_mpris_pos = 0; ui->last_mpris_pos = 0;
mpris_update_playback_status(ui); mpris_update_playback_status(ui);
/* Re-apply subtitle preferences for each new file */
ui_apply_subtitle_preferences(ui);
break; break;
case MPV_EVENT_FILE_LOADED: { case MPV_EVENT_FILE_LOADED: {
@ -1041,12 +1109,20 @@ static gboolean on_video_area_scroll(GtkWidget *widget, GdkEventScroll *event, g
{ {
(void)widget; (void)widget;
AppUI *ui = (AppUI *)data; AppUI *ui = (AppUI *)data;
double vol = player_get_volume(ui->player);
if (event->direction == GDK_SCROLL_UP) { if (ui->prefs->mouse_wheel_seeks) {
player_set_volume(ui->player, vol + 5); if (event->direction == GDK_SCROLL_UP) {
} else if (event->direction == GDK_SCROLL_DOWN) { player_seek(ui->player, 5);
player_set_volume(ui->player, vol - 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; 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) { } else if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
on_playback_play_pause(NULL, ui); on_playback_play_pause(NULL, ui);
return TRUE; 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; 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) static gboolean auto_hide_timer_cb(gpointer data)
{ {
AppUI *ui = (AppUI *)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) static gboolean on_video_area_motion(GtkWidget *widget, GdkEventMotion *event, gpointer data)
{ {
(void)widget; (void)widget;
(void)event;
AppUI *ui = (AppUI *)data; 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) { if (ui->is_fullscreen || ui->prefs->hide_cursor_windowed) {
/* Show controls */ /* Show controls */
if (ui->is_fullscreen && ui->fs_window) { if (ui->is_fullscreen && ui->fs_window) {
@ -1200,6 +1324,13 @@ void update_track_menus(AppUI *ui)
/* Update Subtitle Menu */ /* Update Subtitle Menu */
clear_menu(ui->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; int sub_count = 0;
char **sub_tracks = player_get_subtitle_track_list(ui->player, &sub_count); 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"); GtkWidget *subtitle_item = gtk_menu_item_new_with_mnemonic("_Subtitles");
gtk_menu_item_set_submenu(GTK_MENU_ITEM(subtitle_item), subtitle_root_menu); 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"); GtkWidget *sub_tracks_item = gtk_menu_item_new_with_mnemonic("_Select Track");
ui->subtitle_menu = gtk_menu_new(); ui->subtitle_menu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(sub_tracks_item), ui->subtitle_menu); 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_submenu(GTK_MENU_ITEM(help_item), help_menu);
gtk_menu_item_set_right_justified(GTK_MENU_ITEM(help_item), FALSE); 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); 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); g_signal_connect(about_item, "activate", G_CALLBACK(on_help_about), ui);
gtk_menu_shell_append(GTK_MENU_SHELL(help_menu), about_item); 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); 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) 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); 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) static void on_help_about(GtkMenuItem *item, gpointer data)
{ {
(void)item; (void)item;

View File

@ -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" <<EOF
#!/bin/bash
DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
export LD_LIBRARY_PATH="\$DIR/lib:\$LD_LIBRARY_PATH"
# Ensure GTK2 can find its modules if needed, though most are included in libs
exec "\$DIR/bin/$APP_NAME" "\$@"
EOF
chmod +x "$BUNDLE_DIR/launch.sh"
echo "------------------------------------------------"
echo "Portable bundle created in ${BUNDLE_DIR}"
echo "To run, use: cd ${BUNDLE_DIR} && ./launch.sh"
echo "------------------------------------------------"

View File

@ -1,81 +0,0 @@
#!/bin/bash
# Script to create a SUPER LEAN portable bundle for GTK2 Media Player
# This version only bundles essential GTK2 libs and libmpv.
# It depends on the host system for video codecs (FFmpeg).
# Ensure we are in the project root
cd "$(dirname "$0")/.."
APP_NAME="gtk2-mpv-player"
BUNDLE_DIR="${APP_NAME}-lean"
echo "Creating SUPER LEAN 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 minimal dependencies..."
# In LEAN mode, we EXCLUDE all FFmpeg/Codec libraries.
# This assumes the host has them or that basic playback is enough.
EXCLUDES="libavcodec|libavfilter|libavformat|libavutil|libswresample|libswscale|libpostproc|libx264|libx265|libvpx|libopus|libvorbis|libtheora|libaom|libdav1d|librav1e|libSvtAv1Enc|libplacebo|libcrypto|libssl|libgnutls|libxml2|libfontconfig|libfreetype|libdbus|libasound|libpulse|libudev|libsystemd|libc\.so|libm\.so|libdl\.so|librt\.so|libpthread\.so|libX11|libxcb"
# Use temporary file to track libs
TEMP_LIBS=$(mktemp)
# Get direct dependencies only
ldd "$APP_NAME" | grep "=> /" | 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" <<EOF
#!/bin/bash
DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
export LD_LIBRARY_PATH="\$DIR/lib:\$LD_LIBRARY_PATH"
exec "\$DIR/bin/$APP_NAME" "\$@"
EOF
chmod +x "$BUNDLE_DIR/launch.sh"
echo "------------------------------------------------"
echo "LEAN bundle created in ${BUNDLE_DIR}"
echo "To run, use: cd ${BUNDLE_DIR} && ./launch.sh"
echo "Note: This version requires system codecs to be present."
echo "------------------------------------------------"