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_deb.sh
|
||||||
make_release.sh
|
make_release.sh
|
||||||
gtk2-media-player-v0.7.1-linux.zip
|
gtk2-media-player-v0.7.1-linux.zip
|
||||||
|
GTK2-template
|
||||||
@ -8,7 +8,8 @@ if [ "$EUID" -ne 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
INSTALL_DIR="/usr/local/lib/gtk2-mpv-player"
|
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"
|
DESKTOP_FILE="/usr/share/applications/gtk2-mpv-player.desktop"
|
||||||
|
|
||||||
echo "Installing to $INSTALL_DIR..."
|
echo "Installing to $INSTALL_DIR..."
|
||||||
@ -24,14 +25,15 @@ install -m 644 assets/icon.png "$INSTALL_DIR/"
|
|||||||
rm -rf "$INSTALL_DIR/lib"
|
rm -rf "$INSTALL_DIR/lib"
|
||||||
cp -r lib "$INSTALL_DIR/"
|
cp -r lib "$INSTALL_DIR/"
|
||||||
|
|
||||||
# Create wrapper script
|
# Create wrapper scripts
|
||||||
cat <<EOF > "$BIN_LINK"
|
cat <<EOF > "$BIN_LINK"
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd "$INSTALL_DIR"
|
cd "$INSTALL_DIR"
|
||||||
./gtk2-mpv-player "\$@"
|
./gtk2-mpv-player "\$@"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod +x "$BIN_LINK"
|
cp "$BIN_LINK" "$BIN_LINK_LOCAL"
|
||||||
|
chmod +x "$BIN_LINK" "$BIN_LINK_LOCAL"
|
||||||
|
|
||||||
# Install desktop file
|
# Install desktop file
|
||||||
cp assets/gtk2-mpv-player.desktop "$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 <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
struct Controls {
|
struct Controls {
|
||||||
Player *player;
|
Player *player;
|
||||||
@ -42,18 +43,26 @@ struct Controls {
|
|||||||
/* Icons (stock icons) */
|
/* Icons (stock icons) */
|
||||||
GtkWidget *play_image;
|
GtkWidget *play_image;
|
||||||
GtkWidget *pause_image;
|
GtkWidget *pause_image;
|
||||||
|
|
||||||
|
/* Chapters for snapping/markers */
|
||||||
|
double *chapter_times;
|
||||||
|
int chapter_count;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Forward declarations */
|
/* Forward declarations */
|
||||||
static void on_play_pause_clicked(GtkButton *button, gpointer data);
|
static void on_play_pause_clicked(GtkButton *button, gpointer data);
|
||||||
static void on_stop_clicked(GtkButton *button, gpointer data);
|
static void on_stop_clicked(GtkButton *button, gpointer data);
|
||||||
static void on_prev_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_next_clicked(GtkButton *button, gpointer data);
|
||||||
static void on_mute_clicked(GtkButton *button, gpointer data);
|
static void on_mute_clicked(GtkButton *button, gpointer data);
|
||||||
static void on_fullscreen_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);
|
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_press(GtkWidget *widget, GdkEventButton *event, gpointer data);
|
||||||
static gboolean on_seek_button_release(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 on_volume_changed(GtkRange *range, gpointer data);
|
||||||
|
|
||||||
static void format_time(double seconds, char *buffer, size_t size)
|
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 */
|
/* Main horizontal box */
|
||||||
controls->hbox = gtk_hbox_new(FALSE, 5);
|
controls->hbox = gtk_hbox_new(FALSE, 5);
|
||||||
gtk_container_set_border_width(GTK_CONTAINER(controls->hbox), 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);
|
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 */
|
/* Previous button */
|
||||||
controls->btn_prev = gtk_button_new();
|
controls->btn_prev = gtk_button_new();
|
||||||
gtk_button_set_image(GTK_BUTTON(controls->btn_prev),
|
gtk_button_set_image(GTK_BUTTON(controls->btn_prev),
|
||||||
gtk_image_new_from_stock(GTK_STOCK_MEDIA_PREVIOUS, GTK_ICON_SIZE_BUTTON));
|
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");
|
gtk_widget_set_tooltip_text(controls->btn_prev, "Previous");
|
||||||
g_signal_connect(controls->btn_prev, "clicked", G_CALLBACK(on_prev_clicked), controls);
|
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 */
|
/* Play/Pause button */
|
||||||
controls->btn_play_pause = gtk_button_new();
|
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->play_image);
|
||||||
g_object_ref(controls->pause_image);
|
g_object_ref(controls->pause_image);
|
||||||
gtk_button_set_image(GTK_BUTTON(controls->btn_play_pause), controls->play_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");
|
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);
|
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 */
|
/* Stop button */
|
||||||
controls->btn_stop = gtk_button_new();
|
controls->btn_stop = gtk_button_new();
|
||||||
gtk_button_set_image(GTK_BUTTON(controls->btn_stop),
|
gtk_button_set_image(GTK_BUTTON(controls->btn_stop),
|
||||||
gtk_image_new_from_stock(GTK_STOCK_MEDIA_STOP, GTK_ICON_SIZE_BUTTON));
|
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");
|
gtk_widget_set_tooltip_text(controls->btn_stop, "Stop");
|
||||||
g_signal_connect(controls->btn_stop, "clicked", G_CALLBACK(on_stop_clicked), controls);
|
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 */
|
/* Next button */
|
||||||
controls->btn_next = gtk_button_new();
|
controls->btn_next = gtk_button_new();
|
||||||
gtk_button_set_image(GTK_BUTTON(controls->btn_next),
|
gtk_button_set_image(GTK_BUTTON(controls->btn_next),
|
||||||
gtk_image_new_from_stock(GTK_STOCK_MEDIA_NEXT, GTK_ICON_SIZE_BUTTON));
|
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");
|
gtk_widget_set_tooltip_text(controls->btn_next, "Next");
|
||||||
g_signal_connect(controls->btn_next, "clicked", G_CALLBACK(on_next_clicked), controls);
|
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);
|
gtk_box_pack_start(GTK_BOX(controls->hbox), gtk_vseparator_new(), FALSE, FALSE, 5);
|
||||||
|
|
||||||
/* Seek slider */
|
/* 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, "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-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, "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);
|
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 */
|
/* Time label */
|
||||||
controls->time_label = gtk_label_new("0:00 / 0:00");
|
controls->time_label = gtk_label_new("0:00 / 0:00");
|
||||||
gtk_misc_set_alignment(GTK_MISC(controls->time_label), 0.5, 0.5);
|
gtk_misc_set_alignment(GTK_MISC(controls->time_label), 0.5, 0.5);
|
||||||
gtk_widget_set_size_request(controls->time_label, 85, -1);
|
/* 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_box_pack_start(GTK_BOX(right_box), controls->time_label, FALSE, FALSE, 2);
|
||||||
|
|
||||||
/* Separator */
|
/* 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) */
|
/* Volume button (mute toggle) */
|
||||||
controls->btn_mute = gtk_button_new();
|
controls->btn_mute = gtk_button_new();
|
||||||
GtkWidget *mute_img = gtk_image_new_from_icon_name("audio-volume-high", GTK_ICON_SIZE_BUTTON);
|
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_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");
|
gtk_widget_set_tooltip_text(controls->btn_mute, "Mute/Unmute");
|
||||||
g_signal_connect(controls->btn_mute, "clicked", G_CALLBACK(on_mute_clicked), controls);
|
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 */
|
/* 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);
|
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_range_set_value(GTK_RANGE(controls->volume_scale), 100);
|
||||||
gtk_widget_set_size_request(controls->volume_scale, 80, -1);
|
gtk_widget_set_size_request(controls->volume_scale, 80, -1);
|
||||||
gtk_widget_set_tooltip_text(controls->volume_scale, "Volume");
|
gtk_widget_set_tooltip_text(controls->volume_scale, "Volume");
|
||||||
g_signal_connect(controls->volume_scale, "value-changed", G_CALLBACK(on_volume_changed), controls);
|
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 */
|
/* 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 */
|
/* Fullscreen button */
|
||||||
controls->btn_fullscreen = gtk_button_new();
|
controls->btn_fullscreen = gtk_button_new();
|
||||||
gtk_button_set_image(GTK_BUTTON(controls->btn_fullscreen),
|
gtk_button_set_image(GTK_BUTTON(controls->btn_fullscreen),
|
||||||
gtk_image_new_from_stock(GTK_STOCK_FULLSCREEN, GTK_ICON_SIZE_BUTTON));
|
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");
|
gtk_widget_set_tooltip_text(controls->btn_fullscreen, "Fullscreen");
|
||||||
g_signal_connect(controls->btn_fullscreen, "clicked", G_CALLBACK(on_fullscreen_clicked), controls);
|
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);
|
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->play_image) g_object_unref(controls->play_image);
|
||||||
if (controls->pause_image) g_object_unref(controls->pause_image);
|
if (controls->pause_image) g_object_unref(controls->pause_image);
|
||||||
|
if (controls->chapter_times) g_free(controls->chapter_times);
|
||||||
g_free(controls);
|
g_free(controls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +255,11 @@ void controls_update_duration(Controls *controls, double duration)
|
|||||||
|
|
||||||
/* Update slider range (still use 0-100 percent) */
|
/* Update slider range (still use 0-100 percent) */
|
||||||
gtk_range_set_range(GTK_RANGE(controls->seek_scale), 0, 100);
|
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)
|
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)
|
void controls_reset(Controls *controls)
|
||||||
{
|
{
|
||||||
if (!controls) return;
|
if (!controls) return;
|
||||||
|
controls->duration = 0;
|
||||||
controls->duration = 0.0;
|
|
||||||
gtk_range_set_value(GTK_RANGE(controls->seek_scale), 0);
|
gtk_range_set_value(GTK_RANGE(controls->seek_scale), 0);
|
||||||
gtk_label_set_text(GTK_LABEL(controls->time_label), "0:00 / 0:00");
|
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 */
|
/* Show play icon */
|
||||||
gtk_button_set_image(GTK_BUTTON(controls->btn_play_pause), controls->play_image);
|
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)
|
void controls_set_seeking(Controls *controls, gboolean seeking)
|
||||||
{
|
{
|
||||||
if (!controls) return;
|
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)
|
static gboolean on_seek_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data)
|
||||||
{
|
{
|
||||||
Controls *controls = (Controls *)data;
|
Controls *controls = (Controls *)data;
|
||||||
|
gtk_widget_grab_focus(widget);
|
||||||
controls->seeking = TRUE;
|
controls->seeking = TRUE;
|
||||||
|
|
||||||
if (event->button == 1) {
|
if (event->button == 1) {
|
||||||
@ -378,12 +468,19 @@ static gboolean on_seek_button_press(GtkWidget *widget, GdkEventButton *event, g
|
|||||||
GtkRange *range = GTK_RANGE(widget);
|
GtkRange *range = GTK_RANGE(widget);
|
||||||
GtkAdjustment *adj = gtk_range_get_adjustment(range);
|
GtkAdjustment *adj = gtk_range_get_adjustment(range);
|
||||||
|
|
||||||
/* Calculate relative position (0.0 to 1.0) based on click x coordinate */
|
/* Calculate relative position (0.0 to 1.0) using widget-relative coordinates */
|
||||||
double relative = event->x / (double)widget->allocation.width;
|
gint x;
|
||||||
|
gtk_widget_get_pointer(widget, &x, NULL);
|
||||||
|
|
||||||
|
double relative = x / (double)widget->allocation.width;
|
||||||
if (relative < 0) relative = 0;
|
if (relative < 0) relative = 0;
|
||||||
if (relative > 1) relative = 1;
|
if (relative > 1) relative = 1;
|
||||||
|
|
||||||
double new_val = adj->lower + relative * (adj->upper - adj->lower);
|
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);
|
gtk_range_set_value(range, new_val);
|
||||||
|
|
||||||
/* Perform seek immediately */
|
/* 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;
|
double position = (new_val / 100.0) * controls->duration;
|
||||||
player_seek_absolute(controls->player, position);
|
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;
|
return FALSE;
|
||||||
@ -407,9 +506,89 @@ static gboolean on_seek_button_release(GtkWidget *widget, GdkEventButton *event,
|
|||||||
return FALSE;
|
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)
|
static void on_volume_changed(GtkRange *range, gpointer data)
|
||||||
{
|
{
|
||||||
Controls *controls = (Controls *)data;
|
Controls *controls = (Controls *)data;
|
||||||
double volume = gtk_range_get_value(range);
|
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);
|
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_duration(Controls *controls, double duration);
|
||||||
void controls_update_pause_state(Controls *controls, gboolean paused);
|
void controls_update_pause_state(Controls *controls, gboolean paused);
|
||||||
void controls_update_volume(Controls *controls, double volume);
|
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_mute_state(Controls *controls, gboolean muted);
|
||||||
void controls_update_title(Controls *controls, const char *title);
|
void controls_update_title(Controls *controls, const char *title);
|
||||||
|
void controls_set_chapters(Controls *controls, int count, double *times);
|
||||||
void controls_reset(Controls *controls);
|
void controls_reset(Controls *controls);
|
||||||
|
|
||||||
/* Seek slider interaction (to prevent feedback loop) */
|
/* Seek slider interaction (to prevent feedback loop) */
|
||||||
|
|||||||
155
src/dialogs.c
155
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_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 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);
|
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.");
|
||||||
@ -672,6 +672,8 @@ Preferences* preferences_new(void)
|
|||||||
prefs->seekbar_direct_jump = TRUE; /* Default to TRUE as requested earlier */
|
prefs->seekbar_direct_jump = TRUE; /* Default to TRUE as requested earlier */
|
||||||
prefs->hide_focus_rect = FALSE;
|
prefs->hide_focus_rect = FALSE;
|
||||||
prefs->enable_osd = TRUE;
|
prefs->enable_osd = TRUE;
|
||||||
|
prefs->enable_chapters = TRUE;
|
||||||
|
prefs->enable_volume_boost = FALSE;
|
||||||
prefs->last_dir = NULL;
|
prefs->last_dir = NULL;
|
||||||
prefs->screenshot_directory = g_strdup(g_get_home_dir());
|
prefs->screenshot_directory = g_strdup(g_get_home_dir());
|
||||||
prefs->screenshot_format = g_strdup("png");
|
prefs->screenshot_format = g_strdup("png");
|
||||||
@ -691,6 +693,16 @@ void preferences_free(Preferences *prefs)
|
|||||||
g_free(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)
|
void preferences_load(Preferences *prefs)
|
||||||
{
|
{
|
||||||
if (!prefs) return;
|
if (!prefs) return;
|
||||||
@ -705,13 +717,18 @@ void preferences_load(Preferences *prefs)
|
|||||||
if (!error) prefs->default_volume = volume;
|
if (!error) prefs->default_volume = volume;
|
||||||
else { g_clear_error(&error); }
|
else { g_clear_error(&error); }
|
||||||
|
|
||||||
gboolean remember = g_key_file_get_boolean(keyfile, "General", "remember_position", &error);
|
load_bool_pref(keyfile, "remember_position", &prefs->remember_position);
|
||||||
if (!error) prefs->remember_position = remember;
|
load_bool_pref(keyfile, "show_playlist_v3", &prefs->show_playlist);
|
||||||
else { g_clear_error(&error); }
|
load_bool_pref(keyfile, "auto_resize", &prefs->auto_resize);
|
||||||
|
load_bool_pref(keyfile, "hide_cursor_windowed", &prefs->hide_cursor_windowed);
|
||||||
gboolean playlist = g_key_file_get_boolean(keyfile, "General", "show_playlist_v3", &error);
|
load_bool_pref(keyfile, "remember_last_dir", &prefs->remember_last_dir);
|
||||||
if (!error) prefs->show_playlist = playlist;
|
load_bool_pref(keyfile, "use_mpris", &prefs->use_mpris);
|
||||||
else { g_clear_error(&error); }
|
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);
|
char *screenshot_dir = g_key_file_get_string(keyfile, "General", "screenshot_directory", &error);
|
||||||
if (!error && screenshot_dir) {
|
if (!error && screenshot_dir) {
|
||||||
@ -722,26 +739,6 @@ void preferences_load(Preferences *prefs)
|
|||||||
g_free(screenshot_dir);
|
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);
|
char *last_dir = g_key_file_get_string(keyfile, "General", "last_dir", &error);
|
||||||
if (!error && last_dir) {
|
if (!error && last_dir) {
|
||||||
g_free(prefs->last_dir);
|
g_free(prefs->last_dir);
|
||||||
@ -751,18 +748,6 @@ void preferences_load(Preferences *prefs)
|
|||||||
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);
|
char *ss_format = g_key_file_get_string(keyfile, "General", "screenshot_format", &error);
|
||||||
if (!error && ss_format) {
|
if (!error && ss_format) {
|
||||||
g_free(prefs->screenshot_format);
|
g_free(prefs->screenshot_format);
|
||||||
@ -799,13 +784,27 @@ void preferences_save(Preferences *prefs)
|
|||||||
GKeyFile *keyfile = g_key_file_new();
|
GKeyFile *keyfile = g_key_file_new();
|
||||||
|
|
||||||
g_key_file_set_double(keyfile, "General", "volume", prefs->default_volume);
|
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);
|
struct { const char *key; gboolean val; } bool_prefs[] = {
|
||||||
g_key_file_set_boolean(keyfile, "General", "auto_resize", prefs->auto_resize);
|
{"remember_position", prefs->remember_position},
|
||||||
g_key_file_set_boolean(keyfile, "General", "hide_cursor_windowed", prefs->hide_cursor_windowed);
|
{"show_playlist_v3", prefs->show_playlist},
|
||||||
g_key_file_set_boolean(keyfile, "General", "remember_last_dir", prefs->remember_last_dir);
|
{"auto_resize", prefs->auto_resize},
|
||||||
g_key_file_set_boolean(keyfile, "General", "use_mpris", prefs->use_mpris);
|
{"hide_cursor_windowed", prefs->hide_cursor_windowed},
|
||||||
g_key_file_set_boolean(keyfile, "General", "open_in_new_window", prefs->open_in_new_window);
|
{"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) {
|
if (prefs->last_dir) {
|
||||||
g_key_file_set_string(keyfile, "General", "last_dir", 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) {
|
if (prefs->mpv_config_path) {
|
||||||
g_key_file_set_string(keyfile, "General", "mpv_config_path", prefs->mpv_config_path);
|
g_key_file_set_string(keyfile, "General", "mpv_config_path", prefs->mpv_config_path);
|
||||||
}
|
}
|
||||||
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);
|
||||||
@ -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);
|
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)
|
gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginManager *pm)
|
||||||
{
|
{
|
||||||
if (!prefs) return FALSE;
|
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);
|
GtkWidget *interface_vbox = gtk_vbox_new(FALSE, 10);
|
||||||
gtk_container_set_border_width(GTK_CONTAINER(interface_vbox), 10);
|
gtk_container_set_border_width(GTK_CONTAINER(interface_vbox), 10);
|
||||||
|
|
||||||
GtkWidget *playlist_check = gtk_check_button_new_with_label("Show playlist by default");
|
GtkWidget *playlist_check = create_pref_check(interface_vbox, "Show playlist by default", prefs->show_playlist);
|
||||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(playlist_check), prefs->show_playlist);
|
GtkWidget *resize_check = create_pref_check(interface_vbox, "Automatically resize window to fit video", prefs->auto_resize);
|
||||||
gtk_box_pack_start(GTK_BOX(interface_vbox), playlist_check, FALSE, FALSE, 0);
|
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 *resize_check = gtk_check_button_new_with_label("Automatically resize window to fit video");
|
GtkWidget *jump_check = create_pref_check(interface_vbox, "Seek bar: Jump to clicked position immediately", prefs->seekbar_direct_jump);
|
||||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(resize_check), prefs->auto_resize);
|
GtkWidget *focus_check = create_pref_check(interface_vbox, "Hide focus rectangles (dotted lines) around UI elements", prefs->hide_focus_rect);
|
||||||
gtk_box_pack_start(GTK_BOX(interface_vbox), resize_check, FALSE, FALSE, 0);
|
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);
|
||||||
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);
|
|
||||||
|
|
||||||
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"));
|
||||||
|
|
||||||
@ -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(vol_hbox), vol_spin, FALSE, FALSE, 0);
|
||||||
gtk_box_pack_start(GTK_BOX(misc_vbox), vol_hbox, FALSE, FALSE, 0);
|
gtk_box_pack_start(GTK_BOX(misc_vbox), vol_hbox, FALSE, FALSE, 0);
|
||||||
|
|
||||||
GtkWidget *position_check = gtk_check_button_new_with_label("Remember playback position");
|
GtkWidget *vol_boost_check = create_pref_check(misc_vbox, "Enable volume boost (up to 150%)", prefs->enable_volume_boost);
|
||||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(position_check), prefs->remember_position);
|
GtkWidget *position_check = create_pref_check(misc_vbox, "Remember playback position", prefs->remember_position);
|
||||||
gtk_box_pack_start(GTK_BOX(misc_vbox), position_check, FALSE, FALSE, 0);
|
GtkWidget *dir_check = create_pref_check(misc_vbox, "Remember last folder in file chooser", prefs->remember_last_dir);
|
||||||
|
GtkWidget *mpris_check = create_pref_check(misc_vbox, "Enable MPRIS support (D-Bus)", prefs->use_mpris);
|
||||||
GtkWidget *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);
|
|
||||||
|
|
||||||
/* Screenshot directory */
|
/* Screenshot directory */
|
||||||
GtkWidget *ss_hbox = gtk_hbox_new(FALSE, 10);
|
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->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->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_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);
|
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)));
|
||||||
|
|||||||
@ -21,6 +21,8 @@ typedef struct {
|
|||||||
gboolean seekbar_direct_jump;
|
gboolean seekbar_direct_jump;
|
||||||
gboolean hide_focus_rect;
|
gboolean hide_focus_rect;
|
||||||
gboolean enable_osd;
|
gboolean enable_osd;
|
||||||
|
gboolean enable_chapters;
|
||||||
|
gboolean enable_volume_boost;
|
||||||
char *last_dir;
|
char *last_dir;
|
||||||
char *screenshot_directory;
|
char *screenshot_directory;
|
||||||
char *screenshot_format;
|
char *screenshot_format;
|
||||||
|
|||||||
@ -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 v0.7.1\n");
|
printf("Media Player v9.0\n");
|
||||||
return 0;
|
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_async mpv_command_async = NULL;
|
||||||
p_mpv_command mpv_command = 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_set_property mpv_set_property = 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;
|
||||||
p_mpv_set_property_string mpv_set_property_string = 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_async);
|
||||||
LOAD_SYM(command);
|
LOAD_SYM(command);
|
||||||
LOAD_SYM(set_property_async);
|
LOAD_SYM(set_property_async);
|
||||||
|
LOAD_SYM(set_property);
|
||||||
LOAD_SYM(get_property);
|
LOAD_SYM(get_property);
|
||||||
LOAD_SYM(free);
|
LOAD_SYM(free);
|
||||||
LOAD_SYM(set_property_string);
|
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 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_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_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 int (*p_mpv_get_property)(mpv_handle *ctx, const char *name, mpv_format format, void *data);
|
||||||
typedef void (*p_mpv_free)(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);
|
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_async mpv_command_async;
|
||||||
extern p_mpv_command mpv_command;
|
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_set_property mpv_set_property;
|
||||||
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;
|
||||||
extern p_mpv_set_property_string mpv_set_property_string;
|
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;
|
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)
|
void player_set_config_path(Player *player, const char *path)
|
||||||
{
|
{
|
||||||
if (!player) return;
|
if (!player) return;
|
||||||
@ -227,7 +233,7 @@ void player_seek(Player *player, double seconds)
|
|||||||
|
|
||||||
char sec_str[32];
|
char sec_str[32];
|
||||||
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+exact", NULL};
|
||||||
check_error(mpv_command_async(player->mpv, 0, cmd));
|
check_error(mpv_command_async(player->mpv, 0, cmd));
|
||||||
|
|
||||||
/* Show progress OSD */
|
/* Show progress OSD */
|
||||||
@ -725,6 +731,95 @@ void player_free_track_list(char **list, int count)
|
|||||||
g_free(list);
|
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 */
|
/* Playlist */
|
||||||
|
|
||||||
int player_get_playlist_count(Player *player)
|
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);
|
gboolean player_get_loop(Player *player);
|
||||||
|
|
||||||
/* Property setters */
|
/* Property setters */
|
||||||
|
void player_set_volume_boost(Player *player, gboolean enable);
|
||||||
void player_set_volume(Player *player, double volume);
|
void player_set_volume(Player *player, double volume);
|
||||||
void player_set_muted(Player *player, gboolean muted);
|
void player_set_muted(Player *player, gboolean muted);
|
||||||
void player_set_speed(Player *player, double speed);
|
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);
|
char** player_get_subtitle_track_list(Player *player, int *count);
|
||||||
void player_free_track_list(char **list, 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) */
|
/* Playlist (internal mpv playlist) */
|
||||||
int player_get_playlist_count(Player *player);
|
int player_get_playlist_count(Player *player);
|
||||||
int player_get_playlist_pos(Player *player);
|
int player_get_playlist_pos(Player *player);
|
||||||
|
|||||||
297
src/ui.c
297
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_track_selected(GtkMenuItem *item, gpointer data);
|
||||||
static void on_subtitle_load(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_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_fullscreen(GtkMenuItem *item, gpointer data);
|
||||||
static void on_view_playlist(GtkMenuItem *item, gpointer data);
|
static void on_view_playlist(GtkMenuItem *item, gpointer data);
|
||||||
static void on_view_screenshot(GtkMenuItem *item, gpointer data);
|
static void on_view_screenshot(GtkMenuItem *item, gpointer data);
|
||||||
@ -42,6 +44,7 @@ enum {
|
|||||||
PROP_IDLE_ACTIVE,
|
PROP_IDLE_ACTIVE,
|
||||||
PROP_SHUFFLE,
|
PROP_SHUFFLE,
|
||||||
PROP_LOOP,
|
PROP_LOOP,
|
||||||
|
PROP_CHAPTER,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AppUI {
|
struct AppUI {
|
||||||
@ -86,7 +89,9 @@ struct AppUI {
|
|||||||
GtkWidget *subtitle_menu;
|
GtkWidget *subtitle_menu;
|
||||||
GtkWidget *speed_menu;
|
GtkWidget *speed_menu;
|
||||||
GtkWidget *aspect_menu;
|
GtkWidget *aspect_menu;
|
||||||
|
GtkWidget *chapter_menu;
|
||||||
GtkWidget *recent_menu;
|
GtkWidget *recent_menu;
|
||||||
|
GtkWidget *mute_item;
|
||||||
|
|
||||||
GtkRecentManager *recent_manager;
|
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_screenshot(GtkMenuItem *item, gpointer data);
|
||||||
static void on_view_preferences(GtkMenuItem *item, gpointer data);
|
static void on_view_preferences(GtkMenuItem *item, gpointer data);
|
||||||
static void on_aspect_ratio_selected(GtkMenuItem *item, gpointer data);
|
static void on_aspect_ratio_selected(GtkMenuItem *item, gpointer data);
|
||||||
|
static void on_audio_mute(GtkMenuItem *item, gpointer data);
|
||||||
static void on_help_about(GtkMenuItem *item, gpointer data);
|
static void on_help_about(GtkMenuItem *item, gpointer data);
|
||||||
|
|
||||||
/* Plugin API wrapper forward declarations */
|
/* Plugin API wrapper forward declarations */
|
||||||
@ -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\"");
|
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->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;
|
||||||
@ -355,6 +364,7 @@ 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_boost(ui->player, ui->prefs->enable_volume_boost);
|
||||||
player_set_volume(ui->player, ui->prefs->default_volume);
|
player_set_volume(ui->player, ui->prefs->default_volume);
|
||||||
|
|
||||||
if (ui->prefs->screenshot_directory) {
|
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 */
|
/* Update playlist visibility if UI is already realized */
|
||||||
if (ui->playlist) {
|
if (ui->playlist) {
|
||||||
GtkWidget *playlist_widget = playlist_get_widget(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);
|
gtk_widget_size_request(controls_widget, &req);
|
||||||
int ctrl_h = req.height;
|
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));
|
GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(ui->window));
|
||||||
int sw = gdk_screen_get_width(screen);
|
gint monitor_num = gdk_screen_get_monitor_at_window(screen, ui->window->window);
|
||||||
int sh = gdk_screen_get_height(screen);
|
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_set_default_size(GTK_WINDOW(ui->fs_window), geom.width, ctrl_h);
|
||||||
gtk_window_move(GTK_WINDOW(ui->fs_window), 0, sh - 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_widget_show_all(ui->fs_window);
|
||||||
gtk_window_fullscreen(GTK_WINDOW(ui->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) {
|
if (prop->format == MPV_FORMAT_FLAG) {
|
||||||
int muted = *(int *)prop->data;
|
int muted = *(int *)prop->data;
|
||||||
controls_update_mute_state(ui->controls, muted);
|
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;
|
break;
|
||||||
|
|
||||||
@ -789,6 +809,11 @@ static void on_player_event(Player *player, mpv_event *event, gpointer user_data
|
|||||||
case PROP_LOOP:
|
case PROP_LOOP:
|
||||||
mpris_update_loop_shuffle(ui);
|
mpris_update_loop_shuffle(ui);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_CHAPTER:
|
||||||
|
/* Just refresh the menu to update radio selection */
|
||||||
|
update_chapter_menu(ui);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -808,6 +833,7 @@ static void on_player_event(Player *player, mpv_event *event, gpointer user_data
|
|||||||
/* Update audio/subtitle menus */
|
/* Update audio/subtitle menus */
|
||||||
void update_track_menus(AppUI *ui);
|
void update_track_menus(AppUI *ui);
|
||||||
update_track_menus(ui);
|
update_track_menus(ui);
|
||||||
|
update_chapter_menu(ui);
|
||||||
|
|
||||||
mpris_update_metadata(ui);
|
mpris_update_metadata(ui);
|
||||||
mpris_update_playback_status(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);
|
on_playback_play_pause(NULL, ui);
|
||||||
return TRUE;
|
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:
|
case GDK_Left:
|
||||||
player_seek(ui->player, -5);
|
player_seek(ui->player, -5);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@ -1149,7 +1183,7 @@ void update_track_menus(AppUI *ui)
|
|||||||
char **audio_tracks = player_get_audio_track_list(ui->player, &audio_count);
|
char **audio_tracks = player_get_audio_track_list(ui->player, &audio_count);
|
||||||
|
|
||||||
if (audio_count == 0) {
|
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_widget_set_sensitive(item, FALSE);
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->audio_menu), item);
|
gtk_menu_shell_append(GTK_MENU_SHELL(ui->audio_menu), item);
|
||||||
} else {
|
} else {
|
||||||
@ -1164,25 +1198,21 @@ void update_track_menus(AppUI *ui)
|
|||||||
gtk_widget_show_all(ui->audio_menu);
|
gtk_widget_show_all(ui->audio_menu);
|
||||||
|
|
||||||
/* Update Subtitle Menu */
|
/* Update Subtitle Menu */
|
||||||
/* We keep the first few items: Disable, Separator, Load External */
|
clear_menu(ui->subtitle_menu);
|
||||||
/* 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);
|
|
||||||
|
|
||||||
int sub_count = 0;
|
int sub_count = 0;
|
||||||
char **sub_tracks = player_get_subtitle_track_list(ui->player, &sub_count);
|
char **sub_tracks = player_get_subtitle_track_list(ui->player, &sub_count);
|
||||||
|
|
||||||
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());
|
gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), gtk_separator_menu_item_new());
|
||||||
|
|
||||||
for (int i = 0; i < sub_count; i++) {
|
for (int i = 0; i < sub_count; i++) {
|
||||||
GtkWidget *item = gtk_menu_item_new_with_label(sub_tracks[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));
|
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);
|
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);
|
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);
|
ui->recent_menu = gtk_recent_chooser_menu_new_for_manager(ui->recent_manager);
|
||||||
GtkRecentFilter *filter = gtk_recent_filter_new();
|
GtkRecentFilter *filter = gtk_recent_filter_new();
|
||||||
gtk_recent_filter_add_mime_type(filter, "video/*");
|
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);
|
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);
|
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_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), recent_item);
|
||||||
|
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), gtk_separator_menu_item_new());
|
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...");
|
GtkWidget *save_playlist_item = gtk_menu_item_new_with_mnemonic("_Save Playlist...");
|
||||||
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...");
|
|
||||||
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);
|
||||||
|
|
||||||
@ -1244,7 +1276,6 @@ 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);
|
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_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);
|
||||||
|
|
||||||
@ -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);
|
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);
|
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);
|
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);
|
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);
|
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), next_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 *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);
|
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_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);
|
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_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");
|
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_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...");
|
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_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());
|
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);
|
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), playback_item);
|
||||||
|
|
||||||
/* Audio Menu */
|
/* 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");
|
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_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(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);
|
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), audio_item);
|
||||||
|
|
||||||
/* Subtitles Menu */
|
/* Video Menu */
|
||||||
ui->subtitle_menu = gtk_menu_new();
|
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");
|
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");
|
GtkWidget *sub_tracks_item = gtk_menu_item_new_with_mnemonic("_Select Track");
|
||||||
g_signal_connect(sub_disable_item, "activate", G_CALLBACK(on_subtitle_disable), ui);
|
ui->subtitle_menu = gtk_menu_new();
|
||||||
gtk_menu_shell_append(GTK_MENU_SHELL(ui->subtitle_menu), sub_disable_item);
|
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...");
|
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);
|
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 */
|
/* View Menu */
|
||||||
GtkWidget *view_menu = gtk_menu_new();
|
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);
|
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");
|
||||||
|
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);
|
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), 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 */
|
/* Tools Menu */
|
||||||
ui->aspect_menu = gtk_menu_new();
|
GtkWidget *tools_menu = gtk_menu_new();
|
||||||
GtkWidget *aspect_item = gtk_menu_item_new_with_mnemonic("_Aspect Ratio");
|
GtkWidget *tools_item = gtk_menu_item_new_with_mnemonic("_Tools");
|
||||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(aspect_item), ui->aspect_menu);
|
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};
|
GtkWidget *plugins_menu = plugin_manager_get_menu(ui->plugin_manager);
|
||||||
const char *aspect_vals[] = {"-1", "4:3", "16:9", "16:10", "1.85:1", "2.35:1", NULL};
|
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++) {
|
gtk_menu_shell_append(GTK_MENU_SHELL(tools_menu), gtk_separator_menu_item_new());
|
||||||
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());
|
|
||||||
|
|
||||||
GtkWidget *prefs_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL);
|
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);
|
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);
|
gtk_menu_shell_append(GTK_MENU_SHELL(ui->menubar), tools_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");
|
||||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(help_item), help_menu);
|
gtk_menu_item_set_submenu(GTK_MENU_ITEM(help_item), help_menu);
|
||||||
|
gtk_menu_item_set_right_justified(GTK_MENU_ITEM(help_item), FALSE);
|
||||||
|
|
||||||
GtkWidget *about_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL);
|
GtkWidget *about_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL);
|
||||||
g_signal_connect(about_item, "activate", G_CALLBACK(on_help_about), ui);
|
g_signal_connect(about_item, "activate", G_CALLBACK(on_help_about), ui);
|
||||||
@ -1431,6 +1510,12 @@ static void create_menubar(AppUI *ui)
|
|||||||
|
|
||||||
/* Menu callbacks */
|
/* 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)
|
static void on_file_open(GtkMenuItem *item, gpointer data)
|
||||||
{
|
{
|
||||||
(void)item;
|
(void)item;
|
||||||
@ -1530,6 +1615,58 @@ static void on_playback_next(GtkMenuItem *item, gpointer data)
|
|||||||
playlist_play_next(ui->playlist);
|
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)
|
static void on_playback_seek_forward(GtkMenuItem *item, gpointer data)
|
||||||
{
|
{
|
||||||
(void)item;
|
(void)item;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user