Added keybinds menu. You can change keybinds at will and view then under Help -> Keyboard Shortcuts
This commit is contained in:
parent
06f793f4f4
commit
ac19a6af1e
3
Makefile
3
Makefile
@ -10,7 +10,8 @@ SRCS = $(SRC_DIR)/main.c \
|
|||||||
$(SRC_DIR)/dialogs.c \
|
$(SRC_DIR)/dialogs.c \
|
||||||
$(SRC_DIR)/mpv_loader.c \
|
$(SRC_DIR)/mpv_loader.c \
|
||||||
$(SRC_DIR)/mpris.c \
|
$(SRC_DIR)/mpris.c \
|
||||||
$(SRC_DIR)/plugin_manager.c
|
$(SRC_DIR)/plugin_manager.c \
|
||||||
|
$(SRC_DIR)/keybinds.c
|
||||||
|
|
||||||
OBJS = $(SRCS:.c=.o)
|
OBJS = $(SRCS:.c=.o)
|
||||||
TARGET = Kino
|
TARGET = Kino
|
||||||
|
|||||||
48
install.sh
Executable file
48
install.sh
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simple installation script for Kino
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Please run as root (use sudo)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
INSTALL_DIR="/usr/local/lib/Kino"
|
||||||
|
BIN_LINK="/usr/bin/kino"
|
||||||
|
BIN_LINK_LOCAL="/usr/local/bin/kino"
|
||||||
|
DESKTOP_FILE="/usr/share/applications/kino.desktop"
|
||||||
|
|
||||||
|
echo "Installing to $INSTALL_DIR..."
|
||||||
|
|
||||||
|
# Create directory
|
||||||
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
|
||||||
|
# Copy binary and metadata using 'install' to handle running processes correctly
|
||||||
|
install -m 755 Kino "$INSTALL_DIR/"
|
||||||
|
install -m 644 assets/icon.png "$INSTALL_DIR/"
|
||||||
|
|
||||||
|
# For directory copy, we use cp but remove destination first to ensure clean update
|
||||||
|
rm -rf "$INSTALL_DIR/lib"
|
||||||
|
cp -r lib "$INSTALL_DIR/"
|
||||||
|
|
||||||
|
# Create wrapper scripts
|
||||||
|
cat <<EOF > "$BIN_LINK"
|
||||||
|
#!/bin/bash
|
||||||
|
cd "$INSTALL_DIR"
|
||||||
|
./Kino "\$@"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cp "$BIN_LINK" "$BIN_LINK_LOCAL"
|
||||||
|
chmod +x "$BIN_LINK" "$BIN_LINK_LOCAL"
|
||||||
|
|
||||||
|
# Install desktop file
|
||||||
|
cp assets/kino.desktop "$DESKTOP_FILE"
|
||||||
|
# Update path in desktop file if needed
|
||||||
|
|
||||||
|
# Update MIME database
|
||||||
|
echo "Updating MIME database..."
|
||||||
|
update-desktop-database
|
||||||
|
|
||||||
|
echo "Installation complete!"
|
||||||
|
echo "Installed version timestamp: $(date -r "$INSTALL_DIR/Kino")"
|
||||||
|
echo "You can now run 'kino' or find it in your application menu."
|
||||||
BIN
lib/libmpv.so.2
Normal file
BIN
lib/libmpv.so.2
Normal file
Binary file not shown.
248
src/dialogs.c
248
src/dialogs.c
@ -1,4 +1,5 @@
|
|||||||
#include "dialogs.h"
|
#include "dialogs.h"
|
||||||
|
#include <gdk/gdkkeysyms.h>
|
||||||
#include <pango/pango.h>
|
#include <pango/pango.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -294,27 +295,238 @@ char* dialogs_save_screenshot(GtkWindow *parent)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dialogs_show_keybinds(GtkWindow *parent)
|
/* ---- Keybind helpers ---- */
|
||||||
|
|
||||||
|
enum {
|
||||||
|
KB_COL_ACTION_NAME,
|
||||||
|
KB_COL_SHORTCUT,
|
||||||
|
KB_COL_ACTION_ID,
|
||||||
|
KB_NUM_COLS
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GtkWidget *dialog;
|
||||||
|
guint captured_keyval;
|
||||||
|
GdkModifierType captured_mods;
|
||||||
|
gboolean got_key;
|
||||||
|
} KeyGrabData;
|
||||||
|
|
||||||
|
static gboolean on_keygrab_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
|
||||||
|
{
|
||||||
|
(void)widget;
|
||||||
|
KeyGrabData *kgd = (KeyGrabData *)data;
|
||||||
|
|
||||||
|
switch (event->keyval) {
|
||||||
|
case GDK_Shift_L: case GDK_Shift_R:
|
||||||
|
case GDK_Control_L: case GDK_Control_R:
|
||||||
|
case GDK_Alt_L: case GDK_Alt_R:
|
||||||
|
case GDK_Super_L: case GDK_Super_R:
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GdkModifierType relevant = GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK;
|
||||||
|
kgd->captured_keyval = event->keyval;
|
||||||
|
kgd->captured_mods = event->state & relevant;
|
||||||
|
kgd->got_key = TRUE;
|
||||||
|
|
||||||
|
gtk_dialog_response(GTK_DIALOG(kgd->dialog), GTK_RESPONSE_OK);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_keybind_row_activated(GtkTreeView *tree, GtkTreePath *path,
|
||||||
|
GtkTreeViewColumn *col, gpointer data)
|
||||||
|
{
|
||||||
|
(void)col;
|
||||||
|
KeybindManager *km = (KeybindManager *)data;
|
||||||
|
GtkTreeModel *model = gtk_tree_view_get_model(tree);
|
||||||
|
GtkTreeIter iter;
|
||||||
|
|
||||||
|
if (!gtk_tree_model_get_iter(model, &iter, path)) return;
|
||||||
|
|
||||||
|
int action_id;
|
||||||
|
char *action_name;
|
||||||
|
gtk_tree_model_get(model, &iter, KB_COL_ACTION_NAME, &action_name, KB_COL_ACTION_ID, &action_id, -1);
|
||||||
|
|
||||||
|
GtkWidget *parent_w = gtk_widget_get_toplevel(GTK_WIDGET(tree));
|
||||||
|
GtkWidget *grab_dialog = gtk_dialog_new_with_buttons(
|
||||||
|
"Press a key...",
|
||||||
|
GTK_WINDOW(parent_w),
|
||||||
|
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||||
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
||||||
|
NULL);
|
||||||
|
gtk_window_set_default_size(GTK_WINDOW(grab_dialog), 300, -1);
|
||||||
|
|
||||||
|
GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(grab_dialog));
|
||||||
|
char *prompt = g_strdup_printf("Press a new key combination for:\n\n<b>%s</b>", action_name);
|
||||||
|
GtkWidget *label = gtk_label_new(NULL);
|
||||||
|
gtk_label_set_markup(GTK_LABEL(label), prompt);
|
||||||
|
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
|
||||||
|
gtk_container_set_border_width(GTK_CONTAINER(label), 20);
|
||||||
|
gtk_box_pack_start(GTK_BOX(content), label, TRUE, TRUE, 0);
|
||||||
|
g_free(prompt);
|
||||||
|
|
||||||
|
KeyGrabData kgd = { grab_dialog, 0, 0, FALSE };
|
||||||
|
g_signal_connect(grab_dialog, "key-press-event", G_CALLBACK(on_keygrab_key_press), &kgd);
|
||||||
|
|
||||||
|
gtk_widget_show_all(grab_dialog);
|
||||||
|
int response = gtk_dialog_run(GTK_DIALOG(grab_dialog));
|
||||||
|
gtk_widget_destroy(grab_dialog);
|
||||||
|
|
||||||
|
if (response == GTK_RESPONSE_OK && kgd.got_key) {
|
||||||
|
keybind_manager_set_keybind(km, (KeybindAction)action_id, kgd.captured_keyval, kgd.captured_mods);
|
||||||
|
|
||||||
|
char *display = keybind_get_display_string(kgd.captured_keyval, kgd.captured_mods);
|
||||||
|
gtk_list_store_set(GTK_LIST_STORE(model), &iter, KB_COL_SHORTCUT, display, -1);
|
||||||
|
g_free(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(action_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_keybinds_reset_clicked(GtkButton *button, gpointer data);
|
||||||
|
|
||||||
|
static GtkWidget* create_keybinds_editor_widget(KeybindManager *km)
|
||||||
|
{
|
||||||
|
GtkWidget *vbox = gtk_vbox_new(FALSE, 8);
|
||||||
|
gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
|
||||||
|
|
||||||
|
GtkWidget *info_label = gtk_label_new(NULL);
|
||||||
|
gtk_label_set_markup(GTK_LABEL(info_label), "<i>Double-click a shortcut to change it.</i>");
|
||||||
|
gtk_misc_set_alignment(GTK_MISC(info_label), 0.0, 0.5);
|
||||||
|
gtk_box_pack_start(GTK_BOX(vbox), info_label, FALSE, FALSE, 4);
|
||||||
|
|
||||||
|
GtkListStore *store = gtk_list_store_new(KB_NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
|
||||||
|
|
||||||
|
for (int i = 0; i < KEYBIND_COUNT; i++) {
|
||||||
|
Keybind *kb = keybind_manager_get(km, (KeybindAction)i);
|
||||||
|
if (!kb) continue;
|
||||||
|
|
||||||
|
char *display = keybind_get_display_string(kb->keyval, kb->mods);
|
||||||
|
GtkTreeIter iter;
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter,
|
||||||
|
KB_COL_ACTION_NAME, kb->label,
|
||||||
|
KB_COL_SHORTCUT, display,
|
||||||
|
KB_COL_ACTION_ID, i, -1);
|
||||||
|
g_free(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
|
||||||
|
g_object_unref(store);
|
||||||
|
|
||||||
|
GtkCellRenderer *renderer;
|
||||||
|
GtkTreeViewColumn *column;
|
||||||
|
|
||||||
|
renderer = gtk_cell_renderer_text_new();
|
||||||
|
column = gtk_tree_view_column_new_with_attributes("Action", renderer, "text", KB_COL_ACTION_NAME, NULL);
|
||||||
|
gtk_tree_view_column_set_expand(column, TRUE);
|
||||||
|
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
|
||||||
|
|
||||||
|
renderer = gtk_cell_renderer_text_new();
|
||||||
|
column = gtk_tree_view_column_new_with_attributes("Shortcut", renderer, "text", KB_COL_SHORTCUT, NULL);
|
||||||
|
gtk_tree_view_column_set_min_width(column, 150);
|
||||||
|
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
|
||||||
|
|
||||||
|
g_signal_connect(tree, "row-activated", G_CALLBACK(on_keybind_row_activated), km);
|
||||||
|
|
||||||
|
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_container_add(GTK_CONTAINER(scroll), tree);
|
||||||
|
gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
|
||||||
|
|
||||||
|
/* Reset button */
|
||||||
|
GtkWidget *btn_hbox = gtk_hbox_new(FALSE, 0);
|
||||||
|
GtkWidget *reset_btn = gtk_button_new_with_label("Reset to Defaults");
|
||||||
|
g_object_set_data(G_OBJECT(reset_btn), "km", km);
|
||||||
|
g_object_set_data(G_OBJECT(reset_btn), "store", store);
|
||||||
|
g_signal_connect(reset_btn, "clicked", G_CALLBACK(on_keybinds_reset_clicked), NULL);
|
||||||
|
gtk_box_pack_end(GTK_BOX(btn_hbox), reset_btn, FALSE, FALSE, 0);
|
||||||
|
gtk_box_pack_start(GTK_BOX(vbox), btn_hbox, FALSE, FALSE, 4);
|
||||||
|
|
||||||
|
return vbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_keybinds_reset_clicked(GtkButton *button, gpointer data)
|
||||||
|
{
|
||||||
|
(void)data;
|
||||||
|
KeybindManager *km = g_object_get_data(G_OBJECT(button), "km");
|
||||||
|
GtkListStore *store = g_object_get_data(G_OBJECT(button), "store");
|
||||||
|
|
||||||
|
keybind_manager_reset_defaults(km);
|
||||||
|
|
||||||
|
gtk_list_store_clear(store);
|
||||||
|
for (int i = 0; i < KEYBIND_COUNT; i++) {
|
||||||
|
Keybind *kb = keybind_manager_get(km, (KeybindAction)i);
|
||||||
|
if (!kb) continue;
|
||||||
|
char *display = keybind_get_display_string(kb->keyval, kb->mods);
|
||||||
|
GtkTreeIter iter;
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter,
|
||||||
|
KB_COL_ACTION_NAME, kb->label,
|
||||||
|
KB_COL_SHORTCUT, display,
|
||||||
|
KB_COL_ACTION_ID, i, -1);
|
||||||
|
g_free(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Help > Keyboard Shortcuts: read-only quick reference */
|
||||||
|
void dialogs_show_keybinds(GtkWindow *parent, KeybindManager *km)
|
||||||
{
|
{
|
||||||
GtkWidget *dialog = gtk_dialog_new_with_buttons("Keyboard Shortcuts",
|
GtkWidget *dialog = gtk_dialog_new_with_buttons("Keyboard Shortcuts",
|
||||||
parent, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
|
parent, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||||
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
|
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
|
||||||
|
|
||||||
gtk_window_set_default_size(GTK_WINDOW(dialog), 350, -1);
|
gtk_window_set_default_size(GTK_WINDOW(dialog), 400, 420);
|
||||||
GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||||
|
gtk_container_set_border_width(GTK_CONTAINER(content_area), 12);
|
||||||
|
|
||||||
GtkWidget *label = gtk_label_new(
|
/* Title */
|
||||||
"<b>Space</b>\t\t\tPlay/Pause\n"
|
GtkWidget *title = gtk_label_new(NULL);
|
||||||
"<b>F / F11</b>\t\t\tFullscreen\n"
|
gtk_label_set_markup(GTK_LABEL(title), "<b>Keyboard Shortcuts</b>");
|
||||||
"<b>Left / Right</b>\tSeek Backward/Forward (5s)\n"
|
gtk_misc_set_alignment(GTK_MISC(title), 0.0, 0.5);
|
||||||
"<b>Up / Down</b>\t\tVolume Up/Down\n"
|
gtk_box_pack_start(GTK_BOX(content_area), title, FALSE, FALSE, 4);
|
||||||
"<b>M</b>\t\t\t\tMute\n"
|
|
||||||
"<b>Page Up/Down</b>\tPrevious/Next Chapter\n"
|
GtkWidget *sep = gtk_hseparator_new();
|
||||||
"<b>Esc</b>\t\t\t\tExit Fullscreen"
|
gtk_box_pack_start(GTK_BOX(content_area), sep, FALSE, FALSE, 4);
|
||||||
);
|
|
||||||
gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
|
/* Read-only list */
|
||||||
gtk_container_set_border_width(GTK_CONTAINER(label), 20);
|
GtkListStore *store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
|
||||||
gtk_box_pack_start(GTK_BOX(content_area), label, TRUE, TRUE, 0);
|
|
||||||
|
for (int i = 0; i < KEYBIND_COUNT; i++) {
|
||||||
|
Keybind *kb = keybind_manager_get(km, (KeybindAction)i);
|
||||||
|
if (!kb) continue;
|
||||||
|
char *display = keybind_get_display_string(kb->keyval, kb->mods);
|
||||||
|
GtkTreeIter iter;
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter, 0, kb->label, 1, display, -1);
|
||||||
|
g_free(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
|
||||||
|
g_object_unref(store);
|
||||||
|
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), TRUE);
|
||||||
|
|
||||||
|
GtkCellRenderer *r = gtk_cell_renderer_text_new();
|
||||||
|
GtkTreeViewColumn *c;
|
||||||
|
c = gtk_tree_view_column_new_with_attributes("Action", r, "text", 0, NULL);
|
||||||
|
gtk_tree_view_column_set_expand(c, TRUE);
|
||||||
|
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), c);
|
||||||
|
|
||||||
|
r = gtk_cell_renderer_text_new();
|
||||||
|
c = gtk_tree_view_column_new_with_attributes("Shortcut", r, "text", 1, NULL);
|
||||||
|
gtk_tree_view_column_set_min_width(c, 140);
|
||||||
|
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), c);
|
||||||
|
|
||||||
|
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_container_add(GTK_CONTAINER(scroll), tree);
|
||||||
|
gtk_box_pack_start(GTK_BOX(content_area), scroll, TRUE, TRUE, 0);
|
||||||
|
|
||||||
|
/* Footer hint */
|
||||||
|
GtkWidget *hint = gtk_label_new(NULL);
|
||||||
|
gtk_label_set_markup(GTK_LABEL(hint), "<i>To customise shortcuts, go to Tools \342\206\222 Preferences \342\206\222 Keybinds tab.</i>");
|
||||||
|
gtk_misc_set_alignment(GTK_MISC(hint), 0.0, 0.5);
|
||||||
|
gtk_box_pack_start(GTK_BOX(content_area), hint, FALSE, FALSE, 8);
|
||||||
|
|
||||||
gtk_widget_show_all(dialog);
|
gtk_widget_show_all(dialog);
|
||||||
gtk_dialog_run(GTK_DIALOG(dialog));
|
gtk_dialog_run(GTK_DIALOG(dialog));
|
||||||
@ -1069,7 +1281,7 @@ static void on_sub_font_browse_clicked(GtkButton *btn, gpointer data)
|
|||||||
gtk_widget_destroy(font_dialog);
|
gtk_widget_destroy(font_dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginManager *pm, int initial_tab, PreferencesApplyCallback apply_cb, gpointer user_data)
|
gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginManager *pm, KeybindManager *km, int initial_tab, PreferencesApplyCallback apply_cb, gpointer user_data)
|
||||||
{
|
{
|
||||||
if (!prefs) return FALSE;
|
if (!prefs) return FALSE;
|
||||||
|
|
||||||
@ -1308,6 +1520,12 @@ gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, PluginM
|
|||||||
|
|
||||||
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), config_vbox, gtk_label_new("Config File"));
|
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), config_vbox, gtk_label_new("Config File"));
|
||||||
|
|
||||||
|
/* 7. Keybinds Tab */
|
||||||
|
if (km) {
|
||||||
|
GtkWidget *keybinds_widget = create_keybinds_editor_widget(km);
|
||||||
|
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), keybinds_widget, gtk_label_new("Keybinds"));
|
||||||
|
}
|
||||||
|
|
||||||
if (initial_tab >= 0 && initial_tab < gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook))) {
|
if (initial_tab >= 0 && initial_tab < gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook))) {
|
||||||
gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), initial_tab);
|
gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), initial_tab);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
|
#include "keybinds.h"
|
||||||
|
|
||||||
/* Forward declaration */
|
/* Forward declaration */
|
||||||
struct PluginManager;
|
struct PluginManager;
|
||||||
@ -53,7 +54,7 @@ void preferences_free(Preferences *prefs);
|
|||||||
void preferences_load(Preferences *prefs);
|
void preferences_load(Preferences *prefs);
|
||||||
void preferences_save(Preferences *prefs);
|
void preferences_save(Preferences *prefs);
|
||||||
typedef void (*PreferencesApplyCallback)(gpointer data);
|
typedef void (*PreferencesApplyCallback)(gpointer data);
|
||||||
gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, struct PluginManager *pm, int initial_tab, PreferencesApplyCallback apply_cb, gpointer user_data);
|
gboolean dialogs_show_preferences(GtkWindow *parent, Preferences *prefs, struct PluginManager *pm, KeybindManager *km, int initial_tab, PreferencesApplyCallback apply_cb, gpointer user_data);
|
||||||
|
|
||||||
/* File dialogs */
|
/* File dialogs */
|
||||||
GSList* dialogs_open_files(GtkWindow *parent, Preferences *prefs);
|
GSList* dialogs_open_files(GtkWindow *parent, Preferences *prefs);
|
||||||
@ -65,7 +66,7 @@ char* dialogs_save_screenshot(GtkWindow *parent);
|
|||||||
|
|
||||||
/* About and Keybinds dialogs */
|
/* About and Keybinds dialogs */
|
||||||
void dialogs_show_about(GtkWindow *parent);
|
void dialogs_show_about(GtkWindow *parent);
|
||||||
void dialogs_show_keybinds(GtkWindow *parent);
|
void dialogs_show_keybinds(GtkWindow *parent, KeybindManager *km);
|
||||||
|
|
||||||
/* Go to time dialog */
|
/* Go to time dialog */
|
||||||
double dialogs_goto_time(GtkWindow *parent, double current_time, double duration);
|
double dialogs_goto_time(GtkWindow *parent, double current_time, double duration);
|
||||||
|
|||||||
153
src/keybinds.c
Normal file
153
src/keybinds.c
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
#include "keybinds.h"
|
||||||
|
#include <gdk/gdkkeysyms.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Default keybind table */
|
||||||
|
static const Keybind default_keybinds[KEYBIND_COUNT] = {
|
||||||
|
{ KEYBIND_PLAY_PAUSE, "Play/Pause", GDK_space, 0 },
|
||||||
|
{ KEYBIND_STOP, "Stop", GDK_s, 0 },
|
||||||
|
{ KEYBIND_FULLSCREEN, "Fullscreen", GDK_f, 0 },
|
||||||
|
{ KEYBIND_EXIT_FULLSCREEN, "Exit Fullscreen", GDK_Escape, 0 },
|
||||||
|
{ KEYBIND_SEEK_FORWARD, "Seek Forward", GDK_Right, 0 },
|
||||||
|
{ KEYBIND_SEEK_BACKWARD, "Seek Backward", GDK_Left, 0 },
|
||||||
|
{ KEYBIND_VOLUME_UP, "Volume Up", GDK_Up, 0 },
|
||||||
|
{ KEYBIND_VOLUME_DOWN, "Volume Down", GDK_Down, 0 },
|
||||||
|
{ KEYBIND_MUTE, "Mute", GDK_m, 0 },
|
||||||
|
{ KEYBIND_PREV_CHAPTER, "Previous Chapter", GDK_Page_Up, 0 },
|
||||||
|
{ KEYBIND_NEXT_CHAPTER, "Next Chapter", GDK_Page_Down, 0 },
|
||||||
|
{ KEYBIND_FRAME_STEP, "Frame Step", GDK_period, 0 },
|
||||||
|
{ KEYBIND_OPEN_FILE, "Open File", GDK_o, GDK_CONTROL_MASK },
|
||||||
|
{ KEYBIND_OPEN_URL, "Open URL", GDK_l, GDK_CONTROL_MASK },
|
||||||
|
{ KEYBIND_GOTO_TIME, "Go to Time", GDK_g, GDK_CONTROL_MASK },
|
||||||
|
{ KEYBIND_SCREENSHOT, "Screenshot", GDK_s, GDK_CONTROL_MASK | GDK_SHIFT_MASK },
|
||||||
|
{ KEYBIND_TOGGLE_PLAYLIST, "Toggle Playlist", GDK_p, GDK_CONTROL_MASK },
|
||||||
|
{ KEYBIND_QUIT, "Quit", GDK_q, GDK_CONTROL_MASK },
|
||||||
|
};
|
||||||
|
|
||||||
|
KeybindManager* keybind_manager_new(void)
|
||||||
|
{
|
||||||
|
KeybindManager *km = g_new0(KeybindManager, 1);
|
||||||
|
keybind_manager_reset_defaults(km);
|
||||||
|
return km;
|
||||||
|
}
|
||||||
|
|
||||||
|
void keybind_manager_free(KeybindManager *km)
|
||||||
|
{
|
||||||
|
if (km) g_free(km);
|
||||||
|
}
|
||||||
|
|
||||||
|
void keybind_manager_reset_defaults(KeybindManager *km)
|
||||||
|
{
|
||||||
|
if (!km) return;
|
||||||
|
memcpy(km->binds, default_keybinds, sizeof(default_keybinds));
|
||||||
|
}
|
||||||
|
|
||||||
|
void keybind_manager_load(KeybindManager *km, GKeyFile *keyfile)
|
||||||
|
{
|
||||||
|
if (!km || !keyfile) return;
|
||||||
|
|
||||||
|
/* Start from defaults, then override with saved values */
|
||||||
|
keybind_manager_reset_defaults(km);
|
||||||
|
|
||||||
|
if (!g_key_file_has_group(keyfile, "Keybinds")) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < KEYBIND_COUNT; i++) {
|
||||||
|
const char *action_name = km->binds[i].label;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
/* Build key names like "Play/Pause_key" and "Play/Pause_mods" */
|
||||||
|
char key_name[128];
|
||||||
|
char mod_name[128];
|
||||||
|
snprintf(key_name, sizeof(key_name), "%s_key", action_name);
|
||||||
|
snprintf(mod_name, sizeof(mod_name), "%s_mods", action_name);
|
||||||
|
|
||||||
|
char *keyval_str = g_key_file_get_string(keyfile, "Keybinds", key_name, &error);
|
||||||
|
if (keyval_str && !error) {
|
||||||
|
guint kv = gdk_keyval_from_name(keyval_str);
|
||||||
|
if (kv != 0xffffff) { /* GDK_KEY_VoidSymbol */
|
||||||
|
km->binds[i].keyval = kv;
|
||||||
|
}
|
||||||
|
g_free(keyval_str);
|
||||||
|
}
|
||||||
|
if (error) { g_error_free(error); error = NULL; }
|
||||||
|
|
||||||
|
int mods_val = g_key_file_get_integer(keyfile, "Keybinds", mod_name, &error);
|
||||||
|
if (!error) {
|
||||||
|
km->binds[i].mods = (GdkModifierType)mods_val;
|
||||||
|
}
|
||||||
|
if (error) { g_error_free(error); error = NULL; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void keybind_manager_save(KeybindManager *km, GKeyFile *keyfile)
|
||||||
|
{
|
||||||
|
if (!km || !keyfile) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < KEYBIND_COUNT; i++) {
|
||||||
|
const char *action_name = km->binds[i].label;
|
||||||
|
char key_name[128];
|
||||||
|
char mod_name[128];
|
||||||
|
snprintf(key_name, sizeof(key_name), "%s_key", action_name);
|
||||||
|
snprintf(mod_name, sizeof(mod_name), "%s_mods", action_name);
|
||||||
|
|
||||||
|
const char *keyval_str = gdk_keyval_name(km->binds[i].keyval);
|
||||||
|
if (keyval_str) {
|
||||||
|
g_key_file_set_string(keyfile, "Keybinds", key_name, keyval_str);
|
||||||
|
}
|
||||||
|
g_key_file_set_integer(keyfile, "Keybinds", mod_name, (int)km->binds[i].mods);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void keybind_manager_set_keybind(KeybindManager *km, KeybindAction action, guint keyval, GdkModifierType mods)
|
||||||
|
{
|
||||||
|
if (!km || action < 0 || action >= KEYBIND_COUNT) return;
|
||||||
|
km->binds[action].keyval = keyval;
|
||||||
|
km->binds[action].mods = mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
Keybind* keybind_manager_get(KeybindManager *km, KeybindAction action)
|
||||||
|
{
|
||||||
|
if (!km || action < 0 || action >= KEYBIND_COUNT) return NULL;
|
||||||
|
return &km->binds[action];
|
||||||
|
}
|
||||||
|
|
||||||
|
KeybindAction keybind_manager_find_action(KeybindManager *km, guint keyval, GdkModifierType mods)
|
||||||
|
{
|
||||||
|
if (!km) return KEYBIND_COUNT;
|
||||||
|
|
||||||
|
/* Mask out irrelevant modifier bits (lock keys, etc.) */
|
||||||
|
GdkModifierType relevant = GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK;
|
||||||
|
GdkModifierType clean_mods = mods & relevant;
|
||||||
|
|
||||||
|
for (int i = 0; i < KEYBIND_COUNT; i++) {
|
||||||
|
if (km->binds[i].keyval == keyval && km->binds[i].mods == clean_mods) {
|
||||||
|
return (KeybindAction)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return KEYBIND_COUNT; /* not found */
|
||||||
|
}
|
||||||
|
|
||||||
|
char* keybind_get_display_string(guint keyval, GdkModifierType mods)
|
||||||
|
{
|
||||||
|
GString *str = g_string_new(NULL);
|
||||||
|
|
||||||
|
if (mods & GDK_CONTROL_MASK) g_string_append(str, "Ctrl+");
|
||||||
|
if (mods & GDK_SHIFT_MASK) g_string_append(str, "Shift+");
|
||||||
|
if (mods & GDK_MOD1_MASK) g_string_append(str, "Alt+");
|
||||||
|
|
||||||
|
const char *name = gdk_keyval_name(keyval);
|
||||||
|
if (name) {
|
||||||
|
/* Capitalize first letter for display */
|
||||||
|
if (strlen(name) == 1 && name[0] >= 'a' && name[0] <= 'z') {
|
||||||
|
g_string_append_c(str, name[0] - 32);
|
||||||
|
} else {
|
||||||
|
g_string_append(str, name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g_string_append(str, "???");
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_string_free(str, FALSE);
|
||||||
|
}
|
||||||
59
src/keybinds.h
Normal file
59
src/keybinds.h
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#ifndef KEYBINDS_H
|
||||||
|
#define KEYBINDS_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
/* All bindable actions */
|
||||||
|
typedef enum {
|
||||||
|
KEYBIND_PLAY_PAUSE,
|
||||||
|
KEYBIND_STOP,
|
||||||
|
KEYBIND_FULLSCREEN,
|
||||||
|
KEYBIND_EXIT_FULLSCREEN,
|
||||||
|
KEYBIND_SEEK_FORWARD,
|
||||||
|
KEYBIND_SEEK_BACKWARD,
|
||||||
|
KEYBIND_VOLUME_UP,
|
||||||
|
KEYBIND_VOLUME_DOWN,
|
||||||
|
KEYBIND_MUTE,
|
||||||
|
KEYBIND_PREV_CHAPTER,
|
||||||
|
KEYBIND_NEXT_CHAPTER,
|
||||||
|
KEYBIND_FRAME_STEP,
|
||||||
|
KEYBIND_OPEN_FILE,
|
||||||
|
KEYBIND_OPEN_URL,
|
||||||
|
KEYBIND_GOTO_TIME,
|
||||||
|
KEYBIND_SCREENSHOT,
|
||||||
|
KEYBIND_TOGGLE_PLAYLIST,
|
||||||
|
KEYBIND_QUIT,
|
||||||
|
KEYBIND_COUNT /* must be last */
|
||||||
|
} KeybindAction;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
KeybindAction action;
|
||||||
|
const char *label; /* Human-readable name */
|
||||||
|
guint keyval; /* GDK key value */
|
||||||
|
GdkModifierType mods; /* Modifier mask (Ctrl, Shift, etc.) */
|
||||||
|
} Keybind;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Keybind binds[KEYBIND_COUNT];
|
||||||
|
} KeybindManager;
|
||||||
|
|
||||||
|
/* Lifecycle */
|
||||||
|
KeybindManager* keybind_manager_new(void);
|
||||||
|
void keybind_manager_free(KeybindManager *km);
|
||||||
|
|
||||||
|
/* Persistence */
|
||||||
|
void keybind_manager_load(KeybindManager *km, GKeyFile *keyfile);
|
||||||
|
void keybind_manager_save(KeybindManager *km, GKeyFile *keyfile);
|
||||||
|
|
||||||
|
/* Modification */
|
||||||
|
void keybind_manager_reset_defaults(KeybindManager *km);
|
||||||
|
void keybind_manager_set_keybind(KeybindManager *km, KeybindAction action, guint keyval, GdkModifierType mods);
|
||||||
|
|
||||||
|
/* Lookup */
|
||||||
|
Keybind* keybind_manager_get(KeybindManager *km, KeybindAction action);
|
||||||
|
KeybindAction keybind_manager_find_action(KeybindManager *km, guint keyval, GdkModifierType mods);
|
||||||
|
|
||||||
|
/* Utility */
|
||||||
|
char* keybind_get_display_string(guint keyval, GdkModifierType mods);
|
||||||
|
|
||||||
|
#endif /* KEYBINDS_H */
|
||||||
BIN
src/keybinds.o
Normal file
BIN
src/keybinds.o
Normal file
Binary file not shown.
123
src/ui.c
123
src/ui.c
@ -106,6 +106,9 @@ struct AppUI {
|
|||||||
|
|
||||||
/* Subtitle dragging */
|
/* Subtitle dragging */
|
||||||
gboolean sub_dragging;
|
gboolean sub_dragging;
|
||||||
|
|
||||||
|
/* Keybind manager */
|
||||||
|
KeybindManager *keybinds;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Forward declarations */
|
/* Forward declarations */
|
||||||
@ -192,6 +195,20 @@ AppUI* ui_new(void)
|
|||||||
ui->prefs = preferences_new();
|
ui->prefs = preferences_new();
|
||||||
preferences_load(ui->prefs);
|
preferences_load(ui->prefs);
|
||||||
|
|
||||||
|
/* Load keybinds from config file */
|
||||||
|
ui->keybinds = keybind_manager_new();
|
||||||
|
{
|
||||||
|
char *config_dir = g_build_filename(g_get_user_config_dir(), "gtk2-media-player", NULL);
|
||||||
|
char *config_path = g_build_filename(config_dir, "settings.conf", NULL);
|
||||||
|
GKeyFile *kf = g_key_file_new();
|
||||||
|
if (g_key_file_load_from_file(kf, config_path, G_KEY_FILE_NONE, NULL)) {
|
||||||
|
keybind_manager_load(ui->keybinds, kf);
|
||||||
|
}
|
||||||
|
g_key_file_free(kf);
|
||||||
|
g_free(config_path);
|
||||||
|
g_free(config_dir);
|
||||||
|
}
|
||||||
|
|
||||||
/* Main window */
|
/* Main window */
|
||||||
ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||||
gtk_window_set_title(GTK_WINDOW(ui->window), "Kino");
|
gtk_window_set_title(GTK_WINDOW(ui->window), "Kino");
|
||||||
@ -346,6 +363,24 @@ void ui_destroy(AppUI *ui)
|
|||||||
{
|
{
|
||||||
if (!ui) return;
|
if (!ui) return;
|
||||||
|
|
||||||
|
/* Save keybinds */
|
||||||
|
if (ui->keybinds) {
|
||||||
|
char *config_dir = g_build_filename(g_get_user_config_dir(), "gtk2-media-player", NULL);
|
||||||
|
char *config_path = g_build_filename(config_dir, "settings.conf", NULL);
|
||||||
|
GKeyFile *kf = g_key_file_new();
|
||||||
|
g_key_file_load_from_file(kf, config_path, G_KEY_FILE_NONE, NULL);
|
||||||
|
keybind_manager_save(ui->keybinds, kf);
|
||||||
|
gchar *data = g_key_file_to_data(kf, NULL, NULL);
|
||||||
|
if (data) {
|
||||||
|
g_file_set_contents(config_path, data, -1, NULL);
|
||||||
|
g_free(data);
|
||||||
|
}
|
||||||
|
g_key_file_free(kf);
|
||||||
|
g_free(config_path);
|
||||||
|
g_free(config_dir);
|
||||||
|
keybind_manager_free(ui->keybinds);
|
||||||
|
}
|
||||||
|
|
||||||
if (ui->auto_hide_timer_id > 0) {
|
if (ui->auto_hide_timer_id > 0) {
|
||||||
g_source_remove(ui->auto_hide_timer_id);
|
g_source_remove(ui->auto_hide_timer_id);
|
||||||
}
|
}
|
||||||
@ -1011,50 +1046,72 @@ static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer dat
|
|||||||
(void)widget;
|
(void)widget;
|
||||||
AppUI *ui = (AppUI *)data;
|
AppUI *ui = (AppUI *)data;
|
||||||
|
|
||||||
switch (event->keyval) {
|
GdkModifierType relevant = GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK;
|
||||||
case GDK_Escape:
|
GdkModifierType mods = event->state & relevant;
|
||||||
if (ui->is_fullscreen) {
|
KeybindAction action = keybind_manager_find_action(ui->keybinds, event->keyval, mods);
|
||||||
ui_toggle_fullscreen(ui);
|
|
||||||
}
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
case GDK_F11:
|
switch (action) {
|
||||||
case GDK_f:
|
case KEYBIND_PLAY_PAUSE:
|
||||||
ui_toggle_fullscreen(ui);
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
case GDK_space:
|
|
||||||
on_playback_play_pause(NULL, ui);
|
on_playback_play_pause(NULL, ui);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
case KEYBIND_STOP:
|
||||||
case GDK_Page_Up:
|
player_stop(ui->player);
|
||||||
player_prev_chapter(ui->player);
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
case KEYBIND_FULLSCREEN:
|
||||||
case GDK_Page_Down:
|
ui_toggle_fullscreen(ui);
|
||||||
player_next_chapter(ui->player);
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
case KEYBIND_EXIT_FULLSCREEN:
|
||||||
case GDK_Left:
|
if (ui->is_fullscreen) ui_toggle_fullscreen(ui);
|
||||||
player_seek(ui->player, -5);
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
case KEYBIND_SEEK_FORWARD:
|
||||||
case GDK_Right:
|
|
||||||
player_seek(ui->player, 5);
|
player_seek(ui->player, 5);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
case KEYBIND_SEEK_BACKWARD:
|
||||||
case GDK_Up:
|
player_seek(ui->player, -5);
|
||||||
|
return TRUE;
|
||||||
|
case KEYBIND_VOLUME_UP:
|
||||||
player_set_volume(ui->player, player_get_volume(ui->player) + 5);
|
player_set_volume(ui->player, player_get_volume(ui->player) + 5);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
case KEYBIND_VOLUME_DOWN:
|
||||||
case GDK_Down:
|
|
||||||
player_set_volume(ui->player, player_get_volume(ui->player) - 5);
|
player_set_volume(ui->player, player_get_volume(ui->player) - 5);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
case KEYBIND_MUTE:
|
||||||
case GDK_m:
|
|
||||||
player_set_muted(ui->player, !player_get_muted(ui->player));
|
player_set_muted(ui->player, !player_get_muted(ui->player));
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
case KEYBIND_PREV_CHAPTER:
|
||||||
|
player_prev_chapter(ui->player);
|
||||||
|
return TRUE;
|
||||||
|
case KEYBIND_NEXT_CHAPTER:
|
||||||
|
player_next_chapter(ui->player);
|
||||||
|
return TRUE;
|
||||||
|
case KEYBIND_FRAME_STEP:
|
||||||
|
on_playback_frame_step(NULL, ui);
|
||||||
|
return TRUE;
|
||||||
|
case KEYBIND_OPEN_FILE:
|
||||||
|
ui_open_file(ui);
|
||||||
|
return TRUE;
|
||||||
|
case KEYBIND_OPEN_URL: {
|
||||||
|
char *url = dialogs_open_url(GTK_WINDOW(ui->window), ui->prefs);
|
||||||
|
if (url) {
|
||||||
|
playlist_clear(ui->playlist);
|
||||||
|
playlist_add_file(ui->playlist, url, FALSE);
|
||||||
|
playlist_play_index(ui->playlist, 0);
|
||||||
|
g_free(url);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
case KEYBIND_GOTO_TIME:
|
||||||
|
on_playback_goto_time(NULL, ui);
|
||||||
|
return TRUE;
|
||||||
|
case KEYBIND_SCREENSHOT:
|
||||||
|
on_view_screenshot(NULL, ui);
|
||||||
|
return TRUE;
|
||||||
|
case KEYBIND_TOGGLE_PLAYLIST:
|
||||||
|
on_view_playlist(NULL, ui);
|
||||||
|
return TRUE;
|
||||||
|
case KEYBIND_QUIT:
|
||||||
|
gtk_main_quit();
|
||||||
|
return TRUE;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1946,7 +2003,7 @@ static void on_view_preferences(GtkMenuItem *item, gpointer data)
|
|||||||
{
|
{
|
||||||
(void)item;
|
(void)item;
|
||||||
AppUI *ui = (AppUI *)data;
|
AppUI *ui = (AppUI *)data;
|
||||||
dialogs_show_preferences(GTK_WINDOW(ui->window), ui->prefs, ui->plugin_manager, -1, ui_apply_preferences_internal, ui);
|
dialogs_show_preferences(GTK_WINDOW(ui->window), ui->prefs, ui->plugin_manager, ui->keybinds, -1, ui_apply_preferences_internal, ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_subtitle_settings_clicked(GtkMenuItem *item, gpointer data)
|
static void on_subtitle_settings_clicked(GtkMenuItem *item, gpointer data)
|
||||||
@ -1954,14 +2011,14 @@ static void on_subtitle_settings_clicked(GtkMenuItem *item, gpointer data)
|
|||||||
(void)item;
|
(void)item;
|
||||||
AppUI *ui = (AppUI *)data;
|
AppUI *ui = (AppUI *)data;
|
||||||
/* Open preferences directly on the Subtitles tab (index 1) */
|
/* Open preferences directly on the Subtitles tab (index 1) */
|
||||||
dialogs_show_preferences(GTK_WINDOW(ui->window), ui->prefs, ui->plugin_manager, 1, ui_apply_preferences_internal, ui);
|
dialogs_show_preferences(GTK_WINDOW(ui->window), ui->prefs, ui->plugin_manager, ui->keybinds, 1, ui_apply_preferences_internal, ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_help_keybinds(GtkMenuItem *item, gpointer data)
|
static void on_help_keybinds(GtkMenuItem *item, gpointer data)
|
||||||
{
|
{
|
||||||
(void)item;
|
(void)item;
|
||||||
AppUI *ui = (AppUI *)data;
|
AppUI *ui = (AppUI *)data;
|
||||||
dialogs_show_keybinds(GTK_WINDOW(ui->window));
|
dialogs_show_keybinds(GTK_WINDOW(ui->window), ui->keybinds);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_help_about(GtkMenuItem *item, gpointer data)
|
static void on_help_about(GtkMenuItem *item, gpointer data)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user