#!/usr/bin/env bash set -euo pipefail ROOT="$(pwd)" SRC_DIR="$ROOT/src" PATCH_OUT="$ROOT/ubuntu-lts-gtkmm4.generated.patch" COMPAT_HPP="$SRC_DIR/GtkCompat.hpp" if [[ ! -d "$SRC_DIR" ]]; then echo "ERROR: src/ not found. Run from project root." >&2 exit 1 fi mapfile -t FILES_TO_PATCH < <(find "$SRC_DIR" -type f -name "*.cpp" | sort) TMPDIR="$(mktemp -d)" trap 'rm -rf "$TMPDIR"' EXIT backup_key() { echo "$1" | sed 's|/|__|g'; } echo "[1/6] Backups" for f in "${FILES_TO_PATCH[@]}"; do [[ -f "$f" ]] || continue cp -a "$f" "$TMPDIR/$(backup_key "$f").orig" done if [[ -f "$COMPAT_HPP" ]]; then cp -a "$COMPAT_HPP" "$TMPDIR/GtkCompat.hpp.orig" else : > "$TMPDIR/GtkCompat.hpp.orig" fi echo "[2/6] Writing $COMPAT_HPP (Qt macro-safe)" cat > "$COMPAT_HPP" <<'EOF' #pragma once #if defined(_MSC_VER) || defined(__clang__) || defined(__GNUC__) #define GTKCOMPAT_HAS_PUSH_POP_MACRO 1 #else #define GTKCOMPAT_HAS_PUSH_POP_MACRO 0 #endif #if GTKCOMPAT_HAS_PUSH_POP_MACRO #ifdef signals #pragma push_macro("signals") #undef signals #define GTKCOMPAT_RESTORE_signals 1 #endif #ifdef slots #pragma push_macro("slots") #undef slots #define GTKCOMPAT_RESTORE_slots 1 #endif #ifdef emit #pragma push_macro("emit") #undef emit #define GTKCOMPAT_RESTORE_emit 1 #endif #ifdef foreach #pragma push_macro("foreach") #undef foreach #define GTKCOMPAT_RESTORE_foreach 1 #endif #ifdef Q_FOREACH #pragma push_macro("Q_FOREACH") #undef Q_FOREACH #define GTKCOMPAT_RESTORE_Q_FOREACH 1 #endif #else #ifdef signals #undef signals #endif #ifdef slots #undef slots #endif #ifdef emit #undef emit #endif #ifdef foreach #undef foreach #endif #ifdef Q_FOREACH #undef Q_FOREACH #endif #endif #include #include #include #include namespace GtkCompat { inline void for_each_child(Gtk::Widget& parent, const std::function& fn) { for (auto* child = parent.get_first_child(); child != nullptr; child = child->get_next_sibling()) fn(child); } inline std::vector children(Gtk::Widget& parent) { std::vector out; for_each_child(parent, [&](Gtk::Widget* w){ out.push_back(w); }); return out; } inline void listbox_clear(Gtk::ListBox& lb) { for (auto* child = lb.get_first_child(); child != nullptr; ) { auto* next = child->get_next_sibling(); lb.remove(*child); child = next; } } } inline bool operator==(const Glib::ustring& lhs, const std::string& rhs) { return lhs.raw() == rhs; } inline bool operator==(const std::string& lhs, const Glib::ustring& rhs) { return lhs == rhs.raw(); } inline bool operator!=(const Glib::ustring& lhs, const std::string& rhs) { return !(lhs == rhs); } inline bool operator!=(const std::string& lhs, const Glib::ustring& rhs) { return !(lhs == rhs); } #if GTKCOMPAT_HAS_PUSH_POP_MACRO #ifdef GTKCOMPAT_RESTORE_Q_FOREACH #pragma pop_macro("Q_FOREACH") #undef GTKCOMPAT_RESTORE_Q_FOREACH #endif #ifdef GTKCOMPAT_RESTORE_foreach #pragma pop_macro("foreach") #undef GTKCOMPAT_RESTORE_foreach #endif #ifdef GTKCOMPAT_RESTORE_emit #pragma pop_macro("emit") #undef GTKCOMPAT_RESTORE_emit #endif #ifdef GTKCOMPAT_RESTORE_slots #pragma pop_macro("slots") #undef GTKCOMPAT_RESTORE_slots #endif #ifdef GTKCOMPAT_RESTORE_signals #pragma pop_macro("signals") #undef GTKCOMPAT_RESTORE_signals #endif #endif #undef GTKCOMPAT_HAS_PUSH_POP_MACRO EOF echo "[3/6] Ensuring includes" for f in "${FILES_TO_PATCH[@]}"; do [[ -f "$f" ]] || continue if ! grep -q '^[[:space:]]*#include[[:space:]]*"GtkCompat\.hpp"[[:space:]]*$' "$f"; then perl -0777 -i -pe ' if (s/(#include\s+"[^"]+\.hpp"\s*\n)/$1#include "GtkCompat.hpp"\n/s) { } else { $_ = "#include \"GtkCompat.hpp\"\n" . $_; } ' "$f" fi done echo "[4/6] Fix ListBox::remove_all()" perl -pi -e 's/\b([A-Za-z_][A-Za-z0-9_]*)\.remove_all\(\)\s*;/GtkCompat::listbox_clear($1);/g' "${FILES_TO_PATCH[@]}" echo "[5/6] Fix get_children() vars and loops (supports auto, auto&, auto*, braced and single-line)" for f in "${FILES_TO_PATCH[@]}"; do [[ -f "$f" ]] || continue perl -i -pe ' s/\bauto\s+(\w+)\s*=\s*([A-Za-z_][A-Za-z0-9_:>\-\.\(\)\s]*)\s*\.get_children\s*\(\s*\)\s*;/ auto $1 = GtkCompat::children($2);/gx ' "$f" perl -i -pe ' s/\bauto\s+(\w+)\s*=\s*([A-Za-z_][A-Za-z0-9_:>\-\.\(\)\s]*)\s*->get_children\s*\(\s*\)\s*;/ auto $1 = GtkCompat::children(*($2));/gx ' "$f" perl -0777 -i -pe ' s/for\s*\(\s*auto\s+(\w+)\s*:\s*([^\)]+?)\.get_children\s*\(\s*\)\s*\)\s*\{/ for (auto* $1 = ($2).get_first_child(); $1 != nullptr; $1 = $1->get_next_sibling()) {/gsx ' "$f" perl -0777 -i -pe ' s/for\s*\(\s*auto\s*&\s*(\w+)\s*:\s*([^\)]+?)\.get_children\s*\(\s*\)\s*\)\s*\{/ for (auto* $1 = ($2).get_first_child(); $1 != nullptr; $1 = $1->get_next_sibling()) {/gsx ' "$f" perl -0777 -i -pe ' s/for\s*\(\s*auto\s*\*\s*(\w+)\s*:\s*([^\)]+?)\.get_children\s*\(\s*\)\s*\)\s*\{/ for (auto* $1 = ($2).get_first_child(); $1 != nullptr; $1 = $1->get_next_sibling()) {/gsx ' "$f" perl -0777 -i -pe ' s/for\s*\(\s*auto\s+(\w+)\s*:\s*([^\)]+?)\-\>get_children\s*\(\s*\)\s*\)\s*\{/ for (auto* $1 = ($2)->get_first_child(); $1 != nullptr; $1 = $1->get_next_sibling()) {/gsx ' "$f" perl -0777 -i -pe ' s/for\s*\(\s*auto\s*&\s*(\w+)\s*:\s*([^\)]+?)\-\>get_children\s*\(\s*\)\s*\)\s*\{/ for (auto* $1 = ($2)->get_first_child(); $1 != nullptr; $1 = $1->get_next_sibling()) {/gsx ' "$f" perl -0777 -i -pe ' s/for\s*\(\s*auto\s*\*\s*(\w+)\s*:\s*([^\)]+?)\-\>get_children\s*\(\s*\)\s*\)\s*\{/ for (auto* $1 = ($2)->get_first_child(); $1 != nullptr; $1 = $1->get_next_sibling()) {/gsx ' "$f" perl -0777 -i -pe ' s@for\s*\(\s*auto\s+(\w+)\s*:\s*([^\n;]+?)\.get_children\s*\(\s*\)\s*\)\s*\n(\s*)([^\n;][^\n]*;)@ $3for (auto* $1 = ($2).get_first_child(); $1 != nullptr; ) {\n$3 auto* next = $1->get_next_sibling();\n$3 $4\n$3 $1 = next;\n$3}@gsx ' "$f" perl -0777 -i -pe ' s@for\s*\(\s*auto\s*&\s*(\w+)\s*:\s*([^\n;]+?)\.get_children\s*\(\s*\)\s*\)\s*\n(\s*)([^\n;][^\n]*;)@ $3for (auto* $1 = ($2).get_first_child(); $1 != nullptr; ) {\n$3 auto* next = $1->get_next_sibling();\n$3 $4\n$3 $1 = next;\n$3}@gsx ' "$f" perl -0777 -i -pe ' s@for\s*\(\s*auto\s*\*\s*(\w+)\s*:\s*([^\n;]+?)\.get_children\s*\(\s*\)\s*\)\s*\n(\s*)([^\n;][^\n]*;)@ $3for (auto* $1 = ($2).get_first_child(); $1 != nullptr; ) {\n$3 auto* next = $1->get_next_sibling();\n$3 $4\n$3 $1 = next;\n$3}@gsx ' "$f" perl -0777 -i -pe ' s@for\s*\(\s*auto\s+(\w+)\s*:\s*([^\n;]+?)\-\>get_children\s*\(\s*\)\s*\)\s*\n(\s*)([^\n;][^\n]*;)@ $3for (auto* $1 = ($2)->get_first_child(); $1 != nullptr; ) {\n$3 auto* next = $1->get_next_sibling();\n$3 $4\n$3 $1 = next;\n$3}@gsx ' "$f" perl -0777 -i -pe ' s@for\s*\(\s*auto\s*&\s*(\w+)\s*:\s*([^\n;]+?)\-\>get_children\s*\(\s*\)\s*\)\s*\n(\s*)([^\n;][^\n]*;)@ $3for (auto* $1 = ($2)->get_first_child(); $1 != nullptr; ) {\n$3 auto* next = $1->get_next_sibling();\n$3 $4\n$3 $1 = next;\n$3}@gsx ' "$f" perl -0777 -i -pe ' s@for\s*\(\s*auto\s*\*\s*(\w+)\s*:\s*([^\n;]+?)\-\>get_children\s*\(\s*\)\s*\)\s*\n(\s*)([^\n;][^\n]*;)@ $3for (auto* $1 = ($2)->get_first_child(); $1 != nullptr; ) {\n$3 auto* next = $1->get_next_sibling();\n$3 $4\n$3 $1 = next;\n$3}@gsx ' "$f" done echo "[6/6] Generate patch artifact: $PATCH_OUT" { echo "--- a/src/GtkCompat.hpp" echo "+++ b/src/GtkCompat.hpp" diff -u "$TMPDIR/GtkCompat.hpp.orig" "$COMPAT_HPP" || true echo for f in "${FILES_TO_PATCH[@]}"; do [[ -f "$f" ]] || continue orig="$TMPDIR/$(backup_key "$f").orig" [[ -f "$orig" ]] || continue rel="${f#$ROOT/}" echo "--- a/$rel" echo "+++ b/$rel" diff -u "$orig" "$f" || true echo done } > "$PATCH_OUT" echo "DONE. Residual get_children() occurrences (should be 0):" grep -R --line-number "get_children()" -n "$SRC_DIR" || true echo echo "Patch written to: $PATCH_OUT"