/******************************************************************************\
* GTK-NoCSD                                                                    *
* Copyright (C) 2025-2026 MorsMortium                                          *
* This program is free software: you can redistribute it and/or modify         *
* it under the terms of the GNU General Public License as published by         *
* the Free Software Foundation, either version 3 of the License, or            *
* (at your option) any later version.                                          *
*                                                                              *
* This program is distributed in the hope that it will be useful,              *
* but WITHOUT ANY WARRANTY; without even the implied warranty of               *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                *
* GNU General Public License for more details.                                 *
*                                                                              *
* You should have received a copy of the GNU General Public License            *
* along with this program.  If not, see <http://www.gnu.org/licenses/>.        *
\******************************************************************************/

#define _GNU_SOURCE
#include <libadwaita-1/adwaita.h>
#include <link.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/auxv.h>

// List of arguments and number of them is saved for GTK applications, in case
// of crash, to restart them without the library
char **GTKNoCSDArguments = NULL;
int GTKNoCSDArgumentNumber = 1;

// Copies for the crash handler to use, since it loses the originals
#define TO_CHILD_COPY 3
#define TO_PARENT_COPY 4

// Anonymous pipe data for multi process communication, in both directions
int GTKNoCSDPipeToChild[2], GTKNoCSDPipeToParent[2];

// Original actions for signals, set up by the application
struct sigaction GTKNoCSDSEGVAction;
struct sigaction GTKNoCSDABRTAction;
struct sigaction GTKNoCSDFPEAction;
struct sigaction GTKNoCSDILLAction;
struct sigaction GTKNoCSDBUSAction;

void GTKNoCSDAction(int Signal, siginfo_t *Info, void *UContext) {
	// Send message to the crash loop about caught signals and run the original
	// handlers to keep applications running, in case of not fatal signal

	// Send a message to the other process that a signal has been caught
	G_GNUC_UNUSED ssize_t Ignore = write(GTKNoCSDPipeToChild[1], "1", 1);

	// Get the correct original action
	struct sigaction Action;
	if (Signal == SIGSEGV) {
		Action = GTKNoCSDSEGVAction;
	} else if (Signal == SIGABRT) {
		Action = GTKNoCSDABRTAction;
	} else if (Signal == SIGFPE) {
		Action = GTKNoCSDFPEAction;
	} else if (Signal == SIGILL) {
		Action = GTKNoCSDILLAction;
	} else if (Signal == SIGBUS) {
		Action = GTKNoCSDBUSAction;
	}

	// Run the original action
	if (Action.sa_flags & SA_SIGINFO && Action.sa_sigaction != NULL) {
		Action.sa_sigaction(Signal, Info, UContext);
	} else if (Action.sa_handler == SIG_DFL) {
		sigaction(Signal, &Action, NULL);
		raise(Signal);
	} else if (Action.sa_handler != SIG_IGN) {
		Action.sa_handler(Signal);
	}
}

// Pid of crash handler process, 5 different possible values:
// -3: Have not tried yet | -2: Failed before trying | -1: Failed forking
// 0: In child process | 0<: In parent process
pid_t GTKNoCSDCrashPid = -3;

void GTKNoCSDExit() {
	// Send a message on successful exits so the application is not restarted
	if (0 < GTKNoCSDCrashPid) {
		G_GNUC_UNUSED ssize_t Ignore = write(GTKNoCSDPipeToChild[1], "0", 1);
	}
}

void GTKNoCSDCrashHandlerExit(G_GNUC_UNUSED int Signal) {
	// Handle exit of main process, restart it if needed

	// Go through sent signals, 0 means successful exit, others mean a signal
	// was caught
	bool Restart = false;
	char Buffer;
	while (read(TO_CHILD_COPY, &Buffer, 1) > 0) {
		Restart = true;
		if (Buffer == '0') {
			Restart = false;
			break;
		}
	}

	// Restart if needed, leave
	if (Restart) {
		printf("GTK-NoCSD: Crash detected, restarting without library.\n");
		execve(GTKNoCSDArguments[1], GTKNoCSDArguments + 1, environ);
	}
	_exit(0);
}

__attribute__((visibility("default")))
void GTKNoCSDMain() {
	// This runs when the library is started as an executable

	// Unset library for next start, in case of crash
	setenv("LD_PRELOAD", "", 1);

	// Set up function for when parent process ends this one
	struct sigaction Action = {0};
	Action.sa_handler = GTKNoCSDCrashHandlerExit;
	sigemptyset(&Action.sa_mask);
	Action.sa_flags = 0;
	sigaction(SIGTERM, &Action, NULL);

	// Send handshake to parent, stop execution of process
	G_GNUC_UNUSED ssize_t Ignore = write(TO_PARENT_COPY, "1", 1);
	pause();
}

const char *GTKNoCSDTheme = NULL;

__attribute__((constructor))
static void GTKNoCSDInit(int ArgumentNumber, char **Arguments) {
	// This runs both when preloaded and when started as executable

	// Save arguments. When preloaded, this is the started programs arguments,
	// sent into the library as executable, where it is saved again, for restart
	GTKNoCSDArguments = Arguments;
	GTKNoCSDArgumentNumber = ArgumentNumber;

	// Set the GTK_CSD environment variable for applications that recognize it
	setenv("GTK_CSD", "0", 1);

	// Save theme
	GTKNoCSDTheme = getenv("GTK_THEME");
}

// GTypes used for identifying widgets
GType GTKNoCSDGTKWindow = 0;
GType GTKNoCSDADWWindow = 0;
GType GTKNoCSDHDYWindow = 0;
GType GTKNoCSDGTKApplicationWindow = 0;
GType GTKNoCSDADWApplicationWindow = 0;
GType GTKNoCSDHDYApplicationWindow = 0;
GType GTKNoCSDGTKHeaderBar = 0;
GType GTKNoCSDADWHeaderBar = 0;
GType GTKNoCSDHDYHeaderBar = 0;
GType GTKNoCSDContainer = 0;
GType GTKNoCSDGTKShortcutsWindow = 0;
GType GTKNoCSDGTKBuilder = 0;
GType GTKNoCSDGTKLabel = 0;
GType GTKNoCSDGTKBox = 0;
GType GTKNoCSDGTKSearchBar = 0;
GType GTKNoCSDADWDialog = 0;
GType GTKNoCSDGTKToggleButton = 0;
GType GTKNoCSDGTKImage = 0;
GType GTKNoCSDADWWindowTitle = 0;

// All GTK/GLib functions overwritten or used from GTK3
void (*o_gtk_window_present)(GtkWindow *) = NULL;
void (*o_gtk_widget_set_visible)(GtkWidget *, gboolean) = NULL;
GtkWidget * (*o_gtk_window_get_child)(GtkWindow *) = NULL;
void (*o_gtk_window_set_child)(GtkWindow *, GtkWidget *) = NULL;
GtkWidget * (*o_gtk_widget_get_parent)(GtkWidget *) = NULL;
GtkWidget * (*o_gtk_widget_get_first_child)(GtkWidget *) = NULL;
GtkWidget * (*o_gtk_widget_get_last_child)(GtkWidget *) = NULL;
GtkWidget * (*o_gtk_window_get_titlebar)(GtkWindow *) = NULL;
GtkWidget * (*o_gtk_about_dialog_new)() = NULL;
void (*o_gtk_window_set_titlebar)(GtkWindow *, GtkWidget *) = NULL;
GType (*o_gtk_window_get_type)() = NULL;
GType (*o_gtk_application_window_get_type)() = NULL;
GType (*o_gtk_header_bar_get_type)() = NULL;
GType (*o_adw_window_get_type)() = NULL;
GType (*o_adw_application_window_get_type)() = NULL;
GType (*o_gtk_container_get_type)() = NULL;
GType (*o_hdy_window_get_type)() = NULL;
GType (*o_hdy_application_window_get_type)() = NULL;
GType (*o_adw_header_bar_get_type)() = NULL;
GType (*o_hdy_header_bar_get_type)() = NULL;
GType (*o_gtk_shortcuts_window_get_type)() = NULL;
GType (*o_gtk_builder_get_type)() = NULL;
GType (*o_gtk_label_get_type)() = NULL;
GType (*o_gtk_box_get_type)() = NULL;
GType (*o_gtk_widget_get_type)() = NULL;
GType (*o_gtk_search_bar_get_type)() = NULL;
GType (*o_adw_dialog_get_type)() = NULL;
GType (*o_gtk_toggle_button_get_type)() = NULL;
GType (*o_gtk_image_get_type)() = NULL;
GType (*o_adw_window_title_get_type)() = NULL;
gboolean (*o_gtk_css_provider_load_from_data) (GtkCssProvider *, const gchar *,
	gssize, GError **) = NULL;
void (*o_gtk_style_context_add_provider_for_screen) (void *,
	GtkStyleProvider *, guint) = NULL;
void * (*o_gdk_screen_get_default) () = NULL;
void * (*o_gtk_widget_get_window) (GtkWidget *) = NULL;
GtkWidget *(*o_gtk_bin_get_child) (void *) = NULL;
void (*o_gtk_box_pack_start) (GtkBox *, GtkWidget *, gboolean, gboolean,
	guint) = NULL;
GList *(*o_gtk_container_get_children) (void *) = NULL;
void (*o_gtk_container_remove) (void *, GtkWidget *) = NULL;
void (*o_gtk_container_add) (void *, GtkWidget *) = NULL;
void (*o_gtk_container_set_border_width) (void *, guint) = NULL;
void (*o_gtk_header_bar_set_custom_title) (GtkHeaderBar *, GtkWidget *) = NULL;
GtkWidget * (*o_gtk_header_bar_get_custom_title) (GtkHeaderBar *) = NULL;
void (*o_gtk_container_check_resize) (void *) = NULL;
GtkWidget *(*o_gtk_widget_get_toplevel) (GtkWidget *) = NULL;
int (*o_gtk_window_get_window_type) (GtkWindow *) = NULL;
void (*o_gtk_widget_show_all) (GtkWidget *) = NULL;
void (*o_gtk_window_begin_move_drag) (GtkWindow *, gint, gint, gint,
	guint32) = NULL;
GtkWidget *(*o_gtk_event_box_new) () = NULL;
int (*o_gdk_event_get_event_type) (void *) = NULL;
gboolean (*o_gdk_event_get_button)(const GdkEvent *, guint *) = NULL;
gboolean (*o_gdk_window_show_window_menu) (void *, void *) = NULL;
int (*o_gtk_window_get_type_hint) (GtkWindow *) = NULL;
gboolean (*o_gdk_event_get_root_coords) (const GdkEvent *, gdouble *,
	gdouble *) = NULL;
gboolean (*o_gtk_builder_add_from_string) (GtkBuilder *, const gchar *, gssize,
	GError **) = NULL;
void (*o_gtk_widget_destroy) (GtkWidget *) = NULL;
gboolean (*o_g_module_symbol) (GModule *, const gchar *, gpointer *) = NULL;
void (*o_hdy_header_bar_set_decoration_layout) (GtkWidget *,
	const gchar *) = NULL;
GtkWidget *(*o_hdy_header_bar_get_custom_title) (GtkWidget *) = NULL;
void (*o_hdy_header_bar_set_custom_title) (GtkWidget *, GtkWidget *) = NULL;
void (*o_gtk_widget_reparent) (GtkWidget *, GtkWidget *) = NULL;
GtkSettings *(*o_gtk_settings_get_default) () = NULL;
GtkCssProvider *(*o_gtk_css_provider_new) () = NULL;
const gchar *(*o_gtk_widget_get_name) (GtkWidget *) = NULL;
GtkStyleContext * (*o_gtk_widget_get_style_context) (GtkWidget *) = NULL;
void (*o_gtk_style_context_add_provider) (GtkStyleContext *, GtkStyleProvider *,
	guint) = NULL;
GtkApplication * (*o_gtk_window_get_application) (GtkWindow *) = NULL;
GdkDisplay *(*o_gdk_display_get_default) () = NULL;
void (*o_gtk_css_provider_load_from_string) (GtkCssProvider *,
	const char *) = NULL;
void (*o_gtk_style_context_add_provider_for_display) (GdkDisplay *,
	GtkStyleProvider *, guint) = NULL;
GtkBuilder *(*o_gtk_builder_new) () = NULL;
const char *(*o_gtk_check_version) (guint, guint, guint) = NULL;
void (*o_gtk_widget_unparent) (GtkWidget *) = NULL;
void (*o_gtk_box_append) (GtkBox *, GtkWidget *) = NULL;
void (*o_gtk_widget_set_vexpand) (GtkWidget *, gboolean) = NULL;
GtkWidget *(*o_gtk_widget_get_ancestor) (GtkWidget *, GType) = NULL;
GtkWidget *(*o_gtk_revealer_new) () = NULL;
void (*o_gtk_revealer_set_transition_duration) (GtkRevealer *, guint) = NULL;
void (*o_gtk_widget_set_layout_manager) (GtkWidget *,
	GtkLayoutManager *) = NULL;
GtkWidget *(*o_gtk_box_new) (GtkOrientation, gint) = NULL;
void (*o_gtk_widget_set_name) (GtkWidget *, const gchar *) = NULL;
void (*o_gtk_label_set_label) (GtkLabel *, const char *) = NULL;
const char *(*o_gtk_label_get_label) (GtkLabel *) = NULL;
void (*o_gtk_window_set_title) (GtkWindow *, const char *) = NULL;
GtkWidget * (*o_gtk_header_bar_get_title_widget) (GtkHeaderBar *) = NULL;
gboolean (*o_gtk_window_get_decorated) (GtkWindow *);
GtkWidget * (*o_adw_header_bar_get_title_widget) (AdwHeaderBar *) = NULL;
void (*o_gtk_header_bar_set_title_widget) (GtkHeaderBar *, GtkWidget *) = NULL;
GtkWidget *(*o_gtk_grid_new) () = NULL;
GtkWidget *(*o_gtk_widget_get_next_sibling) (GtkWidget *) = NULL;
GtkShortcutsWindow *(*o_gtk_application_window_get_help_overlay) (
	GtkApplicationWindow *) = NULL;
void (*o_gtk_header_bar_set_decoration_layout) (GtkHeaderBar *,
	const char *) = NULL;
gboolean (*o_gtk_style_context_has_class) (GtkStyleContext *,
	const gchar *) = NULL;
guint32 (*o_gdk_event_get_time) (GdkEvent *) = NULL;
void (*o_gtk_widget_queue_resize) (GtkWidget *) = NULL;
void (*o_gtk_widget_measure) (GtkWidget *, GtkOrientation, int, int *, int *,
	int *, int *) = NULL;
void (*o_gtk_widget_set_child_visible) (GtkWidget *, gboolean) = NULL;
void (*o_gtk_widget_allocate) (GtkWidget *, int, int, int,
	GskTransform *) = NULL;
int (*o_gtk_widget_get_allocated_height) (GtkWidget *) = NULL;
void (*o_gtk_revealer_set_reveal_child) (GtkRevealer *, gboolean) = NULL;
void (*o_gtk_box_remove) (GtkBox *, GtkWidget *) = NULL;
void (*o_gtk_application_set_app_menu) (GtkApplication *, GMenuModel *) = NULL;
const char * (*o_gtk_window_get_title) (GtkWindow *) = NULL;
const char *(*o_gtk_label_get_text) (GtkLabel *) = NULL;
gboolean (*o_gtk_widget_get_mapped) (GtkWidget *) = NULL;
void (*o_gtk_window_set_hide_on_close) (GtkWindow *, gboolean) = NULL;
void (*o_gtk_window_set_modal) (GtkWindow *, gboolean) = NULL;
void (*o_gtk_window_set_transient_for) (GtkWindow *, GtkWindow *) = NULL;
void (*o_gtk_window_set_destroy_with_parent) (GtkWindow *, gboolean) = NULL;
void (*o_gtk_window_set_decorated) (GtkWindow *, gboolean) = NULL;
void (*o_gtk_box_reorder_child) (GtkBox *, GtkWidget *, gint) = NULL;
void (*o_gtk_widget_set_halign) (GtkWidget *, GtkAlign) = NULL;
void (*o_adw_header_bar_set_decoration_layout) (AdwHeaderBar *,
	const char *) = NULL;
void (*o_adw_dialog_present) (AdwDialog *, GtkWidget *) = NULL;
void (*o_adw_dialog_set_content_height) (AdwDialog *, int) = NULL;
void (*o_adw_dialog_set_content_width) (AdwDialog *, int) = NULL;
gulong (*o_g_signal_connect_object) (gpointer, const gchar *, GCallback,
	gpointer, GConnectFlags) = NULL;
GtkLayoutManager * (*o_gtk_bin_layout_new) () = NULL;
AdwDialog * (*o_adw_application_window_get_visible_dialog) (
	AdwApplicationWindow *) = NULL;
AdwDialog * (*o_adw_window_get_visible_dialog) (AdwWindow *) = NULL;
GtkWindow * (*o_gtk_window_get_transient_for) (GtkWindow *) = NULL;
int (*o_gtk_widget_get_width) (GtkWidget *) = NULL;
int (*o_gtk_widget_get_height) (GtkWidget *) = NULL;
int (*o_adw_dialog_get_content_width) (AdwDialog *) = NULL;
int (*o_adw_dialog_get_content_height) (AdwDialog *) = NULL;
void (*o_gtk_window_set_resizable) (GtkWindow *, gboolean) = NULL;
void (*o_gdk_window_get_geometry) (void *, gint *, gint *, gint *,
	gint *) = NULL;
void (*o_gdk_window_get_user_data) (void *, gpointer *) = NULL;
void (*o_gdk_window_get_frame_extents) (void *, GdkRectangle *) = NULL;
void (*o_gtk_widget_insert_action_group) (GtkWidget *, const gchar *,
	GActionGroup *) = NULL;
void (*o_adw_header_bar_set_title_widget) (AdwHeaderBar *, GtkWidget *) = NULL;
void (*o_adw_style_manager_set_color_scheme) (AdwStyleManager *,
	AdwColorScheme) = NULL;
AdwStyleManager * (*o_adw_style_manager_get_default) () = NULL;

// This is needed by an unavoidable GTK macro for type registration
GType gtk_widget_get_type();

// GTK version of application
int GTKNoCSDGTKVersion = -1;

// These are used instead of the GTK_IS functions
bool GTKNoCSDGtkWindow(GObject *Object) {
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDGTKWindow);
}
bool GTKNoCSDGtkApplicatonWindow(GObject *Object) {
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDGTKApplicationWindow);
}
bool GTKNoCSDGtkHeaderBar(GObject *Object) {
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDGTKHeaderBar);
}
bool GTKNoCSDGtkContainer(GObject *Object) {
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDContainer);
}
bool GTKNoCSDGtkShortcutsWindow(GObject *Object) {
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDGTKShortcutsWindow);
}
bool GTKNoCSDGtkBuilder(GObject *Object) {
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDGTKBuilder);
}
bool GTKNoCSDGtkLabel(GObject *Object) {
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDGTKLabel);
}
bool GTKNoCSDGtkSearchBar(GObject *Object) {
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDGTKSearchBar);
}
bool GTKNoCSDAdwWindow(GObject *Object) {
	if (GTKNoCSDADWWindow == 0) {
		return false;
	}
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDADWWindow);
}
bool GTKNoCSDAdwApplicatonWindow(GObject *Object) {
	if (GTKNoCSDADWApplicationWindow == 0) {
		return false;
	}
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDADWApplicationWindow);
}
bool GTKNoCSDAdwHeaderBar(GObject *Object) {
	if (GTKNoCSDADWHeaderBar == 0) {
		return false;
	}
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDADWHeaderBar);
}
bool GTKNoCSDAdwDialog(GObject *Object) {
	if (GTKNoCSDADWDialog == 0) {
		return false;
	}
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDADWDialog);
}
bool GTKNoCSDHdyWindow(GObject *Object) {
	if (GTKNoCSDHDYWindow == 0) {
		return false;
	}
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDHDYWindow);
}
bool GTKNoCSDHdyApplicatonWindow(GObject *Object) {
	if (GTKNoCSDHDYApplicationWindow == 0) {
		return false;
	}
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDHDYApplicationWindow);
}
bool GTKNoCSDHdyHeaderBar(GObject *Object) {
	if (GTKNoCSDHDYHeaderBar == 0) {
		return false;
	}
	return g_type_is_a(G_OBJECT_TYPE(Object), GTKNoCSDHDYHeaderBar);
}

void *GTKNoCSDGetLibrary(const char *Name, bool Fatal) {
	// Load in a library with fatal error reporting

	void *Library = dlopen(Name, RTLD_LAZY);
	if (Library == NULL && Fatal) {
		// This should never happen
		fprintf(stderr, "GTK-NoCSD: Could not load library: %s\n", Name);
		exit(EXIT_FAILURE);
	}

	return Library;
}

// Macro for simplifying type getting
#define GET_TYPE(TYPE, FUNCTION)				   \
		if (TYPE == 0 && o_ ## FUNCTION != NULL) { \
			TYPE = o_ ## FUNCTION();			   \
		}										   \


// Macro for simplifying function getting
#define LOAD_SYMBOL(LIBRARY, NAME)										\
		*(void **) (&o_ ## NAME) = dlsym(LIBRARY, #NAME);				\
		if (o_ ## NAME == NULL) {										\
			fprintf(stderr, "GTK-NoCSD: dlsym failed for %s\n", #NAME);	\
		}																\

int NewGTKVersion = -1;
char *GTKNoCSDLDLinux = NULL;

static int GTKNoCSDGetLinkedLibraries(struct dl_phdr_info *Info,
	G_GNUC_UNUSED size_t Size, G_GNUC_UNUSED void *Data) {
	// Get which libraries are linked with the application and determine GTK
	// version based on that

	if (Info->dlpi_name != NULL) {
		if (NewGTKVersion == -1) {
			if (strstr(Info->dlpi_name, "libgtk-x11-2.0.so.0") != NULL) {
				NewGTKVersion = 2;
			} else if (strstr(Info->dlpi_name, "libgtk-3.so.0") != NULL) {
				NewGTKVersion = 3;
			} else if (strstr(Info->dlpi_name, "libgtk-4.so.1") != NULL) {
				NewGTKVersion = 4;
			}
		}

		// Used for starting the crash handler. If found and GTK version is
		// known, leave
		if (strstr(Info->dlpi_name, "ld-linux")) {
			GTKNoCSDLDLinux = strdup(Info->dlpi_name);
		}

		if (GTKNoCSDLDLinux != NULL && NewGTKVersion != -1) {
			return 1;
		}
	}

	return 0;
}

// Whether GTypes have been fetched already
bool GTKNoCSDGotTypes = false;

void GTKNoCSDGetReferences(bool GetTypes) {
	// Fetch each needed function

	// These functions are always needed
	void *Library = NULL;
	if (o_g_signal_connect_object == NULL) {
		Library = GTKNoCSDGetLibrary("libgobject-2.0.so.0", true);
		LOAD_SYMBOL(Library, g_signal_connect_object);
	}
	if (o_g_module_symbol == NULL) {
		Library = GTKNoCSDGetLibrary("libgmodule-2.0.so.0", true);
		LOAD_SYMBOL(Library, g_module_symbol);
	}

	// Get used GTK version
	*(void **) (&o_gtk_check_version) = dlsym(RTLD_NEXT, "gtk_check_version");
	if (o_gtk_check_version != NULL) {
		if (o_gtk_check_version(2, 0, 0) == NULL) {
			NewGTKVersion = 2;
		} else if (o_gtk_check_version(3, 0, 0) == NULL) {
			NewGTKVersion = 3;
		} else if (o_gtk_check_version(4, 0, 0) == NULL) {
			NewGTKVersion = 4;
		}
	}

	// If gtk_check_version is missing, check linked libraries, plus get crash
	// handler starter
	if (GTKNoCSDLDLinux == NULL || NewGTKVersion == -1) {
		dl_iterate_phdr(GTKNoCSDGetLinkedLibraries, NULL);
	}

	// Get name of used library
	const char *Name = RTLD_NEXT;
	if (NewGTKVersion == 2) {
		Name = "libgtk-x11-2.0.so.0";
	} else if (NewGTKVersion == 3) {
		Name = "libgtk-3.so.0";
	} else if (NewGTKVersion == 4) {
		Name = "libgtk-4.so.1";
	}

	// Save for new type getting if needed
	int OldGTKVersion = GTKNoCSDGTKVersion;

	// Only get functions once or when the version changed
	if (GTKNoCSDGTKVersion != NewGTKVersion) {
		GTKNoCSDGTKVersion = NewGTKVersion;

		// Load in GTK3 functions for passing through hook
		Library = GTKNoCSDGetLibrary(Name, true);

		// In all GTK versions
		LOAD_SYMBOL(Library, gtk_window_present);
		LOAD_SYMBOL(Library, gtk_widget_set_visible);
		LOAD_SYMBOL(Library, gtk_widget_get_parent);
		LOAD_SYMBOL(Library, gtk_about_dialog_new);
		LOAD_SYMBOL(Library, gtk_settings_get_default);
		LOAD_SYMBOL(Library, gtk_window_get_type);
		LOAD_SYMBOL(Library, gtk_builder_add_from_string);
		LOAD_SYMBOL(Library, gdk_display_get_default);
		LOAD_SYMBOL(Library, gtk_builder_new);
		LOAD_SYMBOL(Library, gtk_check_version);
		LOAD_SYMBOL(Library, gtk_builder_get_type);
		LOAD_SYMBOL(Library, gtk_label_get_type);
		LOAD_SYMBOL(Library, gtk_window_set_modal);
		LOAD_SYMBOL(Library, gtk_window_set_transient_for);
		LOAD_SYMBOL(Library, gtk_window_set_destroy_with_parent);
		LOAD_SYMBOL(Library, gtk_box_get_type);
		LOAD_SYMBOL(Library, gtk_widget_get_type);
		LOAD_SYMBOL(Library, gtk_window_set_decorated);

		if (GTKNoCSDGTKVersion < 4) {
			// In GTK2 and GTK3

			LOAD_SYMBOL(Library, gdk_screen_get_default);
			LOAD_SYMBOL(Library, gtk_widget_get_window);
			LOAD_SYMBOL(Library, gtk_bin_get_child);
			LOAD_SYMBOL(Library, gtk_box_pack_start);
			LOAD_SYMBOL(Library, gtk_container_get_children);
			LOAD_SYMBOL(Library, gtk_container_remove);
			LOAD_SYMBOL(Library, gtk_container_add);
			LOAD_SYMBOL(Library, gtk_container_set_border_width);
			LOAD_SYMBOL(Library, gtk_container_get_type);
			LOAD_SYMBOL(Library, gtk_container_check_resize);
			LOAD_SYMBOL(Library, gtk_widget_get_toplevel);
			LOAD_SYMBOL(Library, gtk_window_get_window_type);
			LOAD_SYMBOL(Library, gtk_widget_show_all);
			LOAD_SYMBOL(Library, gtk_window_begin_move_drag);
			LOAD_SYMBOL(Library, gtk_event_box_new);
			LOAD_SYMBOL(Library, gtk_window_get_type_hint);
			LOAD_SYMBOL(Library, gdk_event_get_root_coords);
			LOAD_SYMBOL(Library, gtk_widget_destroy);
			LOAD_SYMBOL(Library, gtk_widget_reparent);
			LOAD_SYMBOL(Library, gdk_window_get_geometry);
			LOAD_SYMBOL(Library, gdk_window_get_user_data);
			LOAD_SYMBOL(Library, gdk_window_get_frame_extents);
		}

		if (GTKNoCSDGTKVersion == 3 || GTKNoCSDGTKVersion == 4) {
			// In GTK3 and GTK4

			LOAD_SYMBOL(Library, gtk_window_get_titlebar);
			LOAD_SYMBOL(Library, gtk_window_set_titlebar);
			LOAD_SYMBOL(Library, gtk_application_window_get_type);
			LOAD_SYMBOL(Library, gtk_header_bar_get_type);
			LOAD_SYMBOL(Library, gtk_css_provider_load_from_data);
			LOAD_SYMBOL(Library, gtk_css_provider_new);
			LOAD_SYMBOL(Library, gtk_widget_get_name);
			LOAD_SYMBOL(Library, gtk_widget_get_style_context);
			LOAD_SYMBOL(Library, gtk_style_context_add_provider);
			LOAD_SYMBOL(Library, gtk_window_get_application);
			LOAD_SYMBOL(Library, gtk_widget_unparent);
			LOAD_SYMBOL(Library, gtk_widget_set_vexpand);
			LOAD_SYMBOL(Library, gtk_widget_get_ancestor);
			LOAD_SYMBOL(Library, gtk_revealer_new);
			LOAD_SYMBOL(Library, gtk_revealer_set_transition_duration);
			LOAD_SYMBOL(Library, gtk_box_new);
			LOAD_SYMBOL(Library, gtk_widget_set_name);
			LOAD_SYMBOL(Library, gtk_label_set_label);
			LOAD_SYMBOL(Library, gtk_label_get_label);
			LOAD_SYMBOL(Library, gtk_window_set_title);
			LOAD_SYMBOL(Library, gtk_shortcuts_window_get_type);
			LOAD_SYMBOL(Library, gtk_window_get_decorated);
			LOAD_SYMBOL(Library, gtk_grid_new);
			LOAD_SYMBOL(Library, gtk_application_window_get_help_overlay);
			LOAD_SYMBOL(Library, gtk_header_bar_set_decoration_layout);
			LOAD_SYMBOL(Library, gtk_style_context_has_class);
			LOAD_SYMBOL(Library, gdk_event_get_time);
			LOAD_SYMBOL(Library, gtk_widget_queue_resize);
			LOAD_SYMBOL(Library, gtk_widget_set_child_visible);
			LOAD_SYMBOL(Library, gtk_widget_get_allocated_height);
			LOAD_SYMBOL(Library, gtk_revealer_set_reveal_child);
			LOAD_SYMBOL(Library, gtk_window_get_title);
			LOAD_SYMBOL(Library, gtk_label_get_text);
			LOAD_SYMBOL(Library, gtk_widget_get_mapped);
			LOAD_SYMBOL(Library, gtk_widget_insert_action_group);
		}

		if (GTKNoCSDGTKVersion == 4) {
			// Only in GTK4

			LOAD_SYMBOL(Library, gtk_window_get_child);
			LOAD_SYMBOL(Library, gtk_window_set_child);
			LOAD_SYMBOL(Library, gtk_widget_get_first_child);
			LOAD_SYMBOL(Library, gtk_widget_get_last_child);
			LOAD_SYMBOL(Library, gtk_css_provider_load_from_string);
			LOAD_SYMBOL(Library, gtk_style_context_add_provider_for_display);
			LOAD_SYMBOL(Library, gtk_box_append);
			LOAD_SYMBOL(Library, gtk_widget_set_layout_manager);
			LOAD_SYMBOL(Library, gtk_header_bar_get_title_widget);
			LOAD_SYMBOL(Library, gtk_header_bar_set_title_widget);
			LOAD_SYMBOL(Library, gtk_widget_get_next_sibling);
			LOAD_SYMBOL(Library, gtk_widget_measure);
			LOAD_SYMBOL(Library, gtk_widget_allocate);
			LOAD_SYMBOL(Library, gtk_box_remove);
			LOAD_SYMBOL(Library, gtk_window_set_hide_on_close);
			LOAD_SYMBOL(Library, gtk_bin_layout_new);
			LOAD_SYMBOL(Library, gtk_window_get_transient_for);
			LOAD_SYMBOL(Library, gtk_widget_get_width);
			LOAD_SYMBOL(Library, gtk_widget_get_height);
			LOAD_SYMBOL(Library, gtk_window_set_resizable);

			Library = GTKNoCSDGetLibrary("libadwaita-1.so.0", false);
			if (Library == NULL) {
				printf("GTK-NoCSD: LibAdwaita not found.\n");
			} else {
				// Only in LibAdwaita

				LOAD_SYMBOL(Library, adw_window_get_type);
				LOAD_SYMBOL(Library, adw_application_window_get_type);
				LOAD_SYMBOL(Library, adw_header_bar_get_type);
				LOAD_SYMBOL(Library, adw_header_bar_get_title_widget);
				LOAD_SYMBOL(Library, adw_dialog_get_type);
				LOAD_SYMBOL(Library, adw_header_bar_set_decoration_layout);
				LOAD_SYMBOL(Library, adw_dialog_present);
				LOAD_SYMBOL(Library, adw_dialog_set_content_height);
				LOAD_SYMBOL(Library, adw_dialog_set_content_width);
				LOAD_SYMBOL(Library, adw_window_title_get_type);
				LOAD_SYMBOL(Library, adw_application_window_get_visible_dialog);
				LOAD_SYMBOL(Library, adw_window_get_visible_dialog);
				LOAD_SYMBOL(Library, adw_dialog_get_content_width);
				LOAD_SYMBOL(Library, adw_dialog_get_content_height);
				LOAD_SYMBOL(Library, adw_header_bar_set_title_widget);
				LOAD_SYMBOL(Library, adw_style_manager_get_default);
				LOAD_SYMBOL(Library, adw_style_manager_set_color_scheme);
			}
		}

		if (GTKNoCSDGTKVersion == 3) {
			// Only in GTK3

			LOAD_SYMBOL(Library, gtk_style_context_add_provider_for_screen);
			LOAD_SYMBOL(Library, gtk_header_bar_set_custom_title);
			LOAD_SYMBOL(Library, gtk_header_bar_get_custom_title);
			LOAD_SYMBOL(Library, gdk_event_get_event_type);
			LOAD_SYMBOL(Library, gdk_event_get_button);
			LOAD_SYMBOL(Library, gdk_window_show_window_menu);
			LOAD_SYMBOL(Library, gtk_application_set_app_menu);
			LOAD_SYMBOL(Library, gtk_box_reorder_child);
			LOAD_SYMBOL(Library, gtk_search_bar_get_type);
			LOAD_SYMBOL(Library, gtk_widget_set_halign);
			LOAD_SYMBOL(Library, gtk_toggle_button_get_type);
			LOAD_SYMBOL(Library, gtk_image_get_type);

			Library = GTKNoCSDGetLibrary("libhandy-1.so.0", false);
			if (Library == NULL) {
				printf("GTK-NoCSD: LibHandy not found.\n");
			} else {
				// Only in LibHandy

				LOAD_SYMBOL(Library, hdy_window_get_type);
				LOAD_SYMBOL(Library, hdy_application_window_get_type);
				LOAD_SYMBOL(Library, hdy_header_bar_get_type);
				LOAD_SYMBOL(Library, hdy_header_bar_set_decoration_layout);
				LOAD_SYMBOL(Library, hdy_header_bar_get_custom_title);
				LOAD_SYMBOL(Library, hdy_header_bar_set_custom_title);
			}
		}
	}

	// g_intern_static_string is too early to fetch GTypes, there they are
	// not fetched yet
	if ((!GTKNoCSDGotTypes || OldGTKVersion != NewGTKVersion) && GetTypes) {
		// Better to get these once and then reuse
		GET_TYPE(GTKNoCSDGTKWindow, gtk_window_get_type);
		GET_TYPE(GTKNoCSDADWWindow, adw_window_get_type);
		GET_TYPE(GTKNoCSDHDYWindow, hdy_window_get_type);
		GET_TYPE(GTKNoCSDGTKApplicationWindow,
			gtk_application_window_get_type);
		GET_TYPE(GTKNoCSDADWApplicationWindow,
			adw_application_window_get_type);
		GET_TYPE(GTKNoCSDHDYApplicationWindow,
			hdy_application_window_get_type);
		GET_TYPE(GTKNoCSDGTKHeaderBar, gtk_header_bar_get_type);
		GET_TYPE(GTKNoCSDADWHeaderBar, adw_header_bar_get_type);
		GET_TYPE(GTKNoCSDHDYHeaderBar, hdy_header_bar_get_type);
		GET_TYPE(GTKNoCSDContainer, gtk_container_get_type);
		GET_TYPE(GTKNoCSDGTKShortcutsWindow, gtk_shortcuts_window_get_type);
		GET_TYPE(GTKNoCSDGTKBuilder, gtk_builder_get_type);
		GET_TYPE(GTKNoCSDGTKLabel, gtk_label_get_type);
		GET_TYPE(GTKNoCSDGTKBox, gtk_box_get_type);
		GET_TYPE(GTKNoCSDGTKSearchBar, gtk_search_bar_get_type);
		GET_TYPE(GTKNoCSDADWDialog, adw_dialog_get_type);
		GET_TYPE(GTKNoCSDGTKToggleButton, gtk_toggle_button_get_type);
		GET_TYPE(GTKNoCSDGTKImage, gtk_image_get_type);
		GET_TYPE(GTKNoCSDADWWindowTitle, adw_window_title_get_type);
		GTKNoCSDGotTypes = true;
	}
}

bool GTKNoCSDMagic(GtkWindow *Window);
gboolean GTKNoCSDRecall(gpointer Data) {
	// Presents the window at a later time

	GTKNoCSDMagic((GtkWindow *) Data);

	return FALSE;
}

// WARNING: These malloced lists will live forever. If the app spawns 100
// regular or undecorated CSD windows, these will grow to 101 length, which will
// use a staggering 808 bytes of memory, on 64bit systems.
// GTKNoCSDWindowList is a list of weak references to be able to
// check that a window has been destroyed, when using g_idle_add or such. The
// actual window values might be NULL
GtkWindow ***GTKNoCSDWindowList = NULL;
GtkWindow **GTKNoCSDUndecoratedWindowList = NULL;

GtkWidget *GTKNoCSDGetWindow(GtkWidget *Widget) {
	// Get the window of a widget in both GTK3 and GTK4, unified

	if (GTKNoCSDGTKVersion == 3) {
		GtkWidget *Window = o_gtk_widget_get_toplevel(Widget);
		return GTKNoCSDGtkWindow(G_OBJECT(Window)) ? Window : NULL;
	} else {
		return o_gtk_widget_get_ancestor(Widget, GTKNoCSDGTKWindow);
	}
}

gboolean GTKNoCSDLabelChange(gpointer Data) {
	// Check title content, and hide it, if it is the same as the window or
	// application title

	// Disconnect signal, this should only run once per change
	g_signal_handlers_disconnect_by_func(
		Data, G_CALLBACK(GTKNoCSDLabelChange), Data);

	// Check if label is part of any kind of headerbar, leave if not
	GtkWidget *Label = (GtkWidget *) Data;
	GtkWidget *Parent = o_gtk_widget_get_ancestor(Label, GTKNoCSDGTKHeaderBar);
	if (Parent == NULL && GTKNoCSDADWHeaderBar != 0) {
		Parent = o_gtk_widget_get_ancestor(Label, GTKNoCSDADWHeaderBar);
	}
	if (Parent == NULL && GTKNoCSDHDYHeaderBar != 0) {
		Parent = o_gtk_widget_get_ancestor(Label, GTKNoCSDHDYHeaderBar);
	}
	if (Parent == NULL) {
		return FALSE;
	}

	// Get label text and window
	const char *Text = o_gtk_label_get_text((GtkLabel *) Label);
	GtkWindow *Window = (GtkWindow *) GTKNoCSDGetWindow(Label);

	// If window has own title, compare with that, if not, it takes application
	// name, compare with that
	const char *Title = Window != NULL ? o_gtk_window_get_title(Window) : NULL;
	Title = Title == NULL ? g_get_application_name() : Title;

	// Only show if different
	if (Title != NULL) {
		o_gtk_widget_set_visible(Label, !strcmp(Text, Title) == 0);
	}

	return FALSE;
}

void GTKNoCSDTitleChanged(GtkLabel *Label, G_GNUC_UNUSED GParamSpec
	*Spec, G_GNUC_UNUSED gpointer Data) {
	// If widget is already in tree, start checking/changing label, otherwise
	// add signal to start when it will be in tree

	if (!o_gtk_widget_get_mapped((GtkWidget *) Label)) {
		g_signal_connect(Label, "map", G_CALLBACK(GTKNoCSDLabelChange), Label);
		return;
	}

	GTKNoCSDLabelChange(Label);
}

bool GTKNoCSDCheckGTK4Header(GtkWidget *Widget) {
	// Check if a widget is a header bar and if it is and the title widget is
	// empty, disable showing it, plus signal it back
	// Also hide simple label titles that are just the window title

	// If widget is label, set up monitor for the content
	if (GTKNoCSDGtkLabel(G_OBJECT(Widget)) && g_signal_handler_find(Widget,
		G_SIGNAL_MATCH_FUNC, 0,
		0, NULL, G_CALLBACK(GTKNoCSDTitleChanged), NULL) == 0) {
		g_signal_connect(Widget, "notify::label",
			G_CALLBACK(GTKNoCSDTitleChanged), NULL);
		GTKNoCSDTitleChanged((GtkLabel *) Widget, NULL, NULL);
	}

	// Hide empty title widgets and force set decoration layouts to empty
	if (GTKNoCSDAdwHeaderBar(G_OBJECT(Widget))) {
		o_adw_header_bar_set_decoration_layout((AdwHeaderBar *) Widget, "");
		if (o_adw_header_bar_get_title_widget((AdwHeaderBar *) Widget) ==
			NULL) {
			o_adw_header_bar_set_title_widget((AdwHeaderBar *) Widget,
				o_gtk_grid_new());
		}
		return true;
	} else if (GTKNoCSDGtkHeaderBar(G_OBJECT(Widget))) {
		// GTK4 headers are never looked for so they are never confirmed
		o_gtk_header_bar_set_decoration_layout((GtkHeaderBar *) Widget, "");
		if (o_gtk_header_bar_get_title_widget((GtkHeaderBar *) Widget) ==
			NULL) {
			o_gtk_header_bar_set_title_widget((GtkHeaderBar *) Widget,
				o_gtk_grid_new());
		}
	}

	return false;
}

GtkWidget *GTKNoCSDGetGTK4Headers(GtkWidget *Widget, bool GetWidget) {
	// Recursively searches the entire widget tree for multiple GTK4 or
	// LibAdwaita headers and disables all empty titles or gives back the first
	// found one

	// Check current widget
	if (GTKNoCSDCheckGTK4Header(Widget) && GetWidget) {
		return Widget;
	}

	// Only containers can have children
	GtkWidget *Child = o_gtk_widget_get_first_child(Widget);
	while (Child != NULL) {
		// Check children of widget if there are
		GtkWidget *PossiblyHeader = GTKNoCSDGetGTK4Headers(Child, GetWidget);
		if (PossiblyHeader != NULL && GetWidget) {
			return PossiblyHeader;
		}

		Child = o_gtk_widget_get_next_sibling(Child);
	}

	return NULL;
}

gboolean GTKNoCSDSetGTK4Headers(gpointer Data) {
	// GTK callback for GTKNoCSDGetGTK4Headers

	GTKNoCSDGetGTK4Headers((GtkWidget *) Data, false);

	return FALSE;
}

bool GTKNoCSDTest(GtkWidget *Widget) {
	// Simple test if the widget we deal with is our own

	return Widget != NULL &&
		   strcmp(o_gtk_widget_get_name(Widget), "GTKNoCSD") == 0;
}

gboolean GTKNoCSDHandleShortcutsWindow(gpointer Data) {
	// Check if an application window has a shortcuts window and add SSD to it

	// If the window got destroyed in the meantime, return
	GtkWindow **Window = (GtkWindow **) Data;
	if (*Window == NULL) {
		return FALSE;
	}

	GtkShortcutsWindow *Overlay = o_gtk_application_window_get_help_overlay(
		(GtkApplicationWindow *) Window[0]);

	if (Overlay != NULL) {
		GTKNoCSDMagic((GtkWindow *) Overlay);
	}

	return FALSE;
}

static void GTKNoCSDWindowAdded(G_GNUC_UNUSED GtkApplication *Application,
	GtkWindow *Window) {
	// Add SSD to all added windows

	GTKNoCSDMagic(Window);
}

static void GTKNoCSDApplicationChanged(GObject *Window,
	G_GNUC_UNUSED GParamSpec *Spec) {
	// If the window got added to an application, add window monitor function to
	// the application

	GtkApplication *Application =
		o_gtk_window_get_application((GtkWindow *) Window);

	if (Application != NULL && g_signal_handler_find(Application,
		G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(GTKNoCSDWindowAdded),
		NULL) == 0) {
		g_signal_connect(Application, "window-added",
			G_CALLBACK(GTKNoCSDWindowAdded), NULL);
	}
}

static gboolean GTKNoCSDWindowMap(G_GNUC_UNUSED GSignalInvocationHint *Hint,
	G_GNUC_UNUSED guint Number, const GValue *Values,
	G_GNUC_UNUSED gpointer Data) {
	// Add SSD to all mapped windows

	// Only if the value is a window (might be redundant)
	GObject *Window = g_value_get_object(&Values[0]);
	if (GTKNoCSDGtkWindow(Window)) {
		GTKNoCSDMagic((GtkWindow *) Window);
	}

	return TRUE;
}

GtkWidget *GTKNoCSDWindowGetChild(GtkWindow *Window) {
	// Get child of window in both GTK3 and GTK4, unified

	if (GTKNoCSDGTKVersion == 3) {
		return o_gtk_bin_get_child(Window);
	} else {
		return o_gtk_window_get_child(Window);
	}
}

void GTKNoCSDWindowSetChild(GtkWindow *Window, GtkWidget *Widget) {
	// Replace child of window in both GTK3 and GTK4, unified

	if (GTKNoCSDGTKVersion == 3) {
		if (Widget == NULL) {
			GtkWidget *Child = o_gtk_bin_get_child(Window);
			if (Child != NULL) {
				o_gtk_container_remove(Window, Child);
			}
		} else {
			o_gtk_container_add(Window, Widget);
		}
	} else {
		o_gtk_window_set_child(Window, Widget);
	}
}

void GTKNoCSDGtkBoxAdd(GtkWidget *Box, GtkWidget *Widget) {
	// Add to box in both GTK3 and GTK4, unified

	if (GTKNoCSDGTKVersion == 3) {
		o_gtk_box_pack_start((GtkBox *) Box, Widget, false, true, 0);
	} else {
		o_gtk_box_append((GtkBox *) Box, Widget);
	}
}

bool GTKNoCSDGetGTK3Headers(GtkWidget *Container) {
	// Removing extra close buttons added by certain applications

	// Certain applications set this layout for themselves, here it is unset
	// Also remove empty titles
	if (GTKNoCSDGtkHeaderBar(G_OBJECT(Container))) {
		o_gtk_header_bar_set_decoration_layout((GtkHeaderBar *) Container, "");
		GtkHeaderBar *Header = (GtkHeaderBar *) Container;
		if (o_gtk_header_bar_get_custom_title(Header) == NULL) {
			o_gtk_header_bar_set_custom_title(Header, o_gtk_grid_new());
		}
	} else if (GTKNoCSDHdyHeaderBar(G_OBJECT(Container))) {
		o_hdy_header_bar_set_decoration_layout(Container, "");
		if (o_hdy_header_bar_get_custom_title(Container) == NULL) {
			o_hdy_header_bar_set_custom_title(Container, o_gtk_grid_new());
		}
	}

	// If widget is label, set up monitor for the content
	if (GTKNoCSDGtkLabel(G_OBJECT(Container)) &&
		g_signal_handler_find(Container, G_SIGNAL_MATCH_FUNC, 0,
		0, NULL, G_CALLBACK(GTKNoCSDTitleChanged), NULL) == 0) {
		g_signal_connect(Container, "notify::label",
			G_CALLBACK(GTKNoCSDTitleChanged), NULL);
		GTKNoCSDTitleChanged((GtkLabel *) Container, NULL, NULL);
	}

	GtkStyleContext *Context = o_gtk_widget_get_style_context(Container);
	if (o_gtk_style_context_has_class(Context, "titlebutton")) {
		o_gtk_widget_destroy(Container);
		return true;
	}

	// If not a container, leave
	if (!GTKNoCSDGtkContainer(G_OBJECT(Container))) {
		return false;
	}

	// Go through all children, count all and removed number of them
	int Amount = 0, Destroyed = 0;
	GList *Children = o_gtk_container_get_children(Container);
	for (GList *Iter = Children; Iter != NULL; Iter = Iter->next) {
		GtkWidget *Child = (GtkWidget *) Iter->data;

		// Go through all children of child too
		if (GTKNoCSDGetGTK3Headers(Child)) {
			o_gtk_widget_destroy(Child);
			++Destroyed;
		} else {
			// If close button, remove
			Context = o_gtk_widget_get_style_context(Child);
			if (o_gtk_style_context_has_class(Context, "titlebutton")) {
				o_gtk_widget_destroy(Child);
				++Destroyed;
			}
		}

		++Amount;
	}
	g_list_free(Children);

	// If all widgets got removed, remove the container too (title button)
	if (Amount == Destroyed && Amount != 0) {
		o_gtk_widget_destroy(Container);
		return true;
	}

	return false;
}

gboolean GTKNoCSDSetGTK3Headers(gpointer Data) {
	// GTK callback for GTKNoCSDGetGTK3Headers

	GTKNoCSDGetGTK3Headers((GtkWidget *) Data);

	return FALSE;
}

bool GTKNoCSDSettings = false;
bool GTKNoCSDHooked = false;

void GTKNoCSDHooker() {
	// Add hook for all mapped windows to get SSD

	if (!GTKNoCSDHooked && GTKNoCSDGTKWindow != 0) {
		unsigned int Hook = g_signal_lookup("map", GTKNoCSDGTKWindow);
		if (0 < Hook) {
			g_signal_add_emission_hook(Hook, 0, GTKNoCSDWindowMap, NULL, NULL);

			// Only add them once
			GTKNoCSDHooked = true;
		}
	}
}

gboolean GTKNoCSDGTK3EventBoxActions(G_GNUC_UNUSED GtkWidget *Widget,
	void *Event, gpointer Data) {
	// Recreate window controls, dragging when menu is dragged, opening window
	// manager menu on right clicking it

	if (o_gdk_event_get_event_type(Event) == 4) {
		// 4 is button press
		unsigned int Button;
		o_gdk_event_get_button(Event, &Button);

		if (Button == 1) {
			// 1 is left button, dragging starts here
			gdouble X, Y;
			o_gdk_event_get_root_coords(Event, &X, &Y);

			// WARNING: Downcast
			o_gtk_window_begin_move_drag((GtkWindow *) Data, 1,
				(gint) X, (gint) Y, o_gdk_event_get_time(Event));
			return TRUE;
		} else if (Button == 3) {
			// 3 is right button, window manager menu opens here
			o_gdk_window_show_window_menu(o_gtk_widget_get_window(Data), Event);
			return TRUE;
		}
	}

	return FALSE;
}

GtkWidget *GTKNoCSDFirstChild(GtkWidget *Parent) {
	// Get the first child in a container in both GTK3 and GTK4, unified

	if (Parent == NULL) {
		return NULL;
	}

	GtkWidget *Child = NULL;
	if (GTKNoCSDGTKVersion == 3) {
		GList *Children = o_gtk_container_get_children(Parent);
		if (Children == NULL) {
			return NULL;
		}
		Child = (GtkWidget *) Children->data;
		g_list_free(Children);
	} else {
		return o_gtk_widget_get_first_child(Parent);
	}

	return Child;
}

static gboolean GTKNoCSDGTK3CurrentWindowStyle(gpointer Data) {
	// The CSS might break when it is applied in the map stage of the window.
	// This makes it properly redraw

	// If the window got destroyed in the meantime, return
	GtkWindow **Window = (GtkWindow **) Data;
	if (*Window == NULL) {
		return FALSE;
	}

	o_gtk_widget_queue_resize((GtkWidget *) Window[0]);
	return FALSE;
}

// These are needed for _get_type() and might be missing in older GLib versions
#undef g_once_init_enter_pointer
#undef g_once_init_leave_pointer
#define g_once_init_enter_pointer g_once_init_enter
#define g_once_init_leave_pointer(location, result)	\
		g_once_init_leave((location), (gsize) (result))

// The max height, with which a header bar is considered empty
const int GTKNoCSDEmptySize = 5;

// Widget used to hide empty header bar on GTK4
G_DECLARE_FINAL_TYPE(GTKNoCSDBox, GTKNoCSDBox, GTKNoCSD, BOX, GtkBox)
struct _GTKNoCSDBox {
	GtkBox ParentInstance;
};

G_DEFINE_TYPE(GTKNoCSDBox, GTKNoCSDBox, GTKNoCSDGTKBox)

static void GTKNoCSDBoxMeasure(GtkWidget *Widget, GtkOrientation Orientation,
	int ForSize, int *Minimum, int *Natural, int *MinimumBaseline,
	int *NaturalBaseline) {
	// Report size to GTK

	// Default no size on no child
	if (Minimum != NULL) {
		*Minimum = 0;
	}
	if (Natural != NULL) {
		*Natural = 0;
	}
	if (MinimumBaseline != NULL) {
		*MinimumBaseline = -1;
	}
	if (NaturalBaseline != NULL) {
		*NaturalBaseline = -1;
	}

	// Get child, return if missing
	GtkWidget *Child = o_gtk_widget_get_first_child(Widget);
	if (Child == NULL) {
		return;
	}

	// Get size needed by child
	int MinimumChild, NaturalChild, MinimumBaselineChild, NaturalBaselineChild;
	o_gtk_widget_measure(Child, Orientation, ForSize, &MinimumChild,
		&NaturalChild, &MinimumBaselineChild, &NaturalBaselineChild);

	// If it is less than 6, hide it
	o_gtk_widget_set_child_visible(Widget, GTKNoCSDEmptySize < NaturalChild);

	// If less than 6 and vertical measurement, return
	if (Orientation == GTK_ORIENTATION_VERTICAL &&
		NaturalChild <= GTKNoCSDEmptySize) {
		return;
	}

	// Report actual size
	if (Minimum != NULL) {
		*Minimum = MinimumChild;
	}
	if (Natural != NULL) {
		*Natural = NaturalChild;
	}
	if (MinimumBaseline != NULL) {
		*MinimumBaseline = MinimumBaselineChild;
	}
	if (NaturalBaseline != NULL) {
		*NaturalBaseline = NaturalBaselineChild;
	}
}

static void GTKNoCSDBoxSizeAllocate(GtkWidget *Widget, int Width, int Height,
	int Baseline) {
	// Allocate widget and child if it exists

	GTK_WIDGET_CLASS(GTKNoCSDBox_parent_class)->size_allocate(Widget, Width,
		Height, Baseline);
	GtkWidget *Child = o_gtk_widget_get_first_child(Widget);
	if (Child != NULL) {
		o_gtk_widget_allocate(Child, Width, Height, Baseline, NULL);
	}
}

static void GTKNoCSDBox_class_init(GTKNoCSDBoxClass *Klass) {
	// Set up measure and size allocate functions
	GtkWidgetClass *WidgetClass = GTK_WIDGET_CLASS(Klass);
	WidgetClass->measure = GTKNoCSDBoxMeasure;
	WidgetClass->size_allocate = GTKNoCSDBoxSizeAllocate;
}

static void GTKNoCSDBox_init(G_GNUC_UNUSED GTKNoCSDBox *Self) {
}

static gboolean GTKNoCSDGTK3Height(gpointer Data) {
	// Hide widget with Revealer if small enough

	// Get height
	GtkWidget *Widget = (GtkWidget *) Data;
	int Height = o_gtk_widget_get_allocated_height(Widget);

	// Get Revealer
	GtkWidget *Revealer = o_gtk_widget_get_parent(Widget);
	Revealer = o_gtk_widget_get_parent(Revealer);

	// Hide or not
	o_gtk_revealer_set_reveal_child((GtkRevealer *) Revealer,
		GTKNoCSDEmptySize < Height);
	return FALSE;
}

static void GTKNoCSDGTK3HeaderSizeAllocate(GtkWidget *Widget,
	G_GNUC_UNUSED GdkRectangle *Allocation, G_GNUC_UNUSED gpointer Data) {
	// Changing visibility in size allocate is bad, so we do it later
	g_idle_add(GTKNoCSDGTK3Height, Widget);
}

GtkWidget *GTKNoCSDGTK3ShortcutsWindowSearchBar(void *container) {
	// Find the SearchBar widget in a container, the ShortcutsWindow

	GList *Children = o_gtk_container_get_children(container);

	// Go through each child
	for (GList *Iter = Children; Iter != NULL; Iter = Iter->next) {
		GtkWidget *Child = (GtkWidget *) Iter->data;

		// Return if found
		if (GTKNoCSDGtkSearchBar(G_OBJECT(Child))) {
			g_list_free(Children);
			return Child;
		}
	}

	// It was not found
	g_list_free(Children);
	return NULL;
}

void GTKNoCSDIgnoreHDYError(const gchar *Domain, GLogLevelFlags Level,
	const gchar *Message, gpointer Data) {
	// LibHandy always tries to draw the empty headerbar. This function removes
	// only that error
	if (g_str_has_prefix(Message, "gtk_container_propagate_draw: assertion "
		"'GTK_IS_WIDGET (child)' failed")) {
		return;
	}

	// All other logging is kept
	g_log_default_handler(Domain, Level, Message, Data);
}

void GTKNoCSDAddToUndecorated(GtkWindow *Window) {
	// Add a window to the list of windows which got the CSD removed and were
	// also set to be undecorated

	if (GTKNoCSDUndecoratedWindowList == NULL) {
		// Create list with single item if first such window
		GTKNoCSDUndecoratedWindowList = malloc(sizeof(GtkWindow *) * 2);
		GTKNoCSDUndecoratedWindowList[0] = Window;
		GTKNoCSDUndecoratedWindowList[1] = NULL;
	} else {
		// Check if window is already added
		size_t Index = 0;
		bool Add = true;
		while (GTKNoCSDUndecoratedWindowList[Index] != NULL) {
			if (GTKNoCSDUndecoratedWindowList[Index] == Window) {
				Add = false;
				break;
			}
			++Index;
		}

		// Add to list, if not a reused window
		if (Add) {
			GTKNoCSDUndecoratedWindowList =
				realloc(GTKNoCSDUndecoratedWindowList,
					sizeof(GtkWindow *) * (Index + 2));
			GTKNoCSDUndecoratedWindowList[Index] = Window;
			GTKNoCSDUndecoratedWindowList[Index + 1] = NULL;
		}
	}
}

bool GTKNoCSDCasildaChecked = false, GTKNoCSDOnCasilda = false;
void GTKNoCSDCasildaCheck() {
	// Check if the library is loaded in the compositor of Cambalache, Casilda.
	// This is a development environment and the library is not needed to run

	const char *Display = getenv("WAYLAND_DISPLAY");
	if (Display != NULL && strstr(Display, "casilda-compositor") != NULL) {
		GTKNoCSDOnCasilda = true;
	}
}

void GTKNoCSDHandlerFailExit() {
	// Simple exit function when crash handler setup failed

	G_GNUC_UNUSED ssize_t Ignore = write(3, "1", 1);
	GTKNoCSDCrashPid = -2;
	_exit(0);
}

bool GTKNoCSDMagic(GtkWindow *Window) {
	// Do all needed changes to disable CSD

	// Disable in Cambalache compositor, Casilda
	if (!GTKNoCSDCasildaChecked) {
		GTKNoCSDCasildaCheck();
		GTKNoCSDCasildaChecked = true;
	}
	if (GTKNoCSDOnCasilda) {
		return true;
	}

	// Set up crash handler
	if (GTKNoCSDCrashPid == -3) {
		// Create crash handler
		struct sigaction Action = {0};
		Action.sa_sigaction = GTKNoCSDAction;
		sigemptyset(&Action.sa_mask);
		Action.sa_flags = SA_SIGINFO;

		// Set up handler for possible crash signals, save original handlers
		sigaction(SIGSEGV, &Action, &GTKNoCSDSEGVAction);
		sigaction(SIGABRT, &Action, &GTKNoCSDABRTAction);
		sigaction(SIGFPE,  &Action, &GTKNoCSDFPEAction);
		sigaction(SIGILL,  &Action, &GTKNoCSDILLAction);
		sigaction(SIGBUS,  &Action, &GTKNoCSDBUSAction);

		if (GTKNoCSDLDLinux == NULL) {
			// If Crash handler starter was not found, exit
			GTKNoCSDCrashPid = -2;
		} else {
			Dl_info Information;
			if (dladdr((void *) (uintptr_t) &GTKNoCSDMagic,
				&Information) == 0) {
				// If library name was not found, exit
				GTKNoCSDCrashPid = -2;
			} else {
				char LibraryFullPath[PATH_MAX];
				G_GNUC_UNUSED char *Ignore = realpath(Information.dli_fname,
						LibraryFullPath);

				if (LibraryFullPath[0] ==  '\0') {
					// If library full path was not found, exit
					GTKNoCSDCrashPid = -2;
				} else {
					// Set up anonymous pipe with no blocking in child direction
					G_GNUC_UNUSED int Ignore = pipe(GTKNoCSDPipeToChild);
					Ignore = pipe(GTKNoCSDPipeToParent);
					fcntl(GTKNoCSDPipeToChild[0], F_SETFL, O_NONBLOCK);

					GTKNoCSDCrashPid = fork();
					if (GTKNoCSDCrashPid == 0) {
						// Finalize pipe directions, make copies
						close(GTKNoCSDPipeToChild[1]);
						dup2(GTKNoCSDPipeToParent[1], TO_PARENT_COPY);
						close(GTKNoCSDPipeToParent[0]);
						dup2(GTKNoCSDPipeToChild[0], TO_CHILD_COPY);

						// End crash detection process with parent
						prctl(PR_SET_PDEATHSIG, SIGTERM);

						// Start assembling arguments for starting crash handler
						char *NewArguments[GTKNoCSDArgumentNumber + 3];
						NewArguments[0] = "--";
						NewArguments[1] = LibraryFullPath;
						NewArguments[2] = (char *) getauxval(AT_EXECFN);

						// If real executed file is not found, exit. This is
						// important for scripts, which will have the
						// interpreter as first command instead of the script
						if (NewArguments[2] == NULL) {
							GTKNoCSDHandlerFailExit();
						}

						// Try to find which argument starts the arguments given
						// to the program
						int StartIndex = -1;
						for (int Index = 0; Index < GTKNoCSDArgumentNumber;
							++Index) {
							if (strcmp(GTKNoCSDArguments[Index],
								NewArguments[2]) == 0) {
								StartIndex = Index + 1;
								break;
							}
						}
						if (StartIndex == -1) {
							const char *Basename =
								basename((char *) NewArguments[2]);
							for (int Index = 0; Index < GTKNoCSDArgumentNumber;
								++Index) {
								if (strcmp(basename(GTKNoCSDArguments[Index]),
									Basename) == 0) {
									StartIndex = Index + 1;
									break;
								}
							}
						}

						// If not found, exit
						if (StartIndex == -1) {
							GTKNoCSDHandlerFailExit();
						}

						// Copy arguments only from found index of script
						int NewArgumentsIndex = 3;
						for (int Index = StartIndex;
							Index <= GTKNoCSDArgumentNumber; ++Index) {
							NewArguments[NewArgumentsIndex++] =
								GTKNoCSDArguments[Index];
						}

						// Start crash handler
						execve(GTKNoCSDLDLinux, NewArguments, environ);

						// This should not be reached, execve failed
						GTKNoCSDHandlerFailExit();
					} else {
						// Finalize pipe directions
						close(GTKNoCSDPipeToChild[0]);
						close(GTKNoCSDPipeToParent[1]);

						// Set up function on successful exit, to not restart
						atexit(GTKNoCSDExit);

						// Blocking, wait for handshake to avoid race conditions
						char Buffer;
						G_GNUC_UNUSED ssize_t Ignore =
							read(GTKNoCSDPipeToParent[0], &Buffer, 1);
					}
				}
			}
		}
	}

	// Hook all windows if not already done
	GTKNoCSDHooker();

	bool SetCSS = false;
	if (!GTKNoCSDSettings) {
		GtkSettings *Settings = o_gtk_settings_get_default();
		if (Settings != NULL) {
			// Disable .ui dialog headers, remove titlebar buttons
			g_object_set(Settings, "gtk-dialogs-use-header", FALSE, NULL);
			g_object_set(Settings, "gtk-decoration-layout", "", NULL);

			// If theme was set from environment
			if (GTKNoCSDTheme != NULL) {
				const char *Colon = strchr(GTKNoCSDTheme, ':');
				if (Colon == NULL) {
					// If dark preference was not set, set as it was given
					g_object_set(Settings, "gtk-theme-name", GTKNoCSDTheme,
						NULL);
				} else {
					// If dark preference was set

					// Copy into local variable, cut off dark preference
					char *Theme = strdup(GTKNoCSDTheme);
					Theme[strlen(Theme) - strlen(Colon)] = '\0';

					// Set theme, free local copy
					g_object_set(Settings, "gtk-theme-name", Theme, NULL);
					free(Theme);

					// Get dark preference
					bool Dark = strcmp(Colon + 1, "dark") == 0;

					// Enforce LibAdwaita color scheme as well
					if (o_adw_style_manager_get_default != NULL &&
						o_adw_style_manager_set_color_scheme != NULL) {
						AdwStyleManager *Manager =
							o_adw_style_manager_get_default();
						AdwColorScheme Scheme = ADW_COLOR_SCHEME_FORCE_LIGHT;
						Scheme = Dark ? ADW_COLOR_SCHEME_FORCE_DARK : Scheme;
						o_adw_style_manager_set_color_scheme(Manager, Scheme);
					}

					// Set dark preference
					g_object_set(Settings, "gtk-application-prefer-dark-theme",
						Dark, NULL);
				}
			}

			// Remove rounding, padding, shadow, hide title
			const char *CSS = "window { border-radius: 0; box-shadow: none; }"
				"headerbar { border-radius: 0; min-height: 0pt; padding: 0pt; }"
				"headerbar * { margin: 0pt; }"
				"headerbar box { padding: 0pt; }"
				"windowtitle { opacity: 0; }"
				"decoration { margin: 0px; }";

			// Load CSS for both GTK3 and GTK4
			GtkCssProvider *Provider = o_gtk_css_provider_new();
			if (GTKNoCSDGTKVersion == 4) {
				o_gtk_css_provider_load_from_string(Provider, CSS);
				o_gtk_style_context_add_provider_for_display(
					o_gdk_display_get_default(),
					(GtkStyleProvider *) Provider,
					GTK_STYLE_PROVIDER_PRIORITY_USER);
			} else {
				o_gtk_css_provider_load_from_data(Provider, CSS, -1, NULL);
				void *Screen = o_gdk_screen_get_default();
				o_gtk_style_context_add_provider_for_screen(Screen,
					(GtkStyleProvider *) Provider,
					GTK_STYLE_PROVIDER_PRIORITY_USER);

				// Force reload current window. We do it later, when we know
				// that it is needed and where the weak reference is stored
				SetCSS = true;
			}
			g_object_unref(Provider);

			GTKNoCSDSettings = true;
		}
	}

	// GTK3 windows can be embedded into some other widgets
	if (o_gtk_widget_get_parent((GtkWidget *) Window) != NULL) {
		return true;
	}

	// Never touch inspector windows
	const char *WindowName = o_gtk_widget_get_name((GtkWidget *) Window);
	if (strstr(WindowName, "GtkInspectorWindow") == WindowName) {
		return true;
	}

	// Popup windows in GTK3 are not needed
	if (GTKNoCSDGTKVersion == 3) {
		if (o_gtk_window_get_window_type(Window) == 1) {
			return true;
		}
		int Hint = o_gtk_window_get_type_hint(Window);
		if (2 < Hint && Hint != 5) {
			return true;
		}
	}

	// Guarantees that each window will only go through this once.
	size_t Index = 0;
	if (GTKNoCSDWindowList == NULL) {
		GTKNoCSDWindowList = malloc(sizeof(GtkWindow * *) * 2);

		// Windows stored as weak pointers
		GTKNoCSDWindowList[0] = g_new0(GtkWindow *, 1);
		*GTKNoCSDWindowList[0] = Window;
		g_object_add_weak_pointer(G_OBJECT(Window),
			(gpointer *) GTKNoCSDWindowList[0]);

		GTKNoCSDWindowList[1] = NULL;
	} else {
		bool Add = true;
		while (GTKNoCSDWindowList[Index] != NULL) {
			if (GTKNoCSDWindowList[Index][0] == Window) {
				// A window might get unrealized, set a header then get
				// realized. It is detected here and the header is put back to
				// the appropriate container
				if (GTKNoCSDTest(GTKNoCSDWindowGetChild(Window))) {
					GtkWidget *PossiblyHeader =
						o_gtk_window_get_titlebar(Window);
					if (PossiblyHeader != NULL) {
						g_object_ref(PossiblyHeader);
						o_gtk_window_set_titlebar(Window, NULL);

						// WARNING: Own call
						gtk_window_set_titlebar(Window, PossiblyHeader);
						g_object_unref(PossiblyHeader);
						return true;
					}
				}

				// GTK Sometimes reuses windows and turns CSD back on. Bad GTK
				if (!GTKNoCSDAdwApplicatonWindow(G_OBJECT(Window)) &&
					!GTKNoCSDAdwWindow(G_OBJECT(Window)) &&
					!GTKNoCSDHdyApplicatonWindow(G_OBJECT(Window)) &&
					!GTKNoCSDHdyWindow(G_OBJECT(Window)) &&
					!GTKNoCSDTest(GTKNoCSDWindowGetChild(Window))) {
					Add = false;
					break;
				}
				return true;
			}
			++Index;
		}

		// Add to list, if not a reused window
		if (Add) {
			GTKNoCSDWindowList = realloc(GTKNoCSDWindowList,
					sizeof(GtkWindow * *) * (Index + 2));

			// Windows stored as weak pointers
			GTKNoCSDWindowList[Index] = g_new0(GtkWindow *, 1);
			*GTKNoCSDWindowList[Index] = Window;
			g_object_add_weak_pointer(G_OBJECT(Window),
				(gpointer *) GTKNoCSDWindowList[Index]);

			GTKNoCSDWindowList[Index + 1] = NULL;
		}
	}

	// Application windows might have a shortcuts window, check and add back
	// titlebar to that tool. The window might get added later, so we check then
	if (GTKNoCSDGtkApplicatonWindow(G_OBJECT(Window))) {
		g_timeout_add(1, GTKNoCSDHandleShortcutsWindow,
			GTKNoCSDWindowList[Index]);
	}

	// This CSS breaks the look of context menus on X, with GTK3, if loaded
	// globally, but it is needed for certain windows (Peek?)
	GtkCssProvider *Provider = o_gtk_css_provider_new();
	GtkStyleContext *Context =
		o_gtk_widget_get_style_context((GtkWidget *) Window);
	const char *CSS = "decoration { box-shadow: none; }";
	if (GTKNoCSDGTKVersion == 4) {
		o_gtk_css_provider_load_from_string(Provider, CSS);
	} else {
		o_gtk_css_provider_load_from_data(Provider, CSS, -1, NULL);
	}
	o_gtk_style_context_add_provider(Context,
		(GtkStyleProvider *) Provider, GTK_STYLE_PROVIDER_PRIORITY_USER);
	g_object_unref(Provider);

	// Reset CSS to apply it on current window
	if (SetCSS) {
		g_idle_add(GTKNoCSDGTK3CurrentWindowStyle, GTKNoCSDWindowList[Index]);
	}

	// Make sure the application always has the signal to add SSD to all windows
	GtkApplication *Application = o_gtk_window_get_application(Window);
	if (Application != NULL) {
		if (g_signal_handler_find(Application, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
			G_CALLBACK(GTKNoCSDWindowAdded), NULL) == 0) {
			g_signal_connect(Application, "window-added",
				G_CALLBACK(GTKNoCSDWindowAdded), NULL);
		}
	} else if (g_signal_handler_find(Window, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
		G_CALLBACK(GTKNoCSDApplicationChanged), NULL) == 0) {
		g_signal_connect(Window, "notify::application",
			G_CALLBACK(GTKNoCSDApplicationChanged), NULL);
	}

	// LibAdwaita and LibHandy windows need special treatment
	if (GTKNoCSDAdwApplicatonWindow(G_OBJECT(Window)) ||
		GTKNoCSDAdwWindow(G_OBJECT(Window)) ||
		GTKNoCSDHdyApplicatonWindow(G_OBJECT(Window)) ||
		GTKNoCSDHdyWindow(G_OBJECT(Window))) {
		// If LibAdwaita or LibHandy window

		// Get titlebar, if not found, come back later
		GtkWidget *TitleBar = o_gtk_window_get_titlebar(Window);
		if (TitleBar == NULL) {
			// Remove window from list of windows done
			int Index = 0;
			while (GTKNoCSDWindowList[Index] != NULL) {
				++Index;
			}
			GTKNoCSDWindowList[Index - 1] = NULL;

			// Call removal later
			g_timeout_add(1, GTKNoCSDRecall, Window);
			return false;
		}

		// Hide the title widget, if it is not a special control. It might be
		// added later so we check then
		if (GTKNoCSDGTKVersion == 4) {
			// Adding this layout manager prevents GTK from calling
			// size_allocate virtual function
			GtkLayoutManager *Layout = o_gtk_bin_layout_new();
			o_gtk_widget_set_layout_manager((GtkWidget *) Window, Layout);
			g_timeout_add(1, GTKNoCSDSetGTK4Headers, Window);
		} else {
			// Disable LibHandy errors about drawing not existing headerbar
			g_log_set_handler("Gtk", G_LOG_LEVEL_CRITICAL,
				GTKNoCSDIgnoreHDYError, NULL);
			g_timeout_add(1, GTKNoCSDSetGTK3Headers, Window);
		}

		// Unset titlebar
		o_gtk_window_set_titlebar(Window, NULL);
		return true;
	}

	// Regular GTK3/GTK4 also needs to rearrange widgets

	// Get top child of window
	GtkWidget *WindowChild = GTKNoCSDWindowGetChild(Window);

	GtkWidget *Header = o_gtk_window_get_titlebar(Window);

	if (GTKNoCSDGtkShortcutsWindow(G_OBJECT(Window))) {
		// This window was already dealt with
		if (strcmp(o_gtk_widget_get_name((GtkWidget *) Window),
			"GTKNoCSDHelp") == 0) {
			return true;
		}
		o_gtk_widget_set_name((GtkWidget *) Window, "GTKNoCSDHelp");

		// GtkShortcutsWindow has a regular label as title, and does not set the
		// the window title. Here we name the window by the translated title in
		// the label and set it to empty, so it is not visible
		GtkWidget *Title;
		if (GTKNoCSDGTKVersion == 3) {
			Title =
				o_gtk_header_bar_get_custom_title((GtkHeaderBar *) Header);
		} else {
			Title = o_gtk_header_bar_get_title_widget((GtkHeaderBar *) Header);
		}
		Title = GTKNoCSDFirstChild(Title);
		o_gtk_window_set_title(Window,
			o_gtk_label_get_label((GtkLabel *) Title));
		o_gtk_label_set_label((GtkLabel *) Title, "");

		// In GTK3 ShortcutsWindows cannot get their content replaced. The
		// search button needs to be recreated
		if (GTKNoCSDGTKVersion == 3) {
			o_gtk_window_set_titlebar(Window, NULL);

			// Create search button, add it to window, display it
			GtkWidget *Icon = g_object_new(GTKNoCSDGTKImage, "visible", TRUE,
					"icon-name", "edit-find-symbolic", NULL);
			GtkWidget *SearchButton = g_object_new(GTKNoCSDGTKToggleButton,
					"child", Icon, "visible", TRUE, NULL);
			GTKNoCSDGtkBoxAdd(WindowChild, SearchButton);
			o_gtk_widget_set_visible(SearchButton, true);

			// Get the ShortcutsWindow search bar, bind it to the button
			GtkWidget *SearchBar =
				GTKNoCSDGTK3ShortcutsWindowSearchBar(WindowChild);
			g_object_bind_property(SearchBar, "search-mode-enabled",
				SearchButton, "active",
				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);

			// Make button the first widget, move it to the left
			o_gtk_box_reorder_child((GtkBox *) (WindowChild), SearchButton, 0);
			o_gtk_widget_set_halign(SearchButton, GTK_ALIGN_START);

			return true;
		}
	}

	// Do not touch windows that have no header bar
	if (Header == NULL) {
		return true;
	}

	// Glade is for designing graphical user interfaces, like Cambalache
	// The library is not needed to run there
	if (strcmp("GladePlaceholder", G_OBJECT_TYPE_NAME(Header)) == 0) {
		return true;
	}

	// Certain applications turn off decorations while having CSD, it is turned
	// back here. These windows are tracked as gdk_window_get_frame_extents only
	// applies to them
	if (!o_gtk_window_get_decorated(Window)) {
		GTKNoCSDAddToUndecorated(Window);
		o_gtk_window_set_decorated(Window, true);
	}

	// Both checks need the window to have content and only appear on GTK4
	// First check: LibAdwaita dialogs only need the titlebar removed
	// Second check: Celluloid uses a regular window, but adds a LibAdwaita
	// headerbar into it. If it is detected, the empty titlebar is just removed
	if (WindowChild != NULL && GTKNoCSDGTKVersion == 4 &&
		(GTKNoCSDAdwDialog(G_OBJECT(WindowChild)) ||
		GTKNoCSDGetGTK4Headers(WindowChild, true) != NULL)) {
		o_gtk_window_set_titlebar(Window, NULL);
		return true;
	}

	// Unset the application menu on GTK3 for applications that have CSD
	// Not sure if this is the right approach. BleachBit has the same menu twice
	// otherwise
	if (Application != NULL && GTKNoCSDGTKVersion == 3) {
		o_gtk_application_set_app_menu(Application, NULL);
	}

	// Create own vertical container
	GtkWidget *Vertical = o_gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
	o_gtk_widget_set_name(Vertical, "GTKNoCSD");

	// Have 2 boxes at all times to contain the header and the content
	GtkWidget *HeaderBox = o_gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
	GtkWidget *ContentBox = o_gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
	GTKNoCSDGtkBoxAdd(Vertical, HeaderBox);
	GTKNoCSDGtkBoxAdd(Vertical, ContentBox);

	// Name for easy identification when going up widget tree
	o_gtk_widget_set_name(ContentBox, "GTKNoCSDContentBox");

	// Remove widget from window, add it to own box, if it exists
	if (WindowChild != NULL) {
		g_object_ref(WindowChild);
		GTKNoCSDWindowSetChild(Window, NULL);
		o_gtk_widget_set_vexpand(WindowChild, TRUE);
		GTKNoCSDGtkBoxAdd(ContentBox, WindowChild);
		g_object_unref(WindowChild);
	}

	// Add everything back to window
	GTKNoCSDWindowSetChild(Window, Vertical);

	// Take header and add into our own box

	g_object_ref(Header);
	o_gtk_window_set_titlebar(Window, NULL);

	if (GTKNoCSDGTKVersion == 4) {
		// Add to our own widget, which handles visibility
		GtkWidget *Revealer = g_object_new(GTKNoCSDBox_get_type(), NULL);

		// Name for easy identification when going up widget tree
		o_gtk_widget_set_name(Revealer, "GTKNoCSDHeaderBox");

		o_gtk_widget_set_layout_manager(Revealer, NULL);
		o_gtk_box_append((GtkBox *) HeaderBox, Revealer);
		o_gtk_box_append((GtkBox *) Revealer, Header);
		g_timeout_add(1, GTKNoCSDSetGTK4Headers, Header);
	} else {
		// GTK3 rearranging

		o_gtk_widget_set_visible((GtkWidget *) Vertical, true);
		o_gtk_widget_set_visible((GtkWidget *) ContentBox, true);

		o_gtk_widget_set_visible(HeaderBox, true);
		o_gtk_container_set_border_width(Window, 0);

		// Create event box for drag/right click

		GtkWidget *EventBox = o_gtk_event_box_new();

		// Name for easy identification when going up widget tree
		o_gtk_widget_set_name(EventBox, "GTKNoCSDHeaderBox");

		// Make event box react to drag/right click
		g_signal_connect(EventBox, "button-press-event",
			G_CALLBACK(GTKNoCSDGTK3EventBoxActions), Window);

		// Set up function and add to regular Revealer
		g_signal_connect(Header, "size-allocate",
			G_CALLBACK(GTKNoCSDGTK3HeaderSizeAllocate), NULL);
		GtkWidget *Revealer = o_gtk_revealer_new();
		o_gtk_revealer_set_transition_duration((GtkRevealer *) Revealer, 0);
		o_gtk_container_add(Revealer, EventBox);
		o_gtk_widget_set_visible(Revealer, true);
		o_gtk_container_add(EventBox, Header);

		// Add finalized header to container, display everything
		o_gtk_container_add(HeaderBox, Revealer);
		o_gtk_widget_set_visible(EventBox, true);

		// Remove extra close buttons
		GTKNoCSDGetGTK3Headers(Header);

		// Lutris center widget got stuck on wrong size
		o_gtk_container_check_resize(Window);
	}
	g_object_unref(Header);

	return true;
}

void GTKNoCSDAboutClose(GtkWidget *Dialog) {
	// Close About window when close button is clicked in GTK3
	o_gtk_widget_set_visible(Dialog, false);
}

GtkWidget *GTKNoCSDGTK4Content(GtkWidget *Child) {
	// Get content of vertical or if not vertical, return as is

	if (GTKNoCSDTest(Child)) {
		// ContentBox, real child
		return o_gtk_widget_get_last_child(o_gtk_widget_get_last_child(Child));
	}

	return Child;
}

void gtk_window_present(GtkWindow *Window) {
	// Entry point used by certain apps to display the window.
	// Hook it, execute our own function then the original

	GTKNoCSDGetReferences(true);

	// Only present window on successful run, unless in GTK2
	if (2 < GTKNoCSDGTKVersion) {
		if (GTKNoCSDMagic(Window)) {
			o_gtk_window_present(Window);
		}
		return;
	}

	o_gtk_window_present(Window);
}

void gtk_widget_set_visible(GtkWidget *Widget, gboolean Visible) {
	// Entry point used by certain apps to display widgets.
	// Hook it, execute our own function then the original

	GTKNoCSDGetReferences(true);

	o_gtk_widget_set_visible(Widget, Visible);

	// Not in GTK2
	if (2 < GTKNoCSDGTKVersion) {
		// This function might not get called with the window
		GtkWidget *Window = GTKNoCSDGetWindow(Widget);
		if (Window != NULL && Visible) {
			GTKNoCSDMagic((GtkWindow *) Window);
		}
	}
}

// The goal of these is to "hide" Vertical through APIs

void gtk_container_add(GtkWidget *Container, GtkWidget *Widget) {
	// GTK3 uses this to add children to widgets, potentially to the window too
	// Hook it and add the widget to our box if already transformed, if not then
	// add and then transform it

	GTKNoCSDGetReferences(true);

	if (2 < GTKNoCSDGTKVersion) {
		GtkWidget *Window = GTKNoCSDGetWindow(Container);
		if (Window == Container) {
			GtkWidget *WindowChild = GTKNoCSDFirstChild(Window);
			if (GTKNoCSDTest(WindowChild)) {
				GList *Children = o_gtk_container_get_children(WindowChild);
				GtkWidget *ContentBox = g_list_nth_data(Children, 1);
				g_list_free(Children);

				// Remove existing child if present
				GtkWidget *OldContent = GTKNoCSDFirstChild(ContentBox);
				if (OldContent != NULL) {
					o_gtk_container_remove(ContentBox, OldContent);
				}

				o_gtk_container_add(ContentBox, Widget);
				o_gtk_widget_set_vexpand(Widget, TRUE);
			} else {
				o_gtk_container_add(Container, Widget);
				GTKNoCSDMagic((GtkWindow *) Window);
			}
			return;
		}
	}

	o_gtk_container_add(Container, Widget);
}

GtkWidget *gtk_window_get_child(GtkWindow *Window) {
	// Get the child under Vertical, if the child is requested

	GTKNoCSDGetReferences(true);

	return GTKNoCSDGTK4Content(o_gtk_window_get_child(Window));
}

void gtk_window_set_child(GtkWindow *Window, GtkWidget *Child) {
	// Get the child under Vertical, if the child is set

	GTKNoCSDGetReferences(true);

	GtkWidget *WindowChild = o_gtk_window_get_child(Window);
	if (GTKNoCSDTest(WindowChild)) {
		// ContentBox
		GtkWidget *Box = o_gtk_widget_get_last_child(WindowChild);

		// Remove existing child if present
		GtkWidget *OldContent = o_gtk_widget_get_last_child(Box);
		if (OldContent != NULL) {
			o_gtk_widget_unparent(OldContent);
		}

		o_gtk_box_append((GtkBox *) Box, Child);
		o_gtk_widget_set_vexpand(Child, TRUE);
		return;
	}

	o_gtk_window_set_child(Window, Child);
	GTKNoCSDMagic(Window);
}

GtkWidget *gtk_widget_get_parent(GtkWidget *Widget) {
	// Get the window if the parent is requested

	GTKNoCSDGetReferences(true);

	// EventBox on GTK3, Revealer on GTK4
	GtkWidget *Parent = o_gtk_widget_get_parent(Widget);

	if (Parent == NULL || GTKNoCSDGTKVersion == 2) {
		return Parent;
	}

	if (strcmp(o_gtk_widget_get_name(Parent), "GTKNoCSDContentBox") == 0 ||
		strcmp(o_gtk_widget_get_name(Parent), "GTKNoCSDHeaderBox") == 0) {
		return GTKNoCSDGetWindow(Parent);
	}

	return Parent;
}

GtkWidget *gtk_widget_get_first_child(GtkWidget *Widget) {
	// If the windows first child is requested, get last of Vertical

	GTKNoCSDGetReferences(true);

	return GTKNoCSDGTK4Content(o_gtk_widget_get_first_child(Widget));
}

GtkWidget *gtk_widget_get_last_child(GtkWidget *Widget) {
	// If the windows last child is requested, get last of Vertical

	GTKNoCSDGetReferences(true);

	return GTKNoCSDGTK4Content(o_gtk_widget_get_last_child(Widget));
}

GtkWidget *gtk_window_get_titlebar(GtkWindow *Window) {
	// If the titlebar is requested, return the one from Vertical

	GTKNoCSDGetReferences(true);

	GtkWidget *WindowChild = GTKNoCSDWindowGetChild(Window);
	if (GTKNoCSDTest(WindowChild)) {
		GtkWidget *Box = GTKNoCSDFirstChild(GTKNoCSDFirstChild(WindowChild));

		// GTK3 has an extra GtkEventBox for drag/right click
		if (GTKNoCSDGTKVersion == 3) {
			return GTKNoCSDFirstChild(GTKNoCSDFirstChild(Box));
		}

		return GTKNoCSDFirstChild(Box);
	}

	return o_gtk_window_get_titlebar(Window);
}

void gtk_window_set_titlebar(GtkWindow *Window, GtkWidget *Header) {
	// If the titlebar is set, try to set our own first, if not possible, set
	// the window, but try to switch it back after. Not sure if the first half
	// is the proper solution, atomix breaks GTK otherwise

	GTKNoCSDGetReferences(true);

	// Celluloid specific fix. It uses a regular window, but adds a LibAdwaita
	// headerbar into it. If it is detected, it is not added
	if (GTKNoCSDGTKVersion == 4 && GTKNoCSDGetGTK4Headers((GtkWidget *) Window,
		true) != NULL) {
		return;
	}

	GTKNoCSDMagic(Window);
	GtkWidget *WindowChild = GTKNoCSDWindowGetChild(Window);
	if (GTKNoCSDTest(WindowChild)) {
		GtkWidget *Box = GTKNoCSDFirstChild(GTKNoCSDFirstChild(WindowChild));

		// GTK3 has an extra GtkEventBox for drag/right click
		if (GTKNoCSDGTKVersion == 3) {
			Box = GTKNoCSDFirstChild(Box);
		}

		// Get current Header
		GtkWidget *OldHeader = GTKNoCSDFirstChild(Box);

		// If already added return
		if (OldHeader == Header) {
			return;
		}

		// If it exists and is different remove it
		if (OldHeader != NULL) {
			if (GTKNoCSDGTKVersion == 3) {
				o_gtk_container_remove(Box, OldHeader);
			} else {
				o_gtk_box_remove((GtkBox *) Box, OldHeader);
			}
		}

		// If new one exists, add it
		if (Header != NULL) {
			if (GTKNoCSDGTKVersion == 3) {
				o_gtk_container_add(Box, Header);
			} else {
				GTKNoCSDGtkBoxAdd(Box, Header);
			}
		}
	} else {
		o_gtk_window_set_titlebar(Window, Header);
		GTKNoCSDMagic(Window);
	}
}

// All supported versions of GTK set title widget differently. When they do,
// they are rechecked for title labels

void gtk_header_bar_set_custom_title(GtkHeaderBar *Widget, GtkWidget *Title) {
	GTKNoCSDGetReferences(true);

	if (Title != NULL) {
		GTKNoCSDGetGTK3Headers(Title);
		o_gtk_header_bar_set_custom_title(Widget, Title);
	} else {
		o_gtk_header_bar_set_custom_title(Widget, o_gtk_grid_new());
	}
}

void hdy_header_bar_set_custom_title(void *Widget, GtkWidget *Title) {
	GTKNoCSDGetReferences(true);

	if (Title != NULL) {
		GTKNoCSDGetGTK3Headers(Title);
		o_hdy_header_bar_set_custom_title(Widget, Title);
	} else {
		o_hdy_header_bar_set_custom_title(Widget, o_gtk_grid_new());
	}
}

void gtk_header_bar_set_title_widget(GtkHeaderBar *Widget, GtkWidget *Title) {
	GTKNoCSDGetReferences(true);

	if (Title != NULL) {
		GTKNoCSDGetGTK4Headers(Title, false);
		o_gtk_header_bar_set_title_widget(Widget, Title);
	} else {
		o_gtk_header_bar_set_title_widget(Widget, o_gtk_grid_new());
	}
}

void adw_header_bar_set_title_widget(AdwHeaderBar *Widget, GtkWidget *Title) {
	GTKNoCSDGetReferences(true);

	if (Title != NULL) {
		GTKNoCSDGetGTK4Headers(Title, false);
		o_adw_header_bar_set_title_widget(Widget, Title);
	} else {
		o_adw_header_bar_set_title_widget(Widget, o_gtk_grid_new());
	}
}

void gtk_window_set_decorated(GtkWindow *Window, gboolean Setting) {
	// An application might unset the decoration, while also having CSD
	// (picture in picture windows). It is disallowed and if detected, the
	// decoration is kept

	GTKNoCSDGetReferences(true);

	// Keep original behavior for GTK2
	if (GTKNoCSDGTKVersion == 2) {
		o_gtk_window_set_decorated(Window, Setting);
		return;
	}

	// Add window to list of undecorated windows if needed
	if (GTKNoCSDTest(GTKNoCSDWindowGetChild(Window)) && !Setting) {
		GTKNoCSDAddToUndecorated(Window);
	}

	o_gtk_window_set_decorated(
		Window, GTKNoCSDTest(GTKNoCSDWindowGetChild(Window)) || Setting);
}

void gtk_widget_reparent(GtkWidget *Widget, GtkWidget *Container) {
	// Certain applications use this to set window content
	// Hook it, execute our own reparenting, if the child is the top child of a
	// transformed window, move our own widget instead

	GTKNoCSDGetReferences(true);

	// WARNING: Own call
	// If something is missing or parent is set already, do not do anything
	if (Widget == NULL || Container == NULL ||
		gtk_widget_get_parent(Widget) == Container) {
		return;
	}

	// Only on GTK3
	if (GTKNoCSDGTKVersion == 3) {
		// Remove existing child if adding to window and present
		if (GTKNoCSDGtkWindow(G_OBJECT(Container))) {
			GtkWidget *ContainerChild =
				GTKNoCSDWindowGetChild((GtkWindow *) Container);
			if (ContainerChild != NULL) {
				o_gtk_container_remove(Container, ContainerChild);
			}
		}

		// WARNING: Own call
		GtkWidget *Window = gtk_widget_get_parent(Widget);
		if (GTKNoCSDGtkWindow(G_OBJECT(Window))) {
			// If in a window
			GtkWidget *WindowChild =
				GTKNoCSDWindowGetChild((GtkWindow *) Window);
			if (GTKNoCSDTest(WindowChild)) {
				// If in a transformed window, move entire transformed widget
				// into new container

				g_object_ref(WindowChild);
				o_gtk_container_remove(Window, WindowChild);

				// WARNING: Own call
				// Also transform new container if needed
				gtk_container_add(Container, WindowChild);
				g_object_unref(WindowChild);
				return;
			}
		}

		// If not transformed or not in a window, only move widget
		g_object_ref(Widget);
		o_gtk_container_remove(Window, Widget);

		// WARNING: Own call
		// Also transform new container if needed
		gtk_container_add(Container, Widget);
		g_object_unref(Widget);
		return;
	}

	o_gtk_widget_reparent(Widget, Container);
}

GtkWidget *GTKNoCSDFindWindowTitle(GtkWidget *Widget) {
	// Find window title widget in headerbar title widget

	// Check if widget is needed, return if it is
	if (G_TYPE_CHECK_INSTANCE_TYPE(Widget, GTKNoCSDADWWindowTitle)) {
		return Widget;
	}

	// Go through all children
	GtkWidget *Child = o_gtk_widget_get_first_child(Widget);
	while (Child != NULL) {
		// Check children of child, return if found, go to next child
		GtkWidget *Found = GTKNoCSDFindWindowTitle(Child);
		if (Found != NULL) {
			return Found;
		}
		Child = o_gtk_widget_get_next_sibling(Child);
	}

	return NULL;
}

void adw_dialog_present(AdwDialog *Dialog, GtkWidget *Parent) {
	// LibAdwaita uses embedded dialogs which break most themes. This pops them
	// out into their own window

	GTKNoCSDGetReferences(true);

	// Display dialog in own window
	o_adw_dialog_present(Dialog, NULL);

	// Hide title widget of dialog, if it only displays window title
	GtkWidget *HeaderBar = GTKNoCSDGetGTK4Headers((GtkWidget *) Dialog, true);
	if (HeaderBar != NULL) {
		GtkWidget *Title =
			o_adw_header_bar_get_title_widget((AdwHeaderBar *) HeaderBar);
		if (Title != NULL) {
			GtkWidget *WindowTitle = GTKNoCSDFindWindowTitle(Title);
			if (WindowTitle != NULL && o_gtk_widget_get_mapped(WindowTitle)) {
				o_adw_header_bar_set_title_widget((AdwHeaderBar *) HeaderBar,
					o_gtk_grid_new());
			}
		}
	}

	// Center new window on parent
	GtkWindow *ParentWindow = (GtkWindow *) o_gtk_widget_get_ancestor(
		Parent, GTKNoCSDGTKWindow);
	GtkWindow *ChildWindow = (GtkWindow *) o_gtk_widget_get_ancestor(
		(GtkWidget *) Dialog, GTKNoCSDGTKWindow);
	o_gtk_window_set_transient_for(ChildWindow, ParentWindow);

	// Add application actions if supposed to have an application
	GtkApplication *Application = o_gtk_window_get_application(ParentWindow);
	if (Application != NULL) {
		o_gtk_widget_insert_action_group((GtkWidget *) Dialog, "app",
			(GActionGroup *) Application);
	}

	// Add window actions if supposed to be in an application window
	if (GTKNoCSDGtkApplicatonWindow(G_OBJECT(ParentWindow))) {
		o_gtk_widget_insert_action_group((GtkWidget *) Dialog, "win",
			(GActionGroup *) ParentWindow);
	}

	// Make windows at most as large as the parent, but resizeable
	int ParentWidth = o_gtk_widget_get_width(Parent);
	int Width = o_adw_dialog_get_content_width(Dialog);
	int ParentHeight = o_gtk_widget_get_height(Parent);
	int Height = o_adw_dialog_get_content_height(Dialog);
	o_adw_dialog_set_content_width(Dialog, MIN(ParentWidth, Width));
	o_adw_dialog_set_content_height(Dialog, MIN(ParentHeight, Height));
	o_gtk_window_set_resizable(ChildWindow, true);
}

AdwDialog *GTKNoCSDAdwDialogForWindow(GtkWindow *Window) {
	// Get dialog belonging to window

	AdwDialog *Dialog = NULL;

	// Go through all windows
	int Index = 0;
	while (GTKNoCSDWindowList[Index] != NULL) {
		GtkWindow *CheckedWindow = GTKNoCSDWindowList[Index][0];

		// Check if window still exists and belongs to asked window
		if (CheckedWindow != NULL &&
			o_gtk_window_get_transient_for(CheckedWindow) == Window) {
			// Check if window contains dialog and set it if so
			// No break so the last created window is returned
			GtkWidget *WindowChild = GTKNoCSDWindowGetChild(CheckedWindow);
			if (WindowChild != NULL &&
				GTKNoCSDAdwDialog(G_OBJECT(WindowChild))) {
				Dialog = (AdwDialog *) WindowChild;
			}
		}
		++Index;
	}

	return Dialog;
}

AdwDialog *adw_application_window_get_visible_dialog(
	AdwApplicationWindow *Window) {
	// Applications might expect dialogs to be returned here, but popped out
	// ones are not added. First it is checked and returned if something did add
	// a dialog, otherwise the last dialog belonging to this window is returned

	GTKNoCSDGetReferences(true);

	AdwDialog *Dialog = o_adw_application_window_get_visible_dialog(Window);
	if (Dialog != NULL) {
		return Dialog;
	}
	return GTKNoCSDAdwDialogForWindow((GtkWindow *) Window);
}

AdwDialog *adw_window_get_visible_dialog(AdwWindow *Window) {
	// Applications might expect dialogs to be returned here, but popped out
	// ones are not added. First it is checked and returned if something did add
	// a dialog, otherwise the last dialog belonging to this window is returned

	GTKNoCSDGetReferences(true);

	AdwDialog *Dialog = o_adw_window_get_visible_dialog(Window);
	if (Dialog != NULL) {
		return Dialog;
	}
	return GTKNoCSDAdwDialogForWindow((GtkWindow *) Window);
}

GtkWidget *gtk_about_dialog_new() {
	// Entry point used by certain apps to create an about dialog.
	// Hook it, execute the original then our own function

	GTKNoCSDGetReferences(true);

	// No change in GTK2
	if (GTKNoCSDGTKVersion < 3) {
		return o_gtk_about_dialog_new();
	}

	GtkWidget *Window = o_gtk_about_dialog_new();
	GTKNoCSDMagic((GtkWindow *) Window);
	return Window;
}

void gtk_show_about_dialog(GtkWindow *Window, const char *FirstProperty, ...) {
	// Entry point used by certain apps to create an about dialog.
	// Hook it, reimplement then use our own function

	// Create dialog, this is where the CSD is removed
	// WARNING: Own call
	GtkWidget *Dialog = gtk_about_dialog_new();

	// Somehow without this the next time it will not have a titlebar
	if (GTKNoCSDGTKVersion == 4) {
		o_gtk_window_set_hide_on_close((GtkWindow *) Dialog, TRUE);
	}

	// GTK3 specific close button signal
	if (GTKNoCSDGTKVersion == 3) {
		g_signal_connect(Dialog, "response", G_CALLBACK(GTKNoCSDAboutClose),
			NULL);
	}

	// Do what GTK does, except no caching of the dialog
	va_list VarArgs;
	va_start(VarArgs, FirstProperty);
	g_object_set_valist(G_OBJECT(Dialog), FirstProperty, VarArgs);
	va_end(VarArgs);
	if (Window) {
		o_gtk_window_set_modal((GtkWindow *) Dialog, TRUE);
		o_gtk_window_set_transient_for((GtkWindow *) Dialog, Window);
		o_gtk_window_set_destroy_with_parent((GtkWindow *) Dialog, TRUE);
	}

	// Display dialog, no need to remove CSD again
	o_gtk_window_present((GtkWindow *) Dialog);
}

// True when the first GTK GObject is loaded
bool GTKNoCSDGTKGObject = false;

gpointer g_object_new(GType Type, const gchar *FirstProperty, ...) {
	// Entry point used by python to create objects.
	// Hook it, reimplement then use our own function

	GTKNoCSDGetReferences(true);

	// Do what GTK does
	GObject *Object;
	if (!FirstProperty) {
		Object = g_object_new_with_properties(Type, 0, NULL, NULL);
	} else {
		va_list VarArgs;
		va_start(VarArgs, FirstProperty);
		Object = g_object_new_valist(Type, FirstProperty, VarArgs);
		va_end(VarArgs);
	}

	if (Type != 0 && GTKNoCSDGTKGObject) {
		// Call our function when the object is a window
		if (GTKNoCSDGtkWindow(Object)) {
			GTKNoCSDMagic((GtkWindow *) Object);
		}

		// If widget is label, set up monitor for the content
		if (GTKNoCSDGtkLabel(Object) && g_signal_handler_find(Object,
			G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK(GTKNoCSDTitleChanged),
			NULL) == 0) {
			g_signal_connect(Object, "notify::label",
				G_CALLBACK(GTKNoCSDTitleChanged), NULL);
			GTKNoCSDTitleChanged((GtkLabel *) Object, NULL, NULL);
		}
	}

	// This is as early as possible to catch all windows with the hook
	if (GTKNoCSDGTKGObject) {
		GTKNoCSDHooker();
	}

	// Python/JS fix, as they create GObjects before loading in GTK, not in GTK2
	if (!GTKNoCSDGTKGObject && 2 < GTKNoCSDGTKVersion) {
		const char *Name = g_type_name(Type);
		if (strncmp(Name, "Gtk", 3) == 0) {
			GTKNoCSDGTKGObject = true;
		}
	}

	return Object;
}

gulong g_signal_connect_object(gpointer Instance, const gchar *Signal,
	GCallback Handler, gpointer GObject, GConnectFlags Flags) {
	// This function is used to connect all kinds of signal
	// LibHandy uses it to connect the function that errors out on settings the
	// titlebar. In that single case the signal will not be connected

	GTKNoCSDGetReferences(true);

	// Do everything to only exclude the correct call, checking is in order of
	// expense
	if (Flags == G_CONNECT_SWAPPED && (GTKNoCSDHdyApplicatonWindow(Instance) ||
		GTKNoCSDHdyWindow(Instance)) && strcmp(Signal, "size-allocate") == 0) {
		// This is a private type, it cannot be directly compared
		if (strcmp(G_OBJECT_TYPE_NAME(GObject), "HdyWindowMixin") == 0) {
			return 0;
		}
	}

	// Do the regular connection for all other signals
	return o_g_signal_connect_object(Instance, Signal, Handler, GObject, Flags);
}

void gtk_widget_show_all(GtkWidget *Widget) {
	// GTK3 function to display a widget. Certain apps use it to display the
	// window. Hook it, execute our own function then the original

	GTKNoCSDGetReferences(true);

	// Not in GTK2
	if (2 < GTKNoCSDGTKVersion) {
		GtkWidget *Window = GTKNoCSDGetWindow(Widget);
		if (Window != NULL) {
			GtkWidget *WindowChild =
				GTKNoCSDWindowGetChild((GtkWindow *) Window);
			if (!GTKNoCSDTest(WindowChild)) {
				GTKNoCSDMagic((GtkWindow *) Window);
			}
		}
	}

	o_gtk_widget_show_all(Widget);
}

// Regex to remove CSD property from builder XML, it lives forever
GRegex *GTKNoCSDRegex;

// Builder files can specify to ignore the header preference. All builder
// creator functions are redefined and a regex disables the CSD property.
// Later, if needed, add objects functions can be implemented too
// TODO: Test whether the slightly different signatures cause an issue for GTK3
gboolean gtk_builder_add_from_string(GtkBuilder *Builder, const char *String,
	gssize Length, GError **Error) {
	// Load into builder from a string. This is where all functions end up

	GTKNoCSDGetReferences(true);

	// Try to load regex once
	if (GTKNoCSDRegex == NULL) {
		GTKNoCSDRegex = g_regex_new(
			"<property\\s+name=[\"']use-header-bar[\"']\\s*>\\s*1\\s*</property>",
			G_REGEX_OPTIMIZE | G_REGEX_MULTILINE, 0, NULL);
	}

	// If still not loaded, it failed, give back XML as is
	if (GTKNoCSDRegex == NULL) {
		return o_gtk_builder_add_from_string(Builder, String, Length, Error);
	}

	// Replace properties with disabled version, return builder with new string
	gchar *Replaced = g_regex_replace(GTKNoCSDRegex, String, Length, 0,
			"<property name=\"use-header-bar\">0</property>", 0, NULL);

	gboolean ReturnValue =
		o_gtk_builder_add_from_string(Builder, Replaced, -1, Error);
	g_free(Replaced);
	return ReturnValue;
}

GtkBuilder *gtk_builder_new_from_string(const gchar *String, gssize Length) {
	// Create a builder from a string

	GTKNoCSDGetReferences(true);

	GtkBuilder *Builder = o_gtk_builder_new();

	// WARNING: Own call
	gtk_builder_add_from_string(Builder, String, Length, NULL);

	return Builder;
}

gboolean gtk_builder_add_from_resource(GtkBuilder *Builder, const char *Path,
	GError **Error) {
	// Load into builder from resource

	GTKNoCSDGetReferences(true);

	GError *TemporaryError = NULL;
	g_return_val_if_fail(GTKNoCSDGtkBuilder(G_OBJECT(Builder)), 0);
	g_return_val_if_fail(Path != NULL, 0);
	g_return_val_if_fail(Error == NULL || *Error == NULL, 0);

	GBytes *Bytes = g_resources_lookup_data(Path, 0, &TemporaryError);
	if (Bytes == NULL) {
		g_propagate_error(Error, TemporaryError);
		return FALSE;
	}

	g_bytes_get_data(Bytes, NULL);
	if (Bytes == NULL) {
		return FALSE;
	}

	gsize Length;
	const gchar *Data = g_bytes_get_data(Bytes, &Length);

	// WARNING: Own call
	// WARNING: Downcast
	gtk_builder_add_from_string(Builder, Data, (gssize) Length,
		&TemporaryError);

	g_bytes_unref(Bytes);

	if (TemporaryError != NULL) {
		g_propagate_error(Error, TemporaryError);
		return FALSE;
	}

	return TRUE;
}

GtkBuilder *gtk_builder_new_from_resource(const char *Path) {
	// Create builder from resource

	GTKNoCSDGetReferences(true);

	GError *Error = NULL;

	GtkBuilder *Builder = o_gtk_builder_new();

	// WARNING: Own call
	if (!gtk_builder_add_from_resource(Builder, Path, &Error)) {
		g_error("Failed to add UI from resource %s: %s", Path, Error->message);
	}

	return Builder;
}

gboolean gtk_builder_add_from_file(GtkBuilder *Builder, const char *File,
	GError **Error) {
	// Load into builder from file

	GTKNoCSDGetReferences(true);

	char *String;
	gsize Length;
	GError *TemporaryError = NULL;

	g_return_val_if_fail(GTKNoCSDGtkBuilder(G_OBJECT(Builder)), 0);
	g_return_val_if_fail(File != NULL, 0);
	g_return_val_if_fail(Error == NULL || *Error == NULL, 0);

	if (!g_file_get_contents(File, &String, &Length, &TemporaryError)) {
		g_propagate_error(Error, TemporaryError);
		return FALSE;
	}

	// Relative paths do not work when loading as string. This is where all
	// files get processed, so we save current path, get the base path of the
	// file and enter it
	char *NewPath = NULL;
	gchar *OldPath = NULL;
	const char *Slash = strrchr(File, '/');
	if (Slash != NULL) {
		// WARNING: Downcast
		NewPath = malloc((size_t) (Slash - File + 1));
		if (NewPath != NULL) {
			OldPath = g_get_current_dir();

			// WARNING: Downcast
			memcpy(NewPath, File, (size_t) (Slash - File + 1));
			NewPath[Slash - File] = '\0';
			G_GNUC_UNUSED int Ignore = chdir(NewPath);
		}
	}

	// WARNING: Own call
	// WARNING: Downcast
	gboolean ReturnValue =
		gtk_builder_add_from_string(Builder, String, (gssize) Length, Error);
	g_free(String);

	// We go back to the original path, in case anything depends on it, and free
	// memory
	G_GNUC_UNUSED int Ignore = chdir(OldPath);
	g_free(OldPath);
	free(NewPath);

	return ReturnValue;
}

GtkBuilder *gtk_builder_new_from_file(const gchar *FilePath) {
	// Create builder from file

	GTKNoCSDGetReferences(true);

	GError *Error = NULL;

	GtkBuilder *Builder = o_gtk_builder_new();

	// WARNING: Own call
	if (!gtk_builder_add_from_file(Builder, FilePath, &Error)) {
		g_error("Failed to add UI from file %s: %s", FilePath, Error->message);
	}

	return Builder;
}

void gdk_window_get_frame_extents(void *Window, GdkRectangle *Rectangle) {
	// Some windows that had CSD might ask for the entire region, expecting the
	// region with CSD. If this is detected, only the window content is provided
	// This only applies to windows that were also undecorated

	GTKNoCSDGetReferences(true);

	// Check if window CSD got removed and the window was undecorated
	gpointer Data = NULL;
	o_gdk_window_get_user_data(Window, &Data);
	if (GTKNoCSDUndecoratedWindowList != NULL && GTKNoCSDGtkWindow(Data)) {
		int Index = 0;
		while (GTKNoCSDUndecoratedWindowList[Index] != NULL) {
			if (GTKNoCSDUndecoratedWindowList[Index] == Data) {
				Index = -1;
				break;
			}
			++Index;
		}

		if (Index == -1) {
			// If so, only fetch the size of the window content
			o_gdk_window_get_geometry(Window, &Rectangle->x, &Rectangle->y,
				&Rectangle->width, &Rectangle->height);
			return;
		}
	}

	// Do original size fetching
	o_gdk_window_get_frame_extents(Window, Rectangle);
}

// Macro for simplifying getting the correct function
#define GET_SYMBOL(Function)						  \
		if (strcmp(Name, #Function) == 0) {			  \
			typeof(Function) * Pointer = &Function;	  \
			memcpy(Symbol, &Pointer, sizeof Pointer); \
			return true;							  \
		}											  \

gboolean g_module_symbol(GModule *Module, const gchar *Name, gpointer *Symbol) {
	// Python and other dynamic loading languages use this to load functions.
	// Hook it and overwrite requested functions with ours.

	GTKNoCSDGetReferences(true);

	if (2 < GTKNoCSDGTKVersion) {
		GET_SYMBOL(gtk_window_present);
		GET_SYMBOL(gtk_widget_set_visible);
		GET_SYMBOL(gtk_container_add);
		GET_SYMBOL(gtk_window_get_child);
		GET_SYMBOL(gtk_window_set_child);
		GET_SYMBOL(gtk_widget_get_parent);
		GET_SYMBOL(gtk_widget_get_first_child);
		GET_SYMBOL(gtk_widget_get_last_child);
		GET_SYMBOL(gtk_window_get_titlebar);
		GET_SYMBOL(gtk_window_set_titlebar);
		GET_SYMBOL(gtk_header_bar_set_custom_title);
		GET_SYMBOL(hdy_header_bar_set_custom_title);
		GET_SYMBOL(gtk_header_bar_set_title_widget);
		GET_SYMBOL(adw_header_bar_set_title_widget);
		GET_SYMBOL(gtk_window_set_decorated);
		GET_SYMBOL(gtk_widget_reparent);
		GET_SYMBOL(adw_dialog_present);
		GET_SYMBOL(adw_application_window_get_visible_dialog);
		GET_SYMBOL(adw_window_get_visible_dialog);
		GET_SYMBOL(gtk_about_dialog_new);
		GET_SYMBOL(gtk_show_about_dialog);
		GET_SYMBOL(g_object_new);
		GET_SYMBOL(g_signal_connect_object);
		GET_SYMBOL(gtk_widget_show_all);
		GET_SYMBOL(gtk_builder_add_from_string);
		GET_SYMBOL(gtk_builder_new_from_string);
		GET_SYMBOL(gtk_builder_add_from_resource);
		GET_SYMBOL(gtk_builder_new_from_resource);
		GET_SYMBOL(gtk_builder_add_from_file);
		GET_SYMBOL(gtk_builder_new_from_file);
		GET_SYMBOL(gdk_window_get_frame_extents);
		GET_SYMBOL(g_module_symbol);
	}

	return o_g_module_symbol(Module, Name, Symbol);
}

GType gtk_widget_get_type() {
	// Redefining for type registration macro

	GTKNoCSDGetReferences(false);
	return o_gtk_widget_get_type();
}
