From 6060a8d874d84fa58117c0b206bc1b246f790959 Mon Sep 17 00:00:00 2001 From: pepaskopec-blip Date: Tue, 15 Sep 2026 12:53:14 +0200 Subject: [PATCH] Show the app icon on the macOS Dock via AppKit. Co-authored-by: Cursor --- CMakeLists.txt | 4 ++++ Makefile | 4 ++++ macos_dock.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ main.c | 37 +++++++++++++++++++++++++++------ maturita.h | 3 +++ 5 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 macos_dock.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3810f57..e82dc71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,10 @@ else() target_link_libraries(maturita PRIVATE PkgConfig::GTK4 m) target_compile_options(maturita PRIVATE -Wall -Wextra -Wno-deprecated-declarations) + if(APPLE) + target_link_libraries(maturita PRIVATE + "-framework AppKit" "-framework Foundation") + endif() endif() # The GTK stylesheet and window icon are loaded at runtime from the working diff --git a/Makefile b/Makefile index 187e2eb..9d30fe4 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,10 @@ TARGET = maturita.exe LIBS += -mwindows else TARGET = maturita +# macOS Dock icon is set via AppKit (see macos_dock.c). +ifeq ($(shell uname -s 2>/dev/null),Darwin) +LIBS += -framework AppKit -framework Foundation +endif endif SRC = $(wildcard *.c) diff --git a/macos_dock.c b/macos_dock.c new file mode 100644 index 0000000..d95a905 --- /dev/null +++ b/macos_dock.c @@ -0,0 +1,55 @@ +#include "maturita.h" + +#ifdef __APPLE__ +#include +#include + +/* Set the macOS Dock tile icon. GTK's window icon-name is ignored on + * Quartz, so we talk to AppKit directly via the Objective-C runtime. */ +void macos_set_dock_icon(const char *path) { + Class nsstring; + Class nsimage; + Class nsapp; + id path_str; + id image; + id app; + + if (!path || !path[0]) + return; + + nsstring = objc_getClass("NSString"); + nsimage = objc_getClass("NSImage"); + nsapp = objc_getClass("NSApplication"); + if (!nsstring || !nsimage || !nsapp) + return; + + path_str = ((id (*)(Class, SEL, const char *))objc_msgSend)( + nsstring, sel_registerName("stringWithUTF8String:"), path); + if (!path_str) + return; + + /* NSImage has -initWithContentsOfFile:, not +imageWithContentsOfFile:. */ + image = ((id (*)(id, SEL, id))objc_msgSend)( + ((id (*)(Class, SEL))objc_msgSend)( + nsimage, sel_registerName("alloc")), + sel_registerName("initWithContentsOfFile:"), + path_str); + if (!image) + return; + + app = ((id (*)(Class, SEL))objc_msgSend)( + nsapp, sel_registerName("sharedApplication")); + if (!app) { + ((void (*)(id, SEL))objc_msgSend)(image, sel_registerName("release")); + return; + } + + ((void (*)(id, SEL, id))objc_msgSend)( + app, sel_registerName("setApplicationIconImage:"), image); + ((void (*)(id, SEL))objc_msgSend)(image, sel_registerName("release")); +} +#else +void macos_set_dock_icon(const char *path) { + (void)path; +} +#endif diff --git a/main.c b/main.c index 28abcc5..1aa538c 100644 --- a/main.c +++ b/main.c @@ -185,26 +185,51 @@ static gboolean ui_scale_tick(GtkWidget *w, GdkFrameClock *clock, return G_SOURCE_CONTINUE; } +static const char *find_app_icon_file(void) { + static const char *paths[] = { + ICON_FILE, + ICON_THEME_DIR "/hicolor/512x512/apps/" ICON_NAME ".png", + ICON_FILE_ALT, + NULL, + }; + int i; + + for (i = 0; paths[i]; i++) { + if (g_file_test(paths[i], G_FILE_TEST_IS_REGULAR)) + return paths[i]; + } + return NULL; +} + /* GTK4 has no gtk_window_set_icon_from_file(); register a local hicolor - * theme dir and set the window icon by name instead. */ + * theme dir and set the window icon by name instead. On macOS the Dock + * tile ignores that, so also push the PNG through AppKit. */ static void setup_window_icon(GtkWindow *window) { GtkIconTheme *theme; static const char *dirs[] = { ICON_THEME_DIR, ICON_THEME_DIR_ALT, NULL }; + const char *icon_file; + int i; theme = gtk_icon_theme_get_for_display(gdk_display_get_default()); - for (int i = 0; dirs[i]; i++) { + for (i = 0; dirs[i]; i++) { if (g_file_test(dirs[i], G_FILE_TEST_IS_DIR)) gtk_icon_theme_add_search_path(theme, dirs[i]); } - if (!gtk_icon_theme_has_icon(theme, ICON_NAME)) { + if (gtk_icon_theme_has_icon(theme, ICON_NAME)) { + gtk_window_set_default_icon_name(ICON_NAME); + gtk_window_set_icon_name(window, ICON_NAME); + } else { g_warning("App icon '%s' not found (tried %s/ and %s/)", ICON_NAME, ICON_THEME_DIR, ICON_THEME_DIR_ALT); - return; } - gtk_window_set_default_icon_name(ICON_NAME); - gtk_window_set_icon_name(window, ICON_NAME); + icon_file = find_app_icon_file(); + if (icon_file) + macos_set_dock_icon(icon_file); + else + g_warning("Dock icon PNG not found (tried %s and %s)", + ICON_FILE, ICON_FILE_ALT); } void activate(GtkApplication *app, gpointer user_data) { diff --git a/maturita.h b/maturita.h index f828f14..b4ce285 100644 --- a/maturita.h +++ b/maturita.h @@ -16,8 +16,10 @@ #define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */ #define CSS_FILE "style.css" #define ICON_NAME "maturita" +#define ICON_FILE "assets/app-icon.png" #define ICON_THEME_DIR "assets/icons" /* cwd when run from source tree */ #define ICON_THEME_DIR_ALT "icons" /* next to the binary after install */ +#define ICON_FILE_ALT "icons/hicolor/512x512/apps/maturita.png" #define PROGRESS_DIR "progress" #define PROGRESS_U1 "progress/unit1.conf" #define PROGRESS_U2 "progress/unit2.conf" @@ -1192,6 +1194,7 @@ GtkWidget *build_net_unit24_exercise_page(void); GtkWidget *build_net_unit25_exercise_page(void); GtkWidget *build_net_unit26_exercise_page(void); GtkWidget *build_net_unit27_exercise_page(void); +void macos_set_dock_icon(const char *path); void activate(GtkApplication *app, gpointer user_data); int main(int argc, char **argv);