From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= <mail@3v1n0.net>
Date: Thu, 11 Sep 2025 23:51:56 +0200
Subject: overrides/Gio: Add wrappers for platform-specific Gio functions

GLib will not expose anymore platform specific functions in Gio
namespace as it used to do when GI Repository 1.0 was used, in order to
keep retro-compatibility in gjs applications, generate wrappers for
Gio platform-specific definitions that we used to provide inside the
main Gio object, but warn the users of these APIs that they should
migrate to GioUnix or GioWin32 instead.
---
 installed-tests/js/testGio.js | 88 +++++++++++++++++++++++++++++++++++++++++++
 modules/core/overrides/Gio.js | 35 +++++++++++++++++
 2 files changed, 123 insertions(+)

diff --git a/installed-tests/js/testGio.js b/installed-tests/js/testGio.js
index 908f906..e74059a 100644
--- a/installed-tests/js/testGio.js
+++ b/installed-tests/js/testGio.js
@@ -4,6 +4,11 @@
 
 const {GLib, Gio, GObject} = imports.gi;
 
+let GioUnix;
+try {
+    GioUnix = imports.gi.GioUnix;
+} catch {}
+
 const Foo = GObject.registerClass({
     Properties: {
         boolval: GObject.ParamSpec.boolean('boolval', '', '',
@@ -390,6 +395,89 @@ describe('Gio.FileEnumerator overrides', function () {
     });
 });
 
+describe('Gio.DesktopAppInfo fallback', function () {
+    let writerFunc;
+    const requiredVersion =
+        GLib.MAJOR_VERSION > 2 ||
+            (GLib.MAJOR_VERSION === 2 && GLib.MINOR_VERSION >= 86);
+    let keyFile;
+    const desktopFileContent = `[Desktop Entry]
+Version=1.0
+Type=Application
+Name=Some Application
+Exec=${GLib.find_program_in_path('sh')}
+`;
+    beforeAll(function () {
+        // Set up log writer for tests to override
+        writerFunc = jasmine.createSpy('parsed writer func');
+        const writerFuncWrapper = jasmine.createSpy('log writer func', (level, fields) => {
+            const decoder = new TextDecoder('utf-8');
+            const domain = decoder.decode(fields?.GLIB_DOMAIN);
+            const message = `${decoder.decode(fields?.MESSAGE)}`;
+            if (level < GLib.LogLevelFlags.LEVEL_WARNING) {
+                level |= GLib.LogLevelFlags.FLAG_RECURSION;
+                GLib.log_default_handler(domain, level, `${message}\n`, null);
+            }
+            writerFunc(domain, level, message);
+            return GLib.LogWriterOutput.HANDLED;
+        });
+        writerFuncWrapper.and.callThrough();
+        GLib.log_set_writer_func(writerFuncWrapper);
+
+        keyFile = new GLib.KeyFile();
+        keyFile.load_from_data(desktopFileContent, desktopFileContent.length,
+            GLib.KeyFileFlags.NONE);
+    });
+
+    afterAll(function () {
+        GLib.log_set_writer_default();
+    });
+
+    beforeEach(function () {
+        if (!GioUnix)
+            pending('Not supported platform');
+
+        writerFunc.calls.reset();
+    });
+
+    it('can be created using GioUnix', function () {
+        expect(GioUnix.DesktopAppInfo.new_from_keyfile(keyFile)).not.toBeNull();
+        expect(writerFunc).not.toHaveBeenCalled();
+    });
+
+    it('can be created using Gio wrapper', function () {
+        expect(Gio.DesktopAppInfo.new_from_keyfile(keyFile)).not.toBeNull();
+        expect(writerFunc).toHaveBeenCalledWith('Cjs-Console',
+            GLib.LogLevelFlags.LEVEL_WARNING,
+            'Gio.DesktopAppInfo is deprecated, please use GioUnix.DesktopAppInfo instead');
+
+        writerFunc.calls.reset();
+        expect(Gio.DesktopAppInfo.new_from_keyfile(keyFile)).not.toBeNull();
+        expect(writerFunc).not.toHaveBeenCalled();
+    });
+
+    describe('provides platform-independent functions', function () {
+        [Gio, GioUnix].forEach(ns => it(`when created from ${ns.__name__}`, function () {
+            if (!requiredVersion)
+                pending('Installed Gio is not new enough for this test');
+
+            const appInfo = ns.DesktopAppInfo.new_from_keyfile(keyFile);
+            expect(appInfo.get_name()).toBe('Some Application');
+        }));
+    });
+
+    describe('provides unix-only functions', function () {
+        [Gio, GioUnix].forEach(ns => it(`when created from ${ns.__name__}`, function () {
+            if (!requiredVersion)
+                pending('Installed Gio is not new enough for this test');
+
+            const appInfo = ns.DesktopAppInfo.new_from_keyfile(keyFile);
+            expect(appInfo.has_key('Name')).toBeTrue();
+            expect(appInfo.get_string('Name')).toBe('Some Application');
+        }));
+    });
+});
+
 describe('Non-introspectable file attribute overrides', function () {
     let numExpectedWarnings, file, info;
     const flags = [Gio.FileQueryInfoFlags.NONE, null];
diff --git a/modules/core/overrides/Gio.js b/modules/core/overrides/Gio.js
index 836d14a..6bcc20f 100644
--- a/modules/core/overrides/Gio.js
+++ b/modules/core/overrides/Gio.js
@@ -479,9 +479,44 @@ function _warnNotIntrospectable(funcName, replacement) {
 
 function _init() {
     Gio = this;
+    let GioPlatform = {};
 
     Gio.Application.prototype.runAsync = GLib.MainLoop.prototype.runAsync;
 
+    // Redefine Gio functions with platform-specific implementations to be
+    // backward compatible with gi-repository 1.0, however when possible we
+    // notify a deprecation warning, to ensure that the surrounding code is
+    // updated.
+    try {
+        GioPlatform = imports.gi.GioUnix;
+    } catch {
+        try {
+            GioPlatform = imports.gi.GioWin32;
+        } catch {}
+    }
+
+    Object.entries(Object.getOwnPropertyDescriptors(GioPlatform)).forEach(([prop, desc]) => {
+        if (Object.hasOwn(Gio, prop)) {
+            console.debug(`Gio already contains property ${prop}`);
+            Gio[prop] = GioPlatform[prop];
+            return;
+        }
+
+        const newDesc = {
+            enumerable: true,
+            configurable: false,
+            get() {
+                if (!newDesc._deprecationWarningDone) {
+                    console.warn(`Gio.${prop} is deprecated, please use ` +
+                        `${GioPlatform.__name__}.${prop} instead`);
+                    newDesc._deprecationWarningDone = true;
+                }
+                return desc.get?.() ?? desc.value;
+            },
+        };
+        Object.defineProperty(Gio, prop, newDesc);
+    });
+
     Gio.DBus = {
         // Namespace some functions
         get: Gio.bus_get,
