Added new chapters/playlist sidebar, and YTDLP resolution toggle

This commit is contained in:
laki 2026-03-30 20:26:39 +01:00
parent ac19a6af1e
commit a3a83894e3
4 changed files with 212 additions and 22 deletions

BIN
Kino

Binary file not shown.

View File

@ -982,6 +982,8 @@ Preferences* preferences_new(void)
prefs->sub_shadow_opacity = 100;
prefs->sub_shadow_enabled = TRUE;
prefs->sub_bold = FALSE;
prefs->sub_italic = FALSE;
prefs->ytdl_max_resolution = 1080;
return prefs;
}
@ -1040,6 +1042,9 @@ void preferences_load(Preferences *prefs)
load_bool_pref(keyfile, "sub_border_enabled", &prefs->sub_border_enabled);
load_bool_pref(keyfile, "sub_shadow_enabled", &prefs->sub_shadow_enabled);
prefs->ytdl_max_resolution = g_key_file_get_integer(keyfile, "General", "ytdl_max_resolution", &error);
if (error) { prefs->ytdl_max_resolution = 1080; g_clear_error(&error); }
char *screenshot_dir = g_key_file_get_string(keyfile, "General", "screenshot_directory", &error);
if (!error && screenshot_dir) {
g_free(prefs->screenshot_directory);
@ -1158,9 +1163,11 @@ void preferences_save(Preferences *prefs)
};
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);
g_key_file_set_boolean(keyfile, (i < 13) ? "General" : "Subtitles", bool_prefs[i].key, bool_prefs[i].val);
}
g_key_file_set_integer(keyfile, "General", "ytdl_max_resolution", prefs->ytdl_max_resolution);
if (prefs->last_dir) {
g_key_file_set_string(keyfile, "General", "last_dir", prefs->last_dir);
}
@ -1473,6 +1480,23 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(format_combo), "webp");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(format_combo), "png");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(format_combo), "jpg");
gtk_box_pack_start(GTK_BOX(format_hbox), format_combo, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(misc_vbox), format_hbox, FALSE, FALSE, 0);
/* Resolution Row */
GtkWidget *res_hbox = gtk_hbox_new(FALSE, 10);
gtk_box_pack_start(GTK_BOX(res_hbox), gtk_label_new("Max Resolution (yt-dlp):"), FALSE, FALSE, 0);
GtkWidget *res_combo = gtk_combo_box_text_new();
const char *res_labels[] = {"Best", "2160p (4K)", "1440p (2K)", "1080p", "720p", "480p", "360p", NULL};
int res_values[] = {0, 2160, 1440, 1080, 720, 480, 360};
int active_idx = 0;
for (int i = 0; res_labels[i] != NULL; i++) {
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(res_combo), res_labels[i]);
if (prefs->ytdl_max_resolution == res_values[i]) active_idx = i;
}
gtk_combo_box_set_active(GTK_COMBO_BOX(res_combo), active_idx);
gtk_box_pack_start(GTK_BOX(res_hbox), res_combo, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(misc_vbox), res_hbox, FALSE, FALSE, 0);
if (g_strcmp0(prefs->screenshot_format, "webp") == 0) gtk_combo_box_set_active(GTK_COMBO_BOX(format_combo), 0);
else if (g_strcmp0(prefs->screenshot_format, "png") == 0) gtk_combo_box_set_active(GTK_COMBO_BOX(format_combo), 1);
@ -1552,6 +1576,10 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM
prefs->enable_volume_boost = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vol_boost_check));
prefs->mouse_wheel_seeks = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mouse_wheel_check));
int res_idx = gtk_combo_box_get_active(GTK_COMBO_BOX(res_combo));
int res_values[] = {0, 2160, 1440, 1080, 720, 480, 360};
if (res_idx >= 0 && res_idx < 7) prefs->ytdl_max_resolution = res_values[res_idx];
/* Subtitles */
prefs->sub_delay = gtk_spin_button_get_value(GTK_SPIN_BUTTON(delay_spin));
prefs->sub_pos = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(subpos_spin));

View File

@ -47,6 +47,7 @@ typedef struct {
gboolean sub_shadow_enabled;
gboolean sub_bold;
gboolean sub_italic;
int ytdl_max_resolution;
} Preferences;
Preferences* preferences_new(void);

203
src/ui.c
View File

@ -70,6 +70,11 @@ struct AppUI {
Controls *controls;
Playlist *playlist;
/* Side Panel */
GtkWidget *side_notebook;
GtkWidget *chapters_tree;
GtkListStore *chapters_store;
/* Player */
Player *player;
@ -158,9 +163,14 @@ static void on_view_screenshot(GtkMenuItem *item, gpointer data);
static void on_view_preferences(GtkMenuItem *item, gpointer data);
static void on_subtitle_settings_clicked(GtkMenuItem *item, gpointer data);
static void on_aspect_ratio_selected(GtkMenuItem *item, gpointer data);
static void ui_apply_ytdl_resolution(AppUI *ui);
static void on_ytdl_resolution_selected(GtkMenuItem *item, gpointer data);
static void on_audio_mute(GtkMenuItem *item, gpointer data);
static void on_help_keybinds(GtkMenuItem *item, gpointer data);
static void on_help_about(GtkMenuItem *item, gpointer data);
static void on_view_chapters(GtkMenuItem *item, gpointer data);
static void on_chapter_row_activated(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data);
static void on_side_panel_close_clicked(GtkButton *button, gpointer data);
/* Plugin API wrapper forward declarations */
static AppUI *g_current_ui;
@ -287,18 +297,57 @@ AppUI* ui_new(void)
target_entries, 1, GDK_ACTION_COPY);
g_signal_connect(ui->video_container, "drag-data-received", G_CALLBACK(on_drag_data_received), ui);
/* Side Panel Notebook */
ui->side_notebook = gtk_notebook_new();
gtk_notebook_set_tab_pos(GTK_NOTEBOOK(ui->side_notebook), GTK_POS_TOP);
GtkWidget *close_btn = gtk_button_new();
gtk_button_set_relief(GTK_BUTTON(close_btn), GTK_RELIEF_NONE);
GtkWidget *close_img = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
gtk_container_add(GTK_CONTAINER(close_btn), close_img);
g_signal_connect(close_btn, "clicked", G_CALLBACK(on_side_panel_close_clicked), ui);
gtk_widget_show_all(close_btn);
gtk_notebook_set_action_widget(GTK_NOTEBOOK(ui->side_notebook), close_btn, GTK_PACK_END);
gtk_widget_set_size_request(ui->side_notebook, 250, -1);
gtk_paned_pack2(GTK_PANED(ui->hpaned), ui->side_notebook, FALSE, TRUE);
/* Handle visibility manually */
gtk_widget_set_no_show_all(ui->side_notebook, TRUE);
/* Playlist */
ui->playlist = playlist_new(ui->player);
GtkWidget *playlist_widget = playlist_get_widget(ui->playlist);
gtk_widget_set_size_request(playlist_widget, 250, -1);
gtk_paned_pack2(GTK_PANED(ui->hpaned), playlist_widget, FALSE, TRUE);
GtkWidget *pl_label = gtk_label_new("Playlist");
gtk_widget_show(pl_label);
gtk_notebook_append_page(GTK_NOTEBOOK(ui->side_notebook), playlist_widget, pl_label);
gtk_widget_show_all(playlist_widget);
/* Handle visibility manually */
gtk_widget_set_no_show_all(playlist_widget, TRUE);
/* Chapters Tree */
ui->chapters_store = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
ui->chapters_tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(ui->chapters_store));
g_object_unref(ui->chapters_store);
GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes("Chapter", renderer, "text", 1, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(ui->chapters_tree), column);
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(ui->chapters_tree), FALSE);
GtkWidget *chapters_scroll = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(chapters_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_container_add(GTK_CONTAINER(chapters_scroll), ui->chapters_tree);
GtkWidget *ch_label = gtk_label_new("Chapters");
gtk_widget_show(ch_label);
gtk_notebook_append_page(GTK_NOTEBOOK(ui->side_notebook), chapters_scroll, ch_label);
gtk_widget_show_all(chapters_scroll);
g_signal_connect(ui->chapters_tree, "row-activated", G_CALLBACK(on_chapter_row_activated), ui);
ui->playlist_visible = ui->prefs->show_playlist;
if (ui->playlist_visible) {
gtk_widget_show(playlist_widget);
gtk_widget_show(ui->side_notebook);
}
/* Controls */
@ -322,6 +371,8 @@ AppUI* ui_new(void)
player_set_option_string(ui->player, "osd-level", ui->prefs->enable_osd ? "1" : "0");
player_set_option_string(ui->player, "osd-bar", ui->prefs->enable_osd ? "yes" : "no");
ui_apply_ytdl_resolution(ui);
if (ui->prefs->hide_focus_rect) {
gtk_rc_parse_string("style \"no-focus-rect\" { GtkWidget::focus-line-width = 0 GtkWidget::focus-padding = 0 } widget \"*\" style \"no-focus-rect\"");
}
@ -421,6 +472,20 @@ void ui_apply_subtitle_preferences(AppUI *ui)
player_set_subtitle_italic(ui->player, ui->prefs->sub_italic);
}
static void ui_apply_ytdl_resolution(AppUI *ui)
{
if (!ui || !ui->player || !ui->prefs) return;
if (ui->prefs->ytdl_max_resolution > 0) {
char ytdl_format[128];
snprintf(ytdl_format, sizeof(ytdl_format), "bestvideo[height<=%d]+bestaudio/best[height<=%d]",
ui->prefs->ytdl_max_resolution, ui->prefs->ytdl_max_resolution);
player_set_option_string(ui->player, "ytdl-format", ytdl_format);
} else {
player_set_option_string(ui->player, "ytdl-format", "bestvideo+bestaudio/best");
}
}
static void ui_apply_preferences_internal(gpointer data)
{
AppUI *ui = (AppUI *)data;
@ -449,8 +514,11 @@ static void ui_apply_preferences_internal(gpointer data)
if (window) gdk_window_set_cursor(window, NULL);
}
}
ui_apply_ytdl_resolution(ui);
}
void ui_set_preferences(AppUI *ui, Preferences *prefs)
{
if (!ui || !prefs) return;
@ -478,12 +546,13 @@ void ui_set_preferences(AppUI *ui, Preferences *prefs)
}
/* Update playlist visibility if UI is already realized */
if (ui->playlist) {
GtkWidget *playlist_widget = playlist_get_widget(ui->playlist);
if (ui->prefs->show_playlist) {
gtk_widget_show(playlist_widget);
if (ui->side_notebook) {
ui->playlist_visible = ui->prefs->show_playlist;
if (ui->playlist_visible) {
gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->side_notebook), 0);
gtk_widget_show(ui->side_notebook);
} else {
gtk_widget_hide(playlist_widget);
gtk_widget_hide(ui->side_notebook);
}
}
}
@ -675,7 +744,7 @@ void ui_toggle_fullscreen(AppUI *ui)
if (ui->is_fullscreen) {
gtk_widget_hide(ui->menubar);
if (ui->playlist_visible) {
gtk_widget_hide(playlist_get_widget(ui->playlist));
gtk_widget_hide(ui->side_notebook);
}
/* Create overlay window for controls */
@ -728,7 +797,7 @@ void ui_toggle_fullscreen(AppUI *ui)
gtk_widget_show(ui->menubar);
gtk_widget_show(controls_widget);
if (ui->playlist_visible) {
gtk_widget_show(playlist_get_widget(ui->playlist));
gtk_widget_show(ui->side_notebook);
}
/* Stop auto-hide timer unless windowed hiding is on */
@ -748,13 +817,29 @@ void ui_toggle_playlist(AppUI *ui)
{
if (!ui) return;
ui->playlist_visible = !ui->playlist_visible;
GtkWidget *playlist_widget = playlist_get_widget(ui->playlist);
if (ui->playlist_visible) {
gtk_widget_show(playlist_widget);
int current_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->side_notebook));
if (ui->playlist_visible && current_page == 0) {
ui->playlist_visible = FALSE;
gtk_widget_hide(ui->side_notebook);
} else {
gtk_widget_hide(playlist_widget);
ui->playlist_visible = TRUE;
gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->side_notebook), 0);
gtk_widget_show(ui->side_notebook);
}
}
void ui_toggle_chapters(AppUI *ui)
{
if (!ui) return;
int current_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->side_notebook));
if (ui->playlist_visible && current_page == 1) {
ui->playlist_visible = FALSE;
gtk_widget_hide(ui->side_notebook);
} else {
ui->playlist_visible = TRUE;
gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->side_notebook), 1);
gtk_widget_show(ui->side_notebook);
}
}
@ -1639,6 +1724,23 @@ static void create_menubar(AppUI *ui)
}
gtk_menu_shell_append(GTK_MENU_SHELL(video_menu), aspect_item);
/* yt-dlp resolution submenu */
GtkWidget *res_item = gtk_menu_item_new_with_mnemonic("_Stream Quality (yt-dlp)");
GtkWidget *res_menu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(res_item), res_menu);
const char *res_labels[] = {"Best", "2160p (4K)", "1440p (2K)", "1080p", "720p", "480p", "360p", NULL};
int res_vals[] = {0, 2160, 1440, 1080, 720, 480, 360};
for (int i = 0; res_labels[i] != NULL; i++) {
GtkWidget *ri = gtk_menu_item_new_with_label(res_labels[i]);
g_object_set_data(G_OBJECT(ri), "res", GINT_TO_POINTER(res_vals[i]));
g_signal_connect(ri, "activate", G_CALLBACK(on_ytdl_resolution_selected), ui);
gtk_menu_shell_append(GTK_MENU_SHELL(res_menu), ri);
}
gtk_menu_shell_append(GTK_MENU_SHELL(video_menu), res_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");
@ -1659,12 +1761,15 @@ static void create_menubar(AppUI *ui)
gtk_widget_add_accelerator(fullscreen_item, "activate", accel_group, GDK_F11, 0, GTK_ACCEL_VISIBLE);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), fullscreen_item);
GtkWidget *playlist_toggle_item = gtk_check_menu_item_new_with_mnemonic("_Playlist");
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(playlist_toggle_item), ui->prefs->show_playlist);
g_signal_connect(playlist_toggle_item, "toggled", G_CALLBACK(on_view_playlist), ui);
GtkWidget *playlist_toggle_item = gtk_menu_item_new_with_mnemonic("_Playlist");
g_signal_connect(playlist_toggle_item, "activate", G_CALLBACK(on_view_playlist), ui);
gtk_widget_add_accelerator(playlist_toggle_item, "activate", accel_group, GDK_p, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), playlist_toggle_item);
GtkWidget *chapters_toggle_item = gtk_menu_item_new_with_mnemonic("C_hapters");
g_signal_connect(chapters_toggle_item, "activate", G_CALLBACK(on_view_chapters), ui);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), chapters_toggle_item);
GtkWidget *on_top_item = gtk_check_menu_item_new_with_mnemonic("Always on _Top");
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(on_top_item), ui->always_on_top);
g_signal_connect(on_top_item, "toggled", G_CALLBACK(on_view_always_on_top), ui);
@ -1833,6 +1938,9 @@ static void update_chapter_menu(AppUI *ui)
}
g_list_free(children);
/* Clear Side panel tree */
gtk_list_store_clear(ui->chapters_store);
int count = 0;
char **chapters = player_get_chapter_list(ui->player, &count);
double *times = player_get_chapter_times(ui->player, &count);
@ -1855,12 +1963,20 @@ static void update_chapter_menu(AppUI *ui)
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);
GtkTreeIter iter;
gtk_list_store_append(ui->chapters_store, &iter);
gtk_list_store_set(ui->chapters_store, &iter, 0, i, 1, chapters[i], -1);
}
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);
GtkTreeIter iter;
gtk_list_store_append(ui->chapters_store, &iter);
gtk_list_store_set(ui->chapters_store, &iter, 0, -1, 1, "(No chapters)", -1);
}
gtk_widget_show_all(ui->chapter_menu);
@ -1976,6 +2092,13 @@ static void on_view_playlist(GtkMenuItem *item, gpointer data)
ui_toggle_playlist(ui);
}
static void on_view_chapters(GtkMenuItem *item, gpointer data)
{
(void)item;
AppUI *ui = (AppUI *)data;
ui_toggle_chapters(ui);
}
static void on_view_always_on_top(GtkMenuItem *item, gpointer data)
{
(void)item;
@ -2128,3 +2251,41 @@ void ui_show_script_manager(AppUI *ui)
dialogs_show_script_manager(GTK_WINDOW(ui->window), ui->player);
}
static void on_ytdl_resolution_selected(GtkMenuItem *item, gpointer data)
{
AppUI *ui = (AppUI *)data;
int res = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "res"));
if (ui->prefs->ytdl_max_resolution == res) return;
ui->prefs->ytdl_max_resolution = res;
preferences_save(ui->prefs);
ui_apply_ytdl_resolution(ui);
}
static void on_side_panel_close_clicked(GtkButton *button, gpointer data)
{
(void)button;
AppUI *ui = (AppUI *)data;
ui->playlist_visible = FALSE;
ui->prefs->show_playlist = FALSE;
gtk_widget_hide(ui->side_notebook);
preferences_save(ui->prefs);
}
static void on_chapter_row_activated(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data)
{
(void)tree_view;
(void)column;
AppUI *ui = (AppUI *)data;
if (!ui || !ui->player) return;
GtkTreeIter iter;
if (gtk_tree_model_get_iter(GTK_TREE_MODEL(ui->chapters_store), &iter, path)) {
int index = -1;
gtk_tree_model_get(GTK_TREE_MODEL(ui->chapters_store), &iter, 0, &index, -1);
if (index >= 0) {
player_set_chapter(ui->player, index);
}
}
}