Added proper plugin system (plugins will be released in a separate repository), added experimental scripting support (Lua, JavaScript)
This commit is contained in:
parent
60255275eb
commit
72f3f88369
14
.gitignore
vendored
14
.gitignore
vendored
@ -1,2 +1,16 @@
|
|||||||
src/dialogs.o
|
src/dialogs.o
|
||||||
gtk2-mpv-player
|
gtk2-mpv-player
|
||||||
|
mpv.conf
|
||||||
|
test_grayscale.conf
|
||||||
|
test_vibrant.conf
|
||||||
|
plugins/audio_player.zip
|
||||||
|
.gitignore
|
||||||
|
plugins/screenshot_timer.zip
|
||||||
|
src/plugin_manager.o
|
||||||
|
src/playlist.o
|
||||||
|
src/player.o
|
||||||
|
src/mpv_loader.o
|
||||||
|
src/mpris.o
|
||||||
|
src/main.o
|
||||||
|
src/controls.o
|
||||||
|
src/ui.o
|
||||||
|
|||||||
5
Makefile
5
Makefile
@ -1,5 +1,5 @@
|
|||||||
CFLAGS = $(shell pkg-config --cflags gtk+-2.0 gio-2.0) -Wall -Wextra -g -std=c99
|
CFLAGS = $(shell pkg-config --cflags gtk+-2.0 gio-2.0) -Wall -Wextra -g -std=c99
|
||||||
LDFLAGS = $(shell pkg-config --libs gtk+-2.0 gio-2.0) -ldl -lm -Wl,--as-needed -Wl,-rpath,./lib
|
LDFLAGS = $(shell pkg-config --libs gtk+-2.0 gio-2.0) -ldl -lm -Wl,--as-needed -Wl,-rpath,./lib -rdynamic
|
||||||
|
|
||||||
SRC_DIR = src
|
SRC_DIR = src
|
||||||
SRCS = $(SRC_DIR)/main.c \
|
SRCS = $(SRC_DIR)/main.c \
|
||||||
@ -9,7 +9,8 @@ SRCS = $(SRC_DIR)/main.c \
|
|||||||
$(SRC_DIR)/playlist.c \
|
$(SRC_DIR)/playlist.c \
|
||||||
$(SRC_DIR)/dialogs.c \
|
$(SRC_DIR)/dialogs.c \
|
||||||
$(SRC_DIR)/mpv_loader.c \
|
$(SRC_DIR)/mpv_loader.c \
|
||||||
$(SRC_DIR)/mpris.c
|
$(SRC_DIR)/mpris.c \
|
||||||
|
$(SRC_DIR)/plugin_manager.c
|
||||||
|
|
||||||
OBJS = $(SRCS:.c=.o)
|
OBJS = $(SRCS:.c=.o)
|
||||||
TARGET = gtk2-mpv-player
|
TARGET = gtk2-mpv-player
|
||||||
|
|||||||
10
install.sh
10
install.sh
@ -16,9 +16,12 @@ echo "Installing to $INSTALL_DIR..."
|
|||||||
# Create directory
|
# Create directory
|
||||||
mkdir -p "$INSTALL_DIR"
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
|
||||||
# Copy binary and metadata
|
# Copy binary and metadata using 'install' to handle running processes correctly
|
||||||
cp gtk2-mpv-player "$INSTALL_DIR/"
|
install -m 755 gtk2-mpv-player "$INSTALL_DIR/"
|
||||||
cp assets/icon.png "$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/"
|
cp -r lib "$INSTALL_DIR/"
|
||||||
|
|
||||||
# Create wrapper script
|
# Create wrapper script
|
||||||
@ -39,4 +42,5 @@ echo "Updating MIME database..."
|
|||||||
update-desktop-database
|
update-desktop-database
|
||||||
|
|
||||||
echo "Installation complete!"
|
echo "Installation complete!"
|
||||||
|
echo "Installed version timestamp: $(date -r "$INSTALL_DIR/gtk2-mpv-player")"
|
||||||
echo "You can now run 'gtk2-mpv-player' or find it in your application menu."
|
echo "You can now run 'gtk2-mpv-player' or find it in your application menu."
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
struct Controls {
|
struct Controls {
|
||||||
Player *player;
|
Player *player;
|
||||||
|
Preferences *prefs;
|
||||||
|
|
||||||
/* Main container */
|
/* Main container */
|
||||||
GtkWidget *root; /* GtkEventBox for background/windowing */
|
GtkWidget *root; /* GtkEventBox for background/windowing */
|
||||||
@ -69,10 +70,11 @@ static void format_time(double seconds, char *buffer, size_t size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controls* controls_new(Player *player)
|
Controls* controls_new(Player *player, Preferences *prefs)
|
||||||
{
|
{
|
||||||
Controls *controls = g_new0(Controls, 1);
|
Controls *controls = g_new0(Controls, 1);
|
||||||
controls->player = player;
|
controls->player = player;
|
||||||
|
controls->prefs = prefs;
|
||||||
controls->seeking = FALSE;
|
controls->seeking = FALSE;
|
||||||
controls->duration = 0.0;
|
controls->duration = 0.0;
|
||||||
controls->muted = FALSE;
|
controls->muted = FALSE;
|
||||||
@ -367,10 +369,32 @@ static gboolean on_seek_change_value(GtkRange *range, GtkScrollType scroll, gdou
|
|||||||
|
|
||||||
static gboolean on_seek_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data)
|
static gboolean on_seek_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data)
|
||||||
{
|
{
|
||||||
(void)widget;
|
|
||||||
(void)event;
|
|
||||||
Controls *controls = (Controls *)data;
|
Controls *controls = (Controls *)data;
|
||||||
controls->seeking = TRUE;
|
controls->seeking = TRUE;
|
||||||
|
|
||||||
|
if (event->button == 1) {
|
||||||
|
if (controls->prefs && controls->prefs->seekbar_direct_jump) {
|
||||||
|
/* Left click jump-to-position */
|
||||||
|
GtkRange *range = GTK_RANGE(widget);
|
||||||
|
GtkAdjustment *adj = gtk_range_get_adjustment(range);
|
||||||
|
|
||||||
|
/* Calculate relative position (0.0 to 1.0) based on click x coordinate */
|
||||||
|
double relative = event->x / (double)widget->allocation.width;
|
||||||
|
if (relative < 0) relative = 0;
|
||||||
|
if (relative > 1) relative = 1;
|
||||||
|
|
||||||
|
double new_val = adj->lower + relative * (adj->upper - adj->lower);
|
||||||
|
gtk_range_set_value(range, new_val);
|
||||||
|
|
||||||
|
/* Perform seek immediately */
|
||||||
|
if (controls->duration > 0) {
|
||||||
|
double position = (new_val / 100.0) * controls->duration;
|
||||||
|
player_seek_absolute(controls->player, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Return FALSE to allow GTK to process the rest of the event (starting the drag/step) */
|
||||||
|
}
|
||||||
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#define CONTROLS_H
|
#define CONTROLS_H
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#include "player.h"
|
#include "dialogs.h"
|
||||||
|
|
||||||
typedef struct Controls Controls;
|
typedef struct Controls Controls;
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ typedef void (*ControlsFullscreenCallback)(gboolean fullscreen, gpointer user_da
|
|||||||
typedef void (*ControlsPlayPauseCallback)(gpointer user_data);
|
typedef void (*ControlsPlayPauseCallback)(gpointer user_data);
|
||||||
|
|
||||||
/* Create controls widget */
|
/* Create controls widget */
|
||||||
Controls* controls_new(Player *player);
|
Controls* controls_new(Player *player, Preferences *prefs);
|
||||||
void controls_destroy(Controls *controls);
|
void controls_destroy(Controls *controls);
|
||||||
|
|
||||||
/* Get main widget */
|
/* Get main widget */
|
||||||
|
|||||||
175
src/dialogs.c
175
src/dialogs.c
@ -2,6 +2,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include "plugin_manager.h"
|
||||||
|
|
||||||
/* File filter helpers */
|
/* File filter helpers */
|
||||||
static void add_video_filters(GtkFileChooser *chooser)
|
static void add_video_filters(GtkFileChooser *chooser)
|
||||||
@ -321,7 +322,7 @@ void dialogs_show_about(GtkWindow *parent)
|
|||||||
gtk_label_set_markup(GTK_LABEL(title_label), "<span size='xx-large' weight='bold'>GTK2 Media Player</span>");
|
gtk_label_set_markup(GTK_LABEL(title_label), "<span size='xx-large' weight='bold'>GTK2 Media Player</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 1.0.0");
|
GtkWidget *version_label = gtk_label_new("Version 0.7.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 using libmpv.");
|
GtkWidget *comment_label = gtk_label_new("A lightweight GTK2 media player frontend using libmpv.");
|
||||||
@ -410,8 +411,15 @@ Preferences* preferences_new(void)
|
|||||||
prefs->remember_last_dir = TRUE; /* Default to TRUE */
|
prefs->remember_last_dir = TRUE; /* Default to TRUE */
|
||||||
prefs->use_mpris = TRUE; /* Default to TRUE */
|
prefs->use_mpris = TRUE; /* Default to TRUE */
|
||||||
prefs->open_in_new_window = TRUE; /* Default to TRUE */
|
prefs->open_in_new_window = TRUE; /* Default to TRUE */
|
||||||
|
prefs->seekbar_direct_jump = TRUE; /* Default to TRUE as requested earlier */
|
||||||
|
prefs->hide_focus_rect = FALSE;
|
||||||
|
prefs->enable_osd = TRUE;
|
||||||
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");
|
||||||
|
|
||||||
|
const char *config_dir = g_get_user_config_dir();
|
||||||
|
prefs->mpv_config_path = g_build_filename(config_dir, "gtk2-media-player", "mpv.conf", NULL);
|
||||||
return prefs;
|
return prefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,6 +427,8 @@ void preferences_free(Preferences *prefs)
|
|||||||
{
|
{
|
||||||
if (!prefs) return;
|
if (!prefs) return;
|
||||||
g_free(prefs->screenshot_directory);
|
g_free(prefs->screenshot_directory);
|
||||||
|
g_free(prefs->screenshot_format);
|
||||||
|
g_free(prefs->mpv_config_path);
|
||||||
g_free(prefs->last_dir);
|
g_free(prefs->last_dir);
|
||||||
g_free(prefs);
|
g_free(prefs);
|
||||||
}
|
}
|
||||||
@ -482,6 +492,36 @@ void preferences_load(Preferences *prefs)
|
|||||||
g_clear_error(&error);
|
g_clear_error(&error);
|
||||||
g_free(last_dir);
|
g_free(last_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean direct_jump = g_key_file_get_boolean(keyfile, "General", "seekbar_direct_jump", &error);
|
||||||
|
if (!error) prefs->seekbar_direct_jump = direct_jump;
|
||||||
|
else { g_clear_error(&error); }
|
||||||
|
|
||||||
|
gboolean hide_focus = g_key_file_get_boolean(keyfile, "General", "hide_focus_rect", &error);
|
||||||
|
if (!error) prefs->hide_focus_rect = hide_focus;
|
||||||
|
else { g_clear_error(&error); }
|
||||||
|
|
||||||
|
gboolean osd = g_key_file_get_boolean(keyfile, "General", "enable_osd", &error);
|
||||||
|
if (!error) prefs->enable_osd = osd;
|
||||||
|
else { g_clear_error(&error); }
|
||||||
|
|
||||||
|
char *ss_format = g_key_file_get_string(keyfile, "General", "screenshot_format", &error);
|
||||||
|
if (!error && ss_format) {
|
||||||
|
g_free(prefs->screenshot_format);
|
||||||
|
prefs->screenshot_format = ss_format;
|
||||||
|
} else {
|
||||||
|
g_clear_error(&error);
|
||||||
|
g_free(ss_format);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *mpv_cfg = g_key_file_get_string(keyfile, "General", "mpv_config_path", &error);
|
||||||
|
if (!error && mpv_cfg) {
|
||||||
|
g_free(prefs->mpv_config_path);
|
||||||
|
prefs->mpv_config_path = mpv_cfg;
|
||||||
|
} else {
|
||||||
|
g_clear_error(&error);
|
||||||
|
g_free(mpv_cfg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g_key_file_free(keyfile);
|
g_key_file_free(keyfile);
|
||||||
@ -515,6 +555,15 @@ void preferences_save(Preferences *prefs)
|
|||||||
g_key_file_set_string(keyfile, "General", "screenshot_directory",
|
g_key_file_set_string(keyfile, "General", "screenshot_directory",
|
||||||
prefs->screenshot_directory);
|
prefs->screenshot_directory);
|
||||||
}
|
}
|
||||||
|
if (prefs->screenshot_format) {
|
||||||
|
g_key_file_set_string(keyfile, "General", "screenshot_format", prefs->screenshot_format);
|
||||||
|
}
|
||||||
|
if (prefs->mpv_config_path) {
|
||||||
|
g_key_file_set_string(keyfile, "General", "mpv_config_path", prefs->mpv_config_path);
|
||||||
|
}
|
||||||
|
g_key_file_set_boolean(keyfile, "General", "seekbar_direct_jump", prefs->seekbar_direct_jump);
|
||||||
|
g_key_file_set_boolean(keyfile, "General", "hide_focus_rect", prefs->hide_focus_rect);
|
||||||
|
g_key_file_set_boolean(keyfile, "General", "enable_osd", prefs->enable_osd);
|
||||||
|
|
||||||
gsize length;
|
gsize length;
|
||||||
char *data = g_key_file_to_data(keyfile, &length, NULL);
|
char *data = g_key_file_to_data(keyfile, &length, NULL);
|
||||||
@ -528,7 +577,41 @@ void preferences_save(Preferences *prefs)
|
|||||||
g_free(config_path);
|
g_free(config_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs)
|
static void on_cfg_browse_clicked(GtkButton *btn, gpointer data)
|
||||||
|
{
|
||||||
|
GtkWindow *parent = GTK_WINDOW(data);
|
||||||
|
GtkWidget *entry = g_object_get_data(G_OBJECT(btn), "entry");
|
||||||
|
GtkWidget *file_dialog = gtk_file_chooser_dialog_new("Select mpv.conf", parent,
|
||||||
|
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
||||||
|
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
|
||||||
|
|
||||||
|
if (gtk_dialog_run(GTK_DIALOG(file_dialog)) == GTK_RESPONSE_ACCEPT) {
|
||||||
|
char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_dialog));
|
||||||
|
gtk_entry_set_text(GTK_ENTRY(entry), filename);
|
||||||
|
g_free(filename);
|
||||||
|
}
|
||||||
|
gtk_widget_destroy(file_dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_cfg_reset_clicked(GtkButton *btn, gpointer entry)
|
||||||
|
{
|
||||||
|
(void)btn;
|
||||||
|
const char *config_dir = g_get_user_config_dir();
|
||||||
|
char *path = g_build_filename(config_dir, "gtk2-media-player", "mpv.conf", NULL);
|
||||||
|
gtk_entry_set_text(GTK_ENTRY(entry), path);
|
||||||
|
g_free(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_speed_preset_clicked(GtkButton *button, gpointer data)
|
||||||
|
{
|
||||||
|
(void)data;
|
||||||
|
int speed_int = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "speed"));
|
||||||
|
GtkWidget *spin = g_object_get_data(G_OBJECT(button), "spin");
|
||||||
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), speed_int / 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginManager *pm)
|
||||||
{
|
{
|
||||||
if (!prefs) return FALSE;
|
if (!prefs) return FALSE;
|
||||||
|
|
||||||
@ -537,8 +620,10 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs)
|
|||||||
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_OK, GTK_RESPONSE_ACCEPT,
|
GTK_STOCK_OK, GTK_RESPONSE_OK,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
|
gtk_window_set_default_size(GTK_WINDOW(dialog), 600, 500);
|
||||||
|
|
||||||
GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||||
gtk_container_set_border_width(GTK_CONTAINER(content_area), 5);
|
gtk_container_set_border_width(GTK_CONTAINER(content_area), 5);
|
||||||
@ -565,14 +650,47 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs)
|
|||||||
GtkWidget *open_new_check = gtk_check_button_new_with_label("Always open in a new window (Single-Instance off)");
|
GtkWidget *open_new_check = gtk_check_button_new_with_label("Always open in a new window (Single-Instance off)");
|
||||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(open_new_check), prefs->open_in_new_window);
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(open_new_check), prefs->open_in_new_window);
|
||||||
gtk_box_pack_start(GTK_BOX(interface_vbox), open_new_check, FALSE, FALSE, 0);
|
gtk_box_pack_start(GTK_BOX(interface_vbox), open_new_check, FALSE, FALSE, 0);
|
||||||
|
|
||||||
|
GtkWidget *jump_check = gtk_check_button_new_with_label("Seek bar: Jump to clicked position immediately");
|
||||||
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(jump_check), prefs->seekbar_direct_jump);
|
||||||
|
gtk_box_pack_start(GTK_BOX(interface_vbox), jump_check, FALSE, FALSE, 0);
|
||||||
|
|
||||||
|
GtkWidget *focus_check = gtk_check_button_new_with_label("Hide focus rectangles (dotted lines) around UI elements");
|
||||||
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(focus_check), prefs->hide_focus_rect);
|
||||||
|
gtk_box_pack_start(GTK_BOX(interface_vbox), focus_check, FALSE, FALSE, 0);
|
||||||
|
|
||||||
|
GtkWidget *osd_check = gtk_check_button_new_with_label("Enable On-Screen Display (OSD)");
|
||||||
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(osd_check), prefs->enable_osd);
|
||||||
|
gtk_box_pack_start(GTK_BOX(interface_vbox), osd_check, FALSE, FALSE, 0);
|
||||||
|
|
||||||
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 (Placeholder) */
|
/* 2. Config File Tab */
|
||||||
GtkWidget *config_vbox = gtk_vbox_new(FALSE, 10);
|
GtkWidget *config_vbox = gtk_vbox_new(FALSE, 10);
|
||||||
gtk_container_set_border_width(GTK_CONTAINER(config_vbox), 10);
|
gtk_container_set_border_width(GTK_CONTAINER(config_vbox), 10);
|
||||||
GtkWidget *config_label = gtk_label_new("Config File settings - Upcoming feature");
|
|
||||||
gtk_box_pack_start(GTK_BOX(config_vbox), config_label, TRUE, TRUE, 0);
|
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"));
|
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), config_vbox, gtk_label_new("Config File"));
|
||||||
|
|
||||||
/* 3. Miscellaneous Tab */
|
/* 3. Miscellaneous Tab */
|
||||||
@ -605,20 +723,32 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs)
|
|||||||
gtk_entry_set_text(GTK_ENTRY(ss_entry), prefs->screenshot_directory ? prefs->screenshot_directory : "");
|
gtk_entry_set_text(GTK_ENTRY(ss_entry), prefs->screenshot_directory ? prefs->screenshot_directory : "");
|
||||||
gtk_box_pack_start(GTK_BOX(ss_hbox), ss_entry, TRUE, TRUE, 0);
|
gtk_box_pack_start(GTK_BOX(ss_hbox), ss_entry, TRUE, TRUE, 0);
|
||||||
gtk_box_pack_start(GTK_BOX(misc_vbox), ss_hbox, FALSE, FALSE, 0);
|
gtk_box_pack_start(GTK_BOX(misc_vbox), ss_hbox, FALSE, FALSE, 0);
|
||||||
|
|
||||||
|
GtkWidget *format_hbox = gtk_hbox_new(FALSE, 10);
|
||||||
|
gtk_box_pack_start(GTK_BOX(format_hbox), gtk_label_new("Screenshot Format:"), FALSE, FALSE, 0);
|
||||||
|
GtkWidget *format_combo = gtk_combo_box_text_new();
|
||||||
|
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(format_combo), "webp");
|
||||||
|
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(format_combo), "png");
|
||||||
|
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(format_combo), "jpg");
|
||||||
|
|
||||||
|
if (g_strcmp0(prefs->screenshot_format, "webp") == 0) gtk_combo_box_set_active(GTK_COMBO_BOX(format_combo), 0);
|
||||||
|
else if (g_strcmp0(prefs->screenshot_format, "png") == 0) gtk_combo_box_set_active(GTK_COMBO_BOX(format_combo), 1);
|
||||||
|
else if (g_strcmp0(prefs->screenshot_format, "jpg") == 0) gtk_combo_box_set_active(GTK_COMBO_BOX(format_combo), 2);
|
||||||
|
else gtk_combo_box_set_active(GTK_COMBO_BOX(format_combo), 0);
|
||||||
|
|
||||||
|
gtk_box_pack_start(GTK_BOX(format_hbox), format_combo, FALSE, FALSE, 0);
|
||||||
|
gtk_box_pack_start(GTK_BOX(misc_vbox), format_hbox, FALSE, FALSE, 0);
|
||||||
|
|
||||||
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), misc_vbox, gtk_label_new("Miscellaneous"));
|
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), misc_vbox, gtk_label_new("Miscellaneous"));
|
||||||
|
|
||||||
/* 4. Plugins Tab (Placeholder) */
|
/* 4. Plugins Tab */
|
||||||
GtkWidget *plugins_vbox = gtk_vbox_new(FALSE, 10);
|
GtkWidget *plugins_vbox = plugin_manager_create_config_widget(pm);
|
||||||
gtk_container_set_border_width(GTK_CONTAINER(plugins_vbox), 10);
|
|
||||||
GtkWidget *plugins_label = gtk_label_new("Plugins management - Upcoming feature");
|
|
||||||
gtk_box_pack_start(GTK_BOX(plugins_vbox), plugins_label, TRUE, TRUE, 0);
|
|
||||||
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"));
|
||||||
|
|
||||||
gtk_widget_show_all(dialog);
|
gtk_widget_show_all(dialog);
|
||||||
|
|
||||||
gboolean result = FALSE;
|
gboolean result = FALSE;
|
||||||
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) {
|
||||||
prefs->default_volume = gtk_spin_button_get_value(GTK_SPIN_BUTTON(vol_spin));
|
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));
|
||||||
@ -627,9 +757,18 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs)
|
|||||||
prefs->remember_last_dir = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dir_check));
|
prefs->remember_last_dir = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dir_check));
|
||||||
prefs->use_mpris = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mpris_check));
|
prefs->use_mpris = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mpris_check));
|
||||||
prefs->open_in_new_window = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(open_new_check));
|
prefs->open_in_new_window = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(open_new_check));
|
||||||
|
prefs->seekbar_direct_jump = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(jump_check));
|
||||||
|
prefs->hide_focus_rect = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(focus_check));
|
||||||
|
prefs->enable_osd = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(osd_check));
|
||||||
|
|
||||||
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)));
|
||||||
|
|
||||||
|
g_free(prefs->screenshot_format);
|
||||||
|
prefs->screenshot_format = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(format_combo));
|
||||||
|
|
||||||
|
g_free(prefs->mpv_config_path);
|
||||||
|
prefs->mpv_config_path = g_strdup(gtk_entry_get_text(GTK_ENTRY(cfg_entry)));
|
||||||
|
|
||||||
preferences_save(prefs);
|
preferences_save(prefs);
|
||||||
result = TRUE;
|
result = TRUE;
|
||||||
@ -709,17 +848,7 @@ double dialogs_select_speed(GtkWindow *parent, double current_speed)
|
|||||||
GtkWidget *btn = gtk_button_new_with_label(label);
|
GtkWidget *btn = gtk_button_new_with_label(label);
|
||||||
g_object_set_data(G_OBJECT(btn), "speed", GINT_TO_POINTER((int)(presets[i] * 100)));
|
g_object_set_data(G_OBJECT(btn), "speed", GINT_TO_POINTER((int)(presets[i] * 100)));
|
||||||
g_object_set_data(G_OBJECT(btn), "spin", speed_spin);
|
g_object_set_data(G_OBJECT(btn), "spin", speed_spin);
|
||||||
g_signal_connect(btn, "clicked", G_CALLBACK(
|
g_signal_connect(btn, "clicked", G_CALLBACK(on_speed_preset_clicked), NULL);
|
||||||
(void (*)(GtkButton*, gpointer)) ({
|
|
||||||
void anon(GtkButton *button, gpointer data) {
|
|
||||||
(void)data;
|
|
||||||
int speed_int = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "speed"));
|
|
||||||
GtkWidget *spin = g_object_get_data(G_OBJECT(button), "spin");
|
|
||||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), speed_int / 100.0);
|
|
||||||
}
|
|
||||||
anon;
|
|
||||||
})
|
|
||||||
), NULL);
|
|
||||||
gtk_box_pack_start(GTK_BOX(hbox), btn, TRUE, TRUE, 0);
|
gtk_box_pack_start(GTK_BOX(hbox), btn, TRUE, TRUE, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
|
|
||||||
|
/* Forward declaration */
|
||||||
|
struct PluginManager;
|
||||||
|
|
||||||
/* Preferences dialog */
|
/* Preferences dialog */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
double default_volume;
|
double default_volume;
|
||||||
@ -15,15 +18,20 @@ typedef struct {
|
|||||||
gboolean remember_last_dir;
|
gboolean remember_last_dir;
|
||||||
gboolean use_mpris;
|
gboolean use_mpris;
|
||||||
gboolean open_in_new_window;
|
gboolean open_in_new_window;
|
||||||
|
gboolean seekbar_direct_jump;
|
||||||
|
gboolean hide_focus_rect;
|
||||||
|
gboolean enable_osd;
|
||||||
char *last_dir;
|
char *last_dir;
|
||||||
char *screenshot_directory;
|
char *screenshot_directory;
|
||||||
|
char *screenshot_format;
|
||||||
|
char *mpv_config_path;
|
||||||
} 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);
|
gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, struct PluginManager *pm);
|
||||||
|
|
||||||
/* File dialogs */
|
/* File dialogs */
|
||||||
GSList* dialogs_open_files(GtkWindow *parent, Preferences *prefs);
|
GSList* dialogs_open_files(GtkWindow *parent, Preferences *prefs);
|
||||||
|
|||||||
@ -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 v1.0.0\n");
|
printf("Media Player v0.7\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("Media Player v1.0.0\n");
|
printf("Media Player v0.7.1\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ p_mpv_terminate_destroy mpv_terminate_destroy = NULL;
|
|||||||
p_mpv_set_option_string mpv_set_option_string = NULL;
|
p_mpv_set_option_string mpv_set_option_string = NULL;
|
||||||
p_mpv_set_wakeup_callback mpv_set_wakeup_callback = NULL;
|
p_mpv_set_wakeup_callback mpv_set_wakeup_callback = NULL;
|
||||||
p_mpv_command_async mpv_command_async = NULL;
|
p_mpv_command_async mpv_command_async = NULL;
|
||||||
|
p_mpv_command mpv_command = NULL;
|
||||||
p_mpv_set_property_async mpv_set_property_async = NULL;
|
p_mpv_set_property_async mpv_set_property_async = NULL;
|
||||||
p_mpv_get_property mpv_get_property = NULL;
|
p_mpv_get_property mpv_get_property = NULL;
|
||||||
p_mpv_free mpv_free = NULL;
|
p_mpv_free mpv_free = NULL;
|
||||||
@ -62,6 +63,7 @@ int load_mpv_library(void)
|
|||||||
LOAD_SYM(set_option_string);
|
LOAD_SYM(set_option_string);
|
||||||
LOAD_SYM(set_wakeup_callback);
|
LOAD_SYM(set_wakeup_callback);
|
||||||
LOAD_SYM(command_async);
|
LOAD_SYM(command_async);
|
||||||
|
LOAD_SYM(command);
|
||||||
LOAD_SYM(set_property_async);
|
LOAD_SYM(set_property_async);
|
||||||
LOAD_SYM(get_property);
|
LOAD_SYM(get_property);
|
||||||
LOAD_SYM(free);
|
LOAD_SYM(free);
|
||||||
|
|||||||
@ -82,6 +82,7 @@ typedef mpv_event *(*p_mpv_wait_event)(mpv_handle *ctx, double timeout);
|
|||||||
typedef int (*p_mpv_observe_property)(mpv_handle *mpv, uint64_t reply_userdata, const char *name, mpv_format format);
|
typedef int (*p_mpv_observe_property)(mpv_handle *mpv, uint64_t reply_userdata, const char *name, mpv_format format);
|
||||||
typedef int (*p_mpv_unobserve_property)(mpv_handle *mpv, uint64_t reply_userdata);
|
typedef int (*p_mpv_unobserve_property)(mpv_handle *mpv, uint64_t reply_userdata);
|
||||||
typedef const char *(*p_mpv_event_name)(mpv_event_id event);
|
typedef const char *(*p_mpv_event_name)(mpv_event_id event);
|
||||||
|
typedef int (*p_mpv_command)(mpv_handle *ctx, const char **args);
|
||||||
|
|
||||||
/* Global function pointers */
|
/* Global function pointers */
|
||||||
extern p_mpv_error_string mpv_error_string;
|
extern p_mpv_error_string mpv_error_string;
|
||||||
@ -91,6 +92,7 @@ extern p_mpv_terminate_destroy mpv_terminate_destroy;
|
|||||||
extern p_mpv_set_option_string mpv_set_option_string;
|
extern p_mpv_set_option_string mpv_set_option_string;
|
||||||
extern p_mpv_set_wakeup_callback mpv_set_wakeup_callback;
|
extern p_mpv_set_wakeup_callback mpv_set_wakeup_callback;
|
||||||
extern p_mpv_command_async mpv_command_async;
|
extern p_mpv_command_async mpv_command_async;
|
||||||
|
extern p_mpv_command mpv_command;
|
||||||
extern p_mpv_set_property_async mpv_set_property_async;
|
extern p_mpv_set_property_async mpv_set_property_async;
|
||||||
extern p_mpv_get_property mpv_get_property;
|
extern p_mpv_get_property mpv_get_property;
|
||||||
extern p_mpv_free mpv_free;
|
extern p_mpv_free mpv_free;
|
||||||
|
|||||||
146
src/player.c
146
src/player.c
@ -2,6 +2,9 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
struct Player {
|
struct Player {
|
||||||
mpv_handle *mpv;
|
mpv_handle *mpv;
|
||||||
@ -9,6 +12,7 @@ struct Player {
|
|||||||
PlayerEventCallback event_callback;
|
PlayerEventCallback event_callback;
|
||||||
gpointer event_callback_data;
|
gpointer event_callback_data;
|
||||||
gboolean initialized;
|
gboolean initialized;
|
||||||
|
char *config_path;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void check_error(int status)
|
static void check_error(int status)
|
||||||
@ -48,7 +52,7 @@ void player_destroy(Player *player)
|
|||||||
if (player->mpv) {
|
if (player->mpv) {
|
||||||
mpv_terminate_destroy(player->mpv);
|
mpv_terminate_destroy(player->mpv);
|
||||||
}
|
}
|
||||||
|
g_free(player->config_path);
|
||||||
g_free(player);
|
g_free(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,38 +62,104 @@ void player_set_window(Player *player, unsigned long wid)
|
|||||||
player->wid = wid;
|
player->wid = wid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void player_set_config_path(Player *player, const char *path)
|
||||||
|
{
|
||||||
|
if (!player) return;
|
||||||
|
g_free(player->config_path);
|
||||||
|
player->config_path = path ? g_strdup(path) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void wakeup_callback(void *ctx)
|
static void wakeup_callback(void *ctx)
|
||||||
{
|
{
|
||||||
/* This is called from mpv's thread. We need to wake up the GTK main loop. */
|
/* This is called from mpv's thread. We need to wake up the GTK main loop. */
|
||||||
g_idle_add_full(G_PRIORITY_HIGH, (GSourceFunc)player_process_events, ctx, NULL);
|
g_idle_add_full(G_PRIORITY_HIGH, (GSourceFunc)player_process_events, ctx, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void load_scripts(mpv_handle *mpv)
|
||||||
|
{
|
||||||
|
const char *config_dir = g_get_user_config_dir();
|
||||||
|
char *scripts_dir = g_build_filename(config_dir, "gtk2-media-player", "scripts", NULL);
|
||||||
|
|
||||||
|
printf("Scanning for scripts in: %s\n", scripts_dir);
|
||||||
|
|
||||||
|
/* Create scripts directory if it doesn't exist */
|
||||||
|
g_mkdir_with_parents(scripts_dir, 0755);
|
||||||
|
|
||||||
|
DIR *dir = opendir(scripts_dir);
|
||||||
|
if (!dir) {
|
||||||
|
printf("Failed to open scripts directory!\n");
|
||||||
|
} else {
|
||||||
|
struct dirent *entry;
|
||||||
|
int count = 0;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
if (entry->d_name[0] == '.') continue;
|
||||||
|
|
||||||
|
char *path = g_build_filename(scripts_dir, entry->d_name, NULL);
|
||||||
|
size_t len = strlen(entry->d_name);
|
||||||
|
|
||||||
|
/* Check extension (Lua or JS) */
|
||||||
|
if ((len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0) ||
|
||||||
|
(len > 3 && strcmp(entry->d_name + len - 3, ".js") == 0)) {
|
||||||
|
|
||||||
|
printf("Loading script: %s\n", path);
|
||||||
|
|
||||||
|
/* Use load-script command (works after mpv_initialize) */
|
||||||
|
const char *cmd[] = {"load-script", path, NULL};
|
||||||
|
int res = mpv_command(mpv, cmd);
|
||||||
|
if (res < 0) {
|
||||||
|
printf(" -> Failed! Error: %s\n", mpv_error_string(res));
|
||||||
|
} else {
|
||||||
|
printf(" -> OK\n");
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_free(path);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
printf("Loaded %d script(s)\n", count);
|
||||||
|
}
|
||||||
|
g_free(scripts_dir);
|
||||||
|
}
|
||||||
|
|
||||||
int player_init(Player *player)
|
int player_init(Player *player)
|
||||||
|
|
||||||
{
|
{
|
||||||
if (!player || !player->mpv) return -1;
|
if (!player || !player->mpv) return -1;
|
||||||
|
|
||||||
/* Set window ID if provided */
|
/* Set window ID if provided - Must be before initialize */
|
||||||
if (player->wid != 0) {
|
if (player->wid != 0) {
|
||||||
char wid_str[64];
|
char wid_str[64];
|
||||||
snprintf(wid_str, sizeof(wid_str), "%lu", player->wid);
|
snprintf(wid_str, sizeof(wid_str), "%lu", player->wid);
|
||||||
check_error(mpv_set_option_string(player->mpv, "wid", wid_str));
|
check_error(mpv_set_option_string(player->mpv, "wid", wid_str));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Basic configuration */
|
/* Basic configuration - Set BEFORE config loading so config can override defaults */
|
||||||
check_error(mpv_set_option_string(player->mpv, "input-default-bindings", "no"));
|
check_error(mpv_set_option_string(player->mpv, "input-default-bindings", "no"));
|
||||||
check_error(mpv_set_option_string(player->mpv, "input-vo-keyboard", "no"));
|
check_error(mpv_set_option_string(player->mpv, "input-vo-keyboard", "no"));
|
||||||
check_error(mpv_set_option_string(player->mpv, "osc", "no")); /* We have our own controls */
|
check_error(mpv_set_option_string(player->mpv, "osc", "no")); /* We have our own controls */
|
||||||
|
check_error(mpv_set_option_string(player->mpv, "osd-bar", "yes"));
|
||||||
|
check_error(mpv_set_option_string(player->mpv, "osd-duration", "2000"));
|
||||||
check_error(mpv_set_option_string(player->mpv, "input-cursor", "no")); /* Let GTK handle the cursor */
|
check_error(mpv_set_option_string(player->mpv, "input-cursor", "no")); /* Let GTK handle the cursor */
|
||||||
check_error(mpv_set_option_string(player->mpv, "keep-open", "yes"));
|
check_error(mpv_set_option_string(player->mpv, "keep-open", "yes"));
|
||||||
check_error(mpv_set_option_string(player->mpv, "idle", "yes"));
|
check_error(mpv_set_option_string(player->mpv, "idle", "yes"));
|
||||||
|
|
||||||
/* Initialize mpv */
|
/* Load external config file if provided */
|
||||||
int ret = mpv_initialize(player->mpv);
|
if (player->config_path && g_file_test(player->config_path, G_FILE_TEST_EXISTS)) {
|
||||||
if (ret < 0) {
|
check_error(mpv_set_option_string(player->mpv, "config", "yes"));
|
||||||
fprintf(stderr, "Failed to initialize mpv: %s\n", mpv_error_string(ret));
|
check_error(mpv_set_option_string(player->mpv, "config-file", player->config_path));
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Initialize MPV */
|
||||||
|
int res = mpv_initialize(player->mpv);
|
||||||
|
if (res < 0) {
|
||||||
|
check_error(res);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load scripts AFTER initialization using load-script command */
|
||||||
|
load_scripts(player->mpv);
|
||||||
|
|
||||||
/* Set wakeup callback for event processing */
|
/* Set wakeup callback for event processing */
|
||||||
mpv_set_wakeup_callback(player->mpv, wakeup_callback, player);
|
mpv_set_wakeup_callback(player->mpv, wakeup_callback, player);
|
||||||
|
|
||||||
@ -147,6 +217,10 @@ void player_seek(Player *player, double seconds)
|
|||||||
snprintf(sec_str, sizeof(sec_str), "%f", seconds);
|
snprintf(sec_str, sizeof(sec_str), "%f", seconds);
|
||||||
const char *cmd[] = {"seek", sec_str, "relative", NULL};
|
const char *cmd[] = {"seek", sec_str, "relative", NULL};
|
||||||
check_error(mpv_command_async(player->mpv, 0, cmd));
|
check_error(mpv_command_async(player->mpv, 0, cmd));
|
||||||
|
|
||||||
|
/* Show progress OSD */
|
||||||
|
const char *osd_cmd[] = {"show-progress", NULL};
|
||||||
|
mpv_command_async(player->mpv, 0, osd_cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void player_seek_absolute(Player *player, double position)
|
void player_seek_absolute(Player *player, double position)
|
||||||
@ -155,8 +229,12 @@ void player_seek_absolute(Player *player, double position)
|
|||||||
|
|
||||||
char pos_str[32];
|
char pos_str[32];
|
||||||
snprintf(pos_str, sizeof(pos_str), "%f", position);
|
snprintf(pos_str, sizeof(pos_str), "%f", position);
|
||||||
const char *cmd[] = {"seek", pos_str, "absolute", NULL};
|
const char *cmd[] = {"seek", pos_str, "absolute+exact", NULL};
|
||||||
check_error(mpv_command_async(player->mpv, 0, cmd));
|
check_error(mpv_command_async(player->mpv, 0, cmd));
|
||||||
|
|
||||||
|
/* Show progress OSD */
|
||||||
|
const char *osd_cmd[] = {"show-progress", NULL};
|
||||||
|
mpv_command_async(player->mpv, 0, osd_cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void player_frame_step(Player *player)
|
void player_frame_step(Player *player)
|
||||||
@ -178,13 +256,22 @@ void player_frame_back_step(Player *player)
|
|||||||
void player_set_option_string(Player *player, const char *name, const char *value)
|
void player_set_option_string(Player *player, const char *name, const char *value)
|
||||||
{
|
{
|
||||||
if (!player || !player->mpv) return;
|
if (!player || !player->mpv) return;
|
||||||
check_error(mpv_set_option_string(player->mpv, name, value));
|
if (player->initialized) {
|
||||||
|
check_error(mpv_set_property_string(player->mpv, name, value));
|
||||||
|
} else {
|
||||||
|
check_error(mpv_set_option_string(player->mpv, name, value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void player_set_option_flag(Player *player, const char *name, int value)
|
void player_set_option_flag(Player *player, const char *name, int value)
|
||||||
{
|
{
|
||||||
if (!player || !player->mpv) return;
|
if (!player || !player->mpv) return;
|
||||||
check_error(mpv_set_property_async(player->mpv, 0, name, MPV_FORMAT_FLAG, &value));
|
if (player->initialized) {
|
||||||
|
const char *val = value ? "yes" : "no";
|
||||||
|
check_error(mpv_set_property_string(player->mpv, name, val));
|
||||||
|
} else {
|
||||||
|
check_error(mpv_set_option_string(player->mpv, name, value ? "yes" : "no"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Property getters */
|
/* Property getters */
|
||||||
@ -349,22 +436,37 @@ void player_set_volume(Player *player, double volume)
|
|||||||
{
|
{
|
||||||
if (!player || !player->mpv) return;
|
if (!player || !player->mpv) return;
|
||||||
|
|
||||||
mpv_set_property_async(player->mpv, 0, "volume", MPV_FORMAT_DOUBLE, &volume);
|
char vol_str[32];
|
||||||
|
snprintf(vol_str, sizeof(vol_str), "%f", volume);
|
||||||
|
|
||||||
|
if (player->initialized) {
|
||||||
|
check_error(mpv_set_property_string(player->mpv, "volume", vol_str));
|
||||||
|
/* Show volume bar on OSD */
|
||||||
|
const char *cmd[] = {"osd-msg-bar", "set", "volume", NULL};
|
||||||
|
mpv_command_async(player->mpv, 0, cmd);
|
||||||
|
} else {
|
||||||
|
check_error(mpv_set_option_string(player->mpv, "volume", vol_str));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void player_set_muted(Player *player, gboolean muted)
|
void player_set_muted(Player *player, gboolean muted)
|
||||||
{
|
{
|
||||||
if (!player || !player->mpv) return;
|
if (!player || !player->mpv) return;
|
||||||
|
if (player->initialized) {
|
||||||
int flag = muted ? 1 : 0;
|
const char *val = muted ? "yes" : "no";
|
||||||
mpv_set_property_async(player->mpv, 0, "mute", MPV_FORMAT_FLAG, &flag);
|
check_error(mpv_set_property_string(player->mpv, "mute", val));
|
||||||
|
} else {
|
||||||
|
check_error(mpv_set_option_string(player->mpv, "mute", muted ? "yes" : "no"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void player_set_speed(Player *player, double speed)
|
void player_set_speed(Player *player, double speed)
|
||||||
{
|
{
|
||||||
if (!player || !player->mpv) return;
|
if (!player || !player->mpv) return;
|
||||||
|
|
||||||
mpv_set_property_async(player->mpv, 0, "speed", MPV_FORMAT_DOUBLE, &speed);
|
char speed_str[32];
|
||||||
|
snprintf(speed_str, sizeof(speed_str), "%f", speed);
|
||||||
|
check_error(mpv_set_property_string(player->mpv, "speed", speed_str));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Track management */
|
/* Track management */
|
||||||
@ -435,16 +537,18 @@ void player_set_audio_track(Player *player, int track)
|
|||||||
{
|
{
|
||||||
if (!player || !player->mpv) return;
|
if (!player || !player->mpv) return;
|
||||||
|
|
||||||
int64_t aid = track;
|
char aid_str[16];
|
||||||
mpv_set_property_async(player->mpv, 0, "aid", MPV_FORMAT_INT64, &aid);
|
snprintf(aid_str, sizeof(aid_str), "%d", track);
|
||||||
|
check_error(mpv_set_property_string(player->mpv, "aid", aid_str));
|
||||||
}
|
}
|
||||||
|
|
||||||
void player_set_subtitle_track(Player *player, int track)
|
void player_set_subtitle_track(Player *player, int track)
|
||||||
{
|
{
|
||||||
if (!player || !player->mpv) return;
|
if (!player || !player->mpv) return;
|
||||||
|
|
||||||
int64_t sid = track;
|
char sid_str[16];
|
||||||
mpv_set_property_async(player->mpv, 0, "sid", MPV_FORMAT_INT64, &sid);
|
snprintf(sid_str, sizeof(sid_str), "%d", track);
|
||||||
|
check_error(mpv_set_property_string(player->mpv, "sid", sid_str));
|
||||||
}
|
}
|
||||||
|
|
||||||
void player_load_subtitle(Player *player, const char *path)
|
void player_load_subtitle(Player *player, const char *path)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ typedef void (*PlayerEventCallback)(Player *player, mpv_event *event, gpointer u
|
|||||||
Player* player_new(void);
|
Player* player_new(void);
|
||||||
void player_destroy(Player *player);
|
void player_destroy(Player *player);
|
||||||
void player_set_window(Player *player, unsigned long wid);
|
void player_set_window(Player *player, unsigned long wid);
|
||||||
|
void player_set_config_path(Player *player, const char *path);
|
||||||
int player_init(Player *player);
|
int player_init(Player *player);
|
||||||
|
|
||||||
/* Playback commands */
|
/* Playback commands */
|
||||||
|
|||||||
194
src/plugin.h
Normal file
194
src/plugin.h
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
* GTK2 Media Player - Plugin API
|
||||||
|
*
|
||||||
|
* This header defines the public API that plugins must implement
|
||||||
|
* and the functions they can call to interact with the application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PLUGIN_H
|
||||||
|
#define PLUGIN_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Plugin API version - plugins must match this */
|
||||||
|
#define PLUGIN_API_VERSION 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin metadata structure
|
||||||
|
* Returned by plugin_get_info()
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
const char *name; /* Display name, e.g. "Screenshot Timer" */
|
||||||
|
const char *version; /* Semantic version, e.g. "1.0.0" */
|
||||||
|
const char *description; /* Short description of functionality */
|
||||||
|
const char *author; /* Author name/email */
|
||||||
|
int api_version; /* Must be PLUGIN_API_VERSION */
|
||||||
|
} PluginInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application API provided to plugins
|
||||||
|
* Passed to plugin_init() for plugins to store and use
|
||||||
|
*/
|
||||||
|
typedef struct PluginAPI {
|
||||||
|
/* Player control */
|
||||||
|
void (*play)(void);
|
||||||
|
void (*pause)(void);
|
||||||
|
void (*stop)(void);
|
||||||
|
void (*toggle_pause)(void);
|
||||||
|
void (*seek)(double seconds);
|
||||||
|
void (*seek_absolute)(double position);
|
||||||
|
|
||||||
|
/* Player state */
|
||||||
|
double (*get_position)(void);
|
||||||
|
double (*get_duration)(void);
|
||||||
|
double (*get_volume)(void);
|
||||||
|
gboolean (*is_paused)(void);
|
||||||
|
gboolean (*is_playing)(void);
|
||||||
|
|
||||||
|
/* Media info */
|
||||||
|
char* (*get_current_file)(void); /* Caller must g_free() */
|
||||||
|
char* (*get_media_title)(void); /* Caller must g_free() */
|
||||||
|
|
||||||
|
/* Screenshot */
|
||||||
|
void (*take_screenshot)(void);
|
||||||
|
void (*take_screenshot_to_file)(const char *path);
|
||||||
|
|
||||||
|
/* UI access */
|
||||||
|
GtkWindow* (*get_main_window)(void);
|
||||||
|
|
||||||
|
/* Message dialogs */
|
||||||
|
void (*show_info)(const char *title, const char *message);
|
||||||
|
void (*show_warning)(const char *title, const char *message);
|
||||||
|
void (*show_error)(const char *title, const char *message);
|
||||||
|
|
||||||
|
/* Menu registration - add items to Plugins menu */
|
||||||
|
void (*register_menu_item)(GtkWidget *menu_item);
|
||||||
|
void (*unregister_menu_item)(GtkWidget *menu_item);
|
||||||
|
|
||||||
|
/* UI layouts (Added for Audio Player plugin) */
|
||||||
|
void (*set_video_area_visible)(gboolean visible);
|
||||||
|
void (*set_window_size)(int width, int height);
|
||||||
|
|
||||||
|
/* Advanced MPV access */
|
||||||
|
void (*execute_mpv_command)(const char **args);
|
||||||
|
|
||||||
|
/* Reserved for future expansion */
|
||||||
|
void *_reserved[5];
|
||||||
|
} PluginAPI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin event types for optional hooks
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
PLUGIN_EVENT_FILE_LOADED, /* New file loaded, data = filepath (const char*) */
|
||||||
|
PLUGIN_EVENT_PLAYBACK_STARTED, /* Playback started, data = NULL */
|
||||||
|
PLUGIN_EVENT_PLAYBACK_PAUSED, /* Playback paused, data = NULL */
|
||||||
|
PLUGIN_EVENT_PLAYBACK_STOPPED, /* Playback stopped, data = NULL */
|
||||||
|
PLUGIN_EVENT_POSITION_CHANGED, /* Position changed, data = position (double*) */
|
||||||
|
PLUGIN_EVENT_VOLUME_CHANGED, /* Volume changed, data = volume (double*) */
|
||||||
|
} PluginEventType;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =============================================================================
|
||||||
|
* REQUIRED PLUGIN EXPORTS
|
||||||
|
*
|
||||||
|
* Every plugin MUST implement and export these three functions:
|
||||||
|
* =============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get plugin metadata
|
||||||
|
* Must return a pointer to static PluginInfo structure
|
||||||
|
*
|
||||||
|
* @return Pointer to plugin info (must remain valid until shutdown)
|
||||||
|
*/
|
||||||
|
#define PLUGIN_EXPORT_GET_INFO "plugin_get_info"
|
||||||
|
typedef const PluginInfo* (*PluginGetInfoFunc)(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize plugin
|
||||||
|
* Called when plugin is loaded. Store the API pointer for later use.
|
||||||
|
*
|
||||||
|
* @param api Application API for plugin to use
|
||||||
|
* @return 0 on success, non-zero on failure
|
||||||
|
*/
|
||||||
|
#define PLUGIN_EXPORT_INIT "plugin_init"
|
||||||
|
typedef int (*PluginInitFunc)(const PluginAPI *api);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown plugin
|
||||||
|
* Called when plugin is unloaded. Clean up any resources.
|
||||||
|
*/
|
||||||
|
#define PLUGIN_EXPORT_SHUTDOWN "plugin_shutdown"
|
||||||
|
typedef void (*PluginShutdownFunc)(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =============================================================================
|
||||||
|
* OPTIONAL PLUGIN EXPORTS
|
||||||
|
*
|
||||||
|
* Plugins MAY implement these for additional functionality:
|
||||||
|
* =============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get plugin's menu item(s)
|
||||||
|
* If plugin wants to add menu items, return a GtkMenuItem.
|
||||||
|
* For multiple items, use a submenu.
|
||||||
|
*
|
||||||
|
* @return GtkWidget* menu item, or NULL if none
|
||||||
|
*/
|
||||||
|
#define PLUGIN_EXPORT_GET_MENU "plugin_get_menu_item"
|
||||||
|
typedef GtkWidget* (*PluginGetMenuFunc)(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle application events
|
||||||
|
* Called when various application events occur.
|
||||||
|
*
|
||||||
|
* @param event Event type
|
||||||
|
* @param data Event-specific data (see PluginEventType comments)
|
||||||
|
*/
|
||||||
|
#define PLUGIN_EXPORT_ON_EVENT "plugin_on_event"
|
||||||
|
typedef void (*PluginOnEventFunc)(PluginEventType event, void *data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure plugin
|
||||||
|
* Called when user selects "Configure" in plugin manager.
|
||||||
|
* Should open a configuration dialog.
|
||||||
|
*/
|
||||||
|
#define PLUGIN_EXPORT_CONFIGURE "plugin_configure"
|
||||||
|
typedef void (*PluginConfigureFunc)(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show about dialog
|
||||||
|
* Called when user selects "About" in plugin manager.
|
||||||
|
* Should open an about dialog.
|
||||||
|
*/
|
||||||
|
#define PLUGIN_EXPORT_ABOUT "plugin_about"
|
||||||
|
typedef void (*PluginAboutFunc)(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =============================================================================
|
||||||
|
* HELPER MACROS FOR PLUGIN DEVELOPMENT
|
||||||
|
* =============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Declare standard plugin info */
|
||||||
|
#define DECLARE_PLUGIN_INFO(p_name, p_version, p_description, p_author) \
|
||||||
|
static const PluginInfo _plugin_info = { \
|
||||||
|
.name = p_name, \
|
||||||
|
.version = p_version, \
|
||||||
|
.description = p_description, \
|
||||||
|
.author = p_author, \
|
||||||
|
.api_version = PLUGIN_API_VERSION \
|
||||||
|
}; \
|
||||||
|
const PluginInfo* plugin_get_info(void) { return &_plugin_info; }
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* PLUGIN_H */
|
||||||
1028
src/plugin_manager.c
Normal file
1028
src/plugin_manager.c
Normal file
File diff suppressed because it is too large
Load Diff
145
src/plugin_manager.h
Normal file
145
src/plugin_manager.h
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* GTK2 Media Player - Plugin Manager
|
||||||
|
*
|
||||||
|
* Internal interface for loading, managing, and unloading plugins.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PLUGIN_MANAGER_H
|
||||||
|
#define PLUGIN_MANAGER_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include "plugin.h"
|
||||||
|
|
||||||
|
typedef struct PluginManager PluginManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loaded plugin instance
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
char *id; /* Plugin ID (directory name) */
|
||||||
|
char *path; /* Path to .so file */
|
||||||
|
void *handle; /* dlopen handle */
|
||||||
|
PluginInfo info; /* Plugin metadata */
|
||||||
|
gboolean enabled; /* Currently active */
|
||||||
|
gboolean has_menu; /* Has menu item */
|
||||||
|
GtkWidget *menu_item; /* Registered menu item */
|
||||||
|
|
||||||
|
/* Function pointers */
|
||||||
|
PluginGetInfoFunc get_info;
|
||||||
|
PluginInitFunc init;
|
||||||
|
PluginShutdownFunc shutdown;
|
||||||
|
PluginGetMenuFunc get_menu_item;
|
||||||
|
PluginOnEventFunc on_event;
|
||||||
|
PluginConfigureFunc configure;
|
||||||
|
PluginAboutFunc about;
|
||||||
|
} LoadedPlugin;
|
||||||
|
|
||||||
|
typedef struct AppUI AppUI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new plugin manager
|
||||||
|
*
|
||||||
|
* @param ui Main application UI structure
|
||||||
|
* @return New plugin manager instance
|
||||||
|
*/
|
||||||
|
PluginManager* plugin_manager_new(AppUI *ui);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy plugin manager and unload all plugins
|
||||||
|
*/
|
||||||
|
void plugin_manager_destroy(PluginManager *pm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set player control callbacks
|
||||||
|
* Must be called before loading plugins
|
||||||
|
*/
|
||||||
|
void plugin_manager_set_player_callbacks(PluginManager *pm,
|
||||||
|
void (*play)(void),
|
||||||
|
void (*pause)(void),
|
||||||
|
void (*stop)(void),
|
||||||
|
void (*toggle_pause)(void),
|
||||||
|
void (*seek)(double),
|
||||||
|
void (*seek_absolute)(double),
|
||||||
|
double (*get_position)(void),
|
||||||
|
double (*get_duration)(void),
|
||||||
|
double (*get_volume)(void),
|
||||||
|
gboolean (*is_paused)(void),
|
||||||
|
gboolean (*is_playing)(void),
|
||||||
|
char* (*get_current_file)(void),
|
||||||
|
char* (*get_media_title)(void),
|
||||||
|
void (*take_screenshot)(void),
|
||||||
|
void (*take_screenshot_to_file)(const char*)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all plugins from the plugins directory
|
||||||
|
*
|
||||||
|
* @return Number of plugins loaded
|
||||||
|
*/
|
||||||
|
int plugin_manager_load_all(PluginManager *pm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a plugin from a .zip file
|
||||||
|
*
|
||||||
|
* @param zip_path Path to the .zip file
|
||||||
|
* @return TRUE on success, FALSE on failure
|
||||||
|
*/
|
||||||
|
gboolean plugin_manager_install_zip(PluginManager *pm, const char *zip_path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall a plugin
|
||||||
|
*
|
||||||
|
* @param plugin_id Plugin ID to uninstall
|
||||||
|
* @return TRUE on success, FALSE on failure
|
||||||
|
*/
|
||||||
|
gboolean plugin_manager_uninstall(PluginManager *pm, const char *plugin_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of loaded plugins
|
||||||
|
*
|
||||||
|
* @param count Output: number of plugins
|
||||||
|
* @return Array of LoadedPlugin pointers (do not free)
|
||||||
|
*/
|
||||||
|
LoadedPlugin** plugin_manager_get_list(PluginManager *pm, int *count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the plugins menu
|
||||||
|
* Creates menu items for all loaded plugins
|
||||||
|
*
|
||||||
|
* @return GtkMenu widget
|
||||||
|
*/
|
||||||
|
GtkWidget* plugin_manager_get_menu(PluginManager *pm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a menu item from a plugin
|
||||||
|
*/
|
||||||
|
void plugin_manager_register_menu_item(PluginManager *pm, GtkWidget *item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a menu item from a plugin
|
||||||
|
*/
|
||||||
|
void plugin_manager_unregister_menu_item(PluginManager *pm, GtkWidget *item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast an event to all loaded plugins
|
||||||
|
*/
|
||||||
|
void plugin_manager_broadcast_event(PluginManager *pm, PluginEventType event, void *data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create configuration widget for preferences dialog
|
||||||
|
* Returns a GtkWidget (VBox) containing the plugin management UI
|
||||||
|
*/
|
||||||
|
GtkWidget* plugin_manager_create_config_widget(PluginManager *pm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show plugin management dialog (Deprecated in favor of Preferences)
|
||||||
|
*/
|
||||||
|
void plugin_manager_show_dialog(PluginManager *pm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the plugins directory path
|
||||||
|
* @return Path string (do not free)
|
||||||
|
*/
|
||||||
|
const char* plugin_manager_get_plugins_dir(PluginManager *pm);
|
||||||
|
|
||||||
|
#endif /* PLUGIN_MANAGER_H */
|
||||||
411
src/ui.c
411
src/ui.c
@ -6,6 +6,28 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include "mpris.h"
|
#include "mpris.h"
|
||||||
|
#include "plugin_manager.h"
|
||||||
|
|
||||||
|
/* Forward declarations */
|
||||||
|
static void on_playback_prev(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_next(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_seek_forward(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_seek_backward(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_frame_step(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_goto_time(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_speed(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_ab_loop_a(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_ab_loop_b(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_ab_loop_clear(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_audio_track_selected(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_subtitle_track_selected(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_subtitle_load(GtkMenuItem *item, gpointer data);
|
||||||
|
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_screenshot(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_open_scripts_folder(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_help_more_software(GtkMenuItem *item, gpointer data);
|
||||||
|
|
||||||
/* Property observation user data IDs */
|
/* Property observation user data IDs */
|
||||||
enum {
|
enum {
|
||||||
@ -74,6 +96,9 @@ struct AppUI {
|
|||||||
|
|
||||||
/* MPRIS state tracking */
|
/* MPRIS state tracking */
|
||||||
double last_mpris_pos;
|
double last_mpris_pos;
|
||||||
|
|
||||||
|
/* Plugin manager */
|
||||||
|
PluginManager *plugin_manager;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Forward declarations */
|
/* Forward declarations */
|
||||||
@ -122,6 +147,24 @@ static void on_view_preferences(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_help_about(GtkMenuItem *item, gpointer data);
|
static void on_help_about(GtkMenuItem *item, gpointer data);
|
||||||
|
|
||||||
|
/* Plugin API wrapper forward declarations */
|
||||||
|
static AppUI *g_current_ui;
|
||||||
|
static void plugin_api_play(void);
|
||||||
|
static void plugin_api_pause(void);
|
||||||
|
static void plugin_api_stop(void);
|
||||||
|
static void plugin_api_toggle_pause(void);
|
||||||
|
static void plugin_api_seek(double seconds);
|
||||||
|
static void plugin_api_seek_absolute(double position);
|
||||||
|
static double plugin_api_get_position(void);
|
||||||
|
static double plugin_api_get_duration(void);
|
||||||
|
static double plugin_api_get_volume(void);
|
||||||
|
static gboolean plugin_api_is_paused(void);
|
||||||
|
static gboolean plugin_api_is_playing(void);
|
||||||
|
static char* plugin_api_get_current_file(void);
|
||||||
|
static char* plugin_api_get_media_title(void);
|
||||||
|
static void plugin_api_take_screenshot(void);
|
||||||
|
static void plugin_api_take_screenshot_to_file(const char *path);
|
||||||
|
|
||||||
AppUI* ui_new(void)
|
AppUI* ui_new(void)
|
||||||
{
|
{
|
||||||
AppUI *ui = g_new0(AppUI, 1);
|
AppUI *ui = g_new0(AppUI, 1);
|
||||||
@ -158,6 +201,30 @@ AppUI* ui_new(void)
|
|||||||
ui->main_vbox = gtk_vbox_new(FALSE, 0);
|
ui->main_vbox = gtk_vbox_new(FALSE, 0);
|
||||||
gtk_container_add(GTK_CONTAINER(ui->window), ui->main_vbox);
|
gtk_container_add(GTK_CONTAINER(ui->window), ui->main_vbox);
|
||||||
|
|
||||||
|
/* Initialize plugin manager (must be before create_menubar) */
|
||||||
|
ui->plugin_manager = plugin_manager_new(ui);
|
||||||
|
|
||||||
|
/* Set up plugin API callbacks and load plugins BEFORE creating menu */
|
||||||
|
g_current_ui = ui;
|
||||||
|
plugin_manager_set_player_callbacks(ui->plugin_manager,
|
||||||
|
plugin_api_play,
|
||||||
|
plugin_api_pause,
|
||||||
|
plugin_api_stop,
|
||||||
|
plugin_api_toggle_pause,
|
||||||
|
plugin_api_seek,
|
||||||
|
plugin_api_seek_absolute,
|
||||||
|
plugin_api_get_position,
|
||||||
|
plugin_api_get_duration,
|
||||||
|
plugin_api_get_volume,
|
||||||
|
plugin_api_is_paused,
|
||||||
|
plugin_api_is_playing,
|
||||||
|
plugin_api_get_current_file,
|
||||||
|
plugin_api_get_media_title,
|
||||||
|
plugin_api_take_screenshot,
|
||||||
|
plugin_api_take_screenshot_to_file
|
||||||
|
);
|
||||||
|
plugin_manager_load_all(ui->plugin_manager);
|
||||||
|
|
||||||
/* Menu bar */
|
/* Menu bar */
|
||||||
create_menubar(ui);
|
create_menubar(ui);
|
||||||
gtk_box_pack_start(GTK_BOX(ui->main_vbox), ui->menubar, FALSE, FALSE, 0);
|
gtk_box_pack_start(GTK_BOX(ui->main_vbox), ui->menubar, FALSE, FALSE, 0);
|
||||||
@ -205,7 +272,7 @@ AppUI* ui_new(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Controls */
|
/* Controls */
|
||||||
ui->controls = controls_new(ui->player);
|
ui->controls = controls_new(ui->player, ui->prefs);
|
||||||
controls_set_fullscreen_callback(ui->controls, on_fullscreen_requested, ui);
|
controls_set_fullscreen_callback(ui->controls, on_fullscreen_requested, ui);
|
||||||
controls_set_play_pause_callback(ui->controls, on_controls_play_pause, ui);
|
controls_set_play_pause_callback(ui->controls, on_controls_play_pause, ui);
|
||||||
gtk_box_pack_start(GTK_BOX(ui->main_vbox), controls_get_widget(ui->controls), FALSE, FALSE, 0);
|
gtk_box_pack_start(GTK_BOX(ui->main_vbox), controls_get_widget(ui->controls), FALSE, FALSE, 0);
|
||||||
@ -213,9 +280,22 @@ AppUI* ui_new(void)
|
|||||||
/* Set player event callback */
|
/* Set player event callback */
|
||||||
player_set_event_callback(ui->player, on_player_event, ui);
|
player_set_event_callback(ui->player, on_player_event, ui);
|
||||||
|
|
||||||
|
/* Set mpv config path */
|
||||||
|
if (ui->prefs->mpv_config_path) {
|
||||||
|
player_set_config_path(ui->player, ui->prefs->mpv_config_path);
|
||||||
|
}
|
||||||
|
|
||||||
/* Set default volume */
|
/* Set default volume */
|
||||||
player_set_volume(ui->player, ui->prefs->default_volume);
|
player_set_volume(ui->player, ui->prefs->default_volume);
|
||||||
|
|
||||||
|
/* Set initial OSD and focus settings */
|
||||||
|
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");
|
||||||
|
|
||||||
|
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\"");
|
||||||
|
}
|
||||||
|
|
||||||
ui->is_fullscreen = FALSE;
|
ui->is_fullscreen = FALSE;
|
||||||
ui->always_on_top = FALSE;
|
ui->always_on_top = FALSE;
|
||||||
ui->auto_hide_timer_id = 0;
|
ui->auto_hide_timer_id = 0;
|
||||||
@ -250,6 +330,9 @@ void ui_destroy(AppUI *ui)
|
|||||||
|
|
||||||
mpris_destroy(ui);
|
mpris_destroy(ui);
|
||||||
|
|
||||||
|
/* Destroy plugin manager */
|
||||||
|
plugin_manager_destroy(ui->plugin_manager);
|
||||||
|
|
||||||
if (ui->fs_window) {
|
if (ui->fs_window) {
|
||||||
gtk_widget_destroy(ui->fs_window);
|
gtk_widget_destroy(ui->fs_window);
|
||||||
}
|
}
|
||||||
@ -274,6 +357,10 @@ void ui_set_preferences(AppUI *ui, Preferences *prefs)
|
|||||||
/* Apply some immediate settings */
|
/* Apply some immediate settings */
|
||||||
if (ui->player) {
|
if (ui->player) {
|
||||||
player_set_volume(ui->player, ui->prefs->default_volume);
|
player_set_volume(ui->player, ui->prefs->default_volume);
|
||||||
|
|
||||||
|
if (ui->prefs->screenshot_directory) {
|
||||||
|
player_set_option_string(ui->player, "screenshot-directory", ui->prefs->screenshot_directory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update playlist visibility if UI is already realized */
|
/* Update playlist visibility if UI is already realized */
|
||||||
@ -305,6 +392,81 @@ Playlist* ui_get_playlist(AppUI *ui)
|
|||||||
return ui->playlist;
|
return ui->playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PluginManager* ui_get_plugin_manager(AppUI *ui)
|
||||||
|
{
|
||||||
|
if (!ui) return NULL;
|
||||||
|
return ui->plugin_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Static wrapper functions for plugin API */
|
||||||
|
|
||||||
|
static void plugin_api_play(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) player_play(g_current_ui->player);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void plugin_api_pause(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) player_pause(g_current_ui->player);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void plugin_api_stop(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) player_stop(g_current_ui->player);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void plugin_api_toggle_pause(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) player_toggle_pause(g_current_ui->player);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void plugin_api_seek(double seconds) {
|
||||||
|
if (g_current_ui && g_current_ui->player) player_seek(g_current_ui->player, seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void plugin_api_seek_absolute(double position) {
|
||||||
|
if (g_current_ui && g_current_ui->player) player_seek_absolute(g_current_ui->player, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double plugin_api_get_position(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) return player_get_position(g_current_ui->player);
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double plugin_api_get_duration(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) return player_get_duration(g_current_ui->player);
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double plugin_api_get_volume(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) return player_get_volume(g_current_ui->player);
|
||||||
|
return 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean plugin_api_is_paused(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) return player_get_paused(g_current_ui->player);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean plugin_api_is_playing(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) return !player_get_paused(g_current_ui->player) && !player_is_idle(g_current_ui->player);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* plugin_api_get_current_file(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) return player_get_filename(g_current_ui->player);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* plugin_api_get_media_title(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) return player_get_media_title(g_current_ui->player);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void plugin_api_take_screenshot(void) {
|
||||||
|
if (g_current_ui && g_current_ui->player) player_screenshot(g_current_ui->player);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void plugin_api_take_screenshot_to_file(const char *path) {
|
||||||
|
if (g_current_ui && g_current_ui->player) player_screenshot_to_file(g_current_ui->player, path);
|
||||||
|
}
|
||||||
|
|
||||||
void ui_run(AppUI *ui)
|
void ui_run(AppUI *ui)
|
||||||
{
|
{
|
||||||
(void)ui;
|
(void)ui;
|
||||||
@ -1037,6 +1199,9 @@ static void create_menubar(AppUI *ui)
|
|||||||
{
|
{
|
||||||
ui->menubar = gtk_menu_bar_new();
|
ui->menubar = gtk_menu_bar_new();
|
||||||
|
|
||||||
|
GtkAccelGroup *accel_group = gtk_accel_group_new();
|
||||||
|
gtk_window_add_accel_group(GTK_WINDOW(ui->window), accel_group);
|
||||||
|
|
||||||
/* File Menu */
|
/* File Menu */
|
||||||
GtkWidget *file_menu = gtk_menu_new();
|
GtkWidget *file_menu = gtk_menu_new();
|
||||||
GtkWidget *file_item = gtk_menu_item_new_with_mnemonic("_File");
|
GtkWidget *file_item = gtk_menu_item_new_with_mnemonic("_File");
|
||||||
@ -1044,6 +1209,7 @@ static void create_menubar(AppUI *ui)
|
|||||||
|
|
||||||
GtkWidget *open_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL);
|
GtkWidget *open_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL);
|
||||||
g_signal_connect(open_item, "activate", G_CALLBACK(on_file_open), ui);
|
g_signal_connect(open_item, "activate", G_CALLBACK(on_file_open), ui);
|
||||||
|
gtk_widget_add_accelerator(open_item, "activate", accel_group, GDK_o, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), open_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), open_item);
|
||||||
|
|
||||||
GtkWidget *open_dir_item = gtk_menu_item_new_with_mnemonic("Open _Folder...");
|
GtkWidget *open_dir_item = gtk_menu_item_new_with_mnemonic("Open _Folder...");
|
||||||
@ -1067,10 +1233,10 @@ static void create_menubar(AppUI *ui)
|
|||||||
|
|
||||||
GtkWidget *open_url_item = gtk_menu_item_new_with_mnemonic("Open _URL...");
|
GtkWidget *open_url_item = gtk_menu_item_new_with_mnemonic("Open _URL...");
|
||||||
g_signal_connect(open_url_item, "activate", G_CALLBACK(on_file_open_url), ui);
|
g_signal_connect(open_url_item, "activate", G_CALLBACK(on_file_open_url), ui);
|
||||||
|
gtk_widget_add_accelerator(open_url_item, "activate", accel_group, GDK_l, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), open_url_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), open_url_item);
|
||||||
|
|
||||||
GtkWidget *save_playlist_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE, NULL);
|
GtkWidget *save_playlist_item = gtk_menu_item_new_with_mnemonic("Save _Playlist...");
|
||||||
gtk_menu_item_set_label(GTK_MENU_ITEM(save_playlist_item), "Save _Playlist...");
|
|
||||||
g_signal_connect(save_playlist_item, "activate", G_CALLBACK(on_file_save_playlist), ui);
|
g_signal_connect(save_playlist_item, "activate", G_CALLBACK(on_file_save_playlist), ui);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), save_playlist_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), save_playlist_item);
|
||||||
|
|
||||||
@ -1078,6 +1244,9 @@ static void create_menubar(AppUI *ui)
|
|||||||
|
|
||||||
GtkWidget *quit_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
|
GtkWidget *quit_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
|
||||||
g_signal_connect(quit_item, "activate", G_CALLBACK(on_file_quit), ui);
|
g_signal_connect(quit_item, "activate", G_CALLBACK(on_file_quit), ui);
|
||||||
|
gtk_widget_add_accelerator(quit_item, "activate", accel_group, GDK_q, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
|
||||||
|
/* Also Q key */
|
||||||
|
gtk_widget_add_accelerator(quit_item, "activate", accel_group, GDK_q, 0, 0);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), quit_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), quit_item);
|
||||||
|
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), file_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), file_item);
|
||||||
@ -1087,30 +1256,49 @@ static void create_menubar(AppUI *ui)
|
|||||||
GtkWidget *playback_item = gtk_menu_item_new_with_mnemonic("_Playback");
|
GtkWidget *playback_item = gtk_menu_item_new_with_mnemonic("_Playback");
|
||||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(playback_item), playback_menu);
|
gtk_menu_item_set_submenu(GTK_MENU_ITEM(playback_item), playback_menu);
|
||||||
|
|
||||||
GtkWidget *play_pause_item = gtk_menu_item_new_with_mnemonic("_Play/Pause");
|
GtkWidget *play_pause_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_MEDIA_PLAY, NULL);
|
||||||
|
gtk_menu_item_set_label(GTK_MENU_ITEM(play_pause_item), "_Play/Pause");
|
||||||
g_signal_connect(play_pause_item, "activate", G_CALLBACK(on_playback_play_pause), ui);
|
g_signal_connect(play_pause_item, "activate", G_CALLBACK(on_playback_play_pause), ui);
|
||||||
|
gtk_widget_add_accelerator(play_pause_item, "activate", accel_group, GDK_space, 0, GTK_ACCEL_VISIBLE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), play_pause_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), play_pause_item);
|
||||||
|
|
||||||
GtkWidget *stop_item = gtk_menu_item_new_with_mnemonic("_Stop");
|
GtkWidget *stop_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_MEDIA_STOP, NULL);
|
||||||
|
gtk_menu_item_set_label(GTK_MENU_ITEM(stop_item), "_Stop");
|
||||||
g_signal_connect(stop_item, "activate", G_CALLBACK(on_playback_stop), ui);
|
g_signal_connect(stop_item, "activate", G_CALLBACK(on_playback_stop), ui);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), stop_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), stop_item);
|
||||||
|
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), gtk_separator_menu_item_new());
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), gtk_separator_menu_item_new());
|
||||||
|
|
||||||
|
GtkWidget *prev_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_MEDIA_PREVIOUS, NULL);
|
||||||
|
g_signal_connect(prev_item, "activate", G_CALLBACK(on_playback_prev), ui);
|
||||||
|
gtk_widget_add_accelerator(prev_item, "activate", accel_group, GDK_Page_Up, 0, GTK_ACCEL_VISIBLE);
|
||||||
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), prev_item);
|
||||||
|
|
||||||
|
GtkWidget *next_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_MEDIA_NEXT, NULL);
|
||||||
|
g_signal_connect(next_item, "activate", G_CALLBACK(on_playback_next), ui);
|
||||||
|
gtk_widget_add_accelerator(next_item, "activate", accel_group, GDK_Page_Down, 0, GTK_ACCEL_VISIBLE);
|
||||||
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), next_item);
|
||||||
|
|
||||||
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), gtk_separator_menu_item_new());
|
||||||
|
|
||||||
GtkWidget *seek_fwd_item = gtk_menu_item_new_with_mnemonic("Seek _Forward 10s");
|
GtkWidget *seek_fwd_item = gtk_menu_item_new_with_mnemonic("Seek _Forward 10s");
|
||||||
g_signal_connect(seek_fwd_item, "activate", G_CALLBACK(on_playback_seek_forward), ui);
|
g_signal_connect(seek_fwd_item, "activate", G_CALLBACK(on_playback_seek_forward), ui);
|
||||||
|
gtk_widget_add_accelerator(seek_fwd_item, "activate", accel_group, GDK_Right, 0, GTK_ACCEL_VISIBLE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), seek_fwd_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), seek_fwd_item);
|
||||||
|
|
||||||
GtkWidget *seek_back_item = gtk_menu_item_new_with_mnemonic("Seek _Backward 10s");
|
GtkWidget *seek_back_item = gtk_menu_item_new_with_mnemonic("Seek _Backward 10s");
|
||||||
g_signal_connect(seek_back_item, "activate", G_CALLBACK(on_playback_seek_backward), ui);
|
g_signal_connect(seek_back_item, "activate", G_CALLBACK(on_playback_seek_backward), ui);
|
||||||
|
gtk_widget_add_accelerator(seek_back_item, "activate", accel_group, GDK_Left, 0, GTK_ACCEL_VISIBLE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), seek_back_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), seek_back_item);
|
||||||
|
|
||||||
GtkWidget *frame_step_item = gtk_menu_item_new_with_mnemonic("_Frame Step");
|
GtkWidget *frame_step_item = gtk_menu_item_new_with_mnemonic("_Frame Step");
|
||||||
g_signal_connect(frame_step_item, "activate", G_CALLBACK(on_playback_frame_step), ui);
|
g_signal_connect(frame_step_item, "activate", G_CALLBACK(on_playback_frame_step), ui);
|
||||||
|
gtk_widget_add_accelerator(frame_step_item, "activate", accel_group, GDK_period, 0, GTK_ACCEL_VISIBLE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), frame_step_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), frame_step_item);
|
||||||
|
|
||||||
GtkWidget *goto_time_item = gtk_menu_item_new_with_mnemonic("_Go to Time...");
|
GtkWidget *goto_time_item = gtk_menu_item_new_with_mnemonic("_Go to Time...");
|
||||||
g_signal_connect(goto_time_item, "activate", G_CALLBACK(on_playback_goto_time), ui);
|
g_signal_connect(goto_time_item, "activate", G_CALLBACK(on_playback_goto_time), ui);
|
||||||
|
gtk_widget_add_accelerator(goto_time_item, "activate", accel_group, GDK_g, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), goto_time_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), goto_time_item);
|
||||||
|
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), gtk_separator_menu_item_new());
|
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), gtk_separator_menu_item_new());
|
||||||
@ -1176,11 +1364,14 @@ static void create_menubar(AppUI *ui)
|
|||||||
|
|
||||||
GtkWidget *fullscreen_item = gtk_menu_item_new_with_mnemonic("_Fullscreen");
|
GtkWidget *fullscreen_item = gtk_menu_item_new_with_mnemonic("_Fullscreen");
|
||||||
g_signal_connect(fullscreen_item, "activate", G_CALLBACK(on_view_fullscreen), ui);
|
g_signal_connect(fullscreen_item, "activate", G_CALLBACK(on_view_fullscreen), ui);
|
||||||
|
gtk_widget_add_accelerator(fullscreen_item, "activate", accel_group, GDK_f, 0, GTK_ACCEL_VISIBLE);
|
||||||
|
gtk_widget_add_accelerator(fullscreen_item, "activate", accel_group, GDK_F11, 0, GTK_ACCEL_VISIBLE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), fullscreen_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), fullscreen_item);
|
||||||
|
|
||||||
GtkWidget *playlist_toggle_item = gtk_check_menu_item_new_with_mnemonic("_Playlist");
|
GtkWidget *playlist_toggle_item = gtk_check_menu_item_new_with_mnemonic("_Playlist");
|
||||||
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(playlist_toggle_item), ui->prefs->show_playlist);
|
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(playlist_toggle_item), ui->prefs->show_playlist);
|
||||||
g_signal_connect(playlist_toggle_item, "toggled", G_CALLBACK(on_view_playlist), ui);
|
g_signal_connect(playlist_toggle_item, "toggled", G_CALLBACK(on_view_playlist), ui);
|
||||||
|
gtk_widget_add_accelerator(playlist_toggle_item, "activate", accel_group, GDK_p, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), playlist_toggle_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), playlist_toggle_item);
|
||||||
|
|
||||||
GtkWidget *on_top_item = gtk_check_menu_item_new_with_mnemonic("Always on _Top");
|
GtkWidget *on_top_item = gtk_check_menu_item_new_with_mnemonic("Always on _Top");
|
||||||
@ -1210,6 +1401,7 @@ static void create_menubar(AppUI *ui)
|
|||||||
|
|
||||||
GtkWidget *screenshot_item = gtk_menu_item_new_with_mnemonic("_Screenshot");
|
GtkWidget *screenshot_item = gtk_menu_item_new_with_mnemonic("_Screenshot");
|
||||||
g_signal_connect(screenshot_item, "activate", G_CALLBACK(on_view_screenshot), ui);
|
g_signal_connect(screenshot_item, "activate", G_CALLBACK(on_view_screenshot), ui);
|
||||||
|
gtk_widget_add_accelerator(screenshot_item, "activate", accel_group, GDK_s, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), screenshot_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), screenshot_item);
|
||||||
|
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), gtk_separator_menu_item_new());
|
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), gtk_separator_menu_item_new());
|
||||||
@ -1220,6 +1412,12 @@ static void create_menubar(AppUI *ui)
|
|||||||
|
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), view_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), view_item);
|
||||||
|
|
||||||
|
/* Plugins Menu */
|
||||||
|
GtkWidget *plugins_menu = plugin_manager_get_menu(ui->plugin_manager);
|
||||||
|
GtkWidget *plugins_item = gtk_menu_item_new_with_mnemonic("P_lugins");
|
||||||
|
gtk_menu_item_set_submenu(GTK_MENU_ITEM(plugins_item), plugins_menu);
|
||||||
|
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), plugins_item);
|
||||||
|
|
||||||
/* Help Menu */
|
/* Help Menu */
|
||||||
GtkWidget *help_menu = gtk_menu_new();
|
GtkWidget *help_menu = gtk_menu_new();
|
||||||
GtkWidget *help_item = gtk_menu_item_new_with_mnemonic("_Help");
|
GtkWidget *help_item = gtk_menu_item_new_with_mnemonic("_Help");
|
||||||
@ -1229,6 +1427,13 @@ static void create_menubar(AppUI *ui)
|
|||||||
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);
|
||||||
|
|
||||||
|
GtkWidget *sep = gtk_separator_menu_item_new();
|
||||||
|
gtk_menu_shell_append(GTK_MENU_SHELL(help_menu), sep);
|
||||||
|
|
||||||
|
GtkWidget *more_item = gtk_menu_item_new_with_label("More GTK2 Software...");
|
||||||
|
g_signal_connect(more_item, "activate", G_CALLBACK(on_help_more_software), ui);
|
||||||
|
gtk_menu_shell_append(GTK_MENU_SHELL(help_menu), more_item);
|
||||||
|
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), help_item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), help_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1319,6 +1524,20 @@ static void on_playback_stop(GtkMenuItem *item, gpointer data)
|
|||||||
ui_update_title(ui, "GTK2 Media Player");
|
ui_update_title(ui, "GTK2 Media Player");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void on_playback_prev(GtkMenuItem *item, gpointer data)
|
||||||
|
{
|
||||||
|
(void)item;
|
||||||
|
AppUI *ui = (AppUI *)data;
|
||||||
|
playlist_play_prev(ui->playlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_playback_next(GtkMenuItem *item, gpointer data)
|
||||||
|
{
|
||||||
|
(void)item;
|
||||||
|
AppUI *ui = (AppUI *)data;
|
||||||
|
playlist_play_next(ui->playlist);
|
||||||
|
}
|
||||||
|
|
||||||
static void on_playback_seek_forward(GtkMenuItem *item, gpointer data)
|
static void on_playback_seek_forward(GtkMenuItem *item, gpointer data)
|
||||||
{
|
{
|
||||||
(void)item;
|
(void)item;
|
||||||
@ -1440,19 +1659,15 @@ static void on_view_screenshot(GtkMenuItem *item, gpointer data)
|
|||||||
{
|
{
|
||||||
(void)item;
|
(void)item;
|
||||||
AppUI *ui = (AppUI *)data;
|
AppUI *ui = (AppUI *)data;
|
||||||
|
/* Use instant screenshot (saves to configured directory) */
|
||||||
char *path = dialogs_save_screenshot(GTK_WINDOW(ui->window));
|
player_screenshot(ui->player);
|
||||||
if (path) {
|
|
||||||
player_screenshot_to_file(ui->player, path);
|
|
||||||
g_free(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_view_preferences(GtkMenuItem *item, gpointer data)
|
static void on_view_preferences(GtkMenuItem *item, gpointer data)
|
||||||
{
|
{
|
||||||
(void)item;
|
(void)item;
|
||||||
AppUI *ui = (AppUI *)data;
|
AppUI *ui = (AppUI *)data;
|
||||||
if (dialogs_show_preferences(GTK_WINDOW(ui->window), ui->prefs)) {
|
if (dialogs_show_preferences(GTK_WINDOW(ui->window), ui->prefs, ui->plugin_manager)) {
|
||||||
/* Apply changes immediately */
|
/* Apply changes immediately */
|
||||||
if (!ui->is_fullscreen) {
|
if (!ui->is_fullscreen) {
|
||||||
if (ui->prefs->hide_cursor_windowed) {
|
if (ui->prefs->hide_cursor_windowed) {
|
||||||
@ -1470,6 +1685,99 @@ static void on_view_preferences(GtkMenuItem *item, gpointer data)
|
|||||||
gtk_widget_show(controls_get_widget(ui->controls));
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1487,6 +1795,17 @@ static void on_help_about(GtkMenuItem *item, gpointer data)
|
|||||||
dialogs_show_about(GTK_WINDOW(ui->window));
|
dialogs_show_about(GTK_WINDOW(ui->window));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void on_help_more_software(GtkMenuItem *item, gpointer data)
|
||||||
|
{
|
||||||
|
(void)item;
|
||||||
|
(void)data;
|
||||||
|
const char *url = "https://lakiweb.net/article/gtk2/software-directory.html";
|
||||||
|
char *cmd = g_strdup_printf("xdg-open '%s'", url);
|
||||||
|
int ret = system(cmd);
|
||||||
|
(void)ret;
|
||||||
|
g_free(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
static void on_controls_play_pause(gpointer data)
|
static void on_controls_play_pause(gpointer data)
|
||||||
{
|
{
|
||||||
on_playback_play_pause(NULL, data);
|
on_playback_play_pause(NULL, data);
|
||||||
@ -1513,3 +1832,71 @@ static void on_recent_item_activated(GtkRecentChooser *chooser, gpointer data)
|
|||||||
g_free(uri);
|
g_free(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Plugin UI Helpers */
|
||||||
|
void ui_set_video_area_visible(AppUI *ui, gboolean visible)
|
||||||
|
{
|
||||||
|
if (!ui || !ui->video_container) return;
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
gtk_widget_show(ui->video_container);
|
||||||
|
gtk_widget_set_no_show_all(ui->video_container, FALSE);
|
||||||
|
} else {
|
||||||
|
gtk_widget_hide(ui->video_container);
|
||||||
|
gtk_widget_set_no_show_all(ui->video_container, TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_set_window_size(AppUI *ui, int width, int height)
|
||||||
|
{
|
||||||
|
if (!ui || !ui->window) return;
|
||||||
|
gtk_window_resize(GTK_WINDOW(ui->window), width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ui_restart(AppUI *ui)
|
||||||
|
{
|
||||||
|
(void)ui;
|
||||||
|
|
||||||
|
/* Get the path to the current executable */
|
||||||
|
char *bin_path = NULL;
|
||||||
|
|
||||||
|
/* If we are installed, use the installed path */
|
||||||
|
if (g_file_test("/usr/local/bin/gtk2-mpv-player", G_FILE_TEST_EXISTS)) {
|
||||||
|
bin_path = g_strdup("/usr/local/bin/gtk2-mpv-player");
|
||||||
|
} else {
|
||||||
|
/* Otherwise try to find it relative to current dir */
|
||||||
|
bin_path = g_build_filename(".", "gtk2-mpv-player", NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Restarting application via %s...\n", bin_path);
|
||||||
|
|
||||||
|
char *argv[] = {bin_path, NULL};
|
||||||
|
execvp(bin_path, argv);
|
||||||
|
|
||||||
|
/* If execvp fails */
|
||||||
|
perror("execvp");
|
||||||
|
g_free(bin_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ui_open_scripts_folder(AppUI *ui)
|
||||||
|
{
|
||||||
|
(void)ui;
|
||||||
|
|
||||||
|
const char *config_dir = g_get_user_config_dir();
|
||||||
|
char *scripts_dir = g_build_filename(config_dir, "gtk2-media-player", "scripts", NULL);
|
||||||
|
|
||||||
|
/* Ensure it exists */
|
||||||
|
g_mkdir_with_parents(scripts_dir, 0755);
|
||||||
|
|
||||||
|
printf("Opening scripts directory: %s\n", scripts_dir);
|
||||||
|
|
||||||
|
char *cmd = g_strdup_printf("xdg-open '%s'", scripts_dir);
|
||||||
|
int ret = system(cmd);
|
||||||
|
(void)ret; /* Suppress unused result warning */
|
||||||
|
|
||||||
|
g_free(cmd);
|
||||||
|
g_free(scripts_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
14
src/ui.h
14
src/ui.h
@ -23,6 +23,11 @@ Player* ui_get_player(AppUI *ui);
|
|||||||
/* Get playlist */
|
/* Get playlist */
|
||||||
Playlist* ui_get_playlist(AppUI *ui);
|
Playlist* ui_get_playlist(AppUI *ui);
|
||||||
|
|
||||||
|
/* Get plugin manager */
|
||||||
|
struct PluginManager;
|
||||||
|
typedef struct PluginManager PluginManager;
|
||||||
|
PluginManager* ui_get_plugin_manager(AppUI *ui);
|
||||||
|
|
||||||
/* Run the application */
|
/* Run the application */
|
||||||
void ui_run(AppUI *ui);
|
void ui_run(AppUI *ui);
|
||||||
|
|
||||||
@ -39,4 +44,13 @@ void ui_toggle_always_on_top(AppUI *ui);
|
|||||||
/* Update window title */
|
/* Update window title */
|
||||||
void ui_update_title(AppUI *ui, const char *media_title);
|
void ui_update_title(AppUI *ui, const char *media_title);
|
||||||
|
|
||||||
|
/* Plugin UI Access */
|
||||||
|
void ui_set_video_area_visible(AppUI *ui, gboolean visible);
|
||||||
|
void ui_set_window_size(AppUI *ui, int width, int height);
|
||||||
|
Player* ui_get_player(AppUI *ui); /* Helper to get player from UI */
|
||||||
|
|
||||||
|
/* Application control */
|
||||||
|
void ui_restart(AppUI *ui);
|
||||||
|
void ui_open_scripts_folder(AppUI *ui);
|
||||||
|
|
||||||
#endif /* UI_H */
|
#endif /* UI_H */
|
||||||
|
|||||||
19
src/ui_callbacks.h
Normal file
19
src/ui_callbacks.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
/* Forward declarations for callbacks used in menu */
|
||||||
|
static void on_playback_prev(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_next(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_seek_forward(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_seek_backward(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_frame_step(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_goto_time(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_speed(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_ab_loop_a(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_ab_loop_b(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_playback_ab_loop_clear(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_audio_track_selected(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_subtitle_track_selected(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_subtitle_load(GtkMenuItem *item, gpointer data);
|
||||||
|
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_screenshot(GtkMenuItem *item, gpointer data);
|
||||||
Loading…
Reference in New Issue
Block a user