VERSION 0.9.0 - Added chapters functionality, volume boost to 150%, organised menus better, adjusted button sizes, fixed fullscreen mode stretching out onto multiple monitors, better plugins/scripts support, etc. This is a major release!
This commit is contained in:
parent
0ef995ae7a
commit
2cb52dcf90
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,3 +20,4 @@ make_deb_32.sh
|
||||
make_deb.sh
|
||||
make_release.sh
|
||||
gtk2-media-player-v0.7.1-linux.zip
|
||||
GTK2-template
|
||||
@ -8,7 +8,8 @@ if [ "$EUID" -ne 0 ]; then
|
||||
fi
|
||||
|
||||
INSTALL_DIR="/usr/local/lib/gtk2-mpv-player"
|
||||
BIN_LINK="/usr/local/bin/gtk2-mpv-player"
|
||||
BIN_LINK="/usr/bin/gtk2-mpv-player"
|
||||
BIN_LINK_LOCAL="/usr/local/bin/gtk2-mpv-player"
|
||||
DESKTOP_FILE="/usr/share/applications/gtk2-mpv-player.desktop"
|
||||
|
||||
echo "Installing to $INSTALL_DIR..."
|
||||
@ -24,14 +25,15 @@ install -m 644 assets/icon.png "$INSTALL_DIR/"
|
||||
rm -rf "$INSTALL_DIR/lib"
|
||||
cp -r lib "$INSTALL_DIR/"
|
||||
|
||||
# Create wrapper script
|
||||
# Create wrapper scripts
|
||||
cat <<EOF > "$BIN_LINK"
|
||||
#!/bin/bash
|
||||
cd "$INSTALL_DIR"
|
||||
./gtk2-mpv-player "\$@"
|
||||
EOF
|
||||
|
||||
chmod +x "$BIN_LINK"
|
||||
cp "$BIN_LINK" "$BIN_LINK_LOCAL"
|
||||
chmod +x "$BIN_LINK" "$BIN_LINK_LOCAL"
|
||||
|
||||
# Install desktop file
|
||||
cp assets/gtk2-mpv-player.desktop "$DESKTOP_FILE"
|
||||
|
||||
217
src/controls.c
217
src/controls.c
@ -2,6 +2,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
struct Controls {
|
||||
Player *player;
|
||||
@ -42,18 +43,26 @@ struct Controls {
|
||||
/* Icons (stock icons) */
|
||||
GtkWidget *play_image;
|
||||
GtkWidget *pause_image;
|
||||
|
||||
/* Chapters for snapping/markers */
|
||||
double *chapter_times;
|
||||
int chapter_count;
|
||||
};
|
||||
|
||||
/* Forward declarations */
|
||||
static void on_play_pause_clicked(GtkButton *button, gpointer data);
|
||||
static void on_stop_clicked(GtkButton *button, gpointer data);
|
||||
static void on_prev_clicked(GtkButton *button, gpointer data);
|
||||
static double snap_to_chapter(Controls *controls, double val);
|
||||
static void on_next_clicked(GtkButton *button, gpointer data);
|
||||
static void on_mute_clicked(GtkButton *button, gpointer data);
|
||||
static void on_fullscreen_clicked(GtkButton *button, gpointer data);
|
||||
static gboolean on_seek_change_value(GtkRange *range, GtkScrollType scroll, gdouble value, gpointer data);
|
||||
|
||||
#define SNAP_THRESHOLD 5.0 /* seconds */
|
||||
static gboolean on_seek_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data);
|
||||
static gboolean on_seek_button_release(GtkWidget *widget, GdkEventButton *event, gpointer data);
|
||||
static gboolean on_seek_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer data);
|
||||
static void on_volume_changed(GtkRange *range, gpointer data);
|
||||
|
||||
static void format_time(double seconds, char *buffer, size_t size)
|
||||
@ -86,15 +95,24 @@ Controls* controls_new(Player *player, Preferences *prefs)
|
||||
/* Main horizontal box */
|
||||
controls->hbox = gtk_hbox_new(FALSE, 5);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(controls->hbox), 5);
|
||||
/* Set a fixed height to prevent layout shift when markers appear at the bottom */
|
||||
gtk_widget_set_size_request(controls->hbox, -1, 50);
|
||||
gtk_container_add(GTK_CONTAINER(controls->root), controls->hbox);
|
||||
|
||||
/* Left side buttons container, wrapped in alignment to prevent vertical stretch */
|
||||
GtkWidget *left_align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
|
||||
GtkWidget *left_box = gtk_hbox_new(FALSE, 5);
|
||||
gtk_container_add(GTK_CONTAINER(left_align), left_box);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), left_align, FALSE, FALSE, 0);
|
||||
|
||||
/* Previous button */
|
||||
controls->btn_prev = gtk_button_new();
|
||||
gtk_button_set_image(GTK_BUTTON(controls->btn_prev),
|
||||
gtk_image_new_from_stock(GTK_STOCK_MEDIA_PREVIOUS, GTK_ICON_SIZE_BUTTON));
|
||||
gtk_widget_set_size_request(controls->btn_prev, 36, 36);
|
||||
gtk_widget_set_tooltip_text(controls->btn_prev, "Previous");
|
||||
g_signal_connect(controls->btn_prev, "clicked", G_CALLBACK(on_prev_clicked), controls);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), controls->btn_prev, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(left_box), controls->btn_prev, FALSE, FALSE, 0);
|
||||
|
||||
/* Play/Pause button */
|
||||
controls->btn_play_pause = gtk_button_new();
|
||||
@ -103,27 +121,30 @@ Controls* controls_new(Player *player, Preferences *prefs)
|
||||
g_object_ref(controls->play_image);
|
||||
g_object_ref(controls->pause_image);
|
||||
gtk_button_set_image(GTK_BUTTON(controls->btn_play_pause), controls->play_image);
|
||||
gtk_widget_set_size_request(controls->btn_play_pause, 36, 36);
|
||||
gtk_widget_set_tooltip_text(controls->btn_play_pause, "Play/Pause");
|
||||
g_signal_connect(controls->btn_play_pause, "clicked", G_CALLBACK(on_play_pause_clicked), controls);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), controls->btn_play_pause, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(left_box), controls->btn_play_pause, FALSE, FALSE, 0);
|
||||
|
||||
/* Stop button */
|
||||
controls->btn_stop = gtk_button_new();
|
||||
gtk_button_set_image(GTK_BUTTON(controls->btn_stop),
|
||||
gtk_image_new_from_stock(GTK_STOCK_MEDIA_STOP, GTK_ICON_SIZE_BUTTON));
|
||||
gtk_widget_set_size_request(controls->btn_stop, 36, 36);
|
||||
gtk_widget_set_tooltip_text(controls->btn_stop, "Stop");
|
||||
g_signal_connect(controls->btn_stop, "clicked", G_CALLBACK(on_stop_clicked), controls);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), controls->btn_stop, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(left_box), controls->btn_stop, FALSE, FALSE, 0);
|
||||
|
||||
/* Next button */
|
||||
controls->btn_next = gtk_button_new();
|
||||
gtk_button_set_image(GTK_BUTTON(controls->btn_next),
|
||||
gtk_image_new_from_stock(GTK_STOCK_MEDIA_NEXT, GTK_ICON_SIZE_BUTTON));
|
||||
gtk_widget_set_size_request(controls->btn_next, 36, 36);
|
||||
gtk_widget_set_tooltip_text(controls->btn_next, "Next");
|
||||
g_signal_connect(controls->btn_next, "clicked", G_CALLBACK(on_next_clicked), controls);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), controls->btn_next, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(left_box), controls->btn_next, FALSE, FALSE, 0);
|
||||
|
||||
/* Separator */
|
||||
/* Separator (inside the stretchable area) */
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), gtk_vseparator_new(), FALSE, FALSE, 5);
|
||||
|
||||
/* Seek slider */
|
||||
@ -134,44 +155,59 @@ Controls* controls_new(Player *player, Preferences *prefs)
|
||||
g_signal_connect(controls->seek_scale, "change-value", G_CALLBACK(on_seek_change_value), controls);
|
||||
g_signal_connect(controls->seek_scale, "button-press-event", G_CALLBACK(on_seek_button_press), controls);
|
||||
g_signal_connect(controls->seek_scale, "button-release-event", G_CALLBACK(on_seek_button_release), controls);
|
||||
g_signal_connect(controls->seek_scale, "motion-notify-event", G_CALLBACK(on_seek_motion_notify), controls);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), controls->seek_scale, TRUE, TRUE, 0);
|
||||
|
||||
/* Right side buttons container, wrapped in alignment to prevent vertical stretch */
|
||||
GtkWidget *right_align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
|
||||
GtkWidget *right_box = gtk_hbox_new(FALSE, 5);
|
||||
gtk_container_add(GTK_CONTAINER(right_align), right_box);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), right_align, FALSE, FALSE, 0);
|
||||
|
||||
/* Time label */
|
||||
controls->time_label = gtk_label_new("0:00 / 0:00");
|
||||
gtk_misc_set_alignment(GTK_MISC(controls->time_label), 0.5, 0.5);
|
||||
gtk_widget_set_size_request(controls->time_label, 85, -1);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), controls->time_label, FALSE, FALSE, 2);
|
||||
/* gtk_widget_set_size_request(controls->time_label, 85, -1); */
|
||||
gtk_box_pack_start(GTK_BOX(right_box), controls->time_label, FALSE, FALSE, 2);
|
||||
|
||||
/* Separator */
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), gtk_vseparator_new(), FALSE, FALSE, 2);
|
||||
gtk_box_pack_start(GTK_BOX(right_box), gtk_vseparator_new(), FALSE, FALSE, 2);
|
||||
|
||||
/* Volume button (mute toggle) */
|
||||
controls->btn_mute = gtk_button_new();
|
||||
GtkWidget *mute_img = gtk_image_new_from_icon_name("audio-volume-high", GTK_ICON_SIZE_BUTTON);
|
||||
gtk_button_set_image(GTK_BUTTON(controls->btn_mute), mute_img);
|
||||
gtk_widget_set_size_request(controls->btn_mute, 36, 36);
|
||||
gtk_widget_set_tooltip_text(controls->btn_mute, "Mute/Unmute");
|
||||
g_signal_connect(controls->btn_mute, "clicked", G_CALLBACK(on_mute_clicked), controls);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), controls->btn_mute, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(right_box), controls->btn_mute, FALSE, FALSE, 0);
|
||||
|
||||
/* Volume slider */
|
||||
controls->volume_scale = gtk_hscale_new_with_range(0, 100, 1);
|
||||
double max_vol = (prefs && prefs->enable_volume_boost) ? 150.0 : 100.0;
|
||||
controls->volume_scale = gtk_hscale_new_with_range(0, max_vol, 1);
|
||||
gtk_scale_set_draw_value(GTK_SCALE(controls->volume_scale), FALSE);
|
||||
#if GTK_CHECK_VERSION(2,16,0)
|
||||
if (prefs && prefs->enable_volume_boost) {
|
||||
gtk_scale_add_mark(GTK_SCALE(controls->volume_scale), 100, GTK_POS_BOTTOM, NULL);
|
||||
}
|
||||
#endif
|
||||
gtk_range_set_value(GTK_RANGE(controls->volume_scale), 100);
|
||||
gtk_widget_set_size_request(controls->volume_scale, 80, -1);
|
||||
gtk_widget_set_tooltip_text(controls->volume_scale, "Volume");
|
||||
g_signal_connect(controls->volume_scale, "value-changed", G_CALLBACK(on_volume_changed), controls);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), controls->volume_scale, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(right_box), controls->volume_scale, FALSE, FALSE, 0);
|
||||
|
||||
/* Separator */
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), gtk_vseparator_new(), FALSE, FALSE, 5);
|
||||
gtk_box_pack_start(GTK_BOX(right_box), gtk_vseparator_new(), FALSE, FALSE, 5);
|
||||
|
||||
/* Fullscreen button */
|
||||
controls->btn_fullscreen = gtk_button_new();
|
||||
gtk_button_set_image(GTK_BUTTON(controls->btn_fullscreen),
|
||||
gtk_image_new_from_stock(GTK_STOCK_FULLSCREEN, GTK_ICON_SIZE_BUTTON));
|
||||
gtk_widget_set_size_request(controls->btn_fullscreen, 36, 36);
|
||||
gtk_widget_set_tooltip_text(controls->btn_fullscreen, "Fullscreen");
|
||||
g_signal_connect(controls->btn_fullscreen, "clicked", G_CALLBACK(on_fullscreen_clicked), controls);
|
||||
gtk_box_pack_start(GTK_BOX(controls->hbox), controls->btn_fullscreen, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(right_box), controls->btn_fullscreen, FALSE, FALSE, 0);
|
||||
|
||||
gtk_widget_show_all(controls->root);
|
||||
|
||||
@ -184,7 +220,7 @@ void controls_destroy(Controls *controls)
|
||||
|
||||
if (controls->play_image) g_object_unref(controls->play_image);
|
||||
if (controls->pause_image) g_object_unref(controls->pause_image);
|
||||
|
||||
if (controls->chapter_times) g_free(controls->chapter_times);
|
||||
g_free(controls);
|
||||
}
|
||||
|
||||
@ -219,6 +255,11 @@ void controls_update_duration(Controls *controls, double duration)
|
||||
|
||||
/* Update slider range (still use 0-100 percent) */
|
||||
gtk_range_set_range(GTK_RANGE(controls->seek_scale), 0, 100);
|
||||
|
||||
/* Refresh markers if we have them */
|
||||
if (controls->chapter_count > 0 && controls->chapter_times) {
|
||||
controls_set_chapters(controls, controls->chapter_count, controls->chapter_times);
|
||||
}
|
||||
}
|
||||
|
||||
void controls_update_pause_state(Controls *controls, gboolean paused)
|
||||
@ -259,15 +300,63 @@ void controls_update_title(Controls *controls, const char *title)
|
||||
void controls_reset(Controls *controls)
|
||||
{
|
||||
if (!controls) return;
|
||||
|
||||
controls->duration = 0.0;
|
||||
controls->duration = 0;
|
||||
gtk_range_set_value(GTK_RANGE(controls->seek_scale), 0);
|
||||
gtk_label_set_text(GTK_LABEL(controls->time_label), "0:00 / 0:00");
|
||||
|
||||
if (controls->chapter_times) {
|
||||
g_free(controls->chapter_times);
|
||||
controls->chapter_times = NULL;
|
||||
}
|
||||
controls->chapter_count = 0;
|
||||
|
||||
/* Clear markers */
|
||||
#if GTK_CHECK_VERSION(2,16,0)
|
||||
gtk_scale_clear_marks(GTK_SCALE(controls->seek_scale));
|
||||
#endif
|
||||
|
||||
/* Show play icon */
|
||||
gtk_button_set_image(GTK_BUTTON(controls->btn_play_pause), controls->play_image);
|
||||
}
|
||||
|
||||
void controls_set_chapters(Controls *controls, int count, double *times)
|
||||
{
|
||||
if (!controls) return;
|
||||
|
||||
if (controls->chapter_times) {
|
||||
g_free(controls->chapter_times);
|
||||
controls->chapter_times = NULL;
|
||||
}
|
||||
|
||||
/* If the preference is visually off, we disable counting and marks entirely */
|
||||
if (controls->prefs && !controls->prefs->enable_chapters) {
|
||||
controls->chapter_count = 0;
|
||||
#if GTK_CHECK_VERSION(2,16,0)
|
||||
gtk_scale_clear_marks(GTK_SCALE(controls->seek_scale));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
controls->chapter_count = count;
|
||||
if (count > 0 && times) {
|
||||
controls->chapter_times = g_new0(double, count);
|
||||
memcpy(controls->chapter_times, times, sizeof(double) * count);
|
||||
} else {
|
||||
controls->chapter_times = NULL;
|
||||
}
|
||||
|
||||
/* Add markers to scale if supported */
|
||||
#if GTK_CHECK_VERSION(2,16,0)
|
||||
gtk_scale_clear_marks(GTK_SCALE(controls->seek_scale));
|
||||
if (controls->duration > 0 && count > 0 && times) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
double val = (times[i] / controls->duration) * 100.0;
|
||||
gtk_scale_add_mark(GTK_SCALE(controls->seek_scale), val, GTK_POS_BOTTOM, NULL);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void controls_set_seeking(Controls *controls, gboolean seeking)
|
||||
{
|
||||
if (!controls) return;
|
||||
@ -370,6 +459,7 @@ static gboolean on_seek_change_value(GtkRange *range, GtkScrollType scroll, gdou
|
||||
static gboolean on_seek_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data)
|
||||
{
|
||||
Controls *controls = (Controls *)data;
|
||||
gtk_widget_grab_focus(widget);
|
||||
controls->seeking = TRUE;
|
||||
|
||||
if (event->button == 1) {
|
||||
@ -378,12 +468,19 @@ static gboolean on_seek_button_press(GtkWidget *widget, GdkEventButton *event, g
|
||||
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;
|
||||
/* Calculate relative position (0.0 to 1.0) using widget-relative coordinates */
|
||||
gint x;
|
||||
gtk_widget_get_pointer(widget, &x, NULL);
|
||||
|
||||
double relative = 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);
|
||||
|
||||
/* Snap to chapter if close */
|
||||
new_val = snap_to_chapter(controls, new_val);
|
||||
|
||||
gtk_range_set_value(range, new_val);
|
||||
|
||||
/* Perform seek immediately */
|
||||
@ -391,8 +488,10 @@ static gboolean on_seek_button_press(GtkWidget *widget, GdkEventButton *event, g
|
||||
double position = (new_val / 100.0) * controls->duration;
|
||||
player_seek_absolute(controls->player, position);
|
||||
}
|
||||
|
||||
/* Signal handled to prevent GTK from doing its own thing (like jumping by page) */
|
||||
return TRUE;
|
||||
}
|
||||
/* Return FALSE to allow GTK to process the rest of the event (starting the drag/step) */
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
@ -407,9 +506,89 @@ static gboolean on_seek_button_release(GtkWidget *widget, GdkEventButton *event,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static double snap_to_chapter(Controls *controls, double val)
|
||||
{
|
||||
/* Get seekbar width to calculate pixel-based threshold */
|
||||
int width = controls->seek_scale->allocation.width;
|
||||
if (width <= 0) return val;
|
||||
|
||||
double snap_pixels = 3.0;
|
||||
double snap_percent = (snap_pixels / (double)width) * 100.0;
|
||||
|
||||
for (int i = 0; i < controls->chapter_count; i++) {
|
||||
if (controls->duration <= 0) continue;
|
||||
double chapter_percent = (controls->chapter_times[i] / controls->duration) * 100.0;
|
||||
if (fabs(val - chapter_percent) < snap_percent) {
|
||||
return chapter_percent;
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static gboolean on_seek_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer data)
|
||||
{
|
||||
Controls *controls = (Controls *)data;
|
||||
|
||||
if (controls->seeking && (event->state & GDK_BUTTON1_MASK)) {
|
||||
GtkRange *range = GTK_RANGE(widget);
|
||||
GtkAdjustment *adj = gtk_range_get_adjustment(range);
|
||||
|
||||
gint x;
|
||||
gtk_widget_get_pointer(widget, &x, NULL);
|
||||
|
||||
double relative = 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);
|
||||
|
||||
/* Snap to chapter if close */
|
||||
new_val = snap_to_chapter(controls, new_val);
|
||||
|
||||
gtk_range_set_value(range, new_val);
|
||||
|
||||
if (controls->duration > 0) {
|
||||
double position = (new_val / 100.0) * controls->duration;
|
||||
player_seek_absolute(controls->player, position);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void on_volume_changed(GtkRange *range, gpointer data)
|
||||
{
|
||||
Controls *controls = (Controls *)data;
|
||||
double volume = gtk_range_get_value(range);
|
||||
|
||||
/* Magnetic snapping to 100% only if boost is on */
|
||||
if (controls->prefs && controls->prefs->enable_volume_boost) {
|
||||
if (fabs(volume - 100.0) < 4.0 && volume != 100.0) {
|
||||
gtk_range_set_value(range, 100.0);
|
||||
return; /* Let recursive call handle backend update */
|
||||
}
|
||||
}
|
||||
|
||||
player_set_volume(controls->player, volume);
|
||||
}
|
||||
|
||||
void controls_update_volume_boost(Controls *controls, gboolean enable)
|
||||
{
|
||||
if (!controls || !controls->volume_scale) return;
|
||||
|
||||
double current = gtk_range_get_value(GTK_RANGE(controls->volume_scale));
|
||||
gtk_range_set_range(GTK_RANGE(controls->volume_scale), 0, enable ? 150.0 : 100.0);
|
||||
|
||||
#if GTK_CHECK_VERSION(2,16,0)
|
||||
gtk_scale_clear_marks(GTK_SCALE(controls->volume_scale));
|
||||
if (enable) {
|
||||
gtk_scale_add_mark(GTK_SCALE(controls->volume_scale), 100, GTK_POS_BOTTOM, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!enable && current > 100.0) {
|
||||
gtk_range_set_value(GTK_RANGE(controls->volume_scale), 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,8 +22,10 @@ void controls_update_position(Controls *controls, double position);
|
||||
void controls_update_duration(Controls *controls, double duration);
|
||||
void controls_update_pause_state(Controls *controls, gboolean paused);
|
||||
void controls_update_volume(Controls *controls, double volume);
|
||||
void controls_update_volume_boost(Controls *controls, gboolean enable);
|
||||
void controls_update_mute_state(Controls *controls, gboolean muted);
|
||||
void controls_update_title(Controls *controls, const char *title);
|
||||
void controls_set_chapters(Controls *controls, int count, double *times);
|
||||
void controls_reset(Controls *controls);
|
||||
|
||||
/* Seek slider interaction (to prevent feedback loop) */
|
||||
|
||||
157
src/dialogs.c
157
src/dialogs.c
@ -326,7 +326,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_box_pack_start(GTK_BOX(about_vbox), title_label, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *version_label = gtk_label_new("Version 0.7.1");
|
||||
GtkWidget *version_label = gtk_label_new("Version 9.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.");
|
||||
@ -672,6 +672,8 @@ Preferences* preferences_new(void)
|
||||
prefs->seekbar_direct_jump = TRUE; /* Default to TRUE as requested earlier */
|
||||
prefs->hide_focus_rect = FALSE;
|
||||
prefs->enable_osd = TRUE;
|
||||
prefs->enable_chapters = TRUE;
|
||||
prefs->enable_volume_boost = FALSE;
|
||||
prefs->last_dir = NULL;
|
||||
prefs->screenshot_directory = g_strdup(g_get_home_dir());
|
||||
prefs->screenshot_format = g_strdup("png");
|
||||
@ -691,6 +693,16 @@ void preferences_free(Preferences *prefs)
|
||||
g_free(prefs);
|
||||
}
|
||||
|
||||
static void load_bool_pref(GKeyFile *keyfile, const char *key, gboolean *target) {
|
||||
GError *error = NULL;
|
||||
gboolean val = g_key_file_get_boolean(keyfile, "General", key, &error);
|
||||
if (!error) {
|
||||
*target = val;
|
||||
} else {
|
||||
g_clear_error(&error);
|
||||
}
|
||||
}
|
||||
|
||||
void preferences_load(Preferences *prefs)
|
||||
{
|
||||
if (!prefs) return;
|
||||
@ -705,14 +717,19 @@ void preferences_load(Preferences *prefs)
|
||||
if (!error) prefs->default_volume = volume;
|
||||
else { g_clear_error(&error); }
|
||||
|
||||
gboolean remember = g_key_file_get_boolean(keyfile, "General", "remember_position", &error);
|
||||
if (!error) prefs->remember_position = remember;
|
||||
else { g_clear_error(&error); }
|
||||
|
||||
gboolean playlist = g_key_file_get_boolean(keyfile, "General", "show_playlist_v3", &error);
|
||||
if (!error) prefs->show_playlist = playlist;
|
||||
else { g_clear_error(&error); }
|
||||
|
||||
load_bool_pref(keyfile, "remember_position", &prefs->remember_position);
|
||||
load_bool_pref(keyfile, "show_playlist_v3", &prefs->show_playlist);
|
||||
load_bool_pref(keyfile, "auto_resize", &prefs->auto_resize);
|
||||
load_bool_pref(keyfile, "hide_cursor_windowed", &prefs->hide_cursor_windowed);
|
||||
load_bool_pref(keyfile, "remember_last_dir", &prefs->remember_last_dir);
|
||||
load_bool_pref(keyfile, "use_mpris", &prefs->use_mpris);
|
||||
load_bool_pref(keyfile, "open_in_new_window", &prefs->open_in_new_window);
|
||||
load_bool_pref(keyfile, "seekbar_direct_jump", &prefs->seekbar_direct_jump);
|
||||
load_bool_pref(keyfile, "hide_focus_rect", &prefs->hide_focus_rect);
|
||||
load_bool_pref(keyfile, "enable_osd", &prefs->enable_osd);
|
||||
load_bool_pref(keyfile, "enable_chapters", &prefs->enable_chapters);
|
||||
load_bool_pref(keyfile, "enable_volume_boost", &prefs->enable_volume_boost);
|
||||
|
||||
char *screenshot_dir = g_key_file_get_string(keyfile, "General", "screenshot_directory", &error);
|
||||
if (!error && screenshot_dir) {
|
||||
g_free(prefs->screenshot_directory);
|
||||
@ -721,26 +738,6 @@ void preferences_load(Preferences *prefs)
|
||||
g_clear_error(&error);
|
||||
g_free(screenshot_dir);
|
||||
}
|
||||
|
||||
gboolean auto_resize = g_key_file_get_boolean(keyfile, "General", "auto_resize", &error);
|
||||
if (!error) prefs->auto_resize = auto_resize;
|
||||
else { g_clear_error(&error); }
|
||||
|
||||
gboolean hide_cursor = g_key_file_get_boolean(keyfile, "General", "hide_cursor_windowed", &error);
|
||||
if (!error) prefs->hide_cursor_windowed = hide_cursor;
|
||||
else { g_clear_error(&error); }
|
||||
|
||||
gboolean remember_dir = g_key_file_get_boolean(keyfile, "General", "remember_last_dir", &error);
|
||||
if (!error) prefs->remember_last_dir = remember_dir;
|
||||
else { g_clear_error(&error); }
|
||||
|
||||
gboolean use_mpris = g_key_file_get_boolean(keyfile, "General", "use_mpris", &error);
|
||||
if (!error) prefs->use_mpris = use_mpris;
|
||||
else { g_clear_error(&error); }
|
||||
|
||||
gboolean open_new = g_key_file_get_boolean(keyfile, "General", "open_in_new_window", &error);
|
||||
if (!error) prefs->open_in_new_window = open_new;
|
||||
else { g_clear_error(&error); }
|
||||
|
||||
char *last_dir = g_key_file_get_string(keyfile, "General", "last_dir", &error);
|
||||
if (!error && last_dir) {
|
||||
@ -751,18 +748,6 @@ void preferences_load(Preferences *prefs)
|
||||
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);
|
||||
@ -799,13 +784,27 @@ void preferences_save(Preferences *prefs)
|
||||
GKeyFile *keyfile = g_key_file_new();
|
||||
|
||||
g_key_file_set_double(keyfile, "General", "volume", prefs->default_volume);
|
||||
g_key_file_set_boolean(keyfile, "General", "remember_position", prefs->remember_position);
|
||||
g_key_file_set_boolean(keyfile, "General", "show_playlist_v3", prefs->show_playlist);
|
||||
g_key_file_set_boolean(keyfile, "General", "auto_resize", prefs->auto_resize);
|
||||
g_key_file_set_boolean(keyfile, "General", "hide_cursor_windowed", prefs->hide_cursor_windowed);
|
||||
g_key_file_set_boolean(keyfile, "General", "remember_last_dir", prefs->remember_last_dir);
|
||||
g_key_file_set_boolean(keyfile, "General", "use_mpris", prefs->use_mpris);
|
||||
g_key_file_set_boolean(keyfile, "General", "open_in_new_window", prefs->open_in_new_window);
|
||||
|
||||
struct { const char *key; gboolean val; } bool_prefs[] = {
|
||||
{"remember_position", prefs->remember_position},
|
||||
{"show_playlist_v3", prefs->show_playlist},
|
||||
{"auto_resize", prefs->auto_resize},
|
||||
{"hide_cursor_windowed", prefs->hide_cursor_windowed},
|
||||
{"remember_last_dir", prefs->remember_last_dir},
|
||||
{"use_mpris", prefs->use_mpris},
|
||||
{"open_in_new_window", prefs->open_in_new_window},
|
||||
{"seekbar_direct_jump", prefs->seekbar_direct_jump},
|
||||
{"hide_focus_rect", prefs->hide_focus_rect},
|
||||
{"enable_osd", prefs->enable_osd},
|
||||
{"enable_chapters", prefs->enable_chapters},
|
||||
{"enable_volume_boost", prefs->enable_volume_boost},
|
||||
{NULL, FALSE}
|
||||
};
|
||||
|
||||
for (int i = 0; bool_prefs[i].key != NULL; i++) {
|
||||
g_key_file_set_boolean(keyfile, "General", bool_prefs[i].key, bool_prefs[i].val);
|
||||
}
|
||||
|
||||
if (prefs->last_dir) {
|
||||
g_key_file_set_string(keyfile, "General", "last_dir", prefs->last_dir);
|
||||
}
|
||||
@ -819,9 +818,6 @@ void preferences_save(Preferences *prefs)
|
||||
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;
|
||||
char *data = g_key_file_to_data(keyfile, &length, NULL);
|
||||
@ -869,6 +865,13 @@ static void on_speed_preset_clicked(GtkButton *button, gpointer data)
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), speed_int / 100.0);
|
||||
}
|
||||
|
||||
static GtkWidget* create_pref_check(GtkWidget *box, const char *label, gboolean is_active) {
|
||||
GtkWidget *check = gtk_check_button_new_with_label(label);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), is_active);
|
||||
gtk_box_pack_start(GTK_BOX(box), check, FALSE, FALSE, 0);
|
||||
return check;
|
||||
}
|
||||
|
||||
gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginManager *pm)
|
||||
{
|
||||
if (!prefs) return FALSE;
|
||||
@ -893,33 +896,14 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM
|
||||
GtkWidget *interface_vbox = gtk_vbox_new(FALSE, 10);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(interface_vbox), 10);
|
||||
|
||||
GtkWidget *playlist_check = gtk_check_button_new_with_label("Show playlist by default");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(playlist_check), prefs->show_playlist);
|
||||
gtk_box_pack_start(GTK_BOX(interface_vbox), playlist_check, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *resize_check = gtk_check_button_new_with_label("Automatically resize window to fit video");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(resize_check), prefs->auto_resize);
|
||||
gtk_box_pack_start(GTK_BOX(interface_vbox), resize_check, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *cursor_check = gtk_check_button_new_with_label("Automatically hide mouse cursor in windowed mode");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cursor_check), prefs->hide_cursor_windowed);
|
||||
gtk_box_pack_start(GTK_BOX(interface_vbox), cursor_check, FALSE, FALSE, 0);
|
||||
|
||||
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_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);
|
||||
GtkWidget *playlist_check = create_pref_check(interface_vbox, "Show playlist by default", prefs->show_playlist);
|
||||
GtkWidget *resize_check = create_pref_check(interface_vbox, "Automatically resize window to fit video", prefs->auto_resize);
|
||||
GtkWidget *cursor_check = create_pref_check(interface_vbox, "Automatically hide mouse cursor in windowed mode", prefs->hide_cursor_windowed);
|
||||
GtkWidget *open_new_check = create_pref_check(interface_vbox, "Always open in a new window (Single-Instance off)", prefs->open_in_new_window);
|
||||
GtkWidget *jump_check = create_pref_check(interface_vbox, "Seek bar: Jump to clicked position immediately", prefs->seekbar_direct_jump);
|
||||
GtkWidget *focus_check = create_pref_check(interface_vbox, "Hide focus rectangles (dotted lines) around UI elements", prefs->hide_focus_rect);
|
||||
GtkWidget *osd_check = create_pref_check(interface_vbox, "Enable On-Screen Display (OSD)", prefs->enable_osd);
|
||||
GtkWidget *chapters_check = create_pref_check(interface_vbox, "Enable Chapters (markers, snapping, and menu)", prefs->enable_chapters);
|
||||
|
||||
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), interface_vbox, gtk_label_new("Interface"));
|
||||
|
||||
@ -962,17 +946,10 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM
|
||||
gtk_box_pack_start(GTK_BOX(vol_hbox), vol_spin, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(misc_vbox), vol_hbox, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *position_check = gtk_check_button_new_with_label("Remember playback position");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(position_check), prefs->remember_position);
|
||||
gtk_box_pack_start(GTK_BOX(misc_vbox), position_check, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *dir_check = gtk_check_button_new_with_label("Remember last folder in file chooser");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dir_check), prefs->remember_last_dir);
|
||||
gtk_box_pack_start(GTK_BOX(misc_vbox), dir_check, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *mpris_check = gtk_check_button_new_with_label("Enable MPRIS support (D-Bus)");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mpris_check), prefs->use_mpris);
|
||||
gtk_box_pack_start(GTK_BOX(misc_vbox), mpris_check, FALSE, FALSE, 0);
|
||||
GtkWidget *vol_boost_check = create_pref_check(misc_vbox, "Enable volume boost (up to 150%)", prefs->enable_volume_boost);
|
||||
GtkWidget *position_check = create_pref_check(misc_vbox, "Remember playback position", prefs->remember_position);
|
||||
GtkWidget *dir_check = create_pref_check(misc_vbox, "Remember last folder in file chooser", prefs->remember_last_dir);
|
||||
GtkWidget *mpris_check = create_pref_check(misc_vbox, "Enable MPRIS support (D-Bus)", prefs->use_mpris);
|
||||
|
||||
/* Screenshot directory */
|
||||
GtkWidget *ss_hbox = gtk_hbox_new(FALSE, 10);
|
||||
@ -1018,6 +995,8 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM
|
||||
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));
|
||||
prefs->enable_chapters = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chapters_check));
|
||||
prefs->enable_volume_boost = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vol_boost_check));
|
||||
|
||||
g_free(prefs->screenshot_directory);
|
||||
prefs->screenshot_directory = g_strdup(gtk_entry_get_text(GTK_ENTRY(ss_entry)));
|
||||
|
||||
@ -21,6 +21,8 @@ typedef struct {
|
||||
gboolean seekbar_direct_jump;
|
||||
gboolean hide_focus_rect;
|
||||
gboolean enable_osd;
|
||||
gboolean enable_chapters;
|
||||
gboolean enable_volume_boost;
|
||||
char *last_dir;
|
||||
char *screenshot_directory;
|
||||
char *screenshot_format;
|
||||
|
||||
@ -99,7 +99,7 @@ int main(int argc, char *argv[])
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) {
|
||||
printf("Media Player v0.7.1\n");
|
||||
printf("Media Player v9.0\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ p_mpv_set_wakeup_callback mpv_set_wakeup_callback = 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 mpv_set_property = NULL;
|
||||
p_mpv_get_property mpv_get_property = NULL;
|
||||
p_mpv_free mpv_free = NULL;
|
||||
p_mpv_set_property_string mpv_set_property_string = NULL;
|
||||
@ -65,6 +66,7 @@ int load_mpv_library(void)
|
||||
LOAD_SYM(command_async);
|
||||
LOAD_SYM(command);
|
||||
LOAD_SYM(set_property_async);
|
||||
LOAD_SYM(set_property);
|
||||
LOAD_SYM(get_property);
|
||||
LOAD_SYM(free);
|
||||
LOAD_SYM(set_property_string);
|
||||
|
||||
@ -75,6 +75,7 @@ typedef int (*p_mpv_set_option_string)(mpv_handle *ctx, const char *name, const
|
||||
typedef void (*p_mpv_set_wakeup_callback)(mpv_handle *ctx, void (*cb)(void *d), void *d);
|
||||
typedef int (*p_mpv_command_async)(mpv_handle *ctx, uint64_t reply_userdata, const char **args);
|
||||
typedef int (*p_mpv_set_property_async)(mpv_handle *ctx, uint64_t reply_userdata, const char *name, mpv_format format, void *data);
|
||||
typedef int (*p_mpv_set_property)(mpv_handle *ctx, const char *name, mpv_format format, void *data);
|
||||
typedef int (*p_mpv_get_property)(mpv_handle *ctx, const char *name, mpv_format format, void *data);
|
||||
typedef void (*p_mpv_free)(void *data);
|
||||
typedef int (*p_mpv_set_property_string)(mpv_handle *ctx, const char *name, const char *data);
|
||||
@ -94,6 +95,7 @@ extern p_mpv_set_wakeup_callback mpv_set_wakeup_callback;
|
||||
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 mpv_set_property;
|
||||
extern p_mpv_get_property mpv_get_property;
|
||||
extern p_mpv_free mpv_free;
|
||||
extern p_mpv_set_property_string mpv_set_property_string;
|
||||
|
||||
97
src/player.c
97
src/player.c
@ -62,6 +62,12 @@ void player_set_window(Player *player, unsigned long wid)
|
||||
player->wid = wid;
|
||||
}
|
||||
|
||||
void player_set_volume_boost(Player *player, gboolean enable)
|
||||
{
|
||||
if (!player || !player->mpv) return;
|
||||
mpv_set_option_string(player->mpv, "volume-max", enable ? "150" : "100");
|
||||
}
|
||||
|
||||
void player_set_config_path(Player *player, const char *path)
|
||||
{
|
||||
if (!player) return;
|
||||
@ -227,7 +233,7 @@ void player_seek(Player *player, double seconds)
|
||||
|
||||
char sec_str[32];
|
||||
snprintf(sec_str, sizeof(sec_str), "%f", seconds);
|
||||
const char *cmd[] = {"seek", sec_str, "relative", NULL};
|
||||
const char *cmd[] = {"seek", sec_str, "relative+exact", NULL};
|
||||
check_error(mpv_command_async(player->mpv, 0, cmd));
|
||||
|
||||
/* Show progress OSD */
|
||||
@ -725,6 +731,95 @@ void player_free_track_list(char **list, int count)
|
||||
g_free(list);
|
||||
}
|
||||
|
||||
/* Chapters */
|
||||
|
||||
int player_get_chapter_count(Player *player)
|
||||
{
|
||||
if (!player || !player->mpv) return 0;
|
||||
|
||||
int64_t count = 0;
|
||||
mpv_get_property(player->mpv, "chapters", MPV_FORMAT_INT64, &count);
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
int player_get_current_chapter(Player *player)
|
||||
{
|
||||
if (!player || !player->mpv) return -1;
|
||||
|
||||
int64_t chapter = -1;
|
||||
mpv_get_property(player->mpv, "chapter", MPV_FORMAT_INT64, &chapter);
|
||||
return (int)chapter;
|
||||
}
|
||||
|
||||
void player_set_chapter(Player *player, int index)
|
||||
{
|
||||
if (!player || !player->mpv) return;
|
||||
|
||||
int64_t idx = index;
|
||||
check_error(mpv_set_property(player->mpv, "chapter", MPV_FORMAT_INT64, &idx));
|
||||
}
|
||||
|
||||
char** player_get_chapter_list(Player *player, int *count)
|
||||
{
|
||||
if (!player || !player->mpv || !count) return NULL;
|
||||
|
||||
int total = player_get_chapter_count(player);
|
||||
*count = total;
|
||||
if (total <= 0) return NULL;
|
||||
|
||||
char **list = g_new0(char*, total);
|
||||
for (int i = 0; i < total; i++) {
|
||||
char prop[64];
|
||||
snprintf(prop, sizeof(prop), "chapter-list/%d/title", i);
|
||||
char *title = NULL;
|
||||
mpv_get_property(player->mpv, prop, MPV_FORMAT_STRING, &title);
|
||||
|
||||
if (title && strlen(title) > 0) {
|
||||
list[i] = g_strdup_printf("%d: %s", i + 1, title);
|
||||
} else {
|
||||
list[i] = g_strdup_printf("Chapter %d", i + 1);
|
||||
}
|
||||
|
||||
if (title) mpv_free(title);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
double* player_get_chapter_times(Player *player, int *count)
|
||||
{
|
||||
if (!player || !player->mpv || !count) return NULL;
|
||||
|
||||
int total = player_get_chapter_count(player);
|
||||
*count = total;
|
||||
if (total <= 0) return NULL;
|
||||
|
||||
double *times = g_new0(double, total);
|
||||
for (int i = 0; i < total; i++) {
|
||||
char prop[64];
|
||||
snprintf(prop, sizeof(prop), "chapter-list/%d/time", i);
|
||||
double time = 0;
|
||||
mpv_get_property(player->mpv, prop, MPV_FORMAT_DOUBLE, &time);
|
||||
times[i] = time;
|
||||
}
|
||||
|
||||
return times;
|
||||
}
|
||||
|
||||
void player_next_chapter(Player *player)
|
||||
{
|
||||
if (!player || !player->mpv) return;
|
||||
const char *cmd[] = {"add", "chapter", "1", NULL};
|
||||
check_error(mpv_command_async(player->mpv, 0, cmd));
|
||||
}
|
||||
|
||||
void player_prev_chapter(Player *player)
|
||||
{
|
||||
if (!player || !player->mpv) return;
|
||||
const char *cmd[] = {"add", "chapter", "-1", NULL};
|
||||
check_error(mpv_command_async(player->mpv, 0, cmd));
|
||||
}
|
||||
|
||||
/* Playlist */
|
||||
|
||||
int player_get_playlist_count(Player *player)
|
||||
|
||||
10
src/player.h
10
src/player.h
@ -47,6 +47,7 @@ gboolean player_get_shuffle(Player *player);
|
||||
gboolean player_get_loop(Player *player);
|
||||
|
||||
/* Property setters */
|
||||
void player_set_volume_boost(Player *player, gboolean enable);
|
||||
void player_set_volume(Player *player, double volume);
|
||||
void player_set_muted(Player *player, gboolean muted);
|
||||
void player_set_speed(Player *player, double speed);
|
||||
@ -66,6 +67,15 @@ void player_get_video_resolution(Player *player, int *w, int *h);
|
||||
char** player_get_subtitle_track_list(Player *player, int *count);
|
||||
void player_free_track_list(char **list, int count);
|
||||
|
||||
/* Chapters */
|
||||
int player_get_chapter_count(Player *player);
|
||||
int player_get_current_chapter(Player *player);
|
||||
void player_set_chapter(Player *player, int index);
|
||||
char** player_get_chapter_list(Player *player, int *count);
|
||||
double* player_get_chapter_times(Player *player, int *count);
|
||||
void player_next_chapter(Player *player);
|
||||
void player_prev_chapter(Player *player);
|
||||
|
||||
/* Playlist (internal mpv playlist) */
|
||||
int player_get_playlist_count(Player *player);
|
||||
int player_get_playlist_pos(Player *player);
|
||||
|
||||
299
src/ui.c
299
src/ui.c
@ -23,6 +23,8 @@ 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 update_chapter_menu(AppUI *ui);
|
||||
static void on_chapter_selected(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);
|
||||
@ -42,6 +44,7 @@ enum {
|
||||
PROP_IDLE_ACTIVE,
|
||||
PROP_SHUFFLE,
|
||||
PROP_LOOP,
|
||||
PROP_CHAPTER,
|
||||
};
|
||||
|
||||
struct AppUI {
|
||||
@ -86,7 +89,9 @@ struct AppUI {
|
||||
GtkWidget *subtitle_menu;
|
||||
GtkWidget *speed_menu;
|
||||
GtkWidget *aspect_menu;
|
||||
GtkWidget *chapter_menu;
|
||||
GtkWidget *recent_menu;
|
||||
GtkWidget *mute_item;
|
||||
|
||||
GtkRecentManager *recent_manager;
|
||||
|
||||
@ -144,6 +149,7 @@ static void on_view_always_on_top(GtkMenuItem *item, gpointer data);
|
||||
static void on_view_screenshot(GtkMenuItem *item, gpointer data);
|
||||
static void on_view_preferences(GtkMenuItem *item, gpointer data);
|
||||
static void on_aspect_ratio_selected(GtkMenuItem *item, gpointer data);
|
||||
static void on_audio_mute(GtkMenuItem *item, gpointer data);
|
||||
static void on_help_about(GtkMenuItem *item, gpointer data);
|
||||
|
||||
/* Plugin API wrapper forward declarations */
|
||||
@ -295,6 +301,9 @@ AppUI* ui_new(void)
|
||||
gtk_rc_parse_string("style \"no-focus-rect\" { GtkWidget::focus-line-width = 0 GtkWidget::focus-padding = 0 } widget \"*\" style \"no-focus-rect\"");
|
||||
}
|
||||
|
||||
/* Observe chapter changes */
|
||||
player_observe_property(ui->player, "chapter", MPV_FORMAT_INT64, PROP_CHAPTER);
|
||||
|
||||
ui->is_fullscreen = FALSE;
|
||||
ui->always_on_top = FALSE;
|
||||
ui->auto_hide_timer_id = 0;
|
||||
@ -355,6 +364,7 @@ void ui_set_preferences(AppUI *ui, Preferences *prefs)
|
||||
|
||||
/* Apply some immediate settings */
|
||||
if (ui->player) {
|
||||
player_set_volume_boost(ui->player, ui->prefs->enable_volume_boost);
|
||||
player_set_volume(ui->player, ui->prefs->default_volume);
|
||||
|
||||
if (ui->prefs->screenshot_directory) {
|
||||
@ -362,6 +372,10 @@ void ui_set_preferences(AppUI *ui, Preferences *prefs)
|
||||
}
|
||||
}
|
||||
|
||||
if (ui->controls) {
|
||||
controls_update_volume_boost(ui->controls, ui->prefs->enable_volume_boost);
|
||||
}
|
||||
|
||||
/* Update playlist visibility if UI is already realized */
|
||||
if (ui->playlist) {
|
||||
GtkWidget *playlist_widget = playlist_get_widget(ui->playlist);
|
||||
@ -578,13 +592,14 @@ void ui_toggle_fullscreen(AppUI *ui)
|
||||
gtk_widget_size_request(controls_widget, &req);
|
||||
int ctrl_h = req.height;
|
||||
|
||||
/* For fullscreen, we should use screen size */
|
||||
/* For fullscreen, we should use the specific monitor size */
|
||||
GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(ui->window));
|
||||
int sw = gdk_screen_get_width(screen);
|
||||
int sh = gdk_screen_get_height(screen);
|
||||
gint monitor_num = gdk_screen_get_monitor_at_window(screen, ui->window->window);
|
||||
GdkRectangle geom;
|
||||
gdk_screen_get_monitor_geometry(screen, monitor_num, &geom);
|
||||
|
||||
gtk_window_set_default_size(GTK_WINDOW(ui->fs_window), sw, ctrl_h);
|
||||
gtk_window_move(GTK_WINDOW(ui->fs_window), 0, sh - ctrl_h);
|
||||
gtk_window_set_default_size(GTK_WINDOW(ui->fs_window), geom.width, ctrl_h);
|
||||
gtk_window_move(GTK_WINDOW(ui->fs_window), geom.x, geom.y + geom.height - ctrl_h);
|
||||
|
||||
gtk_widget_show_all(ui->fs_window);
|
||||
gtk_window_fullscreen(GTK_WINDOW(ui->window));
|
||||
@ -753,6 +768,11 @@ static void on_player_event(Player *player, mpv_event *event, gpointer user_data
|
||||
if (prop->format == MPV_FORMAT_FLAG) {
|
||||
int muted = *(int *)prop->data;
|
||||
controls_update_mute_state(ui->controls, muted);
|
||||
if (ui->mute_item) {
|
||||
g_signal_handlers_block_by_func(ui->mute_item, G_CALLBACK(on_audio_mute), ui);
|
||||
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ui->mute_item), muted);
|
||||
g_signal_handlers_unblock_by_func(ui->mute_item, G_CALLBACK(on_audio_mute), ui);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -789,6 +809,11 @@ static void on_player_event(Player *player, mpv_event *event, gpointer user_data
|
||||
case PROP_LOOP:
|
||||
mpris_update_loop_shuffle(ui);
|
||||
break;
|
||||
|
||||
case PROP_CHAPTER:
|
||||
/* Just refresh the menu to update radio selection */
|
||||
update_chapter_menu(ui);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -808,6 +833,7 @@ static void on_player_event(Player *player, mpv_event *event, gpointer user_data
|
||||
/* Update audio/subtitle menus */
|
||||
void update_track_menus(AppUI *ui);
|
||||
update_track_menus(ui);
|
||||
update_chapter_menu(ui);
|
||||
|
||||
mpris_update_metadata(ui);
|
||||
mpris_update_playback_status(ui);
|
||||
@ -933,6 +959,14 @@ static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer dat
|
||||
on_playback_play_pause(NULL, ui);
|
||||
return TRUE;
|
||||
|
||||
case GDK_Page_Up:
|
||||
player_prev_chapter(ui->player);
|
||||
return TRUE;
|
||||
|
||||
case GDK_Page_Down:
|
||||
player_next_chapter(ui->player);
|
||||
return TRUE;
|
||||
|
||||
case GDK_Left:
|
||||
player_seek(ui->player, -5);
|
||||
return TRUE;
|
||||
@ -1149,7 +1183,7 @@ void update_track_menus(AppUI *ui)
|
||||
char **audio_tracks = player_get_audio_track_list(ui->player, &audio_count);
|
||||
|
||||
if (audio_count == 0) {
|
||||
GtkWidget *item = gtk_menu_item_new_with_label("(No audio tracks)");
|
||||
GtkWidget *item = gtk_menu_item_new_with_label("None available");
|
||||
gtk_widget_set_sensitive(item, FALSE);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->audio_menu), item);
|
||||
} else {
|
||||
@ -1164,25 +1198,21 @@ void update_track_menus(AppUI *ui)
|
||||
gtk_widget_show_all(ui->audio_menu);
|
||||
|
||||
/* Update Subtitle Menu */
|
||||
/* We keep the first few items: Disable, Separator, Load External */
|
||||
/* This clear_menu is a bit too aggressive for subtitle menu if we want to keep Load External */
|
||||
/* Let's manually clear only the track items */
|
||||
GList *children = gtk_container_get_children(GTK_CONTAINER(ui->subtitle_menu));
|
||||
GList *iter;
|
||||
int count = 0;
|
||||
for (iter = children; iter != NULL; iter = iter->next) {
|
||||
if (count > 2) { /* Skip Disable, Separator, Load External */
|
||||
gtk_widget_destroy(GTK_WIDGET(iter->data));
|
||||
}
|
||||
count++;
|
||||
}
|
||||
g_list_free(children);
|
||||
clear_menu(ui->subtitle_menu);
|
||||
|
||||
int sub_count = 0;
|
||||
char **sub_tracks = player_get_subtitle_track_list(ui->player, &sub_count);
|
||||
|
||||
if (sub_count > 0) {
|
||||
if (sub_count == 0) {
|
||||
GtkWidget *item = gtk_menu_item_new_with_label("None available");
|
||||
gtk_widget_set_sensitive(item, FALSE);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), item);
|
||||
} else {
|
||||
GtkWidget *sub_disable_item = gtk_menu_item_new_with_mnemonic("_Disable");
|
||||
g_signal_connect(sub_disable_item, "activate", G_CALLBACK(on_subtitle_disable), ui);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), sub_disable_item);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), gtk_separator_menu_item_new());
|
||||
|
||||
for (int i = 0; i < sub_count; i++) {
|
||||
GtkWidget *item = gtk_menu_item_new_with_label(sub_tracks[i]);
|
||||
g_object_set_data(G_OBJECT(item), "track", GINT_TO_POINTER(i + 1));
|
||||
@ -1215,6 +1245,13 @@ static void create_menubar(AppUI *ui)
|
||||
g_signal_connect(open_dir_item, "activate", G_CALLBACK(on_file_open_directory), ui);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), open_dir_item);
|
||||
|
||||
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);
|
||||
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), gtk_separator_menu_item_new());
|
||||
|
||||
ui->recent_menu = gtk_recent_chooser_menu_new_for_manager(ui->recent_manager);
|
||||
GtkRecentFilter *filter = gtk_recent_filter_new();
|
||||
gtk_recent_filter_add_mime_type(filter, "video/*");
|
||||
@ -1224,18 +1261,13 @@ static void create_menubar(AppUI *ui)
|
||||
gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(ui->recent_menu), GTK_RECENT_SORT_MRU);
|
||||
g_signal_connect(ui->recent_menu, "item-activated", G_CALLBACK(on_recent_item_activated), ui);
|
||||
|
||||
GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic("Recent _Files");
|
||||
GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic("_Recent Files");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), ui->recent_menu);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), recent_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), gtk_separator_menu_item_new());
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
GtkWidget *save_playlist_item = gtk_menu_item_new_with_mnemonic("Save _Playlist...");
|
||||
GtkWidget *save_playlist_item = gtk_menu_item_new_with_mnemonic("_Save Playlist...");
|
||||
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);
|
||||
|
||||
@ -1244,7 +1276,6 @@ static void create_menubar(AppUI *ui)
|
||||
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);
|
||||
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);
|
||||
|
||||
@ -1270,35 +1301,42 @@ static void create_menubar(AppUI *ui)
|
||||
|
||||
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_widget_add_accelerator(prev_item, "activate", accel_group, GDK_Page_Up, GDK_CONTROL_MASK, 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_widget_add_accelerator(next_item, "activate", accel_group, GDK_Page_Down, GDK_CONTROL_MASK, 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");
|
||||
/* Seek Submenu */
|
||||
GtkWidget *seek_menu = gtk_menu_new();
|
||||
GtkWidget *seek_item = gtk_menu_item_new_with_mnemonic("_Seek");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(seek_item), seek_menu);
|
||||
|
||||
GtkWidget *seek_fwd_item = gtk_menu_item_new_with_mnemonic("Forward 10s");
|
||||
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(seek_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("Backward 10s");
|
||||
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(seek_menu), seek_back_item);
|
||||
|
||||
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);
|
||||
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(seek_menu), frame_step_item);
|
||||
|
||||
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);
|
||||
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(seek_menu), goto_time_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), seek_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(playback_menu), gtk_separator_menu_item_new());
|
||||
|
||||
@ -1329,32 +1367,91 @@ static void create_menubar(AppUI *ui)
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), playback_item);
|
||||
|
||||
/* Audio Menu */
|
||||
ui->audio_menu = gtk_menu_new();
|
||||
GtkWidget *audio_root_menu = gtk_menu_new();
|
||||
GtkWidget *audio_item = gtk_menu_item_new_with_mnemonic("_Audio");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(audio_item), ui->audio_menu);
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(audio_item), audio_root_menu);
|
||||
|
||||
GtkWidget *audio_placeholder = gtk_menu_item_new_with_label("(No tracks available)");
|
||||
GtkWidget *audio_tracks_item = gtk_menu_item_new_with_mnemonic("_Select Track");
|
||||
ui->audio_menu = gtk_menu_new();
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(audio_tracks_item), ui->audio_menu);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(audio_root_menu), audio_tracks_item);
|
||||
|
||||
GtkWidget *audio_placeholder = gtk_menu_item_new_with_label("None available");
|
||||
gtk_widget_set_sensitive(audio_placeholder, FALSE);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->audio_menu), audio_placeholder);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(audio_root_menu), gtk_separator_menu_item_new());
|
||||
|
||||
ui->mute_item = gtk_check_menu_item_new_with_mnemonic("_Muted");
|
||||
g_signal_connect(ui->mute_item, "toggled", G_CALLBACK(on_audio_mute), ui);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(audio_root_menu), ui->mute_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), audio_item);
|
||||
|
||||
/* Subtitles Menu */
|
||||
ui->subtitle_menu = gtk_menu_new();
|
||||
/* Video Menu */
|
||||
GtkWidget *video_menu = gtk_menu_new();
|
||||
GtkWidget *video_item = gtk_menu_item_new_with_mnemonic("_Video");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(video_item), video_menu);
|
||||
|
||||
/* Subtitles Submenu */
|
||||
GtkWidget *subtitle_root_menu = gtk_menu_new();
|
||||
GtkWidget *subtitle_item = gtk_menu_item_new_with_mnemonic("_Subtitles");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(subtitle_item), ui->subtitle_menu);
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(subtitle_item), subtitle_root_menu);
|
||||
|
||||
GtkWidget *sub_disable_item = gtk_menu_item_new_with_mnemonic("_Disable");
|
||||
g_signal_connect(sub_disable_item, "activate", G_CALLBACK(on_subtitle_disable), ui);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), sub_disable_item);
|
||||
GtkWidget *sub_tracks_item = gtk_menu_item_new_with_mnemonic("_Select Track");
|
||||
ui->subtitle_menu = gtk_menu_new();
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(sub_tracks_item), ui->subtitle_menu);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(subtitle_root_menu), sub_tracks_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), gtk_separator_menu_item_new());
|
||||
GtkWidget *sub_placeholder = gtk_menu_item_new_with_label("None available");
|
||||
gtk_widget_set_sensitive(sub_placeholder, FALSE);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), sub_placeholder);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(subtitle_root_menu), gtk_separator_menu_item_new());
|
||||
|
||||
GtkWidget *sub_load_item = gtk_menu_item_new_with_mnemonic("_Load External...");
|
||||
g_signal_connect(sub_load_item, "activate", G_CALLBACK(on_subtitle_load), ui);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), sub_load_item);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(subtitle_root_menu), sub_load_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), subtitle_item);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(video_menu), subtitle_item);
|
||||
|
||||
/* Chapters Submenu */
|
||||
ui->chapter_menu = gtk_menu_new();
|
||||
GtkWidget *chapter_item = gtk_menu_item_new_with_mnemonic("_Chapters");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(chapter_item), ui->chapter_menu);
|
||||
|
||||
GtkWidget *chapter_placeholder = gtk_menu_item_new_with_label("(No chapters)");
|
||||
gtk_widget_set_sensitive(chapter_placeholder, FALSE);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->chapter_menu), chapter_placeholder);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(video_menu), chapter_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(video_menu), gtk_separator_menu_item_new());
|
||||
|
||||
/* Aspect ratio submenu */
|
||||
ui->aspect_menu = gtk_menu_new();
|
||||
GtkWidget *aspect_item = gtk_menu_item_new_with_mnemonic("_Aspect Ratio");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(aspect_item), ui->aspect_menu);
|
||||
|
||||
const char *aspects[] = {"Auto", "4:3", "16:9", "16:10", "1.85:1", "2.35:1", NULL};
|
||||
const char *aspect_vals[] = {"-1", "4:3", "16:9", "16:10", "1.85:1", "2.35:1", NULL};
|
||||
|
||||
for (int i = 0; aspects[i] != NULL; i++) {
|
||||
GtkWidget *ar_item = gtk_menu_item_new_with_label(aspects[i]);
|
||||
g_object_set_data(G_OBJECT(ar_item), "aspect", (gpointer)aspect_vals[i]);
|
||||
g_signal_connect(ar_item, "activate", G_CALLBACK(on_aspect_ratio_selected), ui);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->aspect_menu), ar_item);
|
||||
}
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(video_menu), aspect_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(video_menu), gtk_separator_menu_item_new());
|
||||
|
||||
GtkWidget *screenshot_item = gtk_menu_item_new_with_mnemonic("_Screenshot");
|
||||
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(video_menu), screenshot_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), video_item);
|
||||
|
||||
/* View Menu */
|
||||
GtkWidget *view_menu = gtk_menu_new();
|
||||
@ -1374,53 +1471,35 @@ static void create_menubar(AppUI *ui)
|
||||
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");
|
||||
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(on_top_item), ui->always_on_top);
|
||||
g_signal_connect(on_top_item, "toggled", G_CALLBACK(on_view_always_on_top), ui);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), on_top_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), gtk_separator_menu_item_new());
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), view_item);
|
||||
|
||||
/* Aspect ratio submenu */
|
||||
ui->aspect_menu = gtk_menu_new();
|
||||
GtkWidget *aspect_item = gtk_menu_item_new_with_mnemonic("_Aspect Ratio");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(aspect_item), ui->aspect_menu);
|
||||
/* Tools Menu */
|
||||
GtkWidget *tools_menu = gtk_menu_new();
|
||||
GtkWidget *tools_item = gtk_menu_item_new_with_mnemonic("_Tools");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(tools_item), tools_menu);
|
||||
|
||||
const char *aspects[] = {"Auto", "4:3", "16:9", "16:10", "1.85:1", "2.35:1", NULL};
|
||||
const char *aspect_vals[] = {"-1", "4:3", "16:9", "16:10", "1.85:1", "2.35:1", NULL};
|
||||
GtkWidget *plugins_menu = plugin_manager_get_menu(ui->plugin_manager);
|
||||
GtkWidget *plugins_item = gtk_menu_item_new_with_mnemonic("_Plugins");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(plugins_item), plugins_menu);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(tools_menu), plugins_item);
|
||||
|
||||
for (int i = 0; aspects[i] != NULL; i++) {
|
||||
GtkWidget *ar_item = gtk_menu_item_new_with_label(aspects[i]);
|
||||
g_object_set_data(G_OBJECT(ar_item), "aspect", (gpointer)aspect_vals[i]);
|
||||
g_signal_connect(ar_item, "activate", G_CALLBACK(on_aspect_ratio_selected), ui);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->aspect_menu), ar_item);
|
||||
}
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), aspect_item);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), gtk_separator_menu_item_new());
|
||||
|
||||
GtkWidget *screenshot_item = gtk_menu_item_new_with_mnemonic("_Screenshot");
|
||||
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), gtk_separator_menu_item_new());
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(tools_menu), gtk_separator_menu_item_new());
|
||||
|
||||
GtkWidget *prefs_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL);
|
||||
g_signal_connect(prefs_item, "activate", G_CALLBACK(on_view_preferences), ui);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), prefs_item);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(tools_menu), prefs_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);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), tools_item);
|
||||
|
||||
/* Help Menu */
|
||||
GtkWidget *help_menu = gtk_menu_new();
|
||||
GtkWidget *help_item = gtk_menu_item_new_with_mnemonic("_Help");
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(help_item), help_menu);
|
||||
gtk_menu_item_set_right_justified(GTK_MENU_ITEM(help_item), FALSE);
|
||||
|
||||
GtkWidget *about_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL);
|
||||
g_signal_connect(about_item, "activate", G_CALLBACK(on_help_about), ui);
|
||||
@ -1431,6 +1510,12 @@ static void create_menubar(AppUI *ui)
|
||||
|
||||
/* Menu callbacks */
|
||||
|
||||
static void on_audio_mute(GtkMenuItem *item, gpointer data)
|
||||
{
|
||||
AppUI *ui = (AppUI *)data;
|
||||
player_set_muted(ui->player, gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
|
||||
}
|
||||
|
||||
static void on_file_open(GtkMenuItem *item, gpointer data)
|
||||
{
|
||||
(void)item;
|
||||
@ -1530,6 +1615,58 @@ static void on_playback_next(GtkMenuItem *item, gpointer data)
|
||||
playlist_play_next(ui->playlist);
|
||||
}
|
||||
|
||||
static void on_chapter_selected(GtkMenuItem *item, gpointer data)
|
||||
{
|
||||
AppUI *ui = (AppUI *)data;
|
||||
int index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "chapter-index"));
|
||||
player_set_chapter(ui->player, index);
|
||||
}
|
||||
|
||||
static void update_chapter_menu(AppUI *ui)
|
||||
{
|
||||
if (!ui || !ui->chapter_menu) return;
|
||||
if (ui->prefs && !ui->prefs->enable_chapters) return;
|
||||
|
||||
/* Clear old menu */
|
||||
GList *children = gtk_container_get_children(GTK_CONTAINER(ui->chapter_menu));
|
||||
for (GList *iter = children; iter != NULL; iter = iter->next) {
|
||||
gtk_widget_destroy(GTK_WIDGET(iter->data));
|
||||
}
|
||||
g_list_free(children);
|
||||
|
||||
int count = 0;
|
||||
char **chapters = player_get_chapter_list(ui->player, &count);
|
||||
double *times = player_get_chapter_times(ui->player, &count);
|
||||
|
||||
/* Sync markers to controls */
|
||||
controls_set_chapters(ui->controls, count, times);
|
||||
if (times) g_free(times);
|
||||
|
||||
if (count > 0 && chapters) {
|
||||
int current = player_get_current_chapter(ui->player);
|
||||
for (int i = 0; i < count; i++) {
|
||||
GtkWidget *item = gtk_image_menu_item_new_with_label(chapters[i]);
|
||||
|
||||
if (i == current) {
|
||||
GtkWidget *img = gtk_image_new_from_stock(GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_MENU);
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
|
||||
gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
|
||||
}
|
||||
|
||||
g_object_set_data(G_OBJECT(item), "chapter-index", GINT_TO_POINTER(i));
|
||||
g_signal_connect(item, "activate", G_CALLBACK(on_chapter_selected), ui);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->chapter_menu), item);
|
||||
}
|
||||
player_free_track_list(chapters, count);
|
||||
} else {
|
||||
GtkWidget *placeholder = gtk_menu_item_new_with_label("(No chapters)");
|
||||
gtk_widget_set_sensitive(placeholder, FALSE);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->chapter_menu), placeholder);
|
||||
}
|
||||
|
||||
gtk_widget_show_all(ui->chapter_menu);
|
||||
}
|
||||
|
||||
static void on_playback_seek_forward(GtkMenuItem *item, gpointer data)
|
||||
{
|
||||
(void)item;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user