💥 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 // fix ags needing --gtk 4
// import app from "ags/gtk4/app"; // 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 { import {
PluginApps, PluginApps,
PluginClipboard, PluginClipboard,
@@ -9,7 +11,6 @@ import {
PluginWallpapers, PluginWallpapers,
PluginWebSearch PluginWebSearch
} from "./runner/plugins"; } from "./runner/plugins";
import { Wireplumber } from "./scripts/volume"; import { Wireplumber } from "./scripts/volume";
import { handleArguments } from "./scripts/arg-handler"; import { handleArguments } from "./scripts/arg-handler";
import { Runner } from "./runner/Runner"; import { Runner } from "./runner/Runner";
@@ -24,13 +25,13 @@ import { createRoot, getScope } from "ags";
import { triggerOSD } from "./window/OSD"; import { triggerOSD } from "./window/OSD";
import { programArgs, programInvocationName } from "system"; import { programArgs, programInvocationName } from "system";
import { encoder, decoder } from "./scripts/utils"; import { encoder, decoder } from "./scripts/utils";
import { initPlayer } from "./scripts/media";
import GObject, { register } from "ags/gobject"; import GObject, { register } from "ags/gobject";
import AstalNotifd from "gi://AstalNotifd"; import AstalNotifd from "gi://AstalNotifd";
import GLib from "gi://GLib?version=2.0"; import GLib from "gi://GLib?version=2.0";
import Gio from "gi://Gio?version=2.0"; import Gio from "gi://Gio?version=2.0";
import Adw from "gi://Adw?version=1"; import Adw from "gi://Adw?version=1";
import GdkPixbuf from "gi://GdkPixbuf?version=2.0";
const runnerPlugins: Array<Runner.Plugin> = [ const runnerPlugins: Array<Runner.Plugin> = [
@@ -48,8 +49,8 @@ Gtk.init();
Adw.init(); Adw.init();
GLib.unsetenv("LD_PRELOAD"); GLib.unsetenv("LD_PRELOAD");
@register({ GTypeName: "Shell" }) @register({ GTypeName: "Shell", Implements: [Gio.ActionGroup]})
export class Shell extends Gtk.Application { export class Shell extends Gtk.Application implements Gio.ActionMap {
private static instance: Shell; private static instance: Shell;
#loop!: GLib.MainLoop; #loop!: GLib.MainLoop;
@@ -77,7 +78,6 @@ export class Shell extends Gtk.Application {
).map(path => { ).map(path => {
if(/^\$/.test(path)) { if(/^\$/.test(path)) {
const env = GLib.getenv(path.replace(/^\$/, "")); const env = GLib.getenv(path.replace(/^\$/, ""));
if(env === null) if(env === null)
throw new Error(`Couldn't get environment variable: ${path}`); throw new Error(`Couldn't get environment variable: ${path}`);
@@ -106,6 +106,12 @@ export class Shell extends Gtk.Application {
const e = _e as Error; const e = _e as Error;
console.error(`Error: couldn't load gresource! Stderr: ${e.message}\n${e.stack}`); 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 { public static getDefault(): Shell {
@@ -186,11 +192,13 @@ export class Shell extends Gtk.Application {
private main(): void { private main(): void {
this.#loop = GLib.MainLoop.new(null, false); this.#loop = GLib.MainLoop.new(null, false);
this.#connections.set(this, this.connect("shutdown", () => this.#scope.dispose())); createRoot((dispose) => {
createRoot(() => { console.log(`Colorshell: Initializing things`);
console.log(`Colorshell: initializing`); this.#connections.set(this, this.connect("shutdown", () => dispose()));
this.#scope = getScope(); this.#scope = getScope();
initPlayer();
Stylesheet.getDefault(); Stylesheet.getDefault();
// Init clipboard module // Init clipboard module
@@ -276,4 +284,4 @@ export const generalConfig = new Config<keyof typeof generalConfigDefaults,
`${GLib.get_user_config_dir()}/colorshell/config.json`, 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 AstalHyprland from "gi://AstalHyprland";
import AstalIO from "gi://AstalIO"; import AstalIO from "gi://AstalIO";
import { Shell } from "../app";
export namespace Runner { export namespace Runner {
@@ -21,7 +22,7 @@ export type RunnerProps = {
showResultsPlaceHolderOnStartup?: boolean; showResultsPlaceHolderOnStartup?: boolean;
}; };
type Result = ResultWidgetProps; export type Result = ResultWidgetProps;
export interface Plugin { export interface Plugin {
/** prefix to call the plugin. if undefined, will be triggered like applications 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} heightRequest={props.height} exclusivity={Astal.Exclusivity.IGNORE} halign={Gtk.Align.CENTER}
marginTop={(AstalHyprland.get_default().get_monitor(mon)?.height / 2) - (props.height! / 2)} marginTop={(AstalHyprland.get_default().get_monitor(mon)?.height / 2) - (props.height! / 2)}
valign={Gtk.Align.START} hexpand orientation={Gtk.Orientation.VERTICAL} valign={Gtk.Align.START} hexpand orientation={Gtk.Orientation.VERTICAL}
$={() => { $={(self) => {
self.set_application(Shell.getDefault());
plugins.forEach(plugin => plugins.forEach(plugin =>
plugin.init?.()); plugin.init?.());
+1 -1
View File
@@ -1,6 +1,6 @@
import { createBinding, createComputed } from "ags"; import { createBinding, createComputed } from "ags";
import { Runner } from "../Runner"; import { Runner } from "../Runner";
import { player } from "../../widget/bar/Media"; import { player } from "../../scripts/media";
import AstalMpris from "gi://AstalMpris"; import AstalMpris from "gi://AstalMpris";
+1 -1
View File
@@ -5,7 +5,7 @@ import { timeout } from "ags/time";
import { Runner } from "../runner/Runner"; import { Runner } from "../runner/Runner";
import { showWorkspaceNumber } from "../widget/bar/Workspaces"; import { showWorkspaceNumber } from "../widget/bar/Workspaces";
import { playSystemBell } from "./utils"; import { playSystemBell } from "./utils";
import { player, setPlayer } from "../widget/bar/Media"; import { player, setPlayer } from "./media";
import { generalConfig, Shell } from "../app"; import { generalConfig, Shell } from "../app";
import AstalIO from "gi://AstalIO"; 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"; @use "./colors";
.runner.main { .popup-window.runner * {
$radius: 24px; $radius: 24px;
background: rgba($color: colors.$bg-primary, $alpha: .8); background: rgba($color: colors.$bg-primary, $alpha: .8);
@@ -32,7 +32,7 @@
} }
& list { & list {
& .result { & listboxrow > * {
padding: 10px; padding: 10px;
background: colors.$bg-primary; background: colors.$bg-primary;
margin: 2px 0; margin: 2px 0;
+3 -2
View File
@@ -32,9 +32,10 @@ export function NotificationWidget({ notification, actionClicked, holdOnHover, s
AstalNotifd.get_default().get_notification(notification) AstalNotifd.get_default().get_notification(notification)
: notification; : notification;
const actions: Array<AstalNotifd.Action>|undefined = (notification instanceof AstalNotifd.Notification) ? const actions: Array<AstalNotifd.Action>|undefined = ((notification instanceof AstalNotifd.Notification) &&
notification.actions && notification.actions.filter(a => Boolean(a)).length > 0) ?
notification.actions?.filter(a => notification.actions?.filter(a =>
a.id.toLowerCase() !== "view" && a.label.toLowerCase() != "view" a?.id?.toLowerCase() !== "view" && a?.label?.toLowerCase() != "view"
) )
: undefined; : undefined;
+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 { Gtk } from "ags/gtk4";
import { Separator } from "../Separator"; import { Separator } from "../Separator";
import { Windows } from "../../windows"; import { Windows } from "../../windows";
import { Clipboard } from "../../scripts/clipboard"; import { Clipboard } from "../../scripts/clipboard";
import { decoder, getPlayerIconFromBusName, variableToBoolean } from "../../scripts/utils"; import { decoder, getPlayerIconFromBusName, variableToBoolean } from "../../scripts/utils";
import { player, setPlayer } from "../../scripts/media";
import GObject from "ags/gobject"; import GObject from "ags/gobject";
import AstalMpris from "gi://AstalMpris"; import AstalMpris from "gi://AstalMpris";
import Pango from "gi://Pango?version=1.0"; 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 = () => { export const Media = () => {
const connections: Map<GObject.Object, Array<number>|number> = new Map(); 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) => onCleanup(() => connections.forEach((id, obj) =>
Array.isArray(id) ? Array.isArray(id) ?
id.forEach(id => obj.disconnect(id)) id.forEach(id => obj.disconnect(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)} return <Gtk.Box class={"media"} visible={player((pl) => pl.available)}
$={(self) => { $={(self) => {
const gestureClick = Gtk.GestureClick.new(), const gestureClick = Gtk.GestureClick.new(),
controllerMotion = Gtk.EventControllerMotion.new(), controllerMotion = Gtk.EventControllerMotion.new(),
controllerScroll = Gtk.EventControllerScroll.new( controllerScroll = Gtk.EventControllerScroll.new(
Gtk.EventControllerScrollFlags.VERTICAL); Gtk.EventControllerScrollFlags.VERTICAL
);
self.add_controller(gestureClick); self.add_controller(gestureClick);
self.add_controller(controllerMotion); self.add_controller(controllerMotion);
+2 -1
View File
@@ -10,6 +10,7 @@ import GObject from "ags/gobject";
import AstalBluetooth from "gi://AstalBluetooth"; import AstalBluetooth from "gi://AstalBluetooth";
import AstalNetwork from "gi://AstalNetwork"; import AstalNetwork from "gi://AstalNetwork";
import AstalWp from "gi://AstalWp"; import AstalWp from "gi://AstalWp";
import { Shell } from "../../app";
export const Status = () => export const Status = () =>
@@ -133,7 +134,7 @@ function StatusIcons() {
: "preferences-system-notifications-symbolic") : "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"))} visible={variableToBoolean(createBinding(Notifications.getDefault(), "history"))}
/> />
</Gtk.Box> </Gtk.Box>
+2 -1
View File
@@ -1,7 +1,8 @@
import { timeout } from "ags/time"; import { timeout } from "ags/time";
import { Astal, Gtk } from "ags/gtk4"; import { Astal, Gtk } from "ags/gtk4";
import { Clipboard } from "../../scripts/clipboard"; 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 { createBinding, For } from "ags";
import { pathToURI, variableToBoolean } from "../../scripts/utils"; 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 { Apps } from "../widget/bar/Apps";
import { Clock } from "../widget/bar/Clock"; import { Clock } from "../widget/bar/Clock";
import { Status } from "../widget/bar/Status"; import { Status } from "../widget/bar/Status";
import { Media } from "../widget/bar/Media";
export const Bar = (mon: number) => { export const Bar = (mon: number) => {
@@ -29,6 +30,7 @@ export const Bar = (mon: number) => {
$type="center"> $type="center">
<Clock /> <Clock />
<Media />
</Gtk.Box> </Gtk.Box>
<Gtk.Box class={"widgets-right"} homogeneous={false} <Gtk.Box class={"widgets-right"} homogeneous={false}
spacing={widgetSpacing} halign={Gtk.Align.END} 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 { Separator } from "../widget/Separator";
import { PopupWindow } from "../widget/PopupWindow"; import { PopupWindow } from "../widget/PopupWindow";
import { BigMedia } from "../widget/center-window/BigMedia"; import { BigMedia } from "../widget/center-window/BigMedia";
import { time } from "../scripts/utils"; import { time, variableToBoolean } from "../scripts/utils";
import { player } from "../widget/bar/Media"; import { createBinding } from "ags";
import AstalMpris from "gi://AstalMpris?version=0.1";
export const CenterWindow = (mon: number) => export const CenterWindow = (mon: number) =>
<PopupWindow namespace={"center-window"} marginTop={10} monitor={mon} <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={"center-window-container"} spacing={6}>
<Gtk.Box class={"left"} orientation={Gtk.Orientation.VERTICAL}> <Gtk.Box class={"left"} orientation={Gtk.Orientation.VERTICAL}>
<Gtk.Box class={"datetime"} orientation={Gtk.Orientation.VERTICAL} <Gtk.Box class={"datetime"} orientation={Gtk.Orientation.VERTICAL}
halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} vexpand>
vexpand={true}>
<Gtk.Label class={"time"} label={time(t => t.format("%H:%M")!)} /> <Gtk.Label class={"time"} label={time(t => t.format("%H:%M")!)} />
<Gtk.Label class={"date"} label={time(d => d.format("%A, %B %d")!)} /> <Gtk.Label class={"date"} label={time(d => d.format("%A, %B %d")!)} />
@@ -27,8 +27,10 @@ export const CenterWindow = (mon: number) =>
</Gtk.Box> </Gtk.Box>
<Separator orientation={Gtk.Orientation.HORIZONTAL} cssColor="gray" <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 /> <BigMedia />
</Gtk.Box> </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} <NotificationWidget notification={notif} showTime={false}
actionClose={() => Notifications.getDefault().removeNotification(notif)} actionClose={() => Notifications.getDefault().removeNotification(notif)}
holdOnHover={false} actionClicked={() => { holdOnHover={false} actionClicked={() => {
const viewAction = notif.actions.filter(action => const viewAction = notif.actions.filter(a =>
action.label.toLowerCase() === "view")?.[0]; a.id.toLowerCase() === "view" ||
a.label.toLowerCase() === "view"
)?.[0];
viewAction && notif.invoke(viewAction.id); viewAction && notif.invoke(viewAction.id);
}} }}