💥 fix: can't convert non-null pointer to js value

Thanks aylur!!
This commit is contained in:
retrozinndev
2025-08-11 12:38:59 -03:00
parent e82eebca91
commit 7bd159ff10
13 changed files with 120 additions and 61 deletions
+17 -9
View File
@@ -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<Runner.Plugin> = [
@@ -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<keyof typeof generalConfigDefaults,
`${GLib.get_user_config_dir()}/colorshell/config.json`, generalConfigDefaults
);
Shell.getDefault().runAsync([ programInvocationName, ...programArgs ]);
Shell.getDefault().run([ programInvocationName, ...programArgs ]);
+4 -2
View File
@@ -7,6 +7,7 @@ import { timeout } from "ags/time";
import AstalHyprland from "gi://AstalHyprland";
import AstalIO from "gi://AstalIO";
import { Shell } from "../app";
export namespace Runner {
@@ -21,7 +22,7 @@ export type RunnerProps = {
showResultsPlaceHolderOnStartup?: boolean;
};
type Result = ResultWidgetProps;
export type Result = ResultWidgetProps;
export interface Plugin {
/** prefix to call the plugin. if undefined, will be triggered like applications plugin */
@@ -244,7 +245,8 @@ export function openRunner(props: RunnerProps, placeholders?: Array<Result>): As
heightRequest={props.height} exclusivity={Astal.Exclusivity.IGNORE} halign={Gtk.Align.CENTER}
marginTop={(AstalHyprland.get_default().get_monitor(mon)?.height / 2) - (props.height! / 2)}
valign={Gtk.Align.START} hexpand orientation={Gtk.Orientation.VERTICAL}
$={() => {
$={(self) => {
self.set_application(Shell.getDefault());
plugins.forEach(plugin =>
plugin.init?.());
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+65
View File
@@ -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<GObject.Object, Array<number>>();
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");
}
+2 -2
View File
@@ -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;
+5 -4
View File
@@ -32,10 +32,11 @@ export function NotificationWidget({ notification, actionClicked, holdOnHover, s
AstalNotifd.get_default().get_notification(notification)
: notification;
const actions: Array<AstalNotifd.Action>|undefined = (notification instanceof AstalNotifd.Notification) ?
notification.actions?.filter(a =>
a.id.toLowerCase() !== "view" && a.label.toLowerCase() != "view"
)
const actions: Array<AstalNotifd.Action>|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<GObject.Object, Array<number>> = new Map();
+4 -29
View File
@@ -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<GObject.Object, Array<number>|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 <Gtk.Box class={"media"} visible={player((pl) => 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);
+2 -1
View File
@@ -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")
}
/>
<Gtk.Image iconName={"circle-filled-symbolic"} class={"notification-count"}
<Gtk.Image gicon={Shell.getDefault().getGIcon("circle-filled-symbolic")} class={"notification-count"}
visible={variableToBoolean(createBinding(Notifications.getDefault(), "history"))}
/>
</Gtk.Box>
+2 -1
View File
@@ -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";
+2
View File
@@ -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">
<Clock />
<Media />
</Gtk.Box>
<Gtk.Box class={"widgets-right"} homogeneous={false}
spacing={widgetSpacing} halign={Gtk.Align.END}
+10 -8
View File
@@ -1,10 +1,11 @@
import { Astal, Gtk } from "ags/gtk4";
import { Gtk } from "ags/gtk4";
import { Separator } from "../widget/Separator";
import { PopupWindow } from "../widget/PopupWindow";
import { BigMedia } from "../widget/center-window/BigMedia";
import { time } from "../scripts/utils";
import { player } from "../widget/bar/Media";
import { time, variableToBoolean } from "../scripts/utils";
import { createBinding } from "ags";
import AstalMpris from "gi://AstalMpris?version=0.1";
export const CenterWindow = (mon: number) =>
<PopupWindow namespace={"center-window"} marginTop={10} monitor={mon}
@@ -13,8 +14,7 @@ export const CenterWindow = (mon: number) =>
<Gtk.Box class={"center-window-container"} spacing={6}>
<Gtk.Box class={"left"} orientation={Gtk.Orientation.VERTICAL}>
<Gtk.Box class={"datetime"} orientation={Gtk.Orientation.VERTICAL}
halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}
vexpand={true}>
halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} vexpand>
<Gtk.Label class={"time"} label={time(t => t.format("%H:%M")!)} />
<Gtk.Label class={"date"} label={time(d => d.format("%A, %B %d")!)} />
@@ -27,8 +27,10 @@ export const CenterWindow = (mon: number) =>
</Gtk.Box>
<Separator orientation={Gtk.Orientation.HORIZONTAL} cssColor="gray"
margin={5} spacing={8} alpha={.3} visible={player(pl => pl.available)}
margin={5} spacing={8} alpha={.3} visible={variableToBoolean(
createBinding(AstalMpris.get_default(), "players")
)}
/>
<BigMedia />
</Gtk.Box>
</PopupWindow> as Astal.Window;
</PopupWindow>;
+4 -2
View File
@@ -28,8 +28,10 @@ export const FloatingNotifications = (mon: number) =>
<NotificationWidget notification={notif} showTime={false}
actionClose={() => 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);
}}