-
-
Notifications
You must be signed in to change notification settings - Fork 23
Allow js functions as python callbacks #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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
|
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 });
- }),
+ },
);
} |
eliassjogreen
left a comment
There was a problem hiding this 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 :)
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