From 7bd159ff10843b88a75b016b6e60ce6a4e1b7e0f Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 11 Aug 2025 12:38:59 -0300 Subject: [PATCH] :boom: fix: can't convert non-null pointer to js value Thanks aylur!! --- src/app.ts | 26 +++++++---- src/runner/Runner.tsx | 8 ++-- src/runner/plugins/media.ts | 2 +- src/scripts/arg-handler.ts | 2 +- src/scripts/media.ts | 65 +++++++++++++++++++++++++++ src/style/_runner.scss | 4 +- src/widget/Notification.tsx | 9 ++-- src/widget/bar/Media.tsx | 33 ++------------ src/widget/bar/Status.tsx | 3 +- src/widget/center-window/BigMedia.tsx | 3 +- src/window/Bar.tsx | 2 + src/window/CenterWindow.tsx | 18 ++++---- src/window/FloatingNotifications.tsx | 6 ++- 13 files changed, 120 insertions(+), 61 deletions(-) create mode 100644 src/scripts/media.ts diff --git a/src/app.ts b/src/app.ts index 4847b58..caea89c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,8 @@ // fix ags needing --gtk 4 // import app from "ags/gtk4/app"; +// fix can't convert non-null pointer to JS value (thanks Aylur!) +import "/usr/share/ags/js/src/overrides"; import { PluginApps, PluginClipboard, @@ -9,7 +11,6 @@ import { PluginWallpapers, PluginWebSearch } from "./runner/plugins"; - import { Wireplumber } from "./scripts/volume"; import { handleArguments } from "./scripts/arg-handler"; import { Runner } from "./runner/Runner"; @@ -24,13 +25,13 @@ import { createRoot, getScope } from "ags"; import { triggerOSD } from "./window/OSD"; import { programArgs, programInvocationName } from "system"; import { encoder, decoder } from "./scripts/utils"; +import { initPlayer } from "./scripts/media"; import GObject, { register } from "ags/gobject"; import AstalNotifd from "gi://AstalNotifd"; import GLib from "gi://GLib?version=2.0"; import Gio from "gi://Gio?version=2.0"; import Adw from "gi://Adw?version=1"; -import GdkPixbuf from "gi://GdkPixbuf?version=2.0"; const runnerPlugins: Array = [ @@ -48,8 +49,8 @@ Gtk.init(); Adw.init(); GLib.unsetenv("LD_PRELOAD"); -@register({ GTypeName: "Shell" }) -export class Shell extends Gtk.Application { +@register({ GTypeName: "Shell", Implements: [Gio.ActionGroup]}) +export class Shell extends Gtk.Application implements Gio.ActionMap { private static instance: Shell; #loop!: GLib.MainLoop; @@ -77,7 +78,6 @@ export class Shell extends Gtk.Application { ).map(path => { if(/^\$/.test(path)) { const env = GLib.getenv(path.replace(/^\$/, "")); - if(env === null) throw new Error(`Couldn't get environment variable: ${path}`); @@ -106,6 +106,12 @@ export class Shell extends Gtk.Application { const e = _e as Error; console.error(`Error: couldn't load gresource! Stderr: ${e.message}\n${e.stack}`); } + + // create action for gapplication to handle commands via dbus + // (faster than running a remote instance to send arguments) + // TODO: implement support for argument parsing through dbus + const msgAction = Gio.SimpleAction.new("msg", null); + this.add_action(msgAction); } public static getDefault(): Shell { @@ -186,11 +192,13 @@ export class Shell extends Gtk.Application { private main(): void { this.#loop = GLib.MainLoop.new(null, false); - this.#connections.set(this, this.connect("shutdown", () => this.#scope.dispose())); - createRoot(() => { - console.log(`Colorshell: initializing`); + createRoot((dispose) => { + console.log(`Colorshell: Initializing things`); + this.#connections.set(this, this.connect("shutdown", () => dispose())); this.#scope = getScope(); + initPlayer(); + Stylesheet.getDefault(); // Init clipboard module @@ -276,4 +284,4 @@ export const generalConfig = new Config): As { + valign={Gtk.Align.START} hexpand orientation={Gtk.Orientation.VERTICAL} + $={(self) => { + self.set_application(Shell.getDefault()); plugins.forEach(plugin => plugin.init?.()); diff --git a/src/runner/plugins/media.ts b/src/runner/plugins/media.ts index 0a25d3f..e120cc3 100644 --- a/src/runner/plugins/media.ts +++ b/src/runner/plugins/media.ts @@ -1,6 +1,6 @@ import { createBinding, createComputed } from "ags"; import { Runner } from "../Runner"; -import { player } from "../../widget/bar/Media"; +import { player } from "../../scripts/media"; import AstalMpris from "gi://AstalMpris"; diff --git a/src/scripts/arg-handler.ts b/src/scripts/arg-handler.ts index a83f413..1a7068f 100644 --- a/src/scripts/arg-handler.ts +++ b/src/scripts/arg-handler.ts @@ -5,7 +5,7 @@ import { timeout } from "ags/time"; import { Runner } from "../runner/Runner"; import { showWorkspaceNumber } from "../widget/bar/Workspaces"; import { playSystemBell } from "./utils"; -import { player, setPlayer } from "../widget/bar/Media"; +import { player, setPlayer } from "./media"; import { generalConfig, Shell } from "../app"; import AstalIO from "gi://AstalIO"; diff --git a/src/scripts/media.ts b/src/scripts/media.ts new file mode 100644 index 0000000..0591495 --- /dev/null +++ b/src/scripts/media.ts @@ -0,0 +1,65 @@ +import { createRoot, createState, onCleanup } from "ags"; + +import GObject from "ags/gobject"; +import AstalMpris from "gi://AstalMpris"; + + +export const dummyPlayer = { + available: false, + busName: "dummy_player", + bus_name: "dummy_player" +} as AstalMpris.Player; + +export let [player, setPlayer] = createState(dummyPlayer); + +let disposeFun: undefined|(() => void); + +export function initPlayer(): void { + if(disposeFun) { + console.error("Media: cannot initialize, there's already an instance"); + return; + } + + createRoot((dispose) => { + const connections = new Map>(); + disposeFun = dispose; + + if(AstalMpris.get_default().players) + setPlayer(AstalMpris.get_default().players[0]); + + connections.set(AstalMpris.get_default(), [ + AstalMpris.get_default().connect("player-added", (_, player) => + player.available && setPlayer(player)), + + AstalMpris.get_default().connect("player-closed", (_, closedPlayer) => { + const players = AstalMpris.get_default().players.filter(pl => pl?.available && + pl.busName !== closedPlayer.busName); + + if(players.length > 0 && players[0]) { + setPlayer(players[0]); + return; + } + + setPlayer(dummyPlayer); + }) + ]); + + onCleanup(() => { + connections.forEach((ids, obj) => + Array.isArray(ids) ? + ids.forEach(id => obj.disconnect(id)) + : obj.disconnect(ids) + ); + disposeFun = undefined; + }); + }); +} + +export function disposePlayer(): void { + if(disposeFun) { + disposeFun(); + return; + } + + console.error("Media: Couldn't dispose player, there's no instance to dispose of"); +} diff --git a/src/style/_runner.scss b/src/style/_runner.scss index 711c0ca..6104bdc 100644 --- a/src/style/_runner.scss +++ b/src/style/_runner.scss @@ -1,6 +1,6 @@ @use "./colors"; -.runner.main { +.popup-window.runner * { $radius: 24px; background: rgba($color: colors.$bg-primary, $alpha: .8); @@ -32,7 +32,7 @@ } & list { - & .result { + & listboxrow > * { padding: 10px; background: colors.$bg-primary; margin: 2px 0; diff --git a/src/widget/Notification.tsx b/src/widget/Notification.tsx index 9f64631..4bc1ba1 100644 --- a/src/widget/Notification.tsx +++ b/src/widget/Notification.tsx @@ -32,10 +32,11 @@ export function NotificationWidget({ notification, actionClicked, holdOnHover, s AstalNotifd.get_default().get_notification(notification) : notification; - const actions: Array|undefined = (notification instanceof AstalNotifd.Notification) ? - notification.actions?.filter(a => - a.id.toLowerCase() !== "view" && a.label.toLowerCase() != "view" - ) + const actions: Array|undefined = ((notification instanceof AstalNotifd.Notification) && + notification.actions && notification.actions.filter(a => Boolean(a)).length > 0) ? + notification.actions?.filter(a => + a?.id?.toLowerCase() !== "view" && a?.label?.toLowerCase() != "view" + ) : undefined; const conns: Map> = new Map(); diff --git a/src/widget/bar/Media.tsx b/src/widget/bar/Media.tsx index 4c1400c..63c1c5f 100644 --- a/src/widget/bar/Media.tsx +++ b/src/widget/bar/Media.tsx @@ -1,57 +1,32 @@ -import { Accessor, createBinding, createConnection, createState, onCleanup, With } from "ags"; +import { Accessor, createBinding, createConnection, onCleanup, With } from "ags"; import { Gtk } from "ags/gtk4"; import { Separator } from "../Separator"; import { Windows } from "../../windows"; import { Clipboard } from "../../scripts/clipboard"; import { decoder, getPlayerIconFromBusName, variableToBoolean } from "../../scripts/utils"; +import { player, setPlayer } from "../../scripts/media"; import GObject from "ags/gobject"; import AstalMpris from "gi://AstalMpris"; import Pango from "gi://Pango?version=1.0"; -export const dummyPlayer = { - available: false, - busName: "dummy_player", - bus_name: "dummy_player" -} as AstalMpris.Player; -export let [player, setPlayer] = createState(dummyPlayer); - export const Media = () => { const connections: Map|number> = new Map(); - if(AstalMpris.get_default().players[0]) - setPlayer(AstalMpris.get_default().players[0]); - onCleanup(() => connections.forEach((id, obj) => Array.isArray(id) ? id.forEach(id => obj.disconnect(id)) : obj.disconnect(id) )); - connections.set(AstalMpris.get_default(), [ - AstalMpris.get_default().connect("player-added", (_, player) => - player.available && setPlayer(player)), - - AstalMpris.get_default().connect("player-closed", (_, closedPlayer) => { - const players = AstalMpris.get_default().players.filter(pl => pl?.available && - pl.busName !== closedPlayer.busName); - - if(players.length > 0) { - setPlayer(players[0]); - return; - } - - setPlayer(dummyPlayer); - }) - ]); - return pl.available)} $={(self) => { const gestureClick = Gtk.GestureClick.new(), controllerMotion = Gtk.EventControllerMotion.new(), controllerScroll = Gtk.EventControllerScroll.new( - Gtk.EventControllerScrollFlags.VERTICAL); + Gtk.EventControllerScrollFlags.VERTICAL + ); self.add_controller(gestureClick); self.add_controller(controllerMotion); diff --git a/src/widget/bar/Status.tsx b/src/widget/bar/Status.tsx index a33d505..90ca339 100644 --- a/src/widget/bar/Status.tsx +++ b/src/widget/bar/Status.tsx @@ -10,6 +10,7 @@ import GObject from "ags/gobject"; import AstalBluetooth from "gi://AstalBluetooth"; import AstalNetwork from "gi://AstalNetwork"; import AstalWp from "gi://AstalWp"; +import { Shell } from "../../app"; export const Status = () => @@ -133,7 +134,7 @@ function StatusIcons() { : "preferences-system-notifications-symbolic") } /> - diff --git a/src/widget/center-window/BigMedia.tsx b/src/widget/center-window/BigMedia.tsx index 8e77d16..b068d21 100644 --- a/src/widget/center-window/BigMedia.tsx +++ b/src/widget/center-window/BigMedia.tsx @@ -1,7 +1,8 @@ import { timeout } from "ags/time"; import { Astal, Gtk } from "ags/gtk4"; import { Clipboard } from "../../scripts/clipboard"; -import { getMediaUrl, player, setPlayer } from "../bar/Media"; +import { getMediaUrl } from "../bar/Media"; +import { player, setPlayer } from "../../scripts/media"; import { createBinding, For } from "ags"; import { pathToURI, variableToBoolean } from "../../scripts/utils"; diff --git a/src/window/Bar.tsx b/src/window/Bar.tsx index 40925e1..42e1825 100644 --- a/src/window/Bar.tsx +++ b/src/window/Bar.tsx @@ -5,6 +5,7 @@ import { FocusedClient } from "../widget/bar/FocusedClient"; import { Apps } from "../widget/bar/Apps"; import { Clock } from "../widget/bar/Clock"; import { Status } from "../widget/bar/Status"; +import { Media } from "../widget/bar/Media"; export const Bar = (mon: number) => { @@ -29,6 +30,7 @@ export const Bar = (mon: number) => { $type="center"> + + halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} vexpand> t.format("%H:%M")!)} /> d.format("%A, %B %d")!)} /> @@ -27,8 +27,10 @@ export const CenterWindow = (mon: number) => pl.available)} + margin={5} spacing={8} alpha={.3} visible={variableToBoolean( + createBinding(AstalMpris.get_default(), "players") + )} /> - as Astal.Window; + ; diff --git a/src/window/FloatingNotifications.tsx b/src/window/FloatingNotifications.tsx index faa0650..cb1d339 100644 --- a/src/window/FloatingNotifications.tsx +++ b/src/window/FloatingNotifications.tsx @@ -28,8 +28,10 @@ export const FloatingNotifications = (mon: number) => Notifications.getDefault().removeNotification(notif)} holdOnHover={false} actionClicked={() => { - const viewAction = notif.actions.filter(action => - action.label.toLowerCase() === "view")?.[0]; + const viewAction = notif.actions.filter(a => + a.id.toLowerCase() === "view" || + a.label.toLowerCase() === "view" + )?.[0]; viewAction && notif.invoke(viewAction.id); }}