diff --git a/README.md b/README.md
index 2f95606..f43085b 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,27 @@
# Veil
-Veil ist ein auf WebKit basierender Simpler Browser, in GTK
\ No newline at end of file
+Veil ist ein auf WebKit basierender Simpler Browser, in GTK
+
+============================
+DIES IST GERADE NUR EIN HOBBYPROJEKT VON MIR,
+UND DAHER NOCH NICHT PRODUCTION READY!!!!
+============================
+(trozdem freue ich mich über jede pull/push, ein ganzer browser ist nämlich ein bisschen viel arbeit. (heul :,( ))
+
+
+
+┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+┃ Build ┃
+┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+$ ./build.sh clean && ./build.sh build
+
+nach einem erfolgreichem build landet die bin im build/linux ordner und wird in den veil ordner kopiert, die bin muss ich gleichen ordner wie die data/ directory liegen.
+
+
+┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+┃ Code ┃
+┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ja, ich weiß er ist schlecht, und ja ich weiß, er könnte besser sein. bei verbesserungsvorschlägen bitte statt meckern:
+1. Verbessern
+2. Push
+Dankii :)
\ No newline at end of file
diff --git a/Veil/Makefile b/Veil/Makefile
new file mode 100644
index 0000000..060612e
--- /dev/null
+++ b/Veil/Makefile
@@ -0,0 +1,43 @@
+CC = gcc
+PKG_CFLAGS = $(shell pkg-config --cflags gtk+-3.0 webkit2gtk-4.1)
+PKG_LIBS = $(shell pkg-config --libs gtk+-3.0 webkit2gtk-4.1)
+
+# === PERFORMANCE-OPTIMIZED BUILD FLAGS ===
+# -O3: Maximum optimization level
+# -march=native: Optimize for current CPU architecture
+# -mtune=native: Tune for current CPU
+# -flto: Link-time optimization for cross-file inlining
+# -ffast-math: Faster floating point (safe for browser UI)
+# -fomit-frame-pointer: Free up a register
+# -pipe: Faster compilation
+
+CFLAGS = $(PKG_CFLAGS) -O3 -march=native -mtune=native -flto \
+ -ffast-math -fomit-frame-pointer -pipe \
+ -Wall -Wextra -Wno-unused-parameter
+
+LDFLAGS = $(PKG_LIBS) -flto -Wl,-O1 -Wl,--as-needed
+
+TARGET = veil
+SRC = src/veil.c
+
+all: $(TARGET)
+
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+# Debug build (slower but easier to debug)
+debug: CFLAGS = $(PKG_CFLAGS) -O0 -g -Wall -Wextra
+debug: LDFLAGS = $(PKG_LIBS)
+debug: $(TARGET)
+
+# Release build with stripped binary
+release: $(TARGET)
+ strip --strip-all $(TARGET)
+
+clean:
+ rm -f $(TARGET)
+
+install: $(TARGET)
+ install -Dm755 $(TARGET) /usr/local/bin/$(TARGET)
+
+.PHONY: all clean install debug release
diff --git a/Veil/Projektdateien (EXTERN)/Weil Icon.afphoto b/Veil/Projektdateien (EXTERN)/Weil Icon.afphoto
new file mode 100644
index 0000000..8270005
Binary files /dev/null and b/Veil/Projektdateien (EXTERN)/Weil Icon.afphoto differ
diff --git a/Veil/Projektdateien (EXTERN)/Weil Icon.afphoto~lock~ b/Veil/Projektdateien (EXTERN)/Weil Icon.afphoto~lock~
new file mode 100644
index 0000000..e7af586
Binary files /dev/null and b/Veil/Projektdateien (EXTERN)/Weil Icon.afphoto~lock~ differ
diff --git a/Veil/build.sh b/Veil/build.sh
new file mode 100755
index 0000000..0fc5e5d
--- /dev/null
+++ b/Veil/build.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+#
+# Veil Browser Build Script
+# Linux build only
+#
+
+set -e
+
+PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
+BUILD_DIR="$PROJECT_DIR/build"
+SRC="$PROJECT_DIR/src/veil.c"
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+print_status() { echo -e "${GREEN}[*]${NC} $1"; }
+print_warn() { echo -e "${YELLOW}[!]${NC} $1"; }
+print_error() { echo -e "${RED}[x]${NC} $1"; }
+
+show_help() {
+ echo "Veil Browser Build Script"
+ echo ""
+ echo "Usage: $0 [target]"
+ echo ""
+ echo "Targets:"
+ echo " build Build for Linux (default)"
+ echo " clean Remove build artifacts"
+ echo " help Show this help"
+ echo ""
+}
+
+build_linux() {
+ print_status "Building Veil for Linux..."
+
+ # Check dependencies
+ if ! pkg-config --exists gtk+-3.0 webkit2gtk-4.1 2>/dev/null; then
+ print_error "Missing dependencies. Install: gtk3 webkit2gtk"
+ exit 1
+ fi
+
+ CFLAGS=$(pkg-config --cflags gtk+-3.0 webkit2gtk-4.1)
+ LIBS=$(pkg-config --libs gtk+-3.0 webkit2gtk-4.1)
+
+ mkdir -p "$BUILD_DIR/linux"
+
+ gcc $CFLAGS \
+ -O3 -march=native -mtune=native -flto -ffast-math \
+ -fomit-frame-pointer -pipe \
+ -Wall -Wextra -Wno-unused-parameter \
+ -o "$BUILD_DIR/linux/veil" "$SRC" \
+ $LIBS -flto -Wl,-O1 -Wl,--as-needed
+
+ # Copy to project root
+ cp "$BUILD_DIR/linux/veil" "$PROJECT_DIR/veil"
+
+ print_status "Build complete: $BUILD_DIR/linux/veil"
+ print_status "Also copied to: $PROJECT_DIR/veil"
+}
+
+clean() {
+ print_status "Cleaning build artifacts..."
+ rm -rf "$BUILD_DIR"
+ rm -f "$PROJECT_DIR/veil"
+ print_status "Clean complete."
+}
+
+# Main
+cd "$PROJECT_DIR"
+
+case "${1:-build}" in
+ build|linux)
+ build_linux
+ ;;
+ clean)
+ clean
+ ;;
+ help|--help|-h)
+ show_help
+ ;;
+ *)
+ print_error "Unknown target: $1"
+ show_help
+ exit 1
+ ;;
+esac
diff --git a/Veil/build/linux/veil b/Veil/build/linux/veil
new file mode 100755
index 0000000..47a2aff
Binary files /dev/null and b/Veil/build/linux/veil differ
diff --git a/Veil/data/icons/hicolor/128x128/apps/veil.png b/Veil/data/icons/hicolor/128x128/apps/veil.png
new file mode 100644
index 0000000..8256e80
Binary files /dev/null and b/Veil/data/icons/hicolor/128x128/apps/veil.png differ
diff --git a/Veil/data/icons/hicolor/16x16/apps/veil.png b/Veil/data/icons/hicolor/16x16/apps/veil.png
new file mode 100644
index 0000000..03d80c6
Binary files /dev/null and b/Veil/data/icons/hicolor/16x16/apps/veil.png differ
diff --git a/Veil/data/icons/hicolor/24x24/apps/veil.png b/Veil/data/icons/hicolor/24x24/apps/veil.png
new file mode 100644
index 0000000..20d7ca1
Binary files /dev/null and b/Veil/data/icons/hicolor/24x24/apps/veil.png differ
diff --git a/Veil/data/icons/hicolor/256x256/apps/veil.png b/Veil/data/icons/hicolor/256x256/apps/veil.png
new file mode 100644
index 0000000..0d0db88
Binary files /dev/null and b/Veil/data/icons/hicolor/256x256/apps/veil.png differ
diff --git a/Veil/data/icons/hicolor/32x32/apps/veil.png b/Veil/data/icons/hicolor/32x32/apps/veil.png
new file mode 100644
index 0000000..c66c719
Binary files /dev/null and b/Veil/data/icons/hicolor/32x32/apps/veil.png differ
diff --git a/Veil/data/icons/hicolor/48x48/apps/veil.png b/Veil/data/icons/hicolor/48x48/apps/veil.png
new file mode 100644
index 0000000..3dd464e
Binary files /dev/null and b/Veil/data/icons/hicolor/48x48/apps/veil.png differ
diff --git a/Veil/data/icons/hicolor/512x512/apps/veil.png b/Veil/data/icons/hicolor/512x512/apps/veil.png
new file mode 100644
index 0000000..4a69507
Binary files /dev/null and b/Veil/data/icons/hicolor/512x512/apps/veil.png differ
diff --git a/Veil/data/icons/hicolor/64x64/apps/veil.png b/Veil/data/icons/hicolor/64x64/apps/veil.png
new file mode 100644
index 0000000..68fa244
Binary files /dev/null and b/Veil/data/icons/hicolor/64x64/apps/veil.png differ
diff --git a/Veil/data/icons/hicolor/scalable/apps/veil.svg b/Veil/data/icons/hicolor/scalable/apps/veil.svg
new file mode 100644
index 0000000..d7e6944
--- /dev/null
+++ b/Veil/data/icons/hicolor/scalable/apps/veil.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/Veil/data/meson.build b/Veil/data/meson.build
new file mode 100644
index 0000000..24f91bb
--- /dev/null
+++ b/Veil/data/meson.build
@@ -0,0 +1,14 @@
+# Desktop file installation
+install_data('veil.desktop',
+ install_dir: join_paths(get_option('datadir'), 'applications')
+)
+
+# Icon installation (PNG at various sizes)
+icon_sizes = ['16x16', '24x24', '32x32', '48x48', '64x64', '128x128', '256x256', '512x512']
+
+foreach size : icon_sizes
+ install_data(
+ join_paths('icons/hicolor', size, 'apps/veil.png'),
+ install_dir: join_paths(get_option('datadir'), 'icons/hicolor', size, 'apps')
+ )
+endforeach
diff --git a/Veil/data/veil.desktop b/Veil/data/veil.desktop
new file mode 100644
index 0000000..442a386
--- /dev/null
+++ b/Veil/data/veil.desktop
@@ -0,0 +1,10 @@
+#!/usr/bin/env xdg-open
+[Desktop Entry]
+Name=Veil
+Comment=Minimalistischer WebKit-Browser
+Exec=veil
+Icon=veil
+Terminal=false
+Type=Application
+Categories=Network;WebBrowser;
+Keywords=browser;web;internet;
diff --git a/Veil/meson.build b/Veil/meson.build
new file mode 100644
index 0000000..4048ea1
--- /dev/null
+++ b/Veil/meson.build
@@ -0,0 +1,36 @@
+project('veil', 'c',
+ version: '0.1.0',
+ default_options: [
+ 'warning_level=2',
+ 'c_std=c11',
+ 'optimization=3',
+ 'b_lto=true',
+ 'strip=true'
+ ]
+)
+
+gtk3_dep = dependency('gtk+-3.0', version: '>= 3.24')
+webkit2_dep = dependency('webkit2gtk-4.1')
+
+# Performance-optimized compiler flags
+add_project_arguments(
+ '-ffast-math',
+ '-fomit-frame-pointer',
+ '-pipe',
+ language: 'c'
+)
+
+# Native CPU optimization (comment out for portable builds)
+add_project_arguments(
+ '-march=native',
+ '-mtune=native',
+ language: 'c'
+)
+
+executable('veil',
+ 'src/veil.c',
+ dependencies: [gtk3_dep, webkit2_dep],
+ install: true
+)
+
+subdir('data')
diff --git a/Veil/src/pages/settings.h b/Veil/src/pages/settings.h
new file mode 100644
index 0000000..59f7312
--- /dev/null
+++ b/Veil/src/pages/settings.h
@@ -0,0 +1,189 @@
+/*
+ * Veil Browser - Settings Page (about:settings)
+ */
+
+#ifndef VEIL_PAGE_SETTINGS_H
+#define VEIL_PAGE_SETTINGS_H
+
+static const char *SETTINGS_HTML =
+""
+"
Settings"
+""
+""
+""
+"
"
+"
Allgemein
"
+"
"
+" "
+" "
+"
"
+"
"
+" "
+" "
+"
"
+"
"
+" "
+" "
+"
"
+"
"
+" "
+" "
+" "
+"
"
+"
"
+"
"
+"
Anpassung
"
+"
"
+" "
+" "
+"
"
+"
"
+" "
+" "
+"
"
+"
"
+" "
+" "
+"
"
+"
"
+"
Farben
"
+"
"
+" "
+" "
+" "
+"
"
+"
"
+" "
+" "
+" "
+"
"
+"
"
+" "
+" "
+" "
+"
"
+"
"
+" "
+" "
+" "
+"
"
+"
"
+" "
+" "
+" "
+"
"
+"
"
+"
"
+"
"
+"
Info
"
+"
"
+"
Tastenkürzel
"
+"
Ctrl+T Neuer Tab
"
+"
Ctrl+W Tab schließen
"
+"
Ctrl+L URL-Leiste fokussieren
"
+"
Ctrl+R / F5 Neu laden
"
+"
F11 Vollbild umschalten
"
+"
"
+"
"
+"
Über Veil
"
+"
Ein Privater WebKit-Browser, ohne unnötigen Müll :)
"
+"
Gebaut mit GTK3 und WebKit2GTK
"
+"
Entwickelt von Packed https://papp-box.de/
"
+"
"
+"
Veil Browser v1.0
"
+"
"
+"
"
+"";
+
+#endif /* VEIL_PAGE_SETTINGS_H */
diff --git a/Veil/src/pages/start.h b/Veil/src/pages/start.h
new file mode 100644
index 0000000..1c8b9d8
--- /dev/null
+++ b/Veil/src/pages/start.h
@@ -0,0 +1,398 @@
+/*
+ * Veil Browser - Start Page (about:start)
+ */
+
+#ifndef VEIL_PAGE_START_H
+#define VEIL_PAGE_START_H
+
+static const char *START_HTML =
+""
+"Veil"
+""
+""
+"
"
+"
"
+"

"
+"
"
+"
██╗ ██╗███████╗██╗██╗ \n"
+"██║ ██║██╔════╝██║██║ \n"
+"██║ ██║█████╗ ██║██║ \n"
+"╚██╗ ██╔╝██╔══╝ ██║██║ \n"
+" ╚████╔╝ ███████╗██║███████╗\n"
+" ╚═══╝ ╚══════╝╚═╝╚══════╝
"
+"
"
+"
"
+"
"
+"
"
+""
+""
+"";
+
+#endif /* VEIL_PAGE_START_H */
diff --git a/Veil/src/veil.c b/Veil/src/veil.c
new file mode 100644
index 0000000..aad8927
--- /dev/null
+++ b/Veil/src/veil.c
@@ -0,0 +1,2414 @@
+/*
+ * Veil Browser - Minimalistischer WebKit-Browser
+ * Entwickelt von Erik Hellak
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include "pages/settings.h"
+#include "pages/start.h"
+
+typedef struct _VeilTab VeilTab;
+typedef struct _VeilBrowser VeilBrowser;
+typedef struct _VeilSettings VeilSettings;
+
+struct _VeilSettings {
+ char bg_color[8];
+ char accent_color[8];
+ char grad_start[8];
+ char grad_mid[8];
+ char grad_end[8];
+ char download_dir[512]; /* Benutzerdefiniertes Download-Verzeichnis */
+ int search_engine; /* 0=DuckDuckGo, 1=Google, 2=Brave, 3=Startpage */
+ int show_bookmarks; /* 0=versteckt, 1=sichtbar */
+ int show_quicklinks; /* 0=versteckt, 1=sichtbar */
+ int use_bg_image; /* 0=nein, 1=ja */
+ int bg_opacity; /* 0-100 (Deckkraft) */
+};
+
+static const VeilSettings DEFAULT_SETTINGS = {
+ "#0a0a0f",
+ "#4a9eff",
+ "#00d4ff",
+ "#0066ff",
+ "#cc00ff",
+ "", /* Leer = System-Standard-Download-Verzeichnis */
+ 0,
+ 1, /* Lesezeichen standardmäßig sichtbar */
+ 1, /* Quick-Links standardmäßig sichtbar */
+ 0, /* Kein Hintergrundbild standardmäßig */
+ 100 /* Volle Deckkraft standardmäßig */
+};
+
+struct _VeilTab {
+ VeilBrowser *browser;
+ WebKitWebView *webview;
+ GtkWidget *tab_box;
+ GtkWidget *tab_label;
+ GtkWidget *close_button;
+};
+
+typedef struct _VeilBookmark {
+ char *name;
+ char *url; /* NULL für Ordner */
+ gboolean is_folder;
+ GList *children; /* Liste von VeilBookmark-Kindern für Ordner */
+} VeilBookmark;
+
+typedef struct _VeilDownload {
+ char *filename;
+ char *path; /* Vollständiger Dateipfad zum Download */
+ char *url;
+ gint64 timestamp;
+} VeilDownload;
+
+typedef struct _VeilActiveDownload {
+ WebKitDownload *download;
+ GtkWidget *row;
+ GtkWidget *progress_bar;
+ GtkWidget *status_label;
+ GtkWidget *speed_label;
+ gint64 last_bytes;
+ gint64 last_time;
+} VeilActiveDownload;
+
+struct _VeilBrowser {
+ GtkWidget *window;
+ GtkWidget *header;
+ GtkWidget *main_box;
+ GtkWidget *tab_container;
+ GtkWidget *nav_bar;
+ GtkWidget *bookmarks_bar;
+ GtkWidget *back_btn;
+ GtkWidget *forward_btn;
+ GtkWidget *reload_btn;
+ GtkWidget *url_entry;
+ GtkWidget *star_btn;
+ GtkWidget *star_popover;
+ GtkWidget *menu_btn;
+ GtkWidget *menu;
+ GtkWidget *progress_bar;
+ GtkWidget *webview_stack;
+ GtkWidget *gradient_line;
+ GList *tabs;
+ VeilTab *current_tab;
+ gboolean is_maximized;
+ VeilSettings settings;
+ GtkCssProvider *css_provider;
+ WebKitWebContext *web_context;
+ GList *bookmarks; /* Liste von VeilBookmark */
+ GList *downloads; /* Liste von VeilDownload */
+ GList *active_downloads; /* Liste von VeilActiveDownload */
+ GtkWidget *downloads_popover;
+ GtkWidget *downloads_list_box;
+ GtkWidget *active_downloads_box;
+ GtkWidget *current_folder_popover; /* Aktuell geöffnetes Ordner-Popup */
+};
+
+static VeilBrowser *g_browser = NULL;
+
+static void save_settings(VeilBrowser *b);
+static void load_settings(VeilBrowser *b);
+static void apply_css(VeilBrowser *b);
+static void show_settings_page(VeilBrowser *b);
+
+static char *build_css(VeilBrowser *b) {
+ VeilSettings *s = &b->settings;
+ return g_strdup_printf(
+ "@define-color bg %s;\n"
+ "@define-color surface shade(%s, 1.1);\n"
+ "@define-color hover shade(%s, 1.3);\n"
+ "@define-color accent %s;\n"
+ "@define-color text #e0e0e8;\n"
+ "@define-color text_dim #606070;\n"
+ "@define-color red #f85149;\n"
+ "\n"
+ "* { font-family: 'JetBrains Mono', 'Fira Code', monospace; }\n"
+ "window, .background { background: @bg; }\n"
+ "\n"
+ "decoration, decoration:backdrop {\n"
+ " border-radius: 8px;\n"
+ " border: 2px solid;\n"
+ " border-image: linear-gradient(135deg, %s, %s, %s) 1;\n"
+ " background: @bg;\n"
+ " box-shadow: 0 0 15px alpha(%s, 0.3);\n"
+ "}\n"
+ "\n"
+ "headerbar, headerbar:backdrop {\n"
+ " background: @bg;\n"
+ " border: none; box-shadow: none;\n"
+ " min-height: 28px; padding: 0 4px;\n"
+ "}\n"
+ "\n"
+ ".tab-box { background: transparent; border-radius: 4px 4px 0 0; padding: 2px 8px; }\n"
+ ".tab-box:hover { background: @hover; }\n"
+ ".tab-box.active { background: @surface; }\n"
+ ".tab-box label { color: @text_dim; font-size: 12px; }\n"
+ ".tab-box.active label { color: @text; }\n"
+ ".tab-close { min-width: 14px; min-height: 14px; padding: 0; margin-left: 6px; border-radius: 2px; opacity: 0; }\n"
+ ".tab-box:hover .tab-close { opacity: 0.5; }\n"
+ ".tab-close:hover { background: @red; opacity: 1; }\n"
+ "\n"
+ ".header-btn { min-width: 16px; min-height: 16px; padding: 2px; margin: 0 1px; border-radius: 3px; opacity: 0.5; }\n"
+ ".header-btn:hover { background: @hover; opacity: 1; }\n"
+ ".header-btn.close:hover { background: @red; }\n"
+ "\n"
+ ".brand { font-size: 12px; font-weight: bold; color: %s; text-shadow: 0 0 10px %s, 0 0 20px %s; }\n"
+ "\n"
+ ".nav-bar { background: @surface; padding: 3px 4px; }\n"
+ ".nav-btn { min-width: 22px; min-height: 22px; padding: 0; border-radius: 3px; opacity: 0.6; }\n"
+ ".nav-btn:hover { background: @hover; opacity: 1; }\n"
+ ".nav-btn:disabled { opacity: 0.2; }\n"
+ ".star-btn { min-width: 32px; min-height: 28px; padding: 0 8px; border-radius: 4px; font-size: 18px; opacity: 0.7; }\n"
+ ".star-btn:hover { background: @hover; opacity: 1; color: @accent; }\n"
+ "\n"
+ ".url-box { background: @bg; border: 1px solid shade(@bg, 1.5); border-radius: 4px; padding: 0 6px; min-height: 26px; }\n"
+ ".url-box:focus-within { border-color: @accent; }\n"
+ ".url-entry { background: transparent; border: none; box-shadow: none; color: @text; font-size: 13px; }\n"
+ "\n"
+ ".gradient-line { min-height: 2px; background: linear-gradient(90deg, %s, %s, %s); }\n"
+ ".progress { min-height: 2px; background: transparent; }\n"
+ ".progress progress { background: linear-gradient(90deg, %s, %s, %s); }\n"
+ "\n"
+ ".bookmarks-bar { background: @surface; padding: 4px 0; border-bottom: 1px solid shade(@bg, 1.3); }\n"
+ ".bookmark-btn { padding: 4px 10px; font-size: 12px; color: @text_dim; border-radius: 4px; }\n"
+ ".bookmark-btn:hover { color: @text; background: @hover; }\n"
+ "\n"
+ ".veil-menu { background: @surface; border: 2px solid; border-image: linear-gradient(135deg, %s, %s, %s) 1; padding: 4px; box-shadow: 0 4px 20px alpha(%s, 0.3); }\n"
+ ".veil-menu menuitem { padding: 6px 12px; color: @text; font-size: 13px; }\n"
+ ".veil-menu menuitem:hover { background: @hover; }\n"
+ ".veil-popover, .veil-popover.background { background: @surface; border: 2px solid; border-image: linear-gradient(135deg, %s, %s, %s) 1; box-shadow: 0 4px 20px alpha(%s, 0.3); padding: 4px; }\n"
+ ".veil-popover arrow, popover.veil-popover arrow, popover arrow { border: none; background: none; min-width: 0; min-height: 0; box-shadow: none; opacity: 0; -gtk-icon-source: none; }\n"
+ ".veil-popover > contents { background: @surface; border: none; }\n"
+ ".veil-popover label { color: @text; font-size: 13px; }\n"
+ ".veil-popover button { color: @text; }\n"
+ ".veil-popover button:hover { background: @hover; }\n"
+ ".veil-popover entry { background: @bg; border: 1px solid shade(@bg, 1.5); border-radius: 4px; padding: 6px 8px; color: @text; }\n"
+ ".veil-popover entry:focus { border-color: @accent; }\n"
+ ".veil-popover combobox { background: @bg; border: 1px solid shade(@bg, 1.5); border-radius: 4px; }\n"
+ ".veil-popover progressbar { min-height: 6px; }\n"
+ ".veil-popover progressbar trough { background: shade(@bg, 1.2); border-radius: 3px; min-height: 6px; }\n"
+ ".veil-popover progressbar progress { background: linear-gradient(90deg, %s, %s, %s); border-radius: 3px; min-height: 6px; }\n"
+ ".bookmark-folder { padding: 4px 10px; font-size: 12px; color: @text_dim; border-radius: 4px; }\n"
+ ".bookmark-folder:hover { color: @text; background: @hover; }\n"
+ ".dim-label { color: @text_dim; font-size: 11px; }\n"
+ ".suggested-action { background: @accent; color: @bg; }\n"
+ ".suggested-action:hover { background: shade(@accent, 1.1); }\n"
+ ".destructive-action { background: @red; color: white; }\n"
+ ".destructive-action:hover { background: shade(@red, 1.1); }\n"
+ "\n"
+ "button { color: @accent; }\n"
+ "button:hover { background: @hover; }\n"
+ "entry selection { background: alpha(@accent, 0.3); }\n"
+ "scale trough highlight { background: @accent; }\n"
+ "switch:checked slider { background: @accent; }\n"
+ "checkbutton check:checked, radiobutton radio:checked { background: @accent; border-color: @accent; }\n",
+ s->bg_color, s->bg_color, s->bg_color, s->accent_color,
+ s->grad_start, s->grad_mid, s->grad_end, s->grad_start,
+ s->accent_color, s->accent_color, s->accent_color, /* brand glow */
+ s->grad_start, s->grad_mid, s->grad_end,
+ s->grad_start, s->grad_mid, s->grad_end,
+ s->grad_start, s->grad_mid, s->grad_end, s->grad_start, /* Menü-Gradient */
+ s->grad_start, s->grad_mid, s->grad_end, s->grad_start, /* Popover-Gradient */
+ s->grad_start, s->grad_mid, s->grad_end /* Fortschrittsbalken-Gradient */
+ );
+}
+
+static void apply_css(VeilBrowser *b) {
+ char *css = build_css(b);
+ gtk_css_provider_load_from_data(b->css_provider, css, -1, NULL);
+ g_free(css);
+}
+
+static char *get_config_path(void) {
+ const char *home = g_get_home_dir();
+ return g_build_filename(home, ".config", "veil", "settings.conf", NULL);
+}
+
+static void save_settings(VeilBrowser *b) {
+ char *path = get_config_path();
+ char *dir = g_path_get_dirname(path);
+ g_mkdir_with_parents(dir, 0755);
+ g_free(dir);
+
+ FILE *f = fopen(path, "w");
+ if (f) {
+ fprintf(f, "bg=%s\n", b->settings.bg_color);
+ fprintf(f, "accent=%s\n", b->settings.accent_color);
+ fprintf(f, "grad_start=%s\n", b->settings.grad_start);
+ fprintf(f, "grad_mid=%s\n", b->settings.grad_mid);
+ fprintf(f, "grad_end=%s\n", b->settings.grad_end);
+ fprintf(f, "download_dir=%s\n", b->settings.download_dir);
+ fprintf(f, "search_engine=%d\n", b->settings.search_engine);
+ fprintf(f, "show_bookmarks=%d\n", b->settings.show_bookmarks);
+ fprintf(f, "show_quicklinks=%d\n", b->settings.show_quicklinks);
+ fprintf(f, "use_bg_image=%d\n", b->settings.use_bg_image);
+ fprintf(f, "bg_opacity=%d\n", b->settings.bg_opacity);
+ fclose(f);
+ }
+ g_free(path);
+}
+
+static void load_settings(VeilBrowser *b) {
+ b->settings = DEFAULT_SETTINGS;
+
+ char *path = get_config_path();
+ FILE *f = fopen(path, "r");
+ if (f) {
+ char line[600]; /* Larger buffer for download_dir path */
+ while (fgets(line, sizeof(line), f)) {
+ char *eq = strchr(line, '=');
+ if (!eq) continue;
+ *eq = 0;
+ char *val = eq + 1;
+ val[strcspn(val, "\n")] = 0;
+ if (strcmp(line, "bg") == 0) strncpy(b->settings.bg_color, val, 7);
+ else if (strcmp(line, "accent") == 0) strncpy(b->settings.accent_color, val, 7);
+ else if (strcmp(line, "grad_start") == 0) strncpy(b->settings.grad_start, val, 7);
+ else if (strcmp(line, "grad_mid") == 0) strncpy(b->settings.grad_mid, val, 7);
+ else if (strcmp(line, "grad_end") == 0) strncpy(b->settings.grad_end, val, 7);
+ else if (strcmp(line, "download_dir") == 0) strncpy(b->settings.download_dir, val, 511);
+ else if (strcmp(line, "search_engine") == 0) b->settings.search_engine = atoi(val);
+ else if (strcmp(line, "show_bookmarks") == 0) b->settings.show_bookmarks = atoi(val);
+ else if (strcmp(line, "show_quicklinks") == 0) b->settings.show_quicklinks = atoi(val);
+ else if (strcmp(line, "use_bg_image") == 0) b->settings.use_bg_image = atoi(val);
+ else if (strcmp(line, "bg_opacity") == 0) b->settings.bg_opacity = atoi(val);
+ }
+ fclose(f);
+ }
+ g_free(path);
+}
+
+/* === Lesezeichen-Verwaltung === */
+static char *get_bookmarks_path(void) {
+ const char *home = g_get_home_dir();
+ return g_build_filename(home, ".config", "veil", "bookmarks.conf", NULL);
+}
+
+static void save_bookmark_item(FILE *f, VeilBookmark *bm, int depth) {
+ for (int i = 0; i < depth; i++) fprintf(f, ">");
+ if (bm->is_folder) {
+ fprintf(f, "F|%s\n", bm->name);
+ for (GList *c = bm->children; c; c = c->next) {
+ save_bookmark_item(f, c->data, depth + 1);
+ }
+ } else {
+ fprintf(f, "B|%s|%s\n", bm->name, bm->url ? bm->url : "");
+ }
+}
+
+static void save_bookmarks(VeilBrowser *b) {
+ char *path = get_bookmarks_path();
+ char *dir = g_path_get_dirname(path);
+ g_mkdir_with_parents(dir, 0755);
+ g_free(dir);
+
+ FILE *f = fopen(path, "w");
+ if (f) {
+ for (GList *l = b->bookmarks; l; l = l->next) {
+ save_bookmark_item(f, l->data, 0);
+ }
+ fclose(f);
+ }
+ g_free(path);
+}
+
+static void free_bookmark(VeilBookmark *bm) {
+ if (!bm) return;
+ g_free(bm->name);
+ g_free(bm->url);
+ for (GList *c = bm->children; c; c = c->next) {
+ free_bookmark(c->data);
+ }
+ g_list_free(bm->children);
+ g_free(bm);
+}
+
+static void load_bookmarks(VeilBrowser *b) {
+ /* Existierende Lesezeichen leeren */
+ for (GList *l = b->bookmarks; l; l = l->next) {
+ free_bookmark(l->data);
+ }
+ g_list_free(b->bookmarks);
+ b->bookmarks = NULL;
+
+ char *path = get_bookmarks_path();
+ FILE *f = fopen(path, "r");
+ if (f) {
+ char line[512];
+ VeilBookmark *folder_stack[16] = {NULL};
+ int stack_depth = 0;
+
+ while (fgets(line, sizeof(line), f)) {
+ line[strcspn(line, "\n")] = 0;
+
+ /* Verschachtelungstiefe zählen (Anzahl der '>' am Anfang) */
+ int depth = 0;
+ char *p = line;
+ while (*p == '>') { depth++; p++; }
+
+ stack_depth = depth;
+
+ char *type_sep = strchr(p, '|');
+ if (!type_sep) continue;
+
+ char type = *p;
+ p = type_sep + 1;
+
+ VeilBookmark *bm = g_new0(VeilBookmark, 1);
+
+ if (type == 'F') {
+ bm->is_folder = TRUE;
+ bm->name = g_strdup(p);
+ bm->url = NULL;
+ bm->children = NULL;
+ } else {
+ /* Format: B|name|url */
+ char *url_sep = strchr(p, '|');
+ if (url_sep) {
+ *url_sep = 0;
+ bm->name = g_strdup(p);
+ bm->url = g_strdup(url_sep + 1);
+ } else {
+ bm->name = g_strdup(p);
+ bm->url = g_strdup("");
+ }
+ bm->is_folder = FALSE;
+ }
+
+ if (depth > 0 && stack_depth <= 15 && folder_stack[depth - 1]) {
+ folder_stack[depth - 1]->children = g_list_append(
+ folder_stack[depth - 1]->children, bm);
+ } else {
+ b->bookmarks = g_list_append(b->bookmarks, bm);
+ }
+
+ if (bm->is_folder && depth < 15) {
+ folder_stack[depth] = bm;
+ }
+ }
+ fclose(f);
+ }
+ g_free(path);
+}
+
+/* === Download-Verwaltung === */
+static char *get_downloads_path(void) {
+ const char *home = g_get_home_dir();
+ return g_build_filename(home, ".config", "veil", "downloads.conf", NULL);
+}
+
+static void rebuild_downloads_list(VeilBrowser *b);
+
+static void save_downloads(VeilBrowser *b) {
+ char *path = get_downloads_path();
+ char *dir = g_path_get_dirname(path);
+ g_mkdir_with_parents(dir, 0755);
+ g_free(dir);
+
+ FILE *f = fopen(path, "w");
+ if (f) {
+ for (GList *l = b->downloads; l; l = l->next) {
+ VeilDownload *dl = l->data;
+ fprintf(f, "%s|%s|%s|%ld\n", dl->filename, dl->path, dl->url, (long)dl->timestamp);
+ }
+ fclose(f);
+ }
+ g_free(path);
+}
+
+static void load_downloads(VeilBrowser *b) {
+ /* Existierende Downloads freigeben */
+ for (GList *l = b->downloads; l; l = l->next) {
+ VeilDownload *dl = l->data;
+ g_free(dl->filename);
+ g_free(dl->path);
+ g_free(dl->url);
+ g_free(dl);
+ }
+ g_list_free(b->downloads);
+ b->downloads = NULL;
+
+ char *path = get_downloads_path();
+ FILE *f = fopen(path, "r");
+ if (f) {
+ char line[1024];
+ while (fgets(line, sizeof(line), f)) {
+ line[strcspn(line, "\n")] = 0;
+ char *sep1 = strchr(line, '|');
+ if (!sep1) continue;
+ *sep1 = 0;
+ char *sep2 = strchr(sep1 + 1, '|');
+ if (!sep2) continue;
+ *sep2 = 0;
+ char *sep3 = strchr(sep2 + 1, '|');
+ if (!sep3) continue;
+ *sep3 = 0;
+
+ VeilDownload *dl = g_new0(VeilDownload, 1);
+ dl->filename = g_strdup(line);
+ dl->path = g_strdup(sep1 + 1);
+ dl->url = g_strdup(sep2 + 1);
+ dl->timestamp = atol(sep3 + 1);
+ b->downloads = g_list_prepend(b->downloads, dl);
+ }
+ fclose(f);
+ }
+ g_free(path);
+}
+
+static void rebuild_downloads_popover(VeilBrowser *b);
+
+static void on_clear_downloads(GtkButton *btn, VeilBrowser *b) {
+ (void)btn;
+ for (GList *l = b->downloads; l; l = l->next) {
+ VeilDownload *dl = l->data;
+ g_free(dl->filename);
+ g_free(dl->path);
+ g_free(dl->url);
+ g_free(dl);
+ }
+ g_list_free(b->downloads);
+ b->downloads = NULL;
+ save_downloads(b);
+ rebuild_downloads_popover(b);
+}
+
+static void on_open_file(GtkButton *btn, gpointer user_data) {
+ (void)btn;
+ const char *path = (const char *)user_data;
+ if (!path) return;
+
+ char *uri = g_strdup_printf("file://%s", path);
+
+ GError *error = NULL;
+ gtk_show_uri_on_window(NULL, uri, GDK_CURRENT_TIME, &error);
+ if (error) {
+ g_warning("Could not open file: %s", error->message);
+ g_error_free(error);
+ }
+
+ g_free(uri);
+}
+
+static void on_open_folder(GtkButton *btn, gpointer user_data) {
+ (void)btn;
+ const char *path = (const char *)user_data;
+ if (!path) return;
+
+ char *dir = g_path_get_dirname(path);
+
+ /* Use xdg-open for better compatibility with file managers */
+ char *cmd = g_strdup_printf("xdg-open \"%s\"", dir);
+ GError *error = NULL;
+ if (!g_spawn_command_line_async(cmd, &error)) {
+ g_warning("Could not open folder: %s", error->message);
+ g_error_free(error);
+ }
+ g_free(cmd);
+ g_free(dir);
+}
+
+static void on_remove_download(GtkButton *btn, VeilBrowser *b) {
+ VeilDownload *dl = g_object_get_data(G_OBJECT(btn), "download");
+ if (!dl) return;
+
+ b->downloads = g_list_remove(b->downloads, dl);
+ g_free(dl->filename);
+ g_free(dl->path);
+ g_free(dl->url);
+ g_free(dl);
+
+ save_downloads(b);
+ rebuild_downloads_popover(b);
+}
+
+static void on_cancel_download(GtkButton *btn, VeilBrowser *b) {
+ VeilActiveDownload *ad = g_object_get_data(G_OBJECT(btn), "active_download");
+ if (!ad || !ad->download) return;
+
+ webkit_download_cancel(ad->download);
+
+ b->active_downloads = g_list_remove(b->active_downloads, ad);
+ g_free(ad);
+
+ rebuild_downloads_popover(b);
+}
+
+static char *format_size(guint64 bytes) {
+ if (bytes < 1024) {
+ return g_strdup_printf("%lu B", (unsigned long)bytes);
+ } else if (bytes < 1024 * 1024) {
+ return g_strdup_printf("%.1f KB", bytes / 1024.0);
+ } else if (bytes < 1024 * 1024 * 1024) {
+ return g_strdup_printf("%.1f MB", bytes / (1024.0 * 1024.0));
+ } else {
+ return g_strdup_printf("%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
+ }
+}
+
+static char *format_speed(guint64 bytes_per_sec) {
+ if (bytes_per_sec < 1024) {
+ return g_strdup_printf("%lu B/s", (unsigned long)bytes_per_sec);
+ } else if (bytes_per_sec < 1024 * 1024) {
+ return g_strdup_printf("%.1f KB/s", bytes_per_sec / 1024.0);
+ } else {
+ return g_strdup_printf("%.1f MB/s", bytes_per_sec / (1024.0 * 1024.0));
+ }
+}
+
+static void rebuild_downloads_popover(VeilBrowser *b) {
+ if (!b->downloads_list_box || !b->active_downloads_box) return;
+
+ /* Clear active downloads box */
+ GList *children = gtk_container_get_children(GTK_CONTAINER(b->active_downloads_box));
+ for (GList *l = children; l; l = l->next) {
+ gtk_widget_destroy(GTK_WIDGET(l->data));
+ }
+ g_list_free(children);
+
+ /* Clear completed downloads box */
+ children = gtk_container_get_children(GTK_CONTAINER(b->downloads_list_box));
+ for (GList *l = children; l; l = l->next) {
+ gtk_widget_destroy(GTK_WIDGET(l->data));
+ }
+ g_list_free(children);
+
+ /* Rebuild active downloads */
+ if (b->active_downloads) {
+ for (GList *l = b->active_downloads; l; l = l->next) {
+ VeilActiveDownload *ad = l->data;
+
+ GtkWidget *row = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
+ gtk_widget_set_margin_start(row, 4);
+ gtk_widget_set_margin_end(row, 4);
+ gtk_widget_set_margin_top(row, 6);
+ gtk_widget_set_margin_bottom(row, 6);
+
+ /* Top line: filename and cancel button */
+ GtkWidget *top_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
+
+ const char *dest = webkit_download_get_destination(ad->download);
+ char *filename = dest ? g_path_get_basename(dest) : g_strdup("Downloading...");
+
+ GtkWidget *name_label = gtk_label_new(filename);
+ gtk_label_set_ellipsize(GTK_LABEL(name_label), PANGO_ELLIPSIZE_MIDDLE);
+ gtk_label_set_max_width_chars(GTK_LABEL(name_label), 28);
+ gtk_widget_set_halign(name_label, GTK_ALIGN_START);
+ gtk_widget_set_hexpand(name_label, TRUE);
+ gtk_box_pack_start(GTK_BOX(top_box), name_label, TRUE, TRUE, 0);
+ g_free(filename);
+
+ GtkWidget *cancel_btn = gtk_button_new_from_icon_name("process-stop-symbolic", GTK_ICON_SIZE_MENU);
+ gtk_style_context_add_class(gtk_widget_get_style_context(cancel_btn), "flat");
+ gtk_widget_set_tooltip_text(cancel_btn, "Abbrechen");
+ g_object_set_data(G_OBJECT(cancel_btn), "active_download", ad);
+ g_signal_connect(cancel_btn, "clicked", G_CALLBACK(on_cancel_download), b);
+ gtk_box_pack_end(GTK_BOX(top_box), cancel_btn, FALSE, FALSE, 0);
+
+ gtk_box_pack_start(GTK_BOX(row), top_box, FALSE, FALSE, 0);
+
+ /* Progress bar */
+ ad->progress_bar = gtk_progress_bar_new();
+ gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ad->progress_bar),
+ webkit_download_get_estimated_progress(ad->download));
+ gtk_box_pack_start(GTK_BOX(row), ad->progress_bar, FALSE, FALSE, 0);
+
+ /* Status line */
+ GtkWidget *status_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
+
+ guint64 received = webkit_download_get_received_data_length(ad->download);
+ WebKitURIResponse *response = webkit_download_get_response(ad->download);
+ guint64 total = response ? webkit_uri_response_get_content_length(response) : 0;
+
+ char *received_str = format_size(received);
+ char *total_str = total > 0 ? format_size(total) : g_strdup("?");
+ char *status_text = g_strdup_printf("%s / %s", received_str, total_str);
+
+ ad->status_label = gtk_label_new(status_text);
+ gtk_style_context_add_class(gtk_widget_get_style_context(ad->status_label), "dim-label");
+ gtk_widget_set_halign(ad->status_label, GTK_ALIGN_START);
+ gtk_box_pack_start(GTK_BOX(status_box), ad->status_label, FALSE, FALSE, 0);
+
+ g_free(received_str);
+ g_free(total_str);
+ g_free(status_text);
+
+ ad->speed_label = gtk_label_new("");
+ gtk_style_context_add_class(gtk_widget_get_style_context(ad->speed_label), "dim-label");
+ gtk_widget_set_halign(ad->speed_label, GTK_ALIGN_END);
+ gtk_widget_set_hexpand(ad->speed_label, TRUE);
+ gtk_box_pack_end(GTK_BOX(status_box), ad->speed_label, FALSE, FALSE, 0);
+
+ gtk_box_pack_start(GTK_BOX(row), status_box, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(b->active_downloads_box), row);
+ gtk_widget_show_all(row);
+ }
+ }
+
+ /* Abgeschlossene Downloads anzeigen */
+ if (!b->downloads) {
+ if (!b->active_downloads) {
+ GtkWidget *empty_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
+ gtk_widget_set_margin_top(empty_box, 20);
+ gtk_widget_set_margin_bottom(empty_box, 20);
+
+ GtkWidget *icon = gtk_image_new_from_icon_name("folder-download-symbolic", GTK_ICON_SIZE_DND);
+ gtk_style_context_add_class(gtk_widget_get_style_context(icon), "dim-label");
+ gtk_box_pack_start(GTK_BOX(empty_box), icon, FALSE, FALSE, 0);
+
+ GtkWidget *empty_label = gtk_label_new("Keine Downloads");
+ gtk_style_context_add_class(gtk_widget_get_style_context(empty_label), "dim-label");
+ gtk_box_pack_start(GTK_BOX(empty_box), empty_label, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(b->downloads_list_box), empty_box);
+ gtk_widget_show_all(empty_box);
+ }
+ return;
+ }
+
+ /* Abgeschlossene Downloads zur Liste hinzufügen */
+ for (GList *l = b->downloads; l; l = l->next) {
+ VeilDownload *dl = l->data;
+
+ GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
+ gtk_widget_set_margin_start(row, 4);
+ gtk_widget_set_margin_end(row, 4);
+ gtk_widget_set_margin_top(row, 4);
+ gtk_widget_set_margin_bottom(row, 4);
+
+ /* File icon */
+ GtkWidget *icon = gtk_image_new_from_icon_name("text-x-generic-symbolic", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start(GTK_BOX(row), icon, FALSE, FALSE, 0);
+
+ /* Filename - clickable to open */
+ GtkWidget *name_btn = gtk_button_new_with_label(dl->filename);
+ gtk_style_context_add_class(gtk_widget_get_style_context(name_btn), "flat");
+ gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(name_btn))), PANGO_ELLIPSIZE_MIDDLE);
+ gtk_label_set_max_width_chars(GTK_LABEL(gtk_bin_get_child(GTK_BIN(name_btn))), 22);
+ gtk_widget_set_halign(name_btn, GTK_ALIGN_START);
+ gtk_widget_set_hexpand(name_btn, TRUE);
+ gtk_widget_set_tooltip_text(name_btn, dl->path);
+ g_object_set_data_full(G_OBJECT(name_btn), "path", g_strdup(dl->path), g_free);
+ g_signal_connect(name_btn, "clicked", G_CALLBACK(on_open_file),
+ g_object_get_data(G_OBJECT(name_btn), "path"));
+ gtk_box_pack_start(GTK_BOX(row), name_btn, TRUE, TRUE, 0);
+
+ /* Open folder button */
+ GtkWidget *folder_btn = gtk_button_new_from_icon_name("folder-symbolic", GTK_ICON_SIZE_MENU);
+ gtk_style_context_add_class(gtk_widget_get_style_context(folder_btn), "flat");
+ gtk_widget_set_tooltip_text(folder_btn, "Im Ordner zeigen");
+ g_object_set_data_full(G_OBJECT(folder_btn), "path", g_strdup(dl->path), g_free);
+ g_signal_connect(folder_btn, "clicked", G_CALLBACK(on_open_folder),
+ g_object_get_data(G_OBJECT(folder_btn), "path"));
+ gtk_box_pack_start(GTK_BOX(row), folder_btn, FALSE, FALSE, 0);
+
+ /* Remove button */
+ GtkWidget *remove_btn = gtk_button_new_from_icon_name("list-remove-symbolic", GTK_ICON_SIZE_MENU);
+ gtk_style_context_add_class(gtk_widget_get_style_context(remove_btn), "flat");
+ gtk_widget_set_tooltip_text(remove_btn, "Aus Liste entfernen");
+ g_object_set_data(G_OBJECT(remove_btn), "download", dl);
+ g_signal_connect(remove_btn, "clicked", G_CALLBACK(on_remove_download), b);
+ gtk_box_pack_start(GTK_BOX(row), remove_btn, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(b->downloads_list_box), row);
+ gtk_widget_show_all(row);
+ }
+}
+
+
+static void on_bookmark_click(GtkButton *btn, VeilBrowser *b);
+static void rebuild_bookmarks_bar(VeilBrowser *b);
+
+static VeilBookmark *find_bookmark_by_url(VeilBrowser *b, const char *url) {
+ if (!url) return NULL;
+ for (GList *l = b->bookmarks; l; l = l->next) {
+ VeilBookmark *bm = l->data;
+ if (bm->url && strcmp(bm->url, url) == 0) return bm;
+ /* Also search in folder children */
+ if (bm->is_folder) {
+ for (GList *c = bm->children; c; c = c->next) {
+ VeilBookmark *child = c->data;
+ if (child->url && strcmp(child->url, url) == 0) return child;
+ }
+ }
+ }
+ return NULL;
+}
+
+static void update_star_button(VeilBrowser *b) {
+ if (!b->current_tab || !b->star_btn) return;
+ const char *uri = webkit_web_view_get_uri(b->current_tab->webview);
+ if (!uri || g_str_has_prefix(uri, "veil:") || g_str_has_prefix(uri, "about:")) {
+ gtk_button_set_label(GTK_BUTTON(b->star_btn), "☆");
+ return;
+ }
+ VeilBookmark *bm = find_bookmark_by_url(b, uri);
+ gtk_button_set_label(GTK_BUTTON(b->star_btn), bm ? "★" : "☆");
+}
+
+/* Globale Variablen für Lesezeichen-Popover */
+static GtkWidget *g_star_name_entry = NULL;
+static GtkWidget *g_star_url_entry = NULL;
+static GtkWidget *g_star_folder_combo = NULL;
+static VeilBookmark *g_star_editing_bookmark = NULL;
+
+static void on_star_popover_save(GtkButton *btn, VeilBrowser *b) {
+ (void)btn;
+ if (!g_star_name_entry || !g_star_url_entry) return;
+
+ const char *name = gtk_entry_get_text(GTK_ENTRY(g_star_name_entry));
+ const char *url = gtk_entry_get_text(GTK_ENTRY(g_star_url_entry));
+
+ if (!name || !*name || !url || !*url) return;
+
+ /* Ausgewählten Ordner ermitteln */
+ VeilBookmark *target_folder = NULL;
+ if (g_star_folder_combo) {
+ gint active = gtk_combo_box_get_active(GTK_COMBO_BOX(g_star_folder_combo));
+ if (active > 0) {
+ /* Ordner bei Index (active - 1) finden, da 0 "Keine" ist */
+ int idx = 0;
+ for (GList *l = b->bookmarks; l; l = l->next) {
+ VeilBookmark *bm = l->data;
+ if (bm->is_folder) {
+ if (idx == active - 1) {
+ target_folder = bm;
+ break;
+ }
+ idx++;
+ }
+ }
+ }
+ }
+
+ if (g_star_editing_bookmark) {
+ /* Bestehendes Lesezeichen aktualisieren */
+ g_free(g_star_editing_bookmark->name);
+ g_free(g_star_editing_bookmark->url);
+ g_star_editing_bookmark->name = g_strdup(name);
+ g_star_editing_bookmark->url = g_strdup(url);
+ } else {
+ /* Neues Lesezeichen erstellen */
+ VeilBookmark *bm = g_new0(VeilBookmark, 1);
+ bm->name = g_strdup(name);
+ bm->url = g_strdup(url);
+ bm->is_folder = FALSE;
+ bm->children = NULL;
+
+ if (target_folder) {
+ target_folder->children = g_list_append(target_folder->children, bm);
+ } else {
+ b->bookmarks = g_list_append(b->bookmarks, bm);
+ }
+ }
+
+ save_bookmarks(b);
+ rebuild_bookmarks_bar(b);
+ update_star_button(b);
+
+ if (b->star_popover) {
+ gtk_popover_popdown(GTK_POPOVER(b->star_popover));
+ }
+}
+
+static void on_star_popover_remove(GtkButton *btn, VeilBrowser *b) {
+ (void)btn;
+ if (!g_star_editing_bookmark) return;
+
+ /* Von oberster Ebene entfernen */
+ b->bookmarks = g_list_remove(b->bookmarks, g_star_editing_bookmark);
+
+ /* Auch in Ordnern prüfen */
+ for (GList *l = b->bookmarks; l; l = l->next) {
+ VeilBookmark *folder = l->data;
+ if (folder->is_folder) {
+ folder->children = g_list_remove(folder->children, g_star_editing_bookmark);
+ }
+ }
+
+ free_bookmark(g_star_editing_bookmark);
+ g_star_editing_bookmark = NULL;
+
+ save_bookmarks(b);
+ rebuild_bookmarks_bar(b);
+ update_star_button(b);
+
+ if (b->star_popover) {
+ gtk_popover_popdown(GTK_POPOVER(b->star_popover));
+ }
+}
+
+static void create_star_popover(VeilBrowser *b) {
+ if (b->star_popover) {
+ gtk_widget_destroy(b->star_popover);
+ }
+
+ b->star_popover = gtk_popover_new(b->star_btn);
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->star_popover), "veil-popover");
+ gtk_widget_set_size_request(b->star_popover, 280, -1);
+
+ GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
+ gtk_container_set_border_width(GTK_CONTAINER(box), 12);
+
+ /* Titel */
+ const char *uri = b->current_tab ? webkit_web_view_get_uri(b->current_tab->webview) : NULL;
+ g_star_editing_bookmark = uri ? find_bookmark_by_url(b, uri) : NULL;
+
+ GtkWidget *title = gtk_label_new(g_star_editing_bookmark ? "Lesezeichen bearbeiten" : "Lesezeichen hinzufügen");
+ PangoAttrList *attrs = pango_attr_list_new();
+ pango_attr_list_insert(attrs, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+ gtk_label_set_attributes(GTK_LABEL(title), attrs);
+ pango_attr_list_unref(attrs);
+ gtk_widget_set_halign(title, GTK_ALIGN_START);
+ gtk_box_pack_start(GTK_BOX(box), title, FALSE, FALSE, 0);
+
+ /* Namens-Feld */
+ GtkWidget *name_label = gtk_label_new("Name");
+ gtk_widget_set_halign(name_label, GTK_ALIGN_START);
+ gtk_box_pack_start(GTK_BOX(box), name_label, FALSE, FALSE, 0);
+
+ g_star_name_entry = gtk_entry_new();
+ const char *page_title = b->current_tab ? webkit_web_view_get_title(b->current_tab->webview) : "";
+ gtk_entry_set_text(GTK_ENTRY(g_star_name_entry),
+ g_star_editing_bookmark ? g_star_editing_bookmark->name : (page_title ? page_title : ""));
+ gtk_box_pack_start(GTK_BOX(box), g_star_name_entry, FALSE, FALSE, 0);
+
+ /* URL-Feld */
+ GtkWidget *url_label = gtk_label_new("URL");
+ gtk_widget_set_halign(url_label, GTK_ALIGN_START);
+ gtk_box_pack_start(GTK_BOX(box), url_label, FALSE, FALSE, 0);
+
+ g_star_url_entry = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(g_star_url_entry),
+ g_star_editing_bookmark ? g_star_editing_bookmark->url : (uri ? uri : ""));
+ gtk_box_pack_start(GTK_BOX(box), g_star_url_entry, FALSE, FALSE, 0);
+
+ /* Ordner-Auswahl (nur für neue Lesezeichen) */
+ if (!g_star_editing_bookmark) {
+ GtkWidget *folder_label = gtk_label_new("Ordner");
+ gtk_widget_set_halign(folder_label, GTK_ALIGN_START);
+ gtk_box_pack_start(GTK_BOX(box), folder_label, FALSE, FALSE, 0);
+
+ g_star_folder_combo = gtk_combo_box_text_new();
+ gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(g_star_folder_combo), "Keine (Lesezeichenleiste)");
+
+ for (GList *l = b->bookmarks; l; l = l->next) {
+ VeilBookmark *bm = l->data;
+ if (bm->is_folder) {
+ gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(g_star_folder_combo), bm->name);
+ }
+ }
+ gtk_combo_box_set_active(GTK_COMBO_BOX(g_star_folder_combo), 0);
+ gtk_box_pack_start(GTK_BOX(box), g_star_folder_combo, FALSE, FALSE, 0);
+ } else {
+ g_star_folder_combo = NULL;
+ }
+
+ /* Schaltflächen */
+ GtkWidget *btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
+ gtk_widget_set_margin_top(btn_box, 8);
+
+ if (g_star_editing_bookmark) {
+ GtkWidget *remove_btn = gtk_button_new_with_label("Entfernen");
+ gtk_style_context_add_class(gtk_widget_get_style_context(remove_btn), "destructive-action");
+ g_signal_connect(remove_btn, "clicked", G_CALLBACK(on_star_popover_remove), b);
+ gtk_box_pack_start(GTK_BOX(btn_box), remove_btn, TRUE, TRUE, 0);
+ }
+
+ GtkWidget *save_btn = gtk_button_new_with_label(g_star_editing_bookmark ? "Speichern" : "Hinzufügen");
+ gtk_style_context_add_class(gtk_widget_get_style_context(save_btn), "suggested-action");
+ g_signal_connect(save_btn, "clicked", G_CALLBACK(on_star_popover_save), b);
+ gtk_box_pack_end(GTK_BOX(btn_box), save_btn, TRUE, TRUE, 0);
+
+ gtk_box_pack_start(GTK_BOX(box), btn_box, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(b->star_popover), box);
+ gtk_widget_show_all(box);
+}
+
+static void on_star_click(GtkButton *btn, VeilBrowser *b) {
+ (void)btn;
+ if (!b->current_tab) return;
+ const char *uri = webkit_web_view_get_uri(b->current_tab->webview);
+ if (!uri || g_str_has_prefix(uri, "veil:") || g_str_has_prefix(uri, "about:")) return;
+
+ create_star_popover(b);
+ gtk_popover_popup(GTK_POPOVER(b->star_popover));
+}
+
+/* Forward-Deklarationen für Seitenfunktionen */
+static void show_start_page(VeilBrowser *b, VeilTab *tab);
+static void veil_tab_load_url(VeilTab *tab, const char *url);
+static char *get_bg_image_path(void);
+
+/* Alle Tabs mit der Startseite aktualisieren */
+static void refresh_start_page_tabs(VeilBrowser *b) {
+ for (GList *l = b->tabs; l; l = l->next) {
+ VeilTab *tab = l->data;
+ const char *uri = webkit_web_view_get_uri(tab->webview);
+ if (uri && (g_str_has_prefix(uri, "veil:start") || g_str_has_prefix(uri, "veil://start"))) {
+ show_start_page(b, tab);
+ }
+ }
+}
+
+static void on_settings_message(WebKitUserContentManager *mgr, WebKitJavascriptResult *res, VeilBrowser *b) {
+ (void)mgr;
+ JSCValue *val = webkit_javascript_result_get_js_value(res);
+ char *json = jsc_value_to_string(val);
+
+ /* Einfaches JSON parsen: {"key":"bg","value":"#123456"} */
+ char *key_start = strstr(json, "\"key\":\"");
+ char *val_start = strstr(json, "\"value\":\"");
+
+ if (key_start && val_start) {
+ key_start += 7;
+ val_start += 9;
+
+ char key[16] = {0};
+ char *end;
+
+ end = strchr(key_start, '"');
+ if (end && (size_t)(end - key_start) < sizeof(key))
+ strncpy(key, key_start, end - key_start);
+
+ /* Hintergrundbild speziell behandeln - enthält große Base64-Daten */
+ if (strcmp(key, "bg_image") == 0) {
+ char *value_end = strrchr(val_start, '"');
+ if (value_end && value_end > val_start) {
+ size_t len = value_end - val_start;
+ gsize decoded_len;
+ guchar *decoded = g_base64_decode_inplace(g_strndup(val_start, len), &decoded_len);
+ if (decoded && decoded_len > 0) {
+ char *path = get_bg_image_path();
+ char *dir = g_path_get_dirname(path);
+ g_mkdir_with_parents(dir, 0755);
+ g_free(dir);
+ g_file_set_contents(path, (char*)decoded, decoded_len, NULL);
+ g_free(path);
+ b->settings.use_bg_image = 1;
+ save_settings(b);
+ refresh_start_page_tabs(b); /* Startseite mit neuem Bild aktualisieren */
+ }
+ g_free(decoded);
+ }
+ } else if (strcmp(key, "download_dir") == 0) {
+ /* Download-Verzeichnis speziell behandeln - benötigt größeren Puffer für Pfad */
+ char *value_end = strchr(val_start, '"');
+ if (value_end && value_end > val_start) {
+ size_t len = value_end - val_start;
+ if (len < sizeof(b->settings.download_dir)) {
+ memset(b->settings.download_dir, 0, sizeof(b->settings.download_dir));
+ strncpy(b->settings.download_dir, val_start, len);
+ save_settings(b);
+ }
+ }
+ } else {
+ char value[16] = {0};
+ end = strchr(val_start, '"');
+ if (end && (size_t)(end - val_start) < sizeof(value))
+ strncpy(value, val_start, end - val_start);
+
+ if (strcmp(key, "bg") == 0) strncpy(b->settings.bg_color, value, 7);
+ else if (strcmp(key, "accent") == 0) strncpy(b->settings.accent_color, value, 7);
+ else if (strcmp(key, "grad_start") == 0) strncpy(b->settings.grad_start, value, 7);
+ else if (strcmp(key, "grad_mid") == 0) strncpy(b->settings.grad_mid, value, 7);
+ else if (strcmp(key, "grad_end") == 0) strncpy(b->settings.grad_end, value, 7);
+ else if (strcmp(key, "search_engine") == 0) b->settings.search_engine = atoi(value);
+ else if (strcmp(key, "show_bookmarks") == 0) {
+ b->settings.show_bookmarks = atoi(value);
+ if (b->bookmarks_bar) {
+ GtkWidget *event_box = g_object_get_data(G_OBJECT(b->bookmarks_bar), "event_box");
+ if (event_box) {
+ gtk_widget_set_visible(event_box, b->settings.show_bookmarks);
+ }
+ }
+ }
+ else if (strcmp(key, "show_quicklinks") == 0) {
+ b->settings.show_quicklinks = atoi(value);
+ }
+ else if (strcmp(key, "use_bg_image") == 0) {
+ b->settings.use_bg_image = atoi(value);
+ refresh_start_page_tabs(b); /* Startseite aktualisieren */
+ }
+ else if (strcmp(key, "bg_opacity") == 0) {
+ b->settings.bg_opacity = atoi(value);
+ refresh_start_page_tabs(b); /* Startseite aktualisieren */
+ }
+
+ apply_css(b);
+ save_settings(b);
+ }
+ }
+
+ g_free(json);
+}
+
+static void on_navigate_message(WebKitUserContentManager *mgr, WebKitJavascriptResult *res, VeilBrowser *b) {
+ (void)mgr;
+ JSCValue *val = webkit_javascript_result_get_js_value(res);
+ char *query = jsc_value_to_string(val);
+
+ if (b->current_tab && query && *query) {
+ veil_tab_load_url(b->current_tab, query);
+ }
+
+ g_free(query);
+}
+
+static void show_settings_page(VeilBrowser *b) {
+ if (!b->current_tab) return;
+
+ VeilSettings *s = &b->settings;
+ char *html = g_strdup_printf(SETTINGS_HTML,
+ /* :root variables */
+ s->bg_color, s->accent_color, s->grad_start, s->grad_mid, s->grad_end,
+ /* Search engine options (selected attribute) */
+ s->search_engine == 0 ? "selected" : "",
+ s->search_engine == 1 ? "selected" : "",
+ s->search_engine == 2 ? "selected" : "",
+ s->search_engine == 3 ? "selected" : "",
+ /* Bookmarks bar toggle */
+ s->show_bookmarks ? "selected" : "",
+ s->show_bookmarks ? "" : "selected",
+ /* Quick links toggle */
+ s->show_quicklinks ? "selected" : "",
+ s->show_quicklinks ? "" : "selected",
+ /* Download directory */
+ s->download_dir,
+ /* Background image toggle */
+ s->use_bg_image ? "selected" : "",
+ s->use_bg_image ? "" : "selected",
+ /* Background opacity slider */
+ s->bg_opacity, s->bg_opacity,
+ /* bg input */
+ s->bg_color, DEFAULT_SETTINGS.bg_color,
+ /* accent input */
+ s->accent_color, DEFAULT_SETTINGS.accent_color,
+ /* grad_start input */
+ s->grad_start, DEFAULT_SETTINGS.grad_start,
+ /* grad_mid input */
+ s->grad_mid, DEFAULT_SETTINGS.grad_mid,
+ /* grad_end input */
+ s->grad_end, DEFAULT_SETTINGS.grad_end,
+ /* resetAll defaults */
+ DEFAULT_SETTINGS.bg_color, DEFAULT_SETTINGS.accent_color,
+ DEFAULT_SETTINGS.grad_start, DEFAULT_SETTINGS.grad_mid, DEFAULT_SETTINGS.grad_end
+ );
+
+ webkit_web_view_load_html(b->current_tab->webview, html, "veil://settings/");
+ gtk_entry_set_text(GTK_ENTRY(b->url_entry), "veil:settings");
+ g_free(html);
+}
+
+static char *get_bg_image_path(void) {
+ const char *home = g_get_home_dir();
+ return g_build_filename(home, ".config", "veil", "background.png", NULL);
+}
+
+static char *get_bg_image_data_url(void) {
+ char *path = get_bg_image_path();
+ if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
+ g_free(path);
+ return g_strdup("");
+ }
+
+ gchar *contents;
+ gsize length;
+ if (g_file_get_contents(path, &contents, &length, NULL)) {
+ /* Detect MIME type from magic bytes */
+ const char *mime_type = "image/png"; /* default */
+ if (length >= 2) {
+ guchar *data = (guchar*)contents;
+ if (data[0] == 0xFF && data[1] == 0xD8) {
+ mime_type = "image/jpeg";
+ } else if (length >= 4 && data[0] == 0x89 && data[1] == 0x50 &&
+ data[2] == 0x4E && data[3] == 0x47) {
+ mime_type = "image/png";
+ } else if (length >= 4 && data[0] == 'G' && data[1] == 'I' &&
+ data[2] == 'F' && data[3] == '8') {
+ mime_type = "image/gif";
+ } else if (length >= 4 && data[0] == 'R' && data[1] == 'I' &&
+ data[2] == 'F' && data[3] == 'F') {
+ mime_type = "image/webp";
+ }
+ }
+ gchar *base64 = g_base64_encode((guchar*)contents, length);
+ gchar *data_url = g_strdup_printf("data:%s;base64,%s", mime_type, base64);
+ g_free(contents);
+ g_free(base64);
+ g_free(path);
+ return data_url;
+ }
+
+ g_free(path);
+ return g_strdup("");
+}
+
+static char *get_icon_data_url(void) {
+ /* Mehrere Pfade versuchen - 128x128 bevorzugen für bessere Qualität */
+ const char *relative_paths[] = {
+ "data/icons/hicolor/128x128/apps/veil.png",
+ "data/icons/hicolor/256x256/apps/veil.png",
+ NULL
+ };
+ const char *absolute_paths[] = {
+ "/usr/share/icons/hicolor/128x128/apps/veil.png",
+ "/usr/share/icons/hicolor/256x256/apps/veil.png",
+ "/usr/local/share/icons/hicolor/128x128/apps/veil.png",
+ "/usr/local/share/icons/hicolor/256x256/apps/veil.png",
+ NULL
+ };
+
+ /* Zuerst relative Pfade versuchen (Entwicklung) */
+ char *cwd = g_get_current_dir();
+ for (int i = 0; relative_paths[i]; i++) {
+ char *full_path = g_build_filename(cwd, relative_paths[i], NULL);
+ if (g_file_test(full_path, G_FILE_TEST_EXISTS)) {
+ gchar *contents;
+ gsize length;
+ if (g_file_get_contents(full_path, &contents, &length, NULL)) {
+ gchar *base64 = g_base64_encode((guchar*)contents, length);
+ gchar *data_url = g_strdup_printf("data:image/png;base64,%s", base64);
+ g_free(contents);
+ g_free(base64);
+ g_free(full_path);
+ g_free(cwd);
+ return data_url;
+ }
+ }
+ g_free(full_path);
+ }
+ g_free(cwd);
+
+ /* Absolute Pfade versuchen (installiert) */
+ for (int i = 0; absolute_paths[i]; i++) {
+ if (g_file_test(absolute_paths[i], G_FILE_TEST_EXISTS)) {
+ gchar *contents;
+ gsize length;
+ if (g_file_get_contents(absolute_paths[i], &contents, &length, NULL)) {
+ gchar *base64 = g_base64_encode((guchar*)contents, length);
+ gchar *data_url = g_strdup_printf("data:image/png;base64,%s", base64);
+ g_free(contents);
+ g_free(base64);
+ return data_url;
+ }
+ }
+ }
+
+ return g_strdup("");
+}
+
+static void show_start_page(VeilBrowser *b, VeilTab *tab) {
+ if (!tab) tab = b->current_tab;
+ if (!tab) return;
+
+ VeilSettings *s = &b->settings;
+ char *icon_path = get_icon_data_url();
+
+ /* Hintergrundbild-Stil erstellen, wenn aktiviert */
+ char *bg_style = g_strdup("");
+ if (s->use_bg_image) {
+ char *bg_data = get_bg_image_data_url();
+ if (bg_data && *bg_data) {
+ g_free(bg_style);
+ /* Linear-Gradient-Overlay für Deckkraft-Effekt verwenden */
+ float opacity = (100 - s->bg_opacity) / 100.0f;
+ bg_style = g_strdup_printf(
+ "background-image: linear-gradient(rgba(10,10,15,%.2f), rgba(10,10,15,%.2f)), url('%s'); "
+ "background-size: cover, cover; background-position: center, center;",
+ opacity, opacity, bg_data);
+ }
+ g_free(bg_data);
+ }
+
+ char *html = g_strdup_printf(START_HTML,
+ s->bg_color, s->accent_color, s->grad_start, s->grad_mid, s->grad_end,
+ bg_style, /* Hintergrundbild-Stil */
+ s->show_quicklinks ? "block" : "none", /* Quick-Links-Sichtbarkeit */
+ icon_path /* Logo-Bild */
+ );
+
+ webkit_web_view_load_html(tab->webview, html, "veil://start/");
+ if (tab == b->current_tab) {
+ gtk_entry_set_text(GTK_ENTRY(b->url_entry), "veil:start");
+ }
+ g_free(bg_style);
+ g_free(icon_path);
+ g_free(html);
+}
+
+static VeilTab *veil_tab_new(VeilBrowser *browser, const char *url);
+static void veil_browser_new_tab(VeilBrowser *browser, const char *url);
+static void veil_browser_close_tab(VeilBrowser *browser, VeilTab *tab);
+static void veil_browser_switch_to_tab(VeilBrowser *browser, VeilTab *tab);
+
+static void on_minimize(GtkButton *btn, VeilBrowser *b) { (void)btn; gtk_window_iconify(GTK_WINDOW(b->window)); }
+static void on_maximize(GtkButton *btn, VeilBrowser *b) {
+ (void)btn;
+ if (b->is_maximized) gtk_window_unmaximize(GTK_WINDOW(b->window));
+ else gtk_window_maximize(GTK_WINDOW(b->window));
+}
+static void on_close_window(GtkButton *btn, VeilBrowser *b) { (void)btn; (void)b; gtk_main_quit(); }
+static gboolean on_window_state(GtkWidget *w, GdkEventWindowState *e, VeilBrowser *b) {
+ (void)w; b->is_maximized = (e->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0;
+ return FALSE;
+}
+
+static void on_load_changed(WebKitWebView *wv, WebKitLoadEvent ev, VeilTab *tab) {
+ (void)wv; VeilBrowser *b = tab->browser;
+ gtk_widget_set_sensitive(b->back_btn, webkit_web_view_can_go_back(tab->webview));
+ gtk_widget_set_sensitive(b->forward_btn, webkit_web_view_can_go_forward(tab->webview));
+ if (ev == WEBKIT_LOAD_FINISHED) gtk_widget_hide(b->progress_bar);
+}
+
+static void on_title_changed(WebKitWebView *wv, GParamSpec *p, VeilTab *tab) {
+ (void)p;
+ const char *t = webkit_web_view_get_title(wv);
+ const char *uri = webkit_web_view_get_uri(wv);
+ if (uri && (g_str_has_prefix(uri, "veil:settings") || g_str_has_prefix(uri, "veil://settings")))
+ gtk_label_set_text(GTK_LABEL(tab->tab_label), "Settings");
+ else if (uri && (g_str_has_prefix(uri, "veil:start") || g_str_has_prefix(uri, "veil://start")))
+ gtk_label_set_text(GTK_LABEL(tab->tab_label), "Start");
+ else
+ gtk_label_set_text(GTK_LABEL(tab->tab_label), (t && *t) ? t : "New Tab");
+}
+
+static void on_uri_changed(WebKitWebView *wv, GParamSpec *p, VeilTab *tab) {
+ (void)p;
+ if (tab != tab->browser->current_tab) return;
+ const char *uri = webkit_web_view_get_uri(wv);
+ if (uri) gtk_entry_set_text(GTK_ENTRY(tab->browser->url_entry), uri);
+ update_star_button(tab->browser);
+}
+
+static void on_progress(WebKitWebView *wv, GParamSpec *p, VeilTab *tab) {
+ (void)p;
+ if (tab != tab->browser->current_tab) return;
+ double prog = webkit_web_view_get_estimated_load_progress(wv);
+ gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(tab->browser->progress_bar), prog);
+ if (prog < 1.0) gtk_widget_show(tab->browser->progress_bar);
+ else gtk_widget_hide(tab->browser->progress_bar);
+}
+
+static void on_close_tab(GtkButton *btn, VeilTab *tab) { (void)btn; veil_browser_close_tab(tab->browser, tab); }
+
+/* target="_blank"-Links behandeln - in neuem Tab öffnen */
+static GtkWidget *on_create_new_window(WebKitWebView *wv, WebKitNavigationAction *nav, VeilTab *tab) {
+ (void)wv;
+ WebKitURIRequest *req = webkit_navigation_action_get_request(nav);
+ const char *uri = webkit_uri_request_get_uri(req);
+ if (uri) {
+ veil_browser_new_tab(tab->browser, uri);
+ }
+ return NULL; /* NULL zurückgeben, um zu verhindern, dass WebKit ein eigenes Fenster erstellt */
+}
+
+/* Navigations-Policy-Entscheidungen behandeln */
+static gboolean on_decide_policy(WebKitWebView *wv, WebKitPolicyDecision *decision,
+ WebKitPolicyDecisionType type, VeilTab *tab) {
+ (void)wv;
+ (void)tab;
+
+ if (type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION ||
+ type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) {
+ WebKitNavigationPolicyDecision *nav = WEBKIT_NAVIGATION_POLICY_DECISION(decision);
+ WebKitNavigationAction *action = webkit_navigation_policy_decision_get_navigation_action(nav);
+ WebKitURIRequest *req = webkit_navigation_action_get_request(action);
+ const char *uri = webkit_uri_request_get_uri(req);
+
+ /* Alle http/https/file/about-Navigationen erlauben */
+ if (uri && (g_str_has_prefix(uri, "http://") ||
+ g_str_has_prefix(uri, "https://") ||
+ g_str_has_prefix(uri, "file://") ||
+ g_str_has_prefix(uri, "about:"))) {
+ webkit_policy_decision_use(decision);
+ return TRUE;
+ }
+ }
+
+ /* WebKit andere Entscheidungen überlassen */
+ return FALSE;
+}
+
+static gboolean on_tab_click(GtkWidget *w, GdkEventButton *e, VeilTab *tab) {
+ (void)w;
+ if (e->button == 1) { veil_browser_switch_to_tab(tab->browser, tab); return FALSE; }
+ if (e->button == 2) { veil_browser_close_tab(tab->browser, tab); return TRUE; }
+ return FALSE;
+}
+
+static void on_url_activate(GtkEntry *e, VeilBrowser *b) {
+ if (b->current_tab) veil_tab_load_url(b->current_tab, gtk_entry_get_text(e));
+}
+
+/* Gesamten Text automatisch markieren, wenn URL-Leiste angeklickt wird */
+static gboolean on_url_focus(GtkWidget *w, GdkEventFocus *e, VeilBrowser *b) {
+ (void)e; (void)b;
+ gtk_editable_select_region(GTK_EDITABLE(w), 0, -1);
+ return FALSE;
+}
+
+static void on_back(GtkButton *btn, VeilBrowser *b) { (void)btn; if (b->current_tab) webkit_web_view_go_back(b->current_tab->webview); }
+static void on_forward(GtkButton *btn, VeilBrowser *b) { (void)btn; if (b->current_tab) webkit_web_view_go_forward(b->current_tab->webview); }
+static void on_reload(GtkButton *btn, VeilBrowser *b) {
+ (void)btn;
+ if (!b->current_tab) return;
+ const char *uri = gtk_entry_get_text(GTK_ENTRY(b->url_entry));
+ /* Interne veil:-Seiten durch Neu-Rendern aktualisieren */
+ if (uri && g_str_has_prefix(uri, "veil:")) {
+ veil_tab_load_url(b->current_tab, uri);
+ } else {
+ webkit_web_view_reload(b->current_tab->webview);
+ }
+}
+static void on_new_tab(GtkButton *btn, VeilBrowser *b) { (void)btn; veil_browser_new_tab(b, "veil:start"); }
+
+static void on_bookmark_click(GtkButton *btn, VeilBrowser *b) {
+ const char *url = g_object_get_data(G_OBJECT(btn), "url");
+ if (url && b->current_tab) {
+ veil_tab_load_url(b->current_tab, url);
+ }
+}
+
+/* Lesezeichen-Kontextmenü */
+static VeilBookmark *g_context_bookmark = NULL;
+static VeilBookmark *g_context_parent_folder = NULL;
+
+static void on_bookmark_edit(GtkMenuItem *item, VeilBrowser *b) {
+ (void)item;
+ if (!g_context_bookmark) return;
+
+ GtkWidget *dialog = gtk_dialog_new_with_buttons(
+ g_context_bookmark->is_folder ? "Ordner bearbeiten" : "Lesezeichen bearbeiten",
+ GTK_WINDOW(b->window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ "Abbrechen", GTK_RESPONSE_CANCEL, "Speichern", GTK_RESPONSE_OK, NULL);
+
+ GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ gtk_container_set_border_width(GTK_CONTAINER(content), 12);
+
+ GtkWidget *name_entry = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(name_entry), g_context_bookmark->name);
+ gtk_box_pack_start(GTK_BOX(content), gtk_label_new("Name:"), FALSE, FALSE, 4);
+ gtk_box_pack_start(GTK_BOX(content), name_entry, FALSE, FALSE, 4);
+
+ GtkWidget *url_entry = NULL;
+ if (!g_context_bookmark->is_folder) {
+ url_entry = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(url_entry), g_context_bookmark->url ? g_context_bookmark->url : "");
+ gtk_box_pack_start(GTK_BOX(content), gtk_label_new("URL:"), FALSE, FALSE, 4);
+ gtk_box_pack_start(GTK_BOX(content), url_entry, FALSE, FALSE, 4);
+ }
+
+ gtk_widget_show_all(dialog);
+
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) {
+ g_free(g_context_bookmark->name);
+ g_context_bookmark->name = g_strdup(gtk_entry_get_text(GTK_ENTRY(name_entry)));
+ if (url_entry) {
+ g_free(g_context_bookmark->url);
+ g_context_bookmark->url = g_strdup(gtk_entry_get_text(GTK_ENTRY(url_entry)));
+ }
+ save_bookmarks(b);
+ rebuild_bookmarks_bar(b);
+ }
+ gtk_widget_destroy(dialog);
+}
+
+static void on_bookmark_delete(GtkMenuItem *item, VeilBrowser *b) {
+ (void)item;
+ if (!g_context_bookmark) return;
+
+ if (g_context_parent_folder) {
+ g_context_parent_folder->children = g_list_remove(g_context_parent_folder->children, g_context_bookmark);
+ } else {
+ b->bookmarks = g_list_remove(b->bookmarks, g_context_bookmark);
+ }
+
+ free_bookmark(g_context_bookmark);
+ g_context_bookmark = NULL;
+ g_context_parent_folder = NULL;
+
+ save_bookmarks(b);
+ rebuild_bookmarks_bar(b);
+ update_star_button(b);
+}
+
+static void on_bookmark_move_to_folder(GtkMenuItem *item, VeilBrowser *b) {
+ VeilBookmark *target_folder = g_object_get_data(G_OBJECT(item), "folder");
+ if (!g_context_bookmark) return;
+
+ if (g_context_parent_folder) {
+ g_context_parent_folder->children = g_list_remove(g_context_parent_folder->children, g_context_bookmark);
+ } else {
+ b->bookmarks = g_list_remove(b->bookmarks, g_context_bookmark);
+ }
+
+ if (target_folder) {
+ target_folder->children = g_list_append(target_folder->children, g_context_bookmark);
+ } else {
+ b->bookmarks = g_list_append(b->bookmarks, g_context_bookmark);
+ }
+
+ save_bookmarks(b);
+ rebuild_bookmarks_bar(b);
+}
+
+static VeilBookmark *find_parent_folder(VeilBrowser *b, VeilBookmark *child) {
+ for (GList *l = b->bookmarks; l; l = l->next) {
+ VeilBookmark *folder = l->data;
+ if (folder->is_folder) {
+ for (GList *c = folder->children; c; c = c->next) {
+ if (c->data == child) return folder;
+ }
+ }
+ }
+ return NULL;
+}
+
+static gboolean on_bookmark_button_press(GtkWidget *w, GdkEventButton *e, VeilBrowser *b) {
+ if (e->button == 3) { /* Rechtsklick */
+ g_context_bookmark = g_object_get_data(G_OBJECT(w), "bookmark");
+ g_context_parent_folder = find_parent_folder(b, g_context_bookmark);
+
+ GtkWidget *menu = gtk_menu_new();
+ gtk_style_context_add_class(gtk_widget_get_style_context(menu), "veil-menu");
+
+ /* Bearbeiten */
+ GtkWidget *edit_item = gtk_menu_item_new_with_label("Bearbeiten");
+ g_signal_connect(edit_item, "activate", G_CALLBACK(on_bookmark_edit), b);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), edit_item);
+
+ /* Untermenü "Verschieben nach" (nur für Lesezeichen) */
+ if (!g_context_bookmark->is_folder) {
+ gboolean has_folders = FALSE;
+ for (GList *l = b->bookmarks; l; l = l->next) {
+ VeilBookmark *folder = l->data;
+ if (folder->is_folder) { has_folders = TRUE; break; }
+ }
+
+ if (has_folders || g_context_parent_folder) {
+ GtkWidget *move_item = gtk_menu_item_new_with_label("Verschieben nach");
+ GtkWidget *move_menu = gtk_menu_new();
+ gtk_style_context_add_class(gtk_widget_get_style_context(move_menu), "veil-menu");
+
+ if (g_context_parent_folder) {
+ GtkWidget *root_item = gtk_menu_item_new_with_label("Lesezeichenleiste");
+ g_object_set_data(G_OBJECT(root_item), "folder", NULL);
+ g_signal_connect(root_item, "activate", G_CALLBACK(on_bookmark_move_to_folder), b);
+ gtk_menu_shell_append(GTK_MENU_SHELL(move_menu), root_item);
+ }
+
+ for (GList *l = b->bookmarks; l; l = l->next) {
+ VeilBookmark *folder = l->data;
+ if (folder->is_folder && folder != g_context_parent_folder) {
+ GtkWidget *folder_item = gtk_menu_item_new_with_label(folder->name);
+ g_object_set_data(G_OBJECT(folder_item), "folder", folder);
+ g_signal_connect(folder_item, "activate", G_CALLBACK(on_bookmark_move_to_folder), b);
+ gtk_menu_shell_append(GTK_MENU_SHELL(move_menu), folder_item);
+ }
+ }
+
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(move_item), move_menu);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), move_item);
+ }
+ }
+
+ /* Trennlinie */
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
+
+ /* Löschen */
+ GtkWidget *delete_item = gtk_menu_item_new_with_label("Löschen");
+ g_signal_connect(delete_item, "activate", G_CALLBACK(on_bookmark_delete), b);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), delete_item);
+
+ gtk_widget_show_all(menu);
+ gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent*)e);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void on_folder_child_click(GtkButton *btn, VeilBrowser *b) {
+ const char *url = g_object_get_data(G_OBJECT(btn), "url");
+ if (url && b->current_tab) {
+ veil_tab_load_url(b->current_tab, url);
+ }
+ /* Popover schließen und Verfolgung aufheben */
+ GtkWidget *popover = g_object_get_data(G_OBJECT(btn), "popover");
+ if (popover) {
+ gtk_popover_popdown(GTK_POPOVER(popover));
+ if (b->current_folder_popover == popover) {
+ b->current_folder_popover = NULL;
+ }
+ }
+}
+
+static void on_folder_click(GtkButton *btn, VeilBrowser *b) {
+ GtkWidget *popover = g_object_get_data(G_OBJECT(btn), "popover");
+ if (!popover || !GTK_IS_POPOVER(popover)) return;
+
+ /* Zuvor geöffnetes Ordner-Popover schließen */
+ if (b->current_folder_popover && b->current_folder_popover != popover) {
+ gtk_popover_popdown(GTK_POPOVER(b->current_folder_popover));
+ }
+
+ /* Dieses Ordner-Popover öffnen und verfolgen */
+ gtk_popover_popup(GTK_POPOVER(popover));
+ b->current_folder_popover = popover;
+}
+
+static void create_folder_popover(GtkWidget *btn, VeilBookmark *folder, VeilBrowser *b) {
+ GtkWidget *popover = gtk_popover_new(btn);
+ gtk_style_context_add_class(gtk_widget_get_style_context(popover), "veil-popover");
+
+ GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(box), 4);
+
+ if (!folder->children) {
+ GtkWidget *empty = gtk_label_new("(leer)");
+ gtk_style_context_add_class(gtk_widget_get_style_context(empty), "dim-label");
+ gtk_widget_set_margin_start(empty, 12);
+ gtk_widget_set_margin_end(empty, 12);
+ gtk_widget_set_margin_top(empty, 8);
+ gtk_widget_set_margin_bottom(empty, 8);
+ gtk_box_pack_start(GTK_BOX(box), empty, FALSE, FALSE, 0);
+ } else {
+ for (GList *c = folder->children; c; c = c->next) {
+ VeilBookmark *child = c->data;
+
+ GtkWidget *item = gtk_button_new_with_label(child->name);
+ gtk_style_context_add_class(gtk_widget_get_style_context(item), "flat");
+ gtk_style_context_add_class(gtk_widget_get_style_context(item), "bookmark-btn");
+ gtk_widget_set_halign(item, GTK_ALIGN_START);
+
+ if (!child->is_folder && child->url) {
+ g_object_set_data(G_OBJECT(item), "url", child->url);
+ g_object_set_data(G_OBJECT(item), "popover", popover);
+ g_signal_connect(item, "clicked", G_CALLBACK(on_folder_child_click), b);
+ }
+ gtk_box_pack_start(GTK_BOX(box), item, FALSE, FALSE, 0);
+ }
+ }
+
+ gtk_container_add(GTK_CONTAINER(popover), box);
+ gtk_widget_show_all(box);
+
+ g_object_set_data(G_OBJECT(btn), "popover", popover);
+ g_signal_connect(btn, "clicked", G_CALLBACK(on_folder_click), b);
+}
+
+static void on_add_folder(GtkMenuItem *item, VeilBrowser *b) {
+ (void)item;
+
+ GtkWidget *dialog = gtk_dialog_new_with_buttons("Neuer Ordner",
+ GTK_WINDOW(b->window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ "Abbrechen", GTK_RESPONSE_CANCEL, "Erstellen", GTK_RESPONSE_OK, NULL);
+
+ GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ gtk_container_set_border_width(GTK_CONTAINER(content), 12);
+
+ GtkWidget *entry = gtk_entry_new();
+ gtk_entry_set_placeholder_text(GTK_ENTRY(entry), "Ordnername");
+ gtk_box_pack_start(GTK_BOX(content), entry, FALSE, FALSE, 4);
+
+ gtk_widget_show_all(dialog);
+
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) {
+ const char *name = gtk_entry_get_text(GTK_ENTRY(entry));
+ if (name && *name) {
+ VeilBookmark *folder = g_new0(VeilBookmark, 1);
+ folder->name = g_strdup(name);
+ folder->is_folder = TRUE;
+ folder->url = NULL;
+ folder->children = NULL;
+ b->bookmarks = g_list_append(b->bookmarks, folder);
+ save_bookmarks(b);
+ rebuild_bookmarks_bar(b);
+ }
+ }
+ gtk_widget_destroy(dialog);
+}
+
+
+/* Drag & Drop für Lesezeichen */
+static VeilBookmark *g_drag_bookmark = NULL;
+
+static void on_bookmark_drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer data) {
+ (void)context;
+ (void)data;
+ g_drag_bookmark = g_object_get_data(G_OBJECT(widget), "bookmark");
+}
+
+static gboolean on_folder_drag_drop(GtkWidget *widget, GdkDragContext *context,
+ gint x, gint y, guint time, VeilBrowser *b) {
+ (void)x; (void)y;
+ VeilBookmark *folder = g_object_get_data(G_OBJECT(widget), "bookmark");
+
+ if (g_drag_bookmark && folder && folder->is_folder && g_drag_bookmark != folder) {
+ /* Aus übergeordnetem Ordner entfernen, falls vorhanden, andernfalls von oberster Ebene */
+ VeilBookmark *parent = find_parent_folder(b, g_drag_bookmark);
+ if (parent) {
+ parent->children = g_list_remove(parent->children, g_drag_bookmark);
+ } else {
+ b->bookmarks = g_list_remove(b->bookmarks, g_drag_bookmark);
+ }
+ /* Zu Zielordner hinzufügen */
+ folder->children = g_list_append(folder->children, g_drag_bookmark);
+ save_bookmarks(b);
+ rebuild_bookmarks_bar(b);
+ }
+
+ gtk_drag_finish(context, TRUE, FALSE, time);
+ g_drag_bookmark = NULL;
+ return TRUE;
+}
+
+static gboolean on_bookmarks_bar_button_press(GtkWidget *w, GdkEventButton *e, VeilBrowser *b) {
+ (void)w;
+ if (e->button == 3) { /* Rechtsklick auf leeren Bereich */
+ GtkWidget *menu = gtk_menu_new();
+ gtk_style_context_add_class(gtk_widget_get_style_context(menu), "veil-menu");
+
+ GtkWidget *new_folder = gtk_menu_item_new_with_label("Neuer Ordner");
+ g_signal_connect(new_folder, "activate", G_CALLBACK(on_add_folder), b);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), new_folder);
+
+ gtk_widget_show_all(menu);
+ gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent*)e);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void rebuild_bookmarks_bar(VeilBrowser *b) {
+ if (!b->bookmarks_bar) return;
+
+ /* Ordner-Popover-Verfolgung löschen (Popovers werden mit Kindern zerstört) */
+ b->current_folder_popover = NULL;
+
+ /* Alle Kinder entfernen */
+ GList *children = gtk_container_get_children(GTK_CONTAINER(b->bookmarks_bar));
+ for (GList *l = children; l; l = l->next) {
+ gtk_widget_destroy(GTK_WIDGET(l->data));
+ }
+ g_list_free(children);
+
+ /* Drag-Zieltypen */
+ static GtkTargetEntry target_entries[] = {
+ {"VEIL_BOOKMARK", GTK_TARGET_SAME_APP, 0}
+ };
+
+ /* Lesezeichen und Ordner hinzufügen */
+ for (GList *l = b->bookmarks; l; l = l->next) {
+ VeilBookmark *bm = l->data;
+
+ if (bm->is_folder) {
+ GtkWidget *btn = gtk_button_new_with_label(bm->name);
+ gtk_style_context_add_class(gtk_widget_get_style_context(btn), "flat");
+ gtk_style_context_add_class(gtk_widget_get_style_context(btn), "bookmark-folder");
+ gtk_button_set_image(GTK_BUTTON(btn),
+ gtk_image_new_from_icon_name("folder-symbolic", GTK_ICON_SIZE_MENU));
+ gtk_button_set_always_show_image(GTK_BUTTON(btn), TRUE);
+ g_object_set_data(G_OBJECT(btn), "bookmark", bm);
+ g_signal_connect(btn, "button-press-event", G_CALLBACK(on_bookmark_button_press), b);
+ create_folder_popover(btn, bm, b);
+
+ /* Als Drop-Ziel aktivieren */
+ gtk_drag_dest_set(btn, GTK_DEST_DEFAULT_ALL, target_entries, 1, GDK_ACTION_MOVE);
+ g_signal_connect(btn, "drag-drop", G_CALLBACK(on_folder_drag_drop), b);
+
+ gtk_box_pack_start(GTK_BOX(b->bookmarks_bar), btn, FALSE, FALSE, 0);
+ gtk_widget_show(btn);
+ } else {
+ GtkWidget *btn = gtk_button_new_with_label(bm->name);
+ gtk_style_context_add_class(gtk_widget_get_style_context(btn), "flat");
+ gtk_style_context_add_class(gtk_widget_get_style_context(btn), "bookmark-btn");
+ g_object_set_data(G_OBJECT(btn), "url", bm->url);
+ g_object_set_data(G_OBJECT(btn), "bookmark", bm);
+ g_signal_connect(btn, "clicked", G_CALLBACK(on_bookmark_click), b);
+ g_signal_connect(btn, "button-press-event", G_CALLBACK(on_bookmark_button_press), b);
+
+ /* Als Drag-Quelle aktivieren */
+ gtk_drag_source_set(btn, GDK_BUTTON1_MASK, target_entries, 1, GDK_ACTION_MOVE);
+ g_signal_connect(btn, "drag-begin", G_CALLBACK(on_bookmark_drag_begin), NULL);
+
+ gtk_box_pack_start(GTK_BOX(b->bookmarks_bar), btn, FALSE, FALSE, 0);
+ gtk_widget_show(btn);
+ }
+ }
+}
+
+static void on_menu_settings(GtkMenuItem *item, VeilBrowser *b) {
+ (void)item;
+ show_settings_page(b);
+}
+
+static void on_menu_new_tab(GtkMenuItem *item, VeilBrowser *b) {
+ (void)item;
+ veil_browser_new_tab(b, "veil:start");
+}
+
+static gboolean on_key(GtkWidget *w, GdkEventKey *e, VeilBrowser *b) {
+ (void)w;
+ if (e->state & GDK_CONTROL_MASK) {
+ switch (e->keyval) {
+ case GDK_KEY_t: veil_browser_new_tab(b, "veil:start"); return TRUE;
+ case GDK_KEY_w: if (b->current_tab) veil_browser_close_tab(b, b->current_tab); return TRUE;
+ case GDK_KEY_l:
+ gtk_widget_grab_focus(b->url_entry);
+ gtk_editable_select_region(GTK_EDITABLE(b->url_entry), 0, -1);
+ return TRUE;
+ case GDK_KEY_r: on_reload(NULL, b); return TRUE;
+ case GDK_KEY_comma: show_settings_page(b); return TRUE;
+ }
+ }
+ if (e->keyval == GDK_KEY_F5) { on_reload(NULL, b); return TRUE; }
+ if (e->keyval == GDK_KEY_F11) {
+ if (b->is_maximized) gtk_window_unmaximize(GTK_WINDOW(b->window));
+ else gtk_window_maximize(GTK_WINDOW(b->window));
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static WebKitWebView *create_webview_with_settings(VeilBrowser *b) {
+ WebKitWebView *wv = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(b->web_context));
+
+ WebKitSettings *s = webkit_web_view_get_settings(wv);
+ /* Kerneinstellungen */
+ webkit_settings_set_enable_javascript(s, TRUE);
+ webkit_settings_set_javascript_can_access_clipboard(s, TRUE);
+ webkit_settings_set_user_agent(s, "Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0");
+
+ /* === PERFORMANCE-OPTIMIERUNGEN === */
+
+ /* Hardware-Beschleunigung - IMMER für beste Performance (DMABUF in main() deaktiviert) */
+ webkit_settings_set_hardware_acceleration_policy(s, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS);
+ webkit_settings_set_enable_webgl(s, TRUE);
+
+ /* Sanftes Scrollen deaktiviert für schnellere Reaktion */
+ webkit_settings_set_enable_smooth_scrolling(s, FALSE);
+
+ /* Media-Optimierungen */
+ webkit_settings_set_enable_media_stream(s, TRUE);
+ webkit_settings_set_enable_mediasource(s, TRUE);
+ webkit_settings_set_media_playback_requires_user_gesture(s, FALSE);
+
+ /* JavaScript-JIT-Optimierung */
+ webkit_settings_set_enable_javascript_markup(s, TRUE);
+
+ /* Seiten-Cache für schnellere Vor-/Zurück-Navigation */
+ webkit_settings_set_enable_page_cache(s, TRUE);
+
+ /* Konsolennachrichten nicht ausgeben (Debugging kann in Produktion verlangsamen) */
+ webkit_settings_set_enable_write_console_messages_to_stdout(s, FALSE);
+
+ /* Entwickler-Extras für Performance deaktiviert */
+ g_object_set(s, "enable-developer-extras", FALSE, NULL);
+
+ /* Nachrichten-Handler für interne Seiten registrieren */
+ WebKitUserContentManager *mgr = webkit_web_view_get_user_content_manager(wv);
+ g_signal_connect(mgr, "script-message-received::settings", G_CALLBACK(on_settings_message), b);
+ webkit_user_content_manager_register_script_message_handler(mgr, "settings");
+ g_signal_connect(mgr, "script-message-received::navigate", G_CALLBACK(on_navigate_message), b);
+ webkit_user_content_manager_register_script_message_handler(mgr, "navigate");
+
+ /* Farbschema auf dunkel setzen für native dunkle Formularelemente ohne Seitenstile zu beeinträchtigen */
+ const char *dark_scheme_css = ":root { color-scheme: dark; }";
+
+ WebKitUserStyleSheet *style = webkit_user_style_sheet_new(
+ dark_scheme_css,
+ WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
+ WEBKIT_USER_STYLE_LEVEL_USER,
+ NULL, NULL);
+ webkit_user_content_manager_add_style_sheet(mgr, style);
+
+ return wv;
+}
+
+static VeilTab *veil_tab_new(VeilBrowser *browser, const char *url) {
+ VeilTab *tab = g_new0(VeilTab, 1);
+ tab->browser = browser;
+
+ tab->webview = create_webview_with_settings(browser);
+ gtk_widget_set_size_request(GTK_WIDGET(tab->webview), 100, 100);
+ gtk_widget_set_vexpand(GTK_WIDGET(tab->webview), TRUE);
+ gtk_widget_set_hexpand(GTK_WIDGET(tab->webview), TRUE);
+
+ g_signal_connect(tab->webview, "load-changed", G_CALLBACK(on_load_changed), tab);
+ g_signal_connect(tab->webview, "notify::title", G_CALLBACK(on_title_changed), tab);
+ g_signal_connect(tab->webview, "notify::uri", G_CALLBACK(on_uri_changed), tab);
+ g_signal_connect(tab->webview, "notify::estimated-load-progress", G_CALLBACK(on_progress), tab);
+ g_signal_connect(tab->webview, "create", G_CALLBACK(on_create_new_window), tab);
+ g_signal_connect(tab->webview, "decide-policy", G_CALLBACK(on_decide_policy), tab);
+
+ GtkWidget *ebox = gtk_event_box_new();
+ gtk_widget_set_events(ebox, GDK_BUTTON_PRESS_MASK);
+ g_signal_connect(ebox, "button-press-event", G_CALLBACK(on_tab_click), tab);
+
+ tab->tab_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_style_context_add_class(gtk_widget_get_style_context(tab->tab_box), "tab-box");
+ gtk_container_add(GTK_CONTAINER(ebox), tab->tab_box);
+
+ tab->tab_label = gtk_label_new("New Tab");
+ gtk_label_set_ellipsize(GTK_LABEL(tab->tab_label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_width_chars(GTK_LABEL(tab->tab_label), 12);
+ gtk_label_set_max_width_chars(GTK_LABEL(tab->tab_label), 16);
+
+ tab->close_button = gtk_button_new_from_icon_name("window-close-symbolic", GTK_ICON_SIZE_MENU);
+ gtk_style_context_add_class(gtk_widget_get_style_context(tab->close_button), "flat");
+ gtk_style_context_add_class(gtk_widget_get_style_context(tab->close_button), "tab-close");
+ g_signal_connect(tab->close_button, "clicked", G_CALLBACK(on_close_tab), tab);
+
+ gtk_box_pack_start(GTK_BOX(tab->tab_box), tab->tab_label, FALSE, FALSE, 0);
+ gtk_box_pack_end(GTK_BOX(tab->tab_box), tab->close_button, FALSE, FALSE, 0);
+
+ g_object_set_data(G_OBJECT(tab->tab_box), "ebox", ebox);
+
+ gtk_widget_show_all(ebox);
+ gtk_widget_show_all(GTK_WIDGET(tab->webview));
+
+ if (url && strcmp(url, "about:blank") != 0) veil_tab_load_url(tab, url);
+ return tab;
+}
+
+static void veil_tab_load_url(VeilTab *tab, const char *url) {
+ if (!tab || !url || !*url) return;
+
+ /* Interne veil:-Seiten behandeln */
+ if (strcmp(url, "veil:settings") == 0 || strcmp(url, "veil://settings/") == 0) {
+ show_settings_page(tab->browser);
+ return;
+ }
+ if (strcmp(url, "veil:start") == 0 || strcmp(url, "veil://start/") == 0 ||
+ strcmp(url, "about:blank") == 0) {
+ show_start_page(tab->browser, tab);
+ return;
+ }
+
+ char *final;
+ if (g_str_has_prefix(url, "http://") || g_str_has_prefix(url, "https://") ||
+ g_str_has_prefix(url, "veil:") || g_str_has_prefix(url, "file://")) {
+ final = g_strdup(url);
+ } else if (strchr(url, '.') && !strchr(url, ' ')) {
+ final = g_strdup_printf("https://%s", url);
+ } else {
+ char *enc = g_uri_escape_string(url, NULL, TRUE);
+ const char *search_urls[] = {
+ "https://duckduckgo.com/?q=%s",
+ "https://www.google.com/search?q=%s",
+ "https://search.brave.com/search?q=%s",
+ "https://www.startpage.com/do/search?q=%s"
+ };
+ int se = tab->browser->settings.search_engine;
+ if (se < 0 || se > 3) se = 0;
+ final = g_strdup_printf(search_urls[se], enc);
+ g_free(enc);
+ }
+ webkit_web_view_load_uri(tab->webview, final);
+ g_free(final);
+}
+
+static void veil_browser_new_tab(VeilBrowser *b, const char *url) {
+ VeilTab *tab = veil_tab_new(b, url);
+ b->tabs = g_list_append(b->tabs, tab);
+
+ GtkWidget *ebox = g_object_get_data(G_OBJECT(tab->tab_box), "ebox");
+ gtk_box_pack_start(GTK_BOX(b->tab_container), ebox, FALSE, FALSE, 0);
+
+ char *name = g_strdup_printf("t%p", (void*)tab);
+ gtk_stack_add_named(GTK_STACK(b->webview_stack), GTK_WIDGET(tab->webview), name);
+ g_free(name);
+
+ veil_browser_switch_to_tab(b, tab);
+}
+
+static void veil_browser_switch_to_tab(VeilBrowser *b, VeilTab *tab) {
+ if (b->current_tab)
+ gtk_style_context_remove_class(gtk_widget_get_style_context(b->current_tab->tab_box), "active");
+
+ b->current_tab = tab;
+ gtk_style_context_add_class(gtk_widget_get_style_context(tab->tab_box), "active");
+
+ char *name = g_strdup_printf("t%p", (void*)tab);
+ gtk_stack_set_visible_child_name(GTK_STACK(b->webview_stack), name);
+ g_free(name);
+
+ const char *uri = webkit_web_view_get_uri(tab->webview);
+ gtk_entry_set_text(GTK_ENTRY(b->url_entry), uri ? uri : "");
+
+ gtk_widget_set_sensitive(b->back_btn, webkit_web_view_can_go_back(tab->webview));
+ gtk_widget_set_sensitive(b->forward_btn, webkit_web_view_can_go_forward(tab->webview));
+ update_star_button(b);
+ gtk_widget_grab_focus(GTK_WIDGET(tab->webview));
+}
+
+static void veil_browser_close_tab(VeilBrowser *b, VeilTab *tab) {
+ if (g_list_length(b->tabs) <= 1) { gtk_main_quit(); return; }
+
+ GList *node = g_list_find(b->tabs, tab);
+ GList *next = node->next ? node->next : node->prev;
+
+ b->tabs = g_list_remove(b->tabs, tab);
+
+ GtkWidget *ebox = g_object_get_data(G_OBJECT(tab->tab_box), "ebox");
+ gtk_container_remove(GTK_CONTAINER(b->tab_container), ebox);
+ gtk_container_remove(GTK_CONTAINER(b->webview_stack), GTK_WIDGET(tab->webview));
+
+ if (tab == b->current_tab && next) {
+ b->current_tab = NULL;
+ veil_browser_switch_to_tab(b, next->data);
+ }
+ g_free(tab);
+}
+
+static GtkWidget *make_btn(const char *icon, const char *cls, GCallback cb, gpointer data) {
+ GtkWidget *btn = gtk_button_new_from_icon_name(icon, GTK_ICON_SIZE_MENU);
+ gtk_style_context_add_class(gtk_widget_get_style_context(btn), "flat");
+ gtk_style_context_add_class(gtk_widget_get_style_context(btn), cls);
+ if (cb) g_signal_connect(btn, "clicked", cb, data);
+ return btn;
+}
+
+/* === Download-Handler === */
+static VeilActiveDownload *find_active_download(VeilBrowser *b, WebKitDownload *download) {
+ for (GList *l = b->active_downloads; l; l = l->next) {
+ VeilActiveDownload *ad = l->data;
+ if (ad->download == download) return ad;
+ }
+ return NULL;
+}
+
+static void on_download_progress(WebKitDownload *download, GParamSpec *pspec, VeilBrowser *b) {
+ (void)pspec;
+ VeilActiveDownload *ad = find_active_download(b, download);
+ if (!ad || !ad->progress_bar) return;
+
+ double progress = webkit_download_get_estimated_progress(download);
+ gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ad->progress_bar), progress);
+
+ /* Status aktualisieren */
+ if (ad->status_label) {
+ guint64 received = webkit_download_get_received_data_length(download);
+ WebKitURIResponse *response = webkit_download_get_response(download);
+ guint64 total = response ? webkit_uri_response_get_content_length(response) : 0;
+
+ char *received_str = format_size(received);
+ char *total_str = total > 0 ? format_size(total) : g_strdup("?");
+ char *status_text = g_strdup_printf("%s / %s", received_str, total_str);
+ gtk_label_set_text(GTK_LABEL(ad->status_label), status_text);
+ g_free(received_str);
+ g_free(total_str);
+ g_free(status_text);
+ }
+
+ /* Geschwindigkeit berechnen und aktualisieren */
+ if (ad->speed_label) {
+ gint64 now = g_get_monotonic_time();
+ guint64 received = webkit_download_get_received_data_length(download);
+
+ if (ad->last_time > 0 && now - ad->last_time > 500000) { /* Alle 500ms aktualisieren */
+ guint64 bytes_diff = received - ad->last_bytes;
+ gdouble time_diff = (now - ad->last_time) / 1000000.0;
+ guint64 speed = (guint64)(bytes_diff / time_diff);
+
+ char *speed_str = format_speed(speed);
+ gtk_label_set_text(GTK_LABEL(ad->speed_label), speed_str);
+ g_free(speed_str);
+
+ ad->last_bytes = received;
+ ad->last_time = now;
+ } else if (ad->last_time == 0) {
+ ad->last_bytes = received;
+ ad->last_time = now;
+ }
+ }
+}
+
+static void on_download_finished(WebKitDownload *download, VeilBrowser *b) {
+ const char *dest = webkit_download_get_destination(download);
+
+ /* Von aktiven Downloads entfernen */
+ VeilActiveDownload *ad = find_active_download(b, download);
+ if (ad) {
+ b->active_downloads = g_list_remove(b->active_downloads, ad);
+ g_free(ad);
+ }
+
+ if (!dest) {
+ rebuild_downloads_popover(b);
+ return;
+ }
+
+ /* Zu abgeschlossenen Downloads hinzufügen */
+ WebKitURIRequest *req = webkit_download_get_request(download);
+ const char *url = webkit_uri_request_get_uri(req);
+
+ VeilDownload *dl = g_new0(VeilDownload, 1);
+ dl->path = g_strdup(dest);
+ dl->filename = g_path_get_basename(dest);
+ dl->url = g_strdup(url ? url : "");
+ dl->timestamp = g_get_real_time() / 1000000;
+
+ b->downloads = g_list_prepend(b->downloads, dl);
+ save_downloads(b);
+ rebuild_downloads_popover(b);
+}
+
+static void on_download_failed(WebKitDownload *download, GError *error, VeilBrowser *b) {
+ (void)error;
+ /* Von aktiven Downloads entfernen */
+ VeilActiveDownload *ad = find_active_download(b, download);
+ if (ad) {
+ b->active_downloads = g_list_remove(b->active_downloads, ad);
+ g_free(ad);
+ }
+ rebuild_downloads_popover(b);
+}
+
+static gboolean on_download_decide_destination(WebKitDownload *download, gchar *suggested_filename, VeilBrowser *b) {
+ const char *download_dir;
+
+ /* Benutzerdefiniertes Download-Verzeichnis verwenden, falls gesetzt, andernfalls System-Standard */
+ if (b->settings.download_dir[0] != '\0' &&
+ g_file_test(b->settings.download_dir, G_FILE_TEST_IS_DIR)) {
+ download_dir = b->settings.download_dir;
+ } else {
+ download_dir = g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD);
+ if (!download_dir) download_dir = g_get_home_dir();
+ }
+
+ char *dest_path = g_build_filename(download_dir, suggested_filename, NULL);
+
+ /* Existierende Dateien durch Hinzufügen eines Zahlensuffixes behandeln */
+ int counter = 1;
+ while (g_file_test(dest_path, G_FILE_TEST_EXISTS)) {
+ g_free(dest_path);
+ char *base = g_path_get_basename(suggested_filename);
+ char *ext = strrchr(base, '.');
+ if (ext) {
+ *ext = 0;
+ ext++;
+ dest_path = g_build_filename(download_dir,
+ g_strdup_printf("%s (%d).%s", base, counter, ext), NULL);
+ } else {
+ dest_path = g_build_filename(download_dir,
+ g_strdup_printf("%s (%d)", base, counter), NULL);
+ }
+ g_free(base);
+ counter++;
+ }
+
+ char *dest_uri = g_strdup_printf("file://%s", dest_path);
+ webkit_download_set_destination(download, dest_uri);
+ g_free(dest_uri);
+ g_free(dest_path);
+
+ /* Popover aktualisieren, um Dateinamen anzuzeigen */
+ rebuild_downloads_popover(b);
+
+ return TRUE; /* Wir haben das Ziel behandelt */
+}
+
+static void on_download_started(WebKitWebContext *context, WebKitDownload *download, VeilBrowser *b) {
+ (void)context;
+
+ /* Aktiven Download-Tracker erstellen */
+ VeilActiveDownload *ad = g_new0(VeilActiveDownload, 1);
+ ad->download = download;
+ ad->progress_bar = NULL;
+ ad->status_label = NULL;
+ ad->speed_label = NULL;
+ ad->last_bytes = 0;
+ ad->last_time = 0;
+
+ b->active_downloads = g_list_prepend(b->active_downloads, ad);
+
+ /* Signale verbinden */
+ g_signal_connect(download, "decide-destination", G_CALLBACK(on_download_decide_destination), b);
+ g_signal_connect(download, "notify::estimated-progress", G_CALLBACK(on_download_progress), b);
+ g_signal_connect(download, "finished", G_CALLBACK(on_download_finished), b);
+ g_signal_connect(download, "failed", G_CALLBACK(on_download_failed), b);
+
+ /* Downloads-Popover öffnen und neu aufbauen */
+ rebuild_downloads_popover(b);
+ if (b->downloads_popover) {
+ gtk_popover_popup(GTK_POPOVER(b->downloads_popover));
+ }
+}
+
+
+static VeilBrowser *veil_browser_new(void) {
+ VeilBrowser *b = g_new0(VeilBrowser, 1);
+ g_browser = b;
+
+ load_settings(b);
+
+ /* Web-Kontext mit Festplatten-Cache für Performance erstellen */
+ char *cache_dir = g_build_filename(g_get_user_cache_dir(), "veil", NULL);
+ char *data_dir = g_build_filename(g_get_user_data_dir(), "veil", NULL);
+
+ WebKitWebsiteDataManager *data_mgr = webkit_website_data_manager_new(
+ "base-cache-directory", cache_dir,
+ "base-data-directory", data_dir,
+ "is-ephemeral", FALSE, /* Persistenter Cache */
+ NULL);
+
+ b->web_context = webkit_web_context_new_with_website_data_manager(data_mgr);
+
+ /* Download-Handler verbinden */
+ g_signal_connect(b->web_context, "download-started", G_CALLBACK(on_download_started), b);
+
+ /* === WEB-KONTEXT PERFORMANCE-OPTIMIERUNGEN === */
+
+ /* Maximaler Cache für Webbrowser-Nutzungsmuster */
+ webkit_web_context_set_cache_model(b->web_context, WEBKIT_CACHE_MODEL_WEB_BROWSER);
+
+ /* Favicon-Datenbank für schnelleres Icon-Laden aktivieren */
+ char *favicon_db = g_build_filename(data_dir, "favicons", NULL);
+ webkit_web_context_set_favicon_database_directory(b->web_context, favicon_db);
+ g_free(favicon_db);
+
+ /* Hinweis: Multi-Prozess-Modell ist jetzt Standard in WebKit2GTK 2.40+ */
+ /* Hinweis: DNS-Prefetching ist automatisch in modernem WebKit */
+
+ g_free(cache_dir);
+ g_free(data_dir);
+ g_object_unref(data_mgr);
+
+ b->css_provider = gtk_css_provider_new();
+ gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
+ GTK_STYLE_PROVIDER(b->css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ apply_css(b);
+
+ b->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size(GTK_WINDOW(b->window), 1100, 700);
+
+ /* Lokalen Icon-Pfad für Entwicklung hinzufügen, dann Icon setzen */
+ char *cwd = g_get_current_dir();
+ char *local_icons = g_build_filename(cwd, "data", "icons", NULL);
+ gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), local_icons);
+ g_free(local_icons);
+ g_free(cwd);
+ gtk_window_set_icon_name(GTK_WINDOW(b->window), "veil");
+ g_signal_connect(b->window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
+ g_signal_connect(b->window, "key-press-event", G_CALLBACK(on_key), b);
+ g_signal_connect(b->window, "window-state-event", G_CALLBACK(on_window_state), b);
+
+ b->header = gtk_header_bar_new();
+ gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(b->header), FALSE);
+ gtk_header_bar_set_decoration_layout(GTK_HEADER_BAR(b->header), ":");
+ gtk_window_set_titlebar(GTK_WINDOW(b->window), b->header);
+
+ /* VEIL-Branding im Header (links von den Tabs) */
+ GtkWidget *brand = gtk_label_new("VEIL");
+ gtk_style_context_add_class(gtk_widget_get_style_context(brand), "brand");
+ gtk_widget_set_margin_start(brand, 10);
+ gtk_widget_set_margin_end(brand, 10);
+ gtk_header_bar_pack_start(GTK_HEADER_BAR(b->header), brand);
+
+ b->tab_container = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_header_bar_pack_start(GTK_HEADER_BAR(b->header), b->tab_container);
+
+ GtkWidget *add_btn = make_btn("list-add-symbolic", "header-btn", G_CALLBACK(on_new_tab), b);
+ gtk_header_bar_pack_start(GTK_HEADER_BAR(b->header), add_btn);
+
+ GtkWidget *wbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start(GTK_BOX(wbox), make_btn("window-minimize-symbolic", "header-btn", G_CALLBACK(on_minimize), b), FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(wbox), make_btn("window-maximize-symbolic", "header-btn", G_CALLBACK(on_maximize), b), FALSE, FALSE, 0);
+ GtkWidget *close_btn = make_btn("window-close-symbolic", "header-btn", G_CALLBACK(on_close_window), b);
+ gtk_style_context_add_class(gtk_widget_get_style_context(close_btn), "close");
+ gtk_box_pack_start(GTK_BOX(wbox), close_btn, FALSE, FALSE, 0);
+ gtk_header_bar_pack_end(GTK_HEADER_BAR(b->header), wbox);
+
+ b->main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add(GTK_CONTAINER(b->window), b->main_box);
+
+ b->nav_bar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->nav_bar), "nav-bar");
+
+ b->back_btn = make_btn("go-previous-symbolic", "nav-btn", G_CALLBACK(on_back), b);
+ b->forward_btn = make_btn("go-next-symbolic", "nav-btn", G_CALLBACK(on_forward), b);
+ b->reload_btn = make_btn("view-refresh-symbolic", "nav-btn", G_CALLBACK(on_reload), b);
+
+ gtk_box_pack_start(GTK_BOX(b->nav_bar), b->back_btn, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(b->nav_bar), b->forward_btn, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(b->nav_bar), b->reload_btn, FALSE, FALSE, 0);
+
+ GtkWidget *url_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_style_context_add_class(gtk_widget_get_style_context(url_box), "url-box");
+ gtk_widget_set_hexpand(url_box, TRUE);
+ gtk_widget_set_margin_start(url_box, 4);
+ gtk_widget_set_margin_end(url_box, 4);
+
+ b->url_entry = gtk_entry_new();
+ gtk_entry_set_placeholder_text(GTK_ENTRY(b->url_entry), "Search or enter URL");
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->url_entry), "url-entry");
+ gtk_widget_set_hexpand(b->url_entry, TRUE);
+ g_signal_connect(b->url_entry, "activate", G_CALLBACK(on_url_activate), b);
+ g_signal_connect(b->url_entry, "focus-in-event", G_CALLBACK(on_url_focus), b);
+ gtk_box_pack_start(GTK_BOX(url_box), b->url_entry, TRUE, TRUE, 0);
+
+ gtk_box_pack_start(GTK_BOX(b->nav_bar), url_box, TRUE, TRUE, 0);
+
+ /* Menü-Button mit Dropdown-Menü */
+ b->menu_btn = gtk_menu_button_new();
+ gtk_button_set_label(GTK_BUTTON(b->menu_btn), "☰");
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->menu_btn), "flat");
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->menu_btn), "nav-btn");
+
+ b->menu = gtk_menu_new();
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->menu), "veil-menu");
+
+ GtkWidget *new_tab_item = gtk_menu_item_new_with_label("New Tab");
+ g_signal_connect(new_tab_item, "activate", G_CALLBACK(on_menu_new_tab), b);
+ gtk_menu_shell_append(GTK_MENU_SHELL(b->menu), new_tab_item);
+
+ GtkWidget *settings_item = gtk_menu_item_new_with_label("Settings");
+ g_signal_connect(settings_item, "activate", G_CALLBACK(on_menu_settings), b);
+ gtk_menu_shell_append(GTK_MENU_SHELL(b->menu), settings_item);
+
+ gtk_widget_show_all(b->menu);
+ gtk_menu_button_set_popup(GTK_MENU_BUTTON(b->menu_btn), b->menu);
+ gtk_box_pack_end(GTK_BOX(b->nav_bar), b->menu_btn, FALSE, FALSE, 0);
+
+ /* Downloads-Button mit Popover */
+ GtkWidget *downloads_btn = gtk_menu_button_new();
+ gtk_button_set_image(GTK_BUTTON(downloads_btn),
+ gtk_image_new_from_icon_name("folder-download-symbolic", GTK_ICON_SIZE_MENU));
+ gtk_style_context_add_class(gtk_widget_get_style_context(downloads_btn), "flat");
+ gtk_style_context_add_class(gtk_widget_get_style_context(downloads_btn), "nav-btn");
+ gtk_widget_set_tooltip_text(downloads_btn, "Downloads");
+
+ b->downloads_popover = gtk_popover_new(downloads_btn);
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->downloads_popover), "veil-popover");
+ gtk_widget_set_size_request(b->downloads_popover, 340, -1);
+
+ GtkWidget *popover_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(popover_box), 10);
+
+ /* Header mit Titel und Löschen-Button */
+ GtkWidget *header_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
+
+ GtkWidget *header_icon = gtk_image_new_from_icon_name("folder-download-symbolic", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start(GTK_BOX(header_box), header_icon, FALSE, FALSE, 0);
+
+ GtkWidget *title_label = gtk_label_new("Downloads");
+ gtk_widget_set_halign(title_label, GTK_ALIGN_START);
+ gtk_widget_set_hexpand(title_label, TRUE);
+ PangoAttrList *attrs = pango_attr_list_new();
+ pango_attr_list_insert(attrs, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+ gtk_label_set_attributes(GTK_LABEL(title_label), attrs);
+ pango_attr_list_unref(attrs);
+ gtk_box_pack_start(GTK_BOX(header_box), title_label, TRUE, TRUE, 0);
+
+ GtkWidget *clear_btn = gtk_button_new_from_icon_name("edit-clear-all-symbolic", GTK_ICON_SIZE_MENU);
+ gtk_style_context_add_class(gtk_widget_get_style_context(clear_btn), "flat");
+ gtk_widget_set_tooltip_text(clear_btn, "Verlauf leeren");
+ g_signal_connect(clear_btn, "clicked", G_CALLBACK(on_clear_downloads), b);
+ gtk_box_pack_end(GTK_BOX(header_box), clear_btn, FALSE, FALSE, 0);
+
+ gtk_box_pack_start(GTK_BOX(popover_box), header_box, FALSE, FALSE, 0);
+
+ /* Trennlinie */
+ GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_set_margin_top(sep, 8);
+ gtk_widget_set_margin_bottom(sep, 4);
+ gtk_box_pack_start(GTK_BOX(popover_box), sep, FALSE, FALSE, 0);
+
+ /* Aktive Downloads-Bereich */
+ b->active_downloads_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
+ gtk_box_pack_start(GTK_BOX(popover_box), b->active_downloads_box, FALSE, FALSE, 0);
+
+ /* Scrollbare Liste abgeschlossener Downloads */
+ GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_max_content_height(GTK_SCROLLED_WINDOW(scroll), 280);
+ gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(scroll), TRUE);
+
+ b->downloads_list_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
+ gtk_container_add(GTK_CONTAINER(scroll), b->downloads_list_box);
+ gtk_box_pack_start(GTK_BOX(popover_box), scroll, TRUE, TRUE, 0);
+
+ gtk_container_add(GTK_CONTAINER(b->downloads_popover), popover_box);
+ gtk_widget_show_all(popover_box);
+
+ gtk_menu_button_set_popover(GTK_MENU_BUTTON(downloads_btn), b->downloads_popover);
+ gtk_box_pack_end(GTK_BOX(b->nav_bar), downloads_btn, FALSE, FALSE, 0);
+
+ /* Stern-Button für Lesezeichen */
+ b->star_btn = gtk_button_new_with_label("☆");
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->star_btn), "flat");
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->star_btn), "star-btn");
+ g_signal_connect(b->star_btn, "clicked", G_CALLBACK(on_star_click), b);
+ gtk_box_pack_end(GTK_BOX(b->nav_bar), b->star_btn, FALSE, FALSE, 0);
+
+ gtk_box_pack_start(GTK_BOX(b->main_box), b->nav_bar, FALSE, FALSE, 0);
+
+ b->progress_bar = gtk_progress_bar_new();
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->progress_bar), "progress");
+ gtk_widget_set_no_show_all(b->progress_bar, TRUE);
+ gtk_box_pack_start(GTK_BOX(b->main_box), b->progress_bar, FALSE, FALSE, 0);
+
+ b->gradient_line = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->gradient_line), "gradient-line");
+ gtk_box_pack_start(GTK_BOX(b->main_box), b->gradient_line, FALSE, FALSE, 0);
+
+ /* Lesezeichenleiste (in Event-Box für Rechtsklick eingebettet) */
+ GtkWidget *bookmarks_event_box = gtk_event_box_new();
+ gtk_widget_set_events(bookmarks_event_box, GDK_BUTTON_PRESS_MASK);
+ g_signal_connect(bookmarks_event_box, "button-press-event", G_CALLBACK(on_bookmarks_bar_button_press), b);
+
+ b->bookmarks_bar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
+ gtk_style_context_add_class(gtk_widget_get_style_context(b->bookmarks_bar), "bookmarks-bar");
+ gtk_widget_set_margin_start(b->bookmarks_bar, 8);
+ gtk_widget_set_margin_end(b->bookmarks_bar, 8);
+
+ gtk_container_add(GTK_CONTAINER(bookmarks_event_box), b->bookmarks_bar);
+
+ /* Lesezeichen laden und anzeigen */
+ load_bookmarks(b);
+ rebuild_bookmarks_bar(b);
+
+ /* Download-Verlauf laden */
+ load_downloads(b);
+ rebuild_downloads_popover(b);
+
+ gtk_box_pack_start(GTK_BOX(b->main_box), bookmarks_event_box, FALSE, FALSE, 0);
+ gtk_widget_set_visible(bookmarks_event_box, b->settings.show_bookmarks);
+ g_object_set_data(G_OBJECT(b->bookmarks_bar), "event_box", bookmarks_event_box);
+
+ b->webview_stack = gtk_stack_new();
+ gtk_widget_set_vexpand(b->webview_stack, TRUE);
+ gtk_widget_set_hexpand(b->webview_stack, TRUE);
+ gtk_box_pack_start(GTK_BOX(b->main_box), b->webview_stack, TRUE, TRUE, 0);
+
+ gtk_widget_show_all(b->window);
+ gtk_widget_hide(b->progress_bar);
+
+ veil_browser_new_tab(b, "veil:start");
+
+ return b;
+}
+
+int main(int argc, char *argv[]) {
+ /* WebKit2GTK Hardware-Beschleunigung hat Probleme mit nativem Wayland.
+ * X11/XWayland für Stabilität erzwingen. */
+ setenv("GDK_BACKEND", "x11", 1);
+
+ /* GBM-Puffer-Zuweisungsfehler bei einigen GPU/Treiber-Kombinationen beheben */
+ setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", 1);
+
+ gtk_init(&argc, &argv);
+ veil_browser_new();
+ gtk_main();
+ return 0;
+}
diff --git a/Veil/veil b/Veil/veil
new file mode 100755
index 0000000..d8d71a4
Binary files /dev/null and b/Veil/veil differ