/******************************************************************************\
* 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 <dlfcn.h>
#include <fcntl.h>
#include <libadwaita-1/adwaita.h>
#include <link.h>
#include <sys/stat.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;
size_t GTKNoCSDArgumentNumber = 0;

void GTKNoCSDSaveArguments(void) {
	// Save the arguments into a variable

	// Open file, return on failure
	int File = open("/proc/self/cmdline", O_RDONLY);
	if (File < 0) {
		return;
	}

	// Read file, close it, return on failure
	char Buffer[8192];
	ssize_t Length = read(File, Buffer, sizeof(Buffer) - 1);
	close(File);
	if (Length <= 0) {
		return;
	}
	Buffer[Length] = '\0';

	// Count arguments
	size_t Number = 0;
	for (ssize_t Index = 0; Index < Length; Index++) {
		if (Buffer[Index] == '\0') {
			Number++;
		}
	}

	// Allocate memory for array of arguments, return on failure
	GTKNoCSDArguments = calloc(Number + 1, sizeof(char *));
	if (GTKNoCSDArguments == NULL) {
		return;
	}

	// Copy each line into the array, close it, return on failue
	char *Line = Buffer;
	for (size_t Index = 0; Index < Number; Index++) {
		GTKNoCSDArguments[Index] = strdup(Line);

		// Clean up on failure
		if (GTKNoCSDArguments[Index] == NULL) {
			while (Index--) {
				free(GTKNoCSDArguments[Index]);
			}
			free(GTKNoCSDArguments);
			GTKNoCSDArguments = NULL;
			return;
		}
		Line += strlen(Line) + 1;
	}
	GTKNoCSDArguments[Number] = NULL;
	GTKNoCSDArgumentNumber = Number;
}

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

// Anonymous pipe data for multi process communication, in both directions
int GTKNoCSDPipeToChild[2], GTKNoCSDPipeToParent[2], GTKNoCSDPipeToChildEnd[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(void) {
	// 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);
	}
}

// Requires stack realignment because bypassing the C runtime startup leaves
// the stack unaligned, violating the System V AMD64 ABI.
#if defined(__i386__) || defined(__x86_64__)
__attribute__((visibility("default"), force_align_arg_pointer))
#else
__attribute__((visibility("default")))
#endif
void GTKNoCSDMain(void) {
	// This runs when the library is started as an executable

	// Musl does not run constructors for custom entry points
	if (GTKNoCSDArguments == NULL) {
		GTKNoCSDSaveArguments();
	}

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

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

	// Wait until parent ends
	char Buffer;
	Ignore = read(TO_CHILD_END_COPY, &Buffer, 1);

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

	// Restart if needed, leave
	if (Restart && GTKNoCSDArguments != NULL && GTKNoCSDArgumentNumber > 0) {
		printf("GTK-NoCSD: Crash detected, restarting without library.\n");

		// For some reason Hurd ld does not include ld library name and --
		int Shift = 1;
		if (3 < GTKNoCSDArgumentNumber && strcmp(GTKNoCSDArguments[1],
			"--") == 0) {
			Shift = 3;
		}
		execve(GTKNoCSDArguments[Shift], GTKNoCSDArguments + Shift, environ);
	}
	_exit(0);
}

bool GTKNoCSDLibraryInPath(const char *Path) {
	// Check if path contains GTK or Flutter library, for AppImage

	// Dynamic list of paths to visit, initial capacity for less reallocs
	size_t Capacity = 16;
	char **Paths = malloc(Capacity * sizeof(char *));
	if (Paths == NULL) {
		return false;
	}

	// Set initial path
	size_t Count = 0;
	Paths[Count++] = strdup(Path);
	if (Paths[0] == NULL) {
		free(Paths);
		return false;
	}

	// Go through all files
	while (Count > 0) {
		char *CurrentPath = Paths[--Count];

		// Open directory
		DIR *Directory = opendir(CurrentPath);
		if (Directory == NULL) {
			free(CurrentPath);
			continue;
		}

		struct dirent *Entry;
		while ((Entry = readdir(Directory)) != NULL) {
			// Get full path of file, go to next file on failure
			char *FullPath = malloc(
				strlen(CurrentPath) + strlen(Entry->d_name) + 2);
			if (FullPath == NULL) {
				continue;
			}
			sprintf(FullPath, "%s/%s", CurrentPath, Entry->d_name);

			// Check file info, go to next file on failure
			struct stat Stat;
			if (stat(FullPath, &Stat) == -1) {
				free(FullPath);
				continue;
			}

			if (S_ISDIR(Stat.st_mode)) {
				// Add subdirectory to list
				if (Count == Capacity) {
					Capacity *= 2;
					char **Temporary = realloc(Paths,
							Capacity * sizeof(char *));
					if (Temporary == NULL) {
						free(FullPath);
						continue;
					}
					Paths = Temporary;
				}
				Paths[Count++] = FullPath;
			} else if (S_ISREG(Stat.st_mode)) {
				// Check if the filename is GTK or Flutter library. Flutter uses
				// GTK for windowing while might not have it in the directory
				if ((strncmp(Entry->d_name, "libgtk-", 7) == 0 ||
					strncmp(Entry->d_name, "libflutter", 10) == 0) &&
					strstr(Entry->d_name, ".so") != NULL) {
					// If found, clean up everything and return success
					free(FullPath);
					closedir(Directory);
					free(CurrentPath);
					for (size_t Index = 0; Index < Count; ++Index) {
						free(Paths[Index]);
					}
					free(Paths);
					return true;
				}
				free(FullPath);
			} else {
				free(FullPath);
			}
		}

		closedir(Directory);
		free(CurrentPath);
	}

	free(Paths);
	return false;
}

void GTKNoCSDUnsetLDPreload(void) {
	// setenv does not always work before execve in these specific contexts,
	// so LD_PRELOAD is stripped manually from the environment array.
	if (environ != NULL) {
		for (char **Variable = environ; *Variable != NULL; ++Variable) {
			if (strncmp(*Variable, "LD_PRELOAD=", 11) == 0) {
				(*Variable)[11] = '\0';
				break;
			}
		}
	}
}

bool GTKNoCSDOnLomiri = false;
void *(*o_dlsym)(void *, const char *) = NULL;
char *GTKNoCSDLD = NULL, *GTKNoCSDLibC = NULL, *GTKNoCSDTheme = NULL;

void *GTKNoCSDResolvePointer(ElfW(Addr) Base, ElfW(Addr) Pointer) {
	// Musl pointers are offsets from base, so they are added to it
	// GLibC pointers are actual pointers so they are returned as given
	return Pointer >= Base ? (void *) Pointer : (void *) (Base + Pointer);
}

int GTKNoCSDGetDLSym(struct dl_phdr_info *Information,
	G_GNUC_UNUSED size_t Size, G_GNUC_UNUSED void *Data) {
	// Get dlsym for being able to overwrite it for certain language bindings

	// LibC or LD in case of Musl
	if (!Information->dlpi_name || strstr(
			Information->dlpi_name, GTKNoCSDLibC) != Information->dlpi_name) {
		return 0;
	}

	// Shorthand
	ElfW(Addr) Base = Information->dlpi_addr;

	// Get dynamic entries. These contain all library information
	ElfW(Dyn) * Dynamics = NULL;
	for (int Index = 0; Index < Information->dlpi_phnum; ++Index) {
		if (Information->dlpi_phdr[Index].p_type == PT_DYNAMIC) {
			Dynamics = (ElfW(Dyn) *)(Base +
					Information->dlpi_phdr[Index].p_vaddr);
			break;
		}
	}

	// SymTab contains symbols, StrTab contains symbol names. Fetch both
	ElfW(Sym) * SymTab = NULL;
	const char *StrTab = NULL;
	for (ElfW(Dyn) * Dynamic = Dynamics; Dynamic->d_tag != DT_NULL; Dynamic++) {
		if (Dynamic->d_tag == DT_SYMTAB) {
			SymTab = (ElfW(Sym) *)GTKNoCSDResolvePointer(Base,
					Dynamic->d_un.d_ptr);
		} else if (Dynamic->d_tag == DT_STRTAB) {
			StrTab = (const char *) GTKNoCSDResolvePointer(Base,
					Dynamic->d_un.d_ptr);
		}
	}

	// Getting the actual length does not seem to work. This assumes that it
	// will be present. If not, either here or somewhere else will be a crash
	for (size_t Index = 0; Index < 50000; Index++) {
		if (strcmp(StrTab + SymTab[Index].st_name, "dlsym") == 0) {
			o_dlsym = (void *) (Base + SymTab[Index].st_value);
			return 1;
		}
	}

	return 0;
}

int GTKNoCSDGetLDAndLibC(struct dl_phdr_info *Information,
	G_GNUC_UNUSED size_t Size, G_GNUC_UNUSED void *Data) {
	// Get LD and LibC libraries for crash handler and getting dlsym

	if (Information->dlpi_name != NULL) {
		// Used for starting the crash handler. Finds the dynamic linker across
		// GlibC, musl, and Hurd.
		if (GTKNoCSDLD == NULL && strstr(Information->dlpi_name, "/ld-") != NULL
			&& strstr(Information->dlpi_name, ".so.") != NULL) {
			GTKNoCSDLD = strdup(Information->dlpi_name);
		}

		// LibC contains dlsym on GlibC
		if (GTKNoCSDLibC == NULL && strstr(Information->dlpi_name, "/libc.")
			!= NULL && strstr(Information->dlpi_name, ".so.") != NULL) {
			GTKNoCSDLibC = strdup(Information->dlpi_name);
		}

		// If both are found, leave
		if (GTKNoCSDLD != NULL && GTKNoCSDLibC != NULL) {
			return 1;
		}
	}

	return 0;
}

void GTKNoCSDInitDLSym(bool Unload) {
	// Fully get dlsym, exit on failure or force unload

	// Get dlsym
	if (o_dlsym == NULL && !Unload) {
		dl_iterate_phdr(GTKNoCSDGetLDAndLibC, NULL);
		GTKNoCSDLibC = GTKNoCSDLibC == NULL ? GTKNoCSDLD : GTKNoCSDLibC;
		dl_iterate_phdr(GTKNoCSDGetDLSym, NULL);
	}

	// If still not found, unload instead of crash
	if (o_dlsym == NULL || Unload) {
		if (GTKNoCSDArguments == NULL) {
			GTKNoCSDSaveArguments();
		}
		if (GTKNoCSDArguments != NULL) {
			GTKNoCSDUnsetLDPreload();
			execve(GTKNoCSDArguments[0], GTKNoCSDArguments, environ);
			_exit(0);
		}
	}
}

__attribute__((constructor))
static void GTKNoCSDInit(void) {
	// This runs both when preloaded and when started as executable

	GTKNoCSDInitDLSym(false);

	// Save arguments. When preloaded, this is the started programs arguments,
	// sent into the library as executable, where it is saved again, for restart
	GTKNoCSDSaveArguments();

	// If already checked appimage, then no longer check it and unload if needed
	char *AppImage = getenv("GTK-NoCSDAppImage");
	bool Disable = false;

	// AppImages might use GLib functions while not being GTK applications and
	// not supplying the needed functions. They are dealt with here
	for (size_t Index = 0; AppImage == NULL && Index < GTKNoCSDArgumentNumber;
		++Index) {
		// Go through all arguments

		// If argument ends with AppRun or starts with /tmp/.mount_, it most
		// likely is an AppImage
		char *Argument = GTKNoCSDArguments[Index];
		const char *AppRun = strstr(Argument, "AppRun");
		char *(*Find)(const char *, int) = NULL;
		if (AppRun != NULL && strlen(AppRun) == 6) {
			Find = strrchr;
		} else if (strstr(Argument, "/tmp/.mount_") == Argument) {
			Find = strchr;
		}

		// Duplicate argument, find last or next slash
		if (Find != NULL) {
			char *Path = strdup(Argument);
			if (Path != NULL) {
				char *Last = Find(Path + (Find == strchr ? 12 : 0), '/');

				// Cut off everything after slash, check if directory has GTK
				if (Last != NULL) {
					*Last = '\0';
					Disable = !GTKNoCSDLibraryInPath(Path);
				}

				// Only check it once, for speedup, clean up and exit early
				setenv("GTK-NoCSDAppImage", "1", 1);
				free(Path);
				break;
			}
		}
	}

	// Check if the library is loaded into the Cambalache internal app, Merengue
	// This is a development environment and the library is not needed to run
	if (GTKNoCSDArguments != NULL) {
		char *Application = GTKNoCSDArguments[1];
		Disable = Disable || (Application != NULL && strstr(Application,
			"/merengue\0") != NULL);
	}

	// For package level loading disable on everything GNOME except Flashback
	const char *Desktop = getenv("XDG_CURRENT_DESKTOP");
	Disable = Disable || (Desktop != NULL && strstr(Desktop,
		"GNOME") != NULL && strstr(Desktop, "GNOME-Flashback") == NULL);

	// Do not unload from gnome-session
	for (size_t Index = 0; Index < GTKNoCSDArgumentNumber && Disable; ++Index) {
		Disable = Disable && strstr(GTKNoCSDArguments[Index],
				"gnome-session") == NULL;
	}

	// If not needed, unset library, restart
	if (Disable && GTKNoCSDArguments != NULL) {
		GTKNoCSDInitDLSym(true);
	}

	// In Lomiri transient-for changes have to be delayed
	GTKNoCSDOnLomiri = Desktop != NULL && strstr(Desktop, "Lomiri") != NULL;

	// 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)(void) = NULL;
void (*o_gtk_window_set_titlebar)(GtkWindow *, GtkWidget *) = NULL;
GType (*o_gtk_window_get_type)(void) = NULL;
GType (*o_gtk_application_window_get_type)(void) = NULL;
GType (*o_gtk_header_bar_get_type)(void) = NULL;
GType (*o_adw_window_get_type)(void) = NULL;
GType (*o_adw_application_window_get_type)(void) = NULL;
GType (*o_gtk_container_get_type)(void) = NULL;
GType (*o_hdy_window_get_type)(void) = NULL;
GType (*o_hdy_application_window_get_type)(void) = NULL;
GType (*o_adw_header_bar_get_type)(void) = NULL;
GType (*o_hdy_header_bar_get_type)(void) = NULL;
GType (*o_gtk_shortcuts_window_get_type)(void) = NULL;
GType (*o_gtk_builder_get_type)(void) = NULL;
GType (*o_gtk_label_get_type)(void) = NULL;
GType (*o_gtk_box_get_type)(void) = NULL;
GType (*o_gtk_widget_get_type)(void) = NULL;
GType (*o_gtk_search_bar_get_type)(void) = NULL;
GType (*o_adw_dialog_get_type)(void) = NULL;
GType (*o_gtk_toggle_button_get_type)(void) = NULL;
GType (*o_gtk_image_get_type)(void) = NULL;
GType (*o_adw_window_title_get_type)(void) = 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) (void) = 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) (void) = 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;
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) (void) = NULL;
GtkCssProvider *(*o_gtk_css_provider_new) (void) = 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) (void) = 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) (void) = 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;
void (*o_gtk_widget_set_hexpand) (GtkWidget *, gboolean) = NULL;
GtkWidget *(*o_gtk_widget_get_ancestor) (GtkWidget *, GType) = NULL;
GtkWidget *(*o_gtk_revealer_new) (void) = 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) (void) = 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;
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) (void) = NULL;
void (*o_gtk_widget_unrealize) (GtkWidget *) = NULL;
GType (*o_g_type_register_static) (GType, const gchar *, const GTypeInfo *,
	GTypeFlags) = NULL;
GType (*o_g_type_register_static_simple) (GType, const gchar *, guint,
	GClassInitFunc, guint, GInstanceInitFunc, GTypeFlags) = NULL;
typedef void (*GtkCallback)(GtkWidget *widget, gpointer data);
void (*o_gtk_container_forall) (GtkWidget *, GtkCallback, gpointer) = NULL;
void (*o_gtk_widget_set_no_show_all) (GtkWidget *, gboolean) = NULL;
void (*o_gtk_grid_attach) (GtkGrid *, GtkWidget *, int, int, int, int) = NULL;
void (*o_hdy_style_manager_set_color_scheme) (AdwStyleManager *,
	AdwColorScheme) = NULL;
AdwStyleManager * (*o_hdy_style_manager_get_default) (void) = NULL;
void (*o_gtk_widget_destroy) (GtkWidget *) = NULL;
void (*o_gtk_container_propagate_draw) (GtkWidget *, GtkWidget *,
	cairo_t *) = NULL;

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

// 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) = o_dlsym(LIBRARY, #NAME);				\
		if (o_ ## NAME == NULL) {										\
			fprintf(stderr, "GTK-NoCSD: dlsym failed for %s\n", #NAME);	\
		}																\

int GTKNoCSDNewGTKVersion = -1;

int GTKNoCSDGetLinkedLibraries(struct dl_phdr_info *Information,
	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 (Information->dlpi_name != NULL) {
		if (GTKNoCSDNewGTKVersion == -1) {
			if (strstr(Information->dlpi_name, "libgtk-x11-2.0.so.0") != NULL) {
				GTKNoCSDNewGTKVersion = 2;
			} else if (strstr(Information->dlpi_name,
				"libgtk-3.so.0") != NULL) {
				GTKNoCSDNewGTKVersion = 3;
			} else if (strstr(Information->dlpi_name,
				"libgtk-4.so.1") != NULL) {
				GTKNoCSDNewGTKVersion = 4;
			}
		}

		if (GTKNoCSDNewGTKVersion != -1) {
			return 1;
		}
	}

	return 0;
}

// Whether GTypes have been fetched already, whether platform library was found
bool GTKNoCSDGotTypes = false, GTKNoCSDGotPlatform = true;

void GTKNoCSDGetAdwTypes() {
	// Get all used LibAdwaita types

	GET_TYPE(GTKNoCSDADWWindow, adw_window_get_type);
	GET_TYPE(GTKNoCSDADWApplicationWindow, adw_application_window_get_type);
	GET_TYPE(GTKNoCSDADWHeaderBar, adw_header_bar_get_type);
	GET_TYPE(GTKNoCSDADWDialog, adw_dialog_get_type);
	GET_TYPE(GTKNoCSDADWWindowTitle, adw_window_title_get_type);
}

void GTKNoCSDGetHdyTypes() {
	// Get all used LibHandy types

	GET_TYPE(GTKNoCSDHDYWindow, hdy_window_get_type);
	GET_TYPE(GTKNoCSDHDYApplicationWindow, hdy_application_window_get_type);
	GET_TYPE(GTKNoCSDHDYHeaderBar, hdy_header_bar_get_type);
}

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

	GTKNoCSDInitDLSym(false);

	// These functions are always needed
	void *Library = NULL;
	if (o_g_type_register_static == NULL) {
		Library = GTKNoCSDGetLibrary("libgobject-2.0.so.0", true);
		LOAD_SYMBOL(Library, g_type_register_static);
		LOAD_SYMBOL(Library, g_type_register_static_simple);
	}
	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) = o_dlsym(RTLD_NEXT, "gtk_check_version");
	if (o_gtk_check_version != NULL) {
		if (o_gtk_check_version(2, 0, 0) == NULL) {
			GTKNoCSDNewGTKVersion = 2;
		} else if (o_gtk_check_version(3, 0, 0) == NULL) {
			GTKNoCSDNewGTKVersion = 3;
		} else if (o_gtk_check_version(4, 0, 0) == NULL) {
			GTKNoCSDNewGTKVersion = 4;
		}
	}

	// If gtk_check_version is missing, check linked libraries
	if (GTKNoCSDNewGTKVersion == -1) {
		dl_iterate_phdr(GTKNoCSDGetLinkedLibraries, NULL);
	}

	// Get name of used library
	const char *Name = RTLD_NEXT;
	if (GTKNoCSDNewGTKVersion == 2) {
		Name = "libgtk-x11-2.0.so.0";
	} else if (GTKNoCSDNewGTKVersion == 3) {
		Name = "libgtk-3.so.0";
	} else if (GTKNoCSDNewGTKVersion == 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 != GTKNoCSDNewGTKVersion) {
		GTKNoCSDGTKVersion = GTKNoCSDNewGTKVersion;

		// 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_get_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);
		LOAD_SYMBOL(Library, gtk_window_get_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_reparent);
			LOAD_SYMBOL(Library, gdk_window_get_geometry);
			LOAD_SYMBOL(Library, gdk_window_get_user_data);
			LOAD_SYMBOL(Library, gdk_window_get_frame_extents);
			LOAD_SYMBOL(Library, gtk_container_propagate_draw);
		}

		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_set_hexpand);
			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_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);
			LOAD_SYMBOL(Library, gtk_widget_unrealize);
			LOAD_SYMBOL(Library, gtk_grid_attach);
		}

		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_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) {
				GTKNoCSDGotPlatform = false;
				printf("GTK-NoCSD: LibAdwaita not found, will try GModule.\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);
			LOAD_SYMBOL(Library, gtk_container_forall);
			LOAD_SYMBOL(Library, gtk_widget_set_no_show_all);
			LOAD_SYMBOL(Library, gtk_widget_destroy);

			Library = GTKNoCSDGetLibrary("libhandy-1.so.0", false);
			if (Library == NULL) {
				GTKNoCSDGotPlatform = false;
				printf("GTK-NoCSD: LibHandy not found, will try GModule.\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);
				LOAD_SYMBOL(Library, hdy_style_manager_get_default);
				LOAD_SYMBOL(Library, hdy_style_manager_set_color_scheme);
			}
		}
	}

	// g_intern_static_string is too early to fetch GTypes, there they are
	// not fetched yet
	if ((!GTKNoCSDGotTypes || OldGTKVersion != GTKNoCSDNewGTKVersion) &&
		GetTypes) {
		// Better to get these once and then reuse
		GET_TYPE(GTKNoCSDGTKWindow, gtk_window_get_type);
		GET_TYPE(GTKNoCSDGTKApplicationWindow,
			gtk_application_window_get_type);
		GET_TYPE(GTKNoCSDGTKHeaderBar, gtk_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(GTKNoCSDGTKToggleButton, gtk_toggle_button_get_type);
		GET_TYPE(GTKNoCSDGTKImage, gtk_image_get_type);
		GTKNoCSDGetAdwTypes();
		GTKNoCSDGetHdyTypes();
		GTKNoCSDGotTypes = true;
	}
}

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

	GTKNoCSDMagic((GtkWindow *) Data, true);

	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, true);
	}

	return FALSE;
}

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

	GTKNoCSDMagic(Window, true);
}

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, true);
	}

	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);
	}
}

void GTKNoCSDForceInvisible(GtkWidget *Widget, G_GNUC_UNUSED GParamSpec *Spec,
	G_GNUC_UNUSED gpointer Data) {
	// No show all does not always help, if visibility is set manually. It is
	// forced off here

	o_gtk_widget_set_visible(Widget, false);
	o_gtk_widget_set_no_show_all(Widget, true);
}

void GTKNoCSDGTK3SetForceInvisible(GtkWidget *Widget) {
	// Add function to force off visibility, call it once for initial set up

	if (!g_signal_handler_find(Widget, G_SIGNAL_MATCH_FUNC,
		0, 0, NULL, G_CALLBACK(GTKNoCSDForceInvisible), NULL)) {
		g_signal_connect(Widget, "notify::visible",
			G_CALLBACK(GTKNoCSDForceInvisible), NULL);
	}
	GTKNoCSDForceInvisible(Widget, NULL, NULL);
}

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")) {
		GTKNoCSDGTK3SetForceInvisible(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, Hidden = 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)) {
			GTKNoCSDGTK3SetForceInvisible(Child);
			++Hidden;
		} else {
			// If close button, remove
			Context = o_gtk_widget_get_style_context(Child);
			if (o_gtk_style_context_has_class(Context, "titlebutton")) {
				GTKNoCSDGTK3SetForceInvisible(Child);
				++Hidden;
			}
		}

		++Amount;
	}
	g_list_free(Children);

	// If all widgets got removed, remove the container too (title button)
	if (Amount == Hidden && Amount != 0) {
		GTKNoCSDGTK3SetForceInvisible(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 GTKNoCSDAddTransient(GtkWindow *Window, gpointer Data) {
	// Set transient-for property window

	o_gtk_window_set_transient_for(Window, (GtkWindow *) Data);
	g_signal_handlers_disconnect_by_func(Window,
		G_CALLBACK(GTKNoCSDAddTransient), Data);
}

void GTKNoCSDChangeTransient(GtkWindow *Window, G_GNUC_UNUSED GParamSpec *Spec,
	G_GNUC_UNUSED gpointer Data) {
	// If transient-for property window was set, while the window was not mapped
	// yet, unset it and delay it to after it got mapped

	GtkWindow *Current = o_gtk_window_get_transient_for(Window);
	if (Current != NULL && !o_gtk_widget_get_mapped((GtkWidget *) Window)) {
		o_gtk_window_set_transient_for(Window, NULL);
		o_gtk_widget_unrealize((GtkWidget *) Window);
		g_signal_connect(Window, "map",
			G_CALLBACK(GTKNoCSDAddTransient), Current);
	}
}

void GTKNoCSDHooker(void) {
	// 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 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;
		}
	}
}

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

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

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

	// 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 (GTKNoCSDLD == NULL || GTKNoCSDArguments == NULL) {
			// If Crash handler starter or arguments were 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 = realpath(Information.dli_fname, NULL);

				if (LibraryFullPath == NULL || 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);
					Ignore = pipe(GTKNoCSDPipeToChildEnd);

					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);
						close(GTKNoCSDPipeToChildEnd[1]);
						dup2(GTKNoCSDPipeToChildEnd[0], TO_CHILD_END_COPY);

						// Start assembling arguments for starting crash handler
						char *NewArguments[GTKNoCSDArgumentNumber + 4];
						NewArguments[0] = GTKNoCSDLD;
						NewArguments[1] = "--";
						NewArguments[2] = LibraryFullPath;

						// Try to fetch full path, if not found use as is
						NewArguments[3] =
							g_find_program_in_path(GTKNoCSDArguments[0]);
						if (NewArguments[3] == NULL) {
							NewArguments[3] = GTKNoCSDArguments[0];
						}

						// 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[3] == NULL) {
							GTKNoCSDHandlerFailExit(LibraryFullPath);
						}

						// Copy arguments except binary
						for (size_t Index = 1;
							Index <= GTKNoCSDArgumentNumber; ++Index) {
							NewArguments[Index + 3] = GTKNoCSDArguments[Index];
						}

						// Appimages should be completely restarted, instead of
						// just the binary. This is detected and handled here
						// Check if binary is path and APPIMAGE is set
						char *AppImage = getenv("APPIMAGE");
						char *Ending = strrchr(NewArguments[3], '/');
						if (Ending != NULL && AppImage != NULL) {
							size_t Length = strlen(NewArguments[3]) -
								strlen(Ending);
							char *AppRun = malloc(Length + 7);
							if (AppRun != NULL) {
								// If so, copy folder name into variable, add
								// AppRun, check if file and appimage both exist
								strncpy(AppRun, NewArguments[3], Length);
								AppRun[Length] = '\0';
								strcat(AppRun, "/AppRun");
								struct stat Stat;
								if (stat(AppRun, &Stat) == 0 && stat(AppImage,
									&Stat) == 0) {
									// If so, replace binary with AppImage
									NewArguments[3] = AppImage;
								}
								free(AppRun);
							}
						}

						// Stop ld-musl from preloading the library into itself
						GTKNoCSDUnsetLDPreload();

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

						// This should not be reached, execve failed
						GTKNoCSDHandlerFailExit(LibraryFullPath);
					} 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);
					}
				}
				free(LibraryFullPath);
			}
		}
	}

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

	bool SetCSS = false;
	if (!GTKNoCSDSettings && DoSettings) {
		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;
					AdwColorScheme Scheme = ADW_COLOR_SCHEME_FORCE_LIGHT;
					Scheme = Dark ? ADW_COLOR_SCHEME_FORCE_DARK : Scheme;

					// Get functions for the GTK version
					AdwStyleManager * (*GetDefault) (void) =
						o_adw_style_manager_get_default;
					void (*SetScheme) (AdwStyleManager *, AdwColorScheme) =
						o_adw_style_manager_set_color_scheme;
					if (GTKNoCSDGTKVersion == 3) {
						GetDefault = o_hdy_style_manager_get_default;
						SetScheme = o_hdy_style_manager_set_color_scheme;
					}

					// Enforce LibAdwaita or LibHandy color scheme
					if (GetDefault != NULL && SetScheme != NULL) {
						SetScheme(GetDefault(), 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 1;
	}

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

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

	// Lomiri does not decorate windows that are transient for another.
	// To work around this, the setting of the property is delayed to after they
	// got mapped. However since this breaks window centering on other systems
	// (XFCE, possibly everything X11), it will not be done anywhere else
	if (GTKNoCSDOnLomiri) {
		if (!g_signal_handler_find(Window, G_SIGNAL_MATCH_FUNC,
			0, 0, NULL, G_CALLBACK(GTKNoCSDChangeTransient), NULL)) {
			g_signal_connect(Window, "notify::transient-for",
				G_CALLBACK(GTKNoCSDChangeTransient), NULL);
			g_signal_connect(Window, "unmap",
				G_CALLBACK(GTKNoCSDChangeTransient), NULL);
		}

		// Also do it for windows that already came with the property set
		GTKNoCSDChangeTransient(Window, NULL, NULL);
	}

	// 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 5;
					}
				}

				// 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 6;
			}
			++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);
	}

	GtkWidget *Header = o_gtk_window_get_titlebar(Window);

	// 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

		// If header not found, come back later
		if (Header == 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 0;
		}

		// Hide the title widget, if it is not a special control. It might be
		// added later so we check then
		g_timeout_add(1, GTKNoCSDGTKVersion == 4 ?
			GTKNoCSDSetGTK4Headers : GTKNoCSDSetGTK3Headers, Window);

		if (GTKNoCSDGTKVersion == 3) {
			// Unset the header for the window, but save it for returning it in
			// gtk_window_get_titlebar
			g_object_ref(Header);
			o_gtk_window_set_titlebar(Window, NULL);
			g_object_set_data(G_OBJECT(Window), "GTKNoCSDTitleBar", Header);
			return 7;
		}
	}

	// GTK3/GTK4 and LibAdwaita also need to rearrange widgets

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

	if (GTKNoCSDGtkShortcutsWindow(G_OBJECT(Window))) {
		// This window was already dealt with
		if (strcmp(o_gtk_widget_get_name((GtkWidget *) Window),
			"GTKNoCSDHelp") == 0) {
			return 8;
		}
		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) {
			// The header is kept alive as it might be used internally
			g_object_ref(Header);
			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 9;
		}
	}

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

	// 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 11;
	}

	// 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 12;
	}

	// 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_grid_new();
	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);
	o_gtk_grid_attach((GtkGrid *) Vertical, HeaderBox, 0, 0, 1, 1);
	o_gtk_grid_attach((GtkGrid *) Vertical, ContentBox, 0, 1, 1, 10000);

	// Grid has no order guarantee, these GObject properties are used instead
	g_object_set_data(G_OBJECT(Vertical), "HeaderBox", HeaderBox);
	g_object_set_data(G_OBJECT(Vertical), "ContentBox", 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);
		o_gtk_widget_set_hexpand(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);

		// LibAdwaita windows were already checked, AdwGizmo is a placeholder
		if (!GTKNoCSDAdwApplicatonWindow(G_OBJECT(Window)) &&
			!GTKNoCSDAdwWindow(G_OBJECT(Window))) {
			const char *TypeName = g_type_name(G_OBJECT_TYPE(Header));
			if (strstr(TypeName, "AdwGizmo") != TypeName) {
				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 13;
}

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

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(g_object_get_data(G_OBJECT(Child),
				   "ContentBox"));
	}

	return Child;
}

gboolean GTKNoCSDRedisplayContent(gpointer Data) {
	// Display the content of the window again

	GtkWidget *Child = GTKNoCSDWindowGetChild((GtkWindow *) Data);
	if (Child != NULL) {
		o_gtk_widget_set_visible(Child, false);
		o_gtk_widget_set_visible(Child, true);
	}

	return FALSE;
}

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) {
		int MagicStatus = GTKNoCSDMagic(Window, true);
		if (MagicStatus) {
			// This status is what Lutris produces and which is the only window
			// needing this fix
			if (MagicStatus == 6 &&
				!o_gtk_widget_get_mapped((GtkWidget *) Window)) {
				g_idle_add(GTKNoCSDRedisplayContent, 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, true);
		}
	}
}

// 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)) {
				GtkWidget *ContentBox = g_object_get_data(G_OBJECT(WindowChild),
						"ContentBox");

				// 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);
				o_gtk_widget_set_hexpand(Widget, TRUE);
			} else {
				o_gtk_container_add(Container, Widget);
				GTKNoCSDMagic((GtkWindow *) Window, true);
			}
			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)) {
		// Remove existing child if present
		GtkWidget *ContentBox = g_object_get_data(G_OBJECT(WindowChild),
				"ContentBox");
		GtkWidget *OldContent = o_gtk_widget_get_last_child(ContentBox);
		if (OldContent != NULL) {
			o_gtk_widget_unparent(OldContent);
		}

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

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

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);

	// This is only used in LibHandy
	GtkWidget *Header = g_object_get_data(G_OBJECT(Window), "GTKNoCSDTitleBar");
	if (Header != NULL) {
		return Header;
	}

	GtkWidget *WindowChild = GTKNoCSDWindowGetChild(Window);
	if (GTKNoCSDTest(WindowChild)) {
		GtkWidget *Box = GTKNoCSDFirstChild(
			g_object_get_data(G_OBJECT(WindowChild), "HeaderBox"));

		// 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, true);
	GtkWidget *WindowChild = GTKNoCSDWindowGetChild(Window);
	if (GTKNoCSDTest(WindowChild)) {
		GtkWidget *Box = GTKNoCSDFirstChild(
			g_object_get_data(G_OBJECT(WindowChild), "HeaderBox"));

		// 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, true);
	}
}

// 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 *Child, 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(Child, NULL);

	// Hide title widget of dialog, if it only displays window title
	GtkWidget *HeaderBar = GTKNoCSDGetGTK4Headers((GtkWidget *) Child, 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());
			}
		}
	}

	// Get window window of dialog, empty window of parent
	GtkWindow *ChildWindow = (GtkWindow *) o_gtk_widget_get_ancestor(
		(GtkWidget *) Child, GTKNoCSDGTKWindow), *ParentWindow = NULL;

	// Get window of parent
	if (Parent != NULL) {
		ParentWindow = (GtkWindow *) o_gtk_widget_get_ancestor(Parent,
				GTKNoCSDGTKWindow);
	}

	// Get original dialog size
	int Width = o_adw_dialog_get_content_width(Child), ParentWidth = Width;
	int Height = o_adw_dialog_get_content_height(Child), ParentHeight = Height;

	if (ParentWindow != NULL) {
		// Center dialog on parent, prevent closing parent while child exists
		o_gtk_window_set_transient_for(ChildWindow, ParentWindow);
		o_gtk_window_set_modal(ChildWindow, true);

		// 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 *) Child, "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 *) Child, "win",
				(GActionGroup *) ParentWindow);
		}

		// Get size of parent window
		ParentWidth = o_gtk_widget_get_width((GtkWidget *) ParentWindow);
		ParentHeight = o_gtk_widget_get_height((GtkWidget *) ParentWindow);
	}

	// Make windows at most as large as the parent, but resizeable
	o_adw_dialog_set_content_width(Child, MIN(ParentWidth, Width));
	o_adw_dialog_set_content_height(Child, 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 *Content =
				GTKNoCSDGTK4Content(GTKNoCSDWindowGetChild(CheckedWindow));
			if (Content != NULL && GTKNoCSDAdwDialog(G_OBJECT(Content))) {
				Dialog = (AdwDialog *) Content;
			}
		}
		++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(void) {
	// 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, true);
	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;

GtkWidget *GTKNoCSDHeaderBar = NULL;

void GTKNoCSDGetHeaderBar(GtkWidget *Widget, G_GNUC_UNUSED gpointer Data) {
	// Check if a widget is a GtkHeaderBar and has the class for the internal
	// one, set global variable if so

	if (GTKNoCSDGtkHeaderBar(G_OBJECT(Widget))) {
		GtkStyleContext *Context = o_gtk_widget_get_style_context(Widget);
		if (o_gtk_style_context_has_class(Context, "default-decoration")) {
			GTKNoCSDHeaderBar = Widget;
		}
	}
}

gboolean gtk_window_get_decorated(GtkWindow *Window) {
	// This is reimplemented so LibHandy applications will not have to flicker
	// like LibAdwaita, when the built in CSD is removed

	GTKNoCSDGetReferences(true);

	// Only in GTK3
	if (GTKNoCSDGTKVersion == 3) {
		// Look for internal CSD, report not being decorated if found
		GTKNoCSDHeaderBar = NULL;
		o_gtk_container_forall((GtkWidget *) Window, GTKNoCSDGetHeaderBar,
			NULL);
		if (GTKNoCSDHeaderBar != NULL) {
			return false;
		}
	}

	return o_gtk_window_get_decorated(Window);
}

gboolean GTKNoCSDHeaderBarParentFinalize(gpointer Data) {
	// Finish what GTKNoCSDHeaderBarParent started, hide the widget
	o_gtk_widget_set_visible((GtkWidget *) Data, false);
	return FALSE;
}

void GTKNoCSDHeaderBarParent(GtkWidget *Widget, G_GNUC_UNUSED GParamSpec *Spec,
	G_GNUC_UNUSED gpointer Data) {
	// Hide the CSD HeaderBar that GTK sets when the wayland compositor does not
	// support SSD (or the KDE protocol for it) and the application does not set
	// a CSD either

	// Only activate on the specific widget we need
	GtkWidget *Parent = o_gtk_widget_get_parent(Widget);
	if (Parent != NULL && GTKNoCSDGtkWindow(G_OBJECT(Parent))) {
		GtkStyleContext *Context = o_gtk_widget_get_style_context(Widget);
		if (o_gtk_style_context_has_class(Context, "default-decoration")) {
			// Hide it later
			g_idle_add(GTKNoCSDHeaderBarParentFinalize, Widget);
		}
	}
}

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, false);
		}

		// 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 for compositors that do not support the KDE decoration
		// protocol, but also do not want CSD
		if (GTKNoCSDGtkHeaderBar(Object)) {
			g_signal_connect(Object, "notify::parent",
				G_CALLBACK(GTKNoCSDHeaderBarParent), 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;
}

void gtk_container_propagate_draw(GtkWidget *Container, GtkWidget *Child,
	cairo_t *Cairo) {
	// For LibHandy propagating to the fake header is disallowed

	GTKNoCSDGetReferences(true);

	if (Child == g_object_get_data(G_OBJECT(Container), "GTKNoCSDTitleBar")) {
		return;
	}

	o_gtk_container_propagate_draw(Container, Child, Cairo);
}

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, true);
			}
		}
	}

	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);
}

GType g_type_register_static(GType Parent, const gchar *Name,
	const GTypeInfo *Information, GTypeFlags Flags) {
	// Type registration might break when new GTK-NoCSD version is loaded into
	// old GTK versions. This works around that

	GTKNoCSDGetReferences(false);

	// Try to fetch existing type first
	GType Existing = g_type_from_name(Name);
	if (Existing != 0) {
		return Existing;
	}

	// Return new type
	return o_g_type_register_static(Parent, Name, Information, Flags);
}

GType g_type_register_static_simple(GType Parent, const gchar *Name,
	guint ClassSize, GClassInitFunc ClassInit, guint InstanceSize,
	GInstanceInitFunc InstanceInit, GTypeFlags Flags) {
	// Type registration might break when new GTK-NoCSD version is loaded into
	// old GTK versions. This works around that

	GTKNoCSDGetReferences(false);

	// Try to fetch existing type first
	GType Existing = g_type_from_name(Name);
	if (Existing != 0) {
		return Existing;
	}

	// Return new type
	return o_g_type_register_static_simple(Parent, Name, ClassSize, ClassInit,
			   InstanceSize, InstanceInit, Flags);
}

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

#define GET_SYMBOL2(Function)				\
		if (strcmp(Name, #Function) == 0) {	\
			return Function;				\
		}									\

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 platform library was not found but symbols are requested from them,
	// there is a chance of getting what is used by the library. After this also
	// get the used types. Used by script languages on sandboxed systems (Nix)
	if (!GTKNoCSDGotPlatform) {
		if (GTKNoCSDGTKVersion == 4 && strstr(Name, "adw_") == Name) {
			o_g_module_symbol(Module, "adw_window_get_type",
				(gpointer *) &o_adw_window_get_type);
			o_g_module_symbol(Module, "adw_application_window_get_type",
				(gpointer *) &o_adw_application_window_get_type);
			o_g_module_symbol(Module, "adw_header_bar_get_type",
				(gpointer *) &o_adw_header_bar_get_type);
			o_g_module_symbol(Module, "adw_header_bar_get_title_widget",
				(gpointer *) &o_adw_header_bar_get_title_widget);
			o_g_module_symbol(Module, "adw_dialog_get_type",
				(gpointer *) &o_adw_dialog_get_type);
			o_g_module_symbol(Module, "adw_header_bar_set_decoration_layout",
				(gpointer *) &o_adw_header_bar_set_decoration_layout);
			o_g_module_symbol(Module, "adw_dialog_present",
				(gpointer *) &o_adw_dialog_present);
			o_g_module_symbol(Module, "adw_dialog_set_content_height",
				(gpointer *) &o_adw_dialog_set_content_height);
			o_g_module_symbol(Module, "adw_dialog_set_content_width",
				(gpointer *) &o_adw_dialog_set_content_width);
			o_g_module_symbol(Module, "adw_window_title_get_type",
				(gpointer *) &o_adw_window_title_get_type);
			o_g_module_symbol(Module,
				"adw_application_window_get_visible_dialog",
				(gpointer *) &o_adw_application_window_get_visible_dialog);
			o_g_module_symbol(Module, "adw_window_get_visible_dialog",
				(gpointer *) &o_adw_window_get_visible_dialog);
			o_g_module_symbol(Module, "adw_dialog_get_content_width",
				(gpointer *) &o_adw_dialog_get_content_width);
			o_g_module_symbol(Module, "adw_dialog_get_content_height",
				(gpointer *) &o_adw_dialog_get_content_height);
			o_g_module_symbol(Module, "adw_header_bar_set_title_widget",
				(gpointer *) &o_adw_header_bar_set_title_widget);
			o_g_module_symbol(Module, "adw_style_manager_get_default",
				(gpointer *) &o_adw_style_manager_get_default);
			o_g_module_symbol(Module, "adw_style_manager_set_color_scheme",
				(gpointer *) &o_adw_style_manager_set_color_scheme);
			GTKNoCSDGetAdwTypes();
			GTKNoCSDGotPlatform = true;
		} else if (GTKNoCSDGTKVersion == 3 && strstr(Name, "hdy_") == Name) {
			o_g_module_symbol(Module, "hdy_window_get_type",
				(gpointer *) &o_hdy_window_get_type);
			o_g_module_symbol(Module, "hdy_application_window_get_type",
				(gpointer *) &o_hdy_application_window_get_type);
			o_g_module_symbol(Module, "hdy_header_bar_get_type",
				(gpointer *) &o_hdy_header_bar_get_type);
			o_g_module_symbol(Module, "hdy_header_bar_set_decoration_layout",
				(gpointer *) &o_hdy_header_bar_set_decoration_layout);
			o_g_module_symbol(Module, "hdy_header_bar_get_custom_title",
				(gpointer *) &o_hdy_header_bar_get_custom_title);
			o_g_module_symbol(Module, "hdy_header_bar_set_custom_title",
				(gpointer *) &o_hdy_header_bar_set_custom_title);
			o_g_module_symbol(Module, "hdy_style_manager_get_default",
				(gpointer *) &o_hdy_style_manager_get_default);
			o_g_module_symbol(Module, "hdy_style_manager_set_color_scheme",
				(gpointer *) &o_hdy_style_manager_set_color_scheme);
			GTKNoCSDGetHdyTypes();
			GTKNoCSDGotPlatform = true;
		}
	}

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

	return o_g_module_symbol(Module, Name, Symbol);
}

void *dlsym(void *Handle, const char *Name) {
	// Dlsym is used by Gir.Core to get functions. They are also overwritten

	GTKNoCSDInitDLSym(false);

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

	return o_dlsym(Handle, Name);
}

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

	GTKNoCSDGetReferences(false);
	return o_gtk_widget_get_type();
}
