Skip to content

Conversation

@sigmaSd
Copy link
Contributor

@sigmaSd sigmaSd commented Oct 15, 2025

Since we cant determine when to clean the callbacks, and exposing this to the user will give an uggly api, the best thing is to rely on FinalizationRegistry to clean up the resource when the GC clears the callback,

It seems to work as you can see in the test and Ithink this really improves the api

Since we cant determine when to clean the callbacks, and exposing this
to the user will give an uggly api, the best thing is to rely on
FinalizationRegistry to clean up the resource when the GC clears the
callback,

It seems to work as you can see in the test and Ithink this really
improves the api
@sigmaSd
Copy link
Contributor Author

sigmaSd commented Oct 18, 2025

I rebased on top of main, I also added a test to be sure of the lifetime of these auto created callbacks

This seems useful, it makes for a better api, and its better in that you don't have to remember to free resources, its all automatically done now

here is a diff from a project using this

diff --git a/src/indicator/indicator_api.ts b/src/indicator/indicator_api.ts
index e392a17..f5b057d 100644
--- a/src/indicator/indicator_api.ts
+++ b/src/indicator/indicator_api.ts
@@ -1,4 +1,4 @@
-import { type Gio2_, python } from "deno-gtk-py";
+import { type Gio2_ } from "deno-gtk-py";
 import { Gio, GLib, type MainWindow } from "../main.ts";
 import { MESSAGES } from "./messages.ts";
 
@@ -53,12 +53,12 @@ export class Indicator {
   }
 
   #monitorStdout(stdoutPipe: Gio2_.InputStream) {
-    const readCallback = python.callback(() => {
+    const readCallback = () => {
       stdoutPipe.read_bytes_async(
         512, /*buffer size*/
         GLib.PRIORITY_DEFAULT,
         undefined,
-        python.callback((_, __, asyncResult) => {
+        (_, __, asyncResult) => {
           const message = stdoutPipe
             .read_bytes_finish(asyncResult)
             .get_data().decode("utf-8")
@@ -81,10 +81,10 @@ export class Indicator {
           }
 
           GLib.idle_add(readCallback);
-        }),
+        },
       );
       return false;
-    });
+    };
     GLib.idle_add(readCallback);
   }
 }
diff --git a/src/indicator/indicator_app.ts b/src/indicator/indicator_app.ts
index 985e84c..01aac3c 100644
--- a/src/indicator/indicator_app.ts
+++ b/src/indicator/indicator_app.ts
@@ -40,24 +40,24 @@ if (import.meta.main) {
   );
   showApp.connect(
     "activate",
-    python.callback(() => {
+    () => {
       sendMsg(MESSAGES.Show);
       menu.remove(menu.get_children()[0]);
-    }),
+    },
   );
   closeApp.connect(
     "activate",
-    python.callback(() => {
+    () => {
       sendMsg(MESSAGES.Close);
       Gtk.main_quit();
-    }),
+    },
   );
 
   let first_try = true;
   GLib.io_add_watch(
     0, /*stdin*/
     GLib.IO_IN,
-    python.callback(() => {
+    () => {
       const buf = new Uint8Array(512);
       const n = Deno.stdin.readSync(buf);
       if (!n) throw new Error("recieved an empty message");
@@ -103,14 +103,14 @@ if (import.meta.main) {
       }
 
       return true;
-    }),
+    },
   );
 
   menu.append(closeApp);
   menu.show_all();
   indicator.set_menu(menu);
 
-  signal.signal(signal.SIGINT, python.callback(() => Gtk.main_quit()));
+  signal.signal(signal.SIGINT, () => Gtk.main_quit());
 
   Gtk.main();
 }
diff --git a/src/main.ts b/src/main.ts
index e32c02c..37f2478 100755
--- a/src/main.ts
+++ b/src/main.ts
@@ -125,16 +125,14 @@ export class MainWindow {
     builder.add_from_string(stimulatorUi);
     this.#win = builder.get_object("mainWindow");
     this.#win.set_title(APP_NAME);
-    this.#win.connect("close-request", python.callback(this.#onCloseRequest));
+    this.#win.connect("close-request", this.#onCloseRequest);
     this.#mainIcon = builder.get_object("mainIcon");
     this.#suspendRow = builder.get_object("suspendRow");
     this.#suspendRow.set_title(UI_LABELS["Disable Automatic Suspending"]);
     this.#suspendRow.set_subtitle(UI_LABELS["Current state: System default"]);
     this.#suspendRow.connect(
       "notify::active",
-      python.callback(() =>
-        this.#toggleSuspend(this.#suspendRow.get_active().valueOf())
-      ),
+      () => this.#toggleSuspend(this.#suspendRow.get_active().valueOf()),
     );
     this.#idleRow = builder.get_object("idleRow");
     this.#idleRow.set_title(UI_LABELS["Disable Screen Blanking and Locking"]);
@@ -142,9 +140,7 @@ export class MainWindow {
     this.#idleRow.connect(
       "notify::active",
       // NOTE: works but for some reason it issues a warning the first time its called about invalid flags
-      python.callback(() =>
-        this.#toggleIdle(this.#idleRow.get_active().valueOf())
-      ),
+      () => this.#toggleIdle(this.#idleRow.get_active().valueOf()),
     );
 
     this.#preferencesMenu = new PreferencesMenu(this);
@@ -168,7 +164,7 @@ export class MainWindow {
 
     this.#createAction(
       "preferences",
-      python.callback(this.#showPreferences),
+      this.#showPreferences,
       ["<primary>comma"],
     );
     menu.append(UI_LABELS.Preferences, "app.preferences");
@@ -178,16 +174,16 @@ export class MainWindow {
     menu.append(UI_LABELS["About Stimulator"], "app.about");
     this.#createAction(
       "quit",
-      python.callback(() => {
+      () => {
         if (!this.#onCloseRequest()) this.#app.quit();
-      }),
+      },
       ["<primary>q"],
     );
     this.#createAction(
       "close",
-      python.callback(() => {
+      () => {
         if (!this.#onCloseRequest()) this.#app.quit();
-      }),
+      },
       ["<primary>w"],
     );
 
@@ -290,19 +286,23 @@ export class MainWindow {
     );
     dialog.connect(
       "response",
-      python.callback((_, __, id) => {
+      (_, __, id) => {
         if (id === "close") {
           this.#indicator?.close();
           this.#app.quit();
         }
-      }),
+      },
     );
 
     dialog.set_visible(true);
     return true;
   };
 
-  #createAction = (name: string, callback: Callback, shortcuts?: [string]) => {
+  #createAction = (
+    name: string,
+    callback: Callback | (() => void),
+    shortcuts?: [string],
+  ) => {
     const action = Gio.SimpleAction.new(name);
     action.connect("activate", callback);
     this.#app.add_action(action);
@@ -331,7 +331,7 @@ export class MainWindow {
         // Update every minute
         this.#suspendTimerId = GLib.timeout_add_seconds(
           60,
-          python.callback(() => {
+          () => {
             if (this.#suspendRemainingMinutes !== undefined) {
               this.#suspendRemainingMinutes--;
               if (this.#suspendRemainingMinutes <= 0) {
@@ -353,7 +353,7 @@ export class MainWindow {
               this.#updateSuspendSubtitle();
             }
             return true;
-          }),
+          },
         ).valueOf();
       } else {
         this.#suspendRow.set_subtitle(UI_LABELS["Current state: Indefinitely"]);
@@ -460,7 +460,7 @@ export class MainWindow {
       // Update every minute
       this.#suspendTimerId = GLib.timeout_add_seconds(
         60,
-        python.callback(() => {
+        () => {
           if (this.#suspendRemainingMinutes !== undefined) {
             this.#suspendRemainingMinutes--;
             if (this.#suspendRemainingMinutes <= 0) {
@@ -482,7 +482,7 @@ export class MainWindow {
             this.#updateSuspendSubtitle();
           }
           return true;
-        }),
+        },
       ).valueOf();
     } else {
       this.#suspendRow.set_subtitle(UI_LABELS["Current state: Indefinitely"]);
@@ -507,7 +507,7 @@ export class MainWindow {
         // Update every minute
         this.#idleTimerId = GLib.timeout_add_seconds(
           60,
-          python.callback(() => {
+          () => {
             if (this.#idleRemainingMinutes !== undefined) {
               this.#idleRemainingMinutes--;
               if (this.#idleRemainingMinutes <= 0) {
@@ -517,7 +517,7 @@ export class MainWindow {
               this.#updateIdleSubtitle();
             }
             return true;
-          }),
+          },
         ).valueOf();
       } else {
         this.#idleRow.set_subtitle(UI_LABELS["Current state: Indefinitely"]);
@@ -609,7 +609,7 @@ export class MainWindow {
       // Update every minute
       this.#idleTimerId = GLib.timeout_add_seconds(
         60,
-        python.callback(() => {
+        () => {
           if (this.#idleRemainingMinutes !== undefined) {
             this.#idleRemainingMinutes--;
             if (this.#idleRemainingMinutes <= 0) {
@@ -619,14 +619,14 @@ export class MainWindow {
             this.#updateIdleSubtitle();
           }
           return true;
-        }),
+        },
       ).valueOf();
     } else {
       this.#idleRow.set_subtitle(UI_LABELS["Current state: Indefinitely"]);
     }
   };
 
-  #showAbout = python.callback(() => {
+  #showAbout = () => {
     const dialog = Adw.AboutWindow(
       new NamedArgument("transient_for", this.#app.get_active_window()),
     );
@@ -644,9 +644,9 @@ export class MainWindow {
     dialog.set_application_icon(APP_ID);
 
     dialog.set_visible(true);
-  });
+  };
 
-  #showShortcuts = python.callback(() => {
+  #showShortcuts = () => {
     const builder = Gtk.Builder();
     builder.add_from_file(
       new URL(import.meta.resolve("./ui/shortcuts.ui")).pathname,
@@ -678,7 +678,7 @@ export class MainWindow {
     quitShortcut.props.title = UI_LABELS.Quit;
 
     shortcutsWin.present();
-  });
+  };
 
   #platformUnsupportedExit() {
     const dialog = Adw.MessageDialog(
@@ -701,11 +701,11 @@ export class MainWindow {
     );
     dialog.connect(
       "response",
-      python.callback((_, __, id) => {
+      (_, __, id) => {
         // make sure to turn off the buttons
         this.updateState({ suspend: false, idle: false });
         if (id === "close") this.#app.quit();
-      }),
+      },
     );
 
     dialog.set_visible(true);
@@ -719,7 +719,8 @@ class App extends Adw.Application {
     super(kwArg);
     this.connect("activate", this.#onActivate);
   }
-  #onActivate = python.callback((_kwarg, app: Adw_.Application) => {
+  // deno-lint-ignore no-explicit-any
+  #onActivate = (_kwarg: any, app: Adw_.Application) => {
     // NOTE: there could be already an active window
     // if the app is restored after being hidden on exit
     if (!this.#win) this.#win = new MainWindow(app);
@@ -728,7 +729,7 @@ class App extends Adw.Application {
     this.#win.indicator?.hideShowButton();
     // withdraw any active notification
     this.withdraw_notification(APP_ID);
-  });
+  };
 }
 
 if (import.meta.main) {
@@ -744,9 +745,9 @@ if (import.meta.main) {
   GLib.unix_signal_add(
     GLib.PRIORITY_HIGH,
     signal.SIGINT,
-    python.callback(() => {
+    () => {
       app.quit();
-    }),
+    },
   );
   app.run(Deno.args);
 }
diff --git a/src/pref-win.ts b/src/pref-win.ts
index e7f86bc..e619193 100644
--- a/src/pref-win.ts
+++ b/src/pref-win.ts
@@ -1,4 +1,4 @@
-import { type Adw1_ as Adw_, type Gtk4_ as Gtk_, python } from "deno-gtk-py";
+import { type Adw1_ as Adw_, type Gtk4_ as Gtk_ } from "deno-gtk-py";
 import { UI_LABELS } from "./consts.ts";
 import { Indicator } from "./indicator/indicator_api.ts";
 import { Adw, GLib, Gtk, type MainWindow, type TimerDuration } from "./main.ts";
@@ -38,7 +38,7 @@ export class PreferencesMenu {
     );
     themeRow.connect(
       "notify::selected",
-      python.callback(() => {
+      () => {
         const theme = themeItems[themeRow.get_selected().valueOf()];
         //deno-fmt-ignore
         Adw.StyleManager.get_default().set_color_scheme(
@@ -47,7 +47,7 @@ export class PreferencesMenu {
           : Adw.ColorScheme.FORCE_DARK,
         );
         mainWindow.updateState({ themeV2: theme });
-      }),
+      },
     );
 
     const behaviorOnExitRow = builder.get_object(
@@ -74,7 +74,7 @@ export class PreferencesMenu {
 
     behaviorOnExitRow.connect(
       "notify::selected",
-      python.callback(() => {
+      () => {
         const behavior = behaviorOnExitItems[
           behaviorOnExitRow
             .get_selected().valueOf()
@@ -93,12 +93,12 @@ export class PreferencesMenu {
           // NOTE: run this after a bit of time, so messages don't get mixed up in the write buffer
           GLib.timeout_add(
             500,
-            python.callback(() => mainWindow.indicator?.hide()),
+            () => mainWindow.indicator?.hide(),
           );
         }
 
         mainWindow.updateState({ exitBehaviorV2: behavior });
-      }),
+      },
     );
 
     const suspendTimer = builder.get_object<Adw_.ComboRow>("suspendTimer");
@@ -131,10 +131,10 @@ export class PreferencesMenu {
     );
     suspendTimer.connect(
       "notify::selected",
-      python.callback(() => {
+      () => {
         const duration = timerOptions[suspendTimer.get_selected().valueOf()];
         mainWindow.updateState({ suspendTimer: duration });
-      }),
+      },
     );
 
     const idleTimer = builder.get_object<Adw_.ComboRow>("idleTimer");
@@ -148,10 +148,10 @@ export class PreferencesMenu {
     );
     idleTimer.connect(
       "notify::selected",
-      python.callback(() => {
+      () => {
         const duration = timerOptions[idleTimer.get_selected().valueOf()];
         mainWindow.updateState({ idleTimer: duration });
-      }),
+      },
     );
   }

Copy link
Member

@eliassjogreen eliassjogreen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me :)

@eliassjogreen eliassjogreen merged commit d5b37fc into denosaurs:main Oct 19, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants