GTK
Multi-platform GUI toolkit serving as foundation for GNOME desktop environment. High customizability through object-oriented design and theming system. Rich accessibility support and internationalization features.
GitHub Overview
GNOME/gtk
Read-only mirror of https://gitlab.gnome.org/GNOME/gtk
Topics
Star History
Framework
GTK
Overview
GTK is a cross-platform GUI toolkit that serves as the foundation for the GNOME desktop environment. Developed in C and built on the GObject type system, it occupies a standard position in desktop application development for Linux/Unix systems.
Details
As of 2025, GTK 4 is provided as a stable version, representing a mature GUI framework with over 30 years of history. It is used in major Linux desktop environments such as GNOME, XFCE, and LXDE, and serves as the foundation for many open-source applications.
Key features of GTK:
- Native Performance: High-performance native execution through C language
- Rich Widgets: Comprehensive set of GUI components
- Theme Support: Flexible customization through CSS-like style system
- Accessibility: Excellent accessibility support via AT-SPI
- Internationalization: Complete i18n/l10n support
- Cross-platform: Runs on Linux, Windows, and macOS
As of 2025, many major applications including Firefox, GIMP, LibreOffice (partially), and VS Code adopt GTK.
Advantages & Disadvantages
Advantages
- Linux Integration: Complete native experience on Linux/UNIX systems
- High Performance: Optimized performance through C language
- Stability: High stability from over 30 years of proven track record
- Rich Features: Comprehensive widget set and layout managers
- Free Software: Free usage under LGPL license
- Community: Large and active development community
- Documentation: Abundant official documentation and tutorials
- Language Bindings: Multi-language bindings for Python, Rust, Vala, etc.
Disadvantages
- Learning Curve: Requires understanding of C language and GObject system
- Windows/macOS Experience: Limited native look & feel
- Development Tools: Limited IDE support compared to Qt and others
- Packaging: Complex distribution across platforms
- Modern UI: Limited adaptation to latest UI design trends
- Memory Usage: Overhead due to GObject system
Key Links
- GTK Official Site
- GTK 4 Documentation
- GTK GitLab Repository
- GNOME Developer Center
- GTK Tutorials
- GObject Reference
Code Examples
Basic GTK Application
// main.c
#include <gtk/gtk.h>
static void
print_hello (GtkWidget *widget,
gpointer data)
{
g_print ("Hello World\n");
}
static void
activate (GtkApplication *app,
gpointer user_data)
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *box;
// Create main window
window = gtk_application_window_new (app);
gtk_window_set_title (GTK_WINDOW (window), "GTK Hello World");
gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
// Create layout container
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_halign (box, GTK_ALIGN_CENTER);
gtk_widget_set_valign (box, GTK_ALIGN_CENTER);
gtk_window_set_child (GTK_WINDOW (window), box);
// Create button
button = gtk_button_new_with_label ("Hello World");
// Connect signals
g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);
g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_destroy), window);
gtk_box_append (GTK_BOX (box), button);
// Show window
gtk_window_present (GTK_WINDOW (window));
}
int
main (int argc,
char **argv)
{
GtkApplication *app;
int status;
app = gtk_application_new ("org.example.HelloWorld", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
status = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return status;
}
UI Design using GtkBuilder
<!-- interface.ui -->
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object id="window" class="GtkWindow">
<property name="title">GTK Builder Example</property>
<property name="default-width">400</property>
<property name="default-height">300</property>
<child>
<object id="main_box" class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<property name="margin-top">20</property>
<property name="margin-bottom">20</property>
<property name="margin-start">20</property>
<property name="margin-end">20</property>
<child>
<object id="header_label" class="GtkLabel">
<property name="label">GTK Builder Demo</property>
<property name="halign">center</property>
<style>
<class name="title-1"/>
</style>
</object>
</child>
<child>
<object id="input_frame" class="GtkFrame">
<property name="label">Input Area</property>
<child>
<object id="input_grid" class="GtkGrid">
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="row-spacing">10</property>
<property name="column-spacing">10</property>
<child>
<object id="name_label" class="GtkLabel">
<property name="label">Name:</property>
<property name="halign">end</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object id="name_entry" class="GtkEntry">
<property name="placeholder-text">Enter your name</property>
<property name="hexpand">true</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object id="message_label" class="GtkLabel">
<property name="label">Message:</property>
<property name="halign">end</property>
<property name="valign">start</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object id="message_textview" class="GtkTextView">
<property name="height-request">100</property>
<property name="hexpand">true</property>
<property name="vexpand">true</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object id="button_box" class="GtkBox">
<property name="orientation">horizontal</property>
<property name="spacing">10</property>
<property name="halign">center</property>
<child>
<object id="submit_button" class="GtkButton">
<property name="label">Submit</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
<child>
<object id="clear_button" class="GtkButton">
<property name="label">Clear</property>
</object>
</child>
</object>
</child>
<child>
<object id="output_frame" class="GtkFrame">
<property name="label">Output</property>
<child>
<object id="output_label" class="GtkLabel">
<property name="label">Results will be displayed here</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="wrap">true</property>
<property name="selectable">true</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
// builder_app.c
#include <gtk/gtk.h>
typedef struct {
GtkWidget *window;
GtkWidget *name_entry;
GtkWidget *message_textview;
GtkWidget *output_label;
GtkTextBuffer *text_buffer;
} AppWidgets;
static void
on_submit_clicked (GtkButton *button, AppWidgets *widgets)
{
const char *name;
char *message;
char *output_text;
GtkTextIter start, end;
// Get name
name = gtk_editable_get_text (GTK_EDITABLE (widgets->name_entry));
// Get message
gtk_text_buffer_get_bounds (widgets->text_buffer, &start, &end);
message = gtk_text_buffer_get_text (widgets->text_buffer, &start, &end, FALSE);
// Create output text
if (strlen (name) > 0 && strlen (message) > 0) {
output_text = g_strdup_printf ("Name: %s\nMessage: %s", name, message);
} else {
output_text = g_strdup ("Please enter both name and message.");
}
// Display result
gtk_label_set_text (GTK_LABEL (widgets->output_label), output_text);
g_free (message);
g_free (output_text);
}
static void
on_clear_clicked (GtkButton *button, AppWidgets *widgets)
{
// Clear fields
gtk_editable_set_text (GTK_EDITABLE (widgets->name_entry), "");
gtk_text_buffer_set_text (widgets->text_buffer, "", -1);
gtk_label_set_text (GTK_LABEL (widgets->output_label), "Results will be displayed here");
}
static void
activate (GtkApplication *app, gpointer user_data)
{
AppWidgets *widgets = g_new0 (AppWidgets, 1);
GtkBuilder *builder;
GtkWidget *submit_button, *clear_button;
GError *error = NULL;
// Load UI with Builder
builder = gtk_builder_new ();
if (!gtk_builder_add_from_file (builder, "interface.ui", &error)) {
g_printerr ("Error loading UI file: %s\n", error->message);
g_error_free (error);
return;
}
// Get widgets
widgets->window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
widgets->name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry"));
widgets->message_textview = GTK_WIDGET (gtk_builder_get_object (builder, "message_textview"));
widgets->output_label = GTK_WIDGET (gtk_builder_get_object (builder, "output_label"));
submit_button = GTK_WIDGET (gtk_builder_get_object (builder, "submit_button"));
clear_button = GTK_WIDGET (gtk_builder_get_object (builder, "clear_button"));
// Get TextBuffer
widgets->text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widgets->message_textview));
// Associate application with window
gtk_window_set_application (GTK_WINDOW (widgets->window), app);
// Connect signals
g_signal_connect (submit_button, "clicked", G_CALLBACK (on_submit_clicked), widgets);
g_signal_connect (clear_button, "clicked", G_CALLBACK (on_clear_clicked), widgets);
// Show window
gtk_window_present (GTK_WINDOW (widgets->window));
// Clean up builder
g_object_unref (builder);
}
int
main (int argc, char *argv[])
{
GtkApplication *app;
int status;
app = gtk_application_new ("org.example.BuilderApp", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
status = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return status;
}
Drawing and Canvas Application
// drawing_app.c
#include <gtk/gtk.h>
/* Surface for drawing */
static cairo_surface_t *surface = NULL;
static void
clear_surface (void)
{
cairo_t *cr;
cr = cairo_create (surface);
cairo_set_source_rgb (cr, 1, 1, 1); // Fill with white
cairo_paint (cr);
cairo_destroy (cr);
}
/* Drawing area resize */
static void
resize_cb (GtkWidget *widget,
int width,
int height,
gpointer data)
{
if (surface) {
cairo_surface_destroy (surface);
surface = NULL;
}
if (gtk_native_get_surface (gtk_widget_get_native (widget))) {
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
gtk_widget_get_width (widget),
gtk_widget_get_height (widget));
clear_surface ();
}
}
/* Draw callback */
static void
draw_cb (GtkDrawingArea *drawing_area,
cairo_t *cr,
int width,
int height,
gpointer data)
{
cairo_set_source_surface (cr, surface, 0, 0);
cairo_paint (cr);
}
/* Draw with brush */
static void
draw_brush (GtkWidget *widget, double x, double y)
{
cairo_t *cr;
if (surface == NULL)
return;
cr = cairo_create (surface);
cairo_rectangle (cr, x - 3, y - 3, 6, 6);
cairo_fill (cr);
cairo_destroy (cr);
/* Update drawing area */
gtk_widget_queue_draw (widget);
}
/* Drag begin */
static void
drag_begin (GtkGestureDrag *gesture,
double x,
double y,
GtkWidget *area)
{
draw_brush (area, x, y);
}
/* Drag update */
static void
drag_update (GtkGestureDrag *gesture,
double x,
double y,
GtkWidget *area)
{
double start_x, start_y;
gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
draw_brush (area, start_x + x, start_y + y);
}
/* Right click to clear */
static void
pressed (GtkGestureClick *gesture,
int n_press,
double x,
double y,
GtkWidget *area)
{
clear_surface ();
gtk_widget_queue_draw (area);
}
/* Cleanup on window close */
static void
close_window (void)
{
if (surface)
cairo_surface_destroy (surface);
}
static void
activate (GtkApplication *app, gpointer user_data)
{
GtkWidget *window;
GtkWidget *frame;
GtkWidget *drawing_area;
GtkGesture *drag;
GtkGesture *press;
// Create main window
window = gtk_application_window_new (app);
gtk_window_set_title (GTK_WINDOW (window), "Drawing Application");
gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
// Create frame
frame = gtk_frame_new (NULL);
gtk_window_set_child (GTK_WINDOW (window), frame);
// Create drawing area
drawing_area = gtk_drawing_area_new ();
gtk_widget_set_size_request (drawing_area, 100, 100);
gtk_frame_set_child (GTK_FRAME (frame), drawing_area);
// Set draw callback
gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (drawing_area), draw_cb, NULL, NULL);
// Connect resize signal
g_signal_connect_after (drawing_area, "resize", G_CALLBACK (resize_cb), NULL);
// Set up drag gesture (left button)
drag = gtk_gesture_drag_new ();
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (drag), GDK_BUTTON_PRIMARY);
gtk_widget_add_controller (drawing_area, GTK_EVENT_CONTROLLER (drag));
g_signal_connect (drag, "drag-begin", G_CALLBACK (drag_begin), drawing_area);
g_signal_connect (drag, "drag-update", G_CALLBACK (drag_update), drawing_area);
// Set up click gesture (right button)
press = gtk_gesture_click_new ();
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (press), GDK_BUTTON_SECONDARY);
gtk_widget_add_controller (drawing_area, GTK_EVENT_CONTROLLER (press));
g_signal_connect (press, "pressed", G_CALLBACK (pressed), drawing_area);
// Show window
gtk_window_present (GTK_WINDOW (window));
}
int
main (int argc, char **argv)
{
GtkApplication *app;
int status;
app = gtk_application_new ("org.example.DrawingApp", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
status = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return status;
}
ListView and Model
// listview_app.c
#include <gtk/gtk.h>
/* Data item structure */
typedef struct {
GObject parent_instance;
char *name;
char *description;
int priority;
} TaskItem;
typedef struct {
GObjectClass parent_class;
} TaskItemClass;
G_DEFINE_TYPE (TaskItem, task_item, G_TYPE_OBJECT)
static void
task_item_finalize (GObject *object)
{
TaskItem *item = (TaskItem *)object;
g_free (item->name);
g_free (item->description);
G_OBJECT_CLASS (task_item_parent_class)->finalize (object);
}
static void
task_item_class_init (TaskItemClass *class)
{
G_OBJECT_CLASS (class)->finalize = task_item_finalize;
}
static void
task_item_init (TaskItem *item)
{
// Initialization
}
static TaskItem *
task_item_new (const char *name, const char *description, int priority)
{
TaskItem *item = g_object_new (task_item_get_type (), NULL);
item->name = g_strdup (name);
item->description = g_strdup (description);
item->priority = priority;
return item;
}
/* List item setup */
static void
setup_listitem (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *box, *name_label, *desc_label, *priority_label;
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
gtk_widget_set_margin_top (box, 8);
gtk_widget_set_margin_bottom (box, 8);
gtk_widget_set_margin_start (box, 12);
gtk_widget_set_margin_end (box, 12);
name_label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (name_label), 0.0);
gtk_widget_add_css_class (name_label, "heading");
desc_label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (desc_label), 0.0);
gtk_widget_add_css_class (desc_label, "dim-label");
priority_label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (priority_label), 1.0);
gtk_widget_add_css_class (priority_label, "caption");
gtk_box_append (GTK_BOX (box), name_label);
gtk_box_append (GTK_BOX (box), desc_label);
gtk_box_append (GTK_BOX (box), priority_label);
gtk_list_item_set_child (list_item, box);
g_object_set_data (G_OBJECT (list_item), "name_label", name_label);
g_object_set_data (G_OBJECT (list_item), "desc_label", desc_label);
g_object_set_data (G_OBJECT (list_item), "priority_label", priority_label);
}
/* List item data binding */
static void
bind_listitem (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *name_label, *desc_label, *priority_label;
TaskItem *item;
char *priority_text;
item = gtk_list_item_get_item (list_item);
name_label = g_object_get_data (G_OBJECT (list_item), "name_label");
desc_label = g_object_get_data (G_OBJECT (list_item), "desc_label");
priority_label = g_object_get_data (G_OBJECT (list_item), "priority_label");
gtk_label_set_text (GTK_LABEL (name_label), item->name);
gtk_label_set_text (GTK_LABEL (desc_label), item->description);
priority_text = g_strdup_printf ("Priority: %d", item->priority);
gtk_label_set_text (GTK_LABEL (priority_label), priority_text);
g_free (priority_text);
}
static void
activate (GtkApplication *app, gpointer user_data)
{
GtkWidget *window;
GtkWidget *scrolled_window;
GtkWidget *list_view;
GListStore *model;
GtkListItemFactory *factory;
GtkSingleSelection *selection_model;
// Create main window
window = gtk_application_window_new (app);
gtk_window_set_title (GTK_WINDOW (window), "Task List");
gtk_window_set_default_size (GTK_WINDOW (window), 400, 600);
// Create scrolled window
scrolled_window = gtk_scrolled_window_new ();
gtk_window_set_child (GTK_WINDOW (window), scrolled_window);
// Create list model
model = g_list_store_new (task_item_get_type ());
// Add sample data
g_list_store_append (model, task_item_new ("Create Documentation", "Write technical specifications for the project", 1));
g_list_store_append (model, task_item_new ("Code Review", "Review team member's code", 2));
g_list_store_append (model, task_item_new ("Bug Fix", "Fix reported bugs", 1));
g_list_store_append (model, task_item_new ("New Feature", "Implement user authentication feature", 3));
g_list_store_append (model, task_item_new ("Write Tests", "Create unit tests and E2E tests", 2));
// Create selection model
selection_model = gtk_single_selection_new (G_LIST_MODEL (model));
// Create list item factory
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_listitem), NULL);
g_signal_connect (factory, "bind", G_CALLBACK (bind_listitem), NULL);
// Create list view
list_view = gtk_list_view_new (GTK_SELECTION_MODEL (selection_model), factory);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), list_view);
// Show window
gtk_window_present (GTK_WINDOW (window));
}
int
main (int argc, char **argv)
{
GtkApplication *app;
int status;
app = gtk_application_new ("org.example.TaskListApp", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
status = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return status;
}
CSS Styling
/* style.css */
@import url("resource:///org/gtk/libgtk/theme/Adwaita/gtk.css");
.main-window {
background-color: #f6f5f4;
color: #2e3436;
}
.title-1 {
font-size: 2em;
font-weight: bold;
color: #1c71d8;
margin-bottom: 1em;
}
.card {
background-color: white;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
margin: 12px;
padding: 16px;
}
.header-bar {
background: linear-gradient(to bottom, #3584e4, #1c71d8);
color: white;
}
.sidebar {
background-color: #fafafa;
border-right: 1px solid #d3d3d3;
min-width: 250px;
}
.content-area {
padding: 20px;
background-color: white;
}
.suggested-action {
background: linear-gradient(to bottom, #57e389, #33d17a);
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-weight: bold;
}
.suggested-action:hover {
background: linear-gradient(to bottom, #33d17a, #26a269);
}
.destructive-action {
background: linear-gradient(to bottom, #f66151, #e01b24);
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-weight: bold;
}
.destructive-action:hover {
background: linear-gradient(to bottom, #e01b24, #a51d2d);
}
.entry {
border: 1px solid #d3d3d3;
border-radius: 6px;
padding: 8px 12px;
background-color: white;
}
.entry:focus {
border-color: #3584e4;
box-shadow: 0 0 0 2px rgba(53, 132, 228, 0.3);
}
.frame {
border: 1px solid #d3d3d3;
border-radius: 6px;
background-color: white;
}
.frame > label {
color: #5e5c64;
font-weight: bold;
padding: 0 6px;
background-color: white;
}
listview row {
border-bottom: 1px solid #f0f0f0;
padding: 8px;
}
listview row:hover {
background-color: rgba(53, 132, 228, 0.1);
}
listview row:selected {
background-color: #3584e4;
color: white;
}
.dim-label {
opacity: 0.65;
}
.caption {
font-size: 0.8em;
opacity: 0.75;
}
.status-bar {
background-color: #f0f0f0;
border-top: 1px solid #d3d3d3;
padding: 4px 8px;
}
.toolbar {
background: linear-gradient(to bottom, #fafafa, #ededed);
border-bottom: 1px solid #d3d3d3;
padding: 6px;
}
.circular {
border-radius: 50%;
}
.warning {
color: #e5a50a;
}
.error {
color: #e01b24;
}
.success {
color: #26a269;
}
Meson Build Configuration
# meson.build
project('gtk-demo-app', 'c',
version : '1.0.0',
default_options : ['warning_level=3',
'c_std=c11'])
# Dependencies
gtk_dep = dependency('gtk4')
glib_dep = dependency('glib-2.0')
# Resource file processing
gnome = import('gnome')
resources = gnome.compile_resources('app-resources',
'resources.gresource.xml',
source_dir: 'resources')
# Executable
executable('gtk-demo-app',
['main.c', 'window.c'] + resources,
dependencies : [gtk_dep, glib_dep],
install : true)
# Desktop file
install_data('org.example.GtkDemoApp.desktop',
install_dir : get_option('datadir') / 'applications')
# Icon
install_data('icons/org.example.GtkDemoApp.svg',
install_dir : get_option('datadir') / 'icons/hicolor/scalable/apps')
<!-- resources.gresource.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/example/GtkDemoApp">
<file preprocess="xml-stripblanks">ui/window.ui</file>
<file preprocess="xml-stripblanks">ui/preferences.ui</file>
<file>css/style.css</file>
<file>icons/preferences-symbolic.svg</file>
<file>icons/help-symbolic.svg</file>
</gresource>
</gresources>
Build and Installation
# Configure Meson project
meson setup builddir
# Build
meson compile -C builddir
# Run tests
meson test -C builddir
# Install
meson install -C builddir
# Run for development
./builddir/gtk-demo-app
# Clean build
rm -rf builddir
meson setup builddir
meson compile -C builddir
Compilation using pkg-config
# Compile single file
gcc -o hello `pkg-config --cflags --libs gtk4` hello.c
# Compile multiple files
gcc -o myapp `pkg-config --cflags --libs gtk4` main.c window.c
# Debug build
gcc -g -O0 -o myapp `pkg-config --cflags --libs gtk4` *.c
# Optimized build
gcc -O2 -o myapp `pkg-config --cflags --libs gtk4` *.c
Platform-specific Considerations
Linux
- Easy installation via package managers
- Rich themes and icon sets
- Full Wayland protocol support
- System integration (desktop files, DBus)
Windows
- Development environment setup with MSYS2 or Vcpkg
- Interoperability with Windows API
- HiDPI support
- MSI installer creation
macOS
- Installation via Homebrew
- macOS look & feel adjustments
- App Bundle packaging
- Adapting to macOS-specific UI/UX guidelines