Aylur / astal

Building blocks for creating custom desktop shells
https://aylur.github.io/astal/
GNU Lesser General Public License v2.1
276 stars 33 forks source link

Crashing since f9a35261aeb8ea135ff0f279acbf2d12164e427a #124

Closed matt1432 closed 1 hour ago

matt1432 commented 1 day ago

Describe the bug AGS crashes after some time since f9a35261aeb8ea135ff0f279acbf2d12164e427a with the message: error: signal: segmentation fault (core dumped).

To Reproduce Here's my code for current Hyprland client: https://github.com/matt1432/nixos-configs/blob/master/nixosModules/ags/config/widgets/bar/items/current-client.tsx

Joao-Queiroga commented 1 day ago

I have the same problem.

Aylur commented 20 hours ago

could you provide a simple app.tsx to repro?

import Hyprland from "gi://AstalHyprland"

App.start({
  instanceName: "hyprland-segfault",
  main() {
    const hyprland = Hyprland.get_default()
    return <window>
      // make it crash
    </window>
  }
})
matt1432 commented 12 hours ago

@Aylur I managed to break it down to about 200 lines. I figured out that I need both my current client widget and workspace indicator widget for the segfault to happen. The workspace indicator doesn't even need to be visible.

import { App, Astal, Gtk, Widget } from 'astal/gtk3';
import { bind, Variable } from 'astal';

import AstalApps from 'gi://AstalApps';
import AstalHyprland from 'gi://AstalHyprland';

const CurrentClient = () => {
    const applications = AstalApps.Apps.new();
    const hyprland = AstalHyprland.get_default();

    const visibleIcon = Variable<boolean>(false);
    const focusedIcon = Variable<string>('');
    const focusedTitle = Variable<string>('');

    let lastFocusedAddress: string | null;

    const updateVars = () => setTimeout(() => {
        const client = hyprland.get_focused_client();

        lastFocusedAddress = client ? client.get_address() : null;

        const app = applications.fuzzy_query(
            client?.get_class() ?? '',
        )[0];

        const icon = app?.iconName;

        if (icon) {
            visibleIcon.set(true);
            focusedIcon.set(icon);
        }
        else {
            visibleIcon.set(false);
        }

        focusedTitle.set(client?.get_title() ?? '');
        const id = client?.connect('notify::title', (c) => {
            if (c.get_address() !== lastFocusedAddress) {
                c.disconnect(id);
            }
            focusedTitle.set(c.get_title());
        });
    }, 5 * 10);

    updateVars();
    hyprland.connect('notify::focused-client', () => updateVars());

    return (
        <box
            visible={bind(focusedTitle).as((title) => title !== '')}
        >
            <icon
                visible={bind(visibleIcon)}
                css="font-size: 32px;"
                icon={bind(focusedIcon)}
            />

            <label
                label={bind(focusedTitle)}
                truncate
            />
        </box>
    );
};

const Workspace = ({ id = 0 }) => {
    const hyprland = AstalHyprland.get_default();

    return (
        <revealer name={id.toString()}>
            <eventbox>
                <box
                    setup={(self) => {
                        const update = (
                            _: Widget.Box,
                            _c?: AstalHyprland.Client,
                        ) => {
                            const workspace = hyprland.get_workspace(id);
                            const occupied = workspace && workspace.get_clients().length > 0;

                            self.toggleClassName('occupied', occupied);
                        };

                        update(self);
                        self
                            .hook(hyprland, 'event', () => update(self))

                            // Deal with urgent windows
                            .hook(hyprland, 'urgent', update)

                            .hook(hyprland, 'notify::focused-workspace', () => {
                                if (hyprland.get_focused_workspace().get_id() === id) {
                                    self.toggleClassName('urgent', false);
                                }
                            });
                    }}
                />
            </eventbox>
        </revealer>
    );
};

const Workspaces = () => {
    const Hyprland = AstalHyprland.get_default();

    const L_PADDING = 2;
    const WS_WIDTH = 30;

    const updateHighlight = (self: Widget.Box) => {
        const currentId = Hyprland.get_focused_workspace().get_id().toString();

        const indicators = ((self.get_parent() as Widget.Overlay)
            .child as Widget.Box)
            .children as Widget.Revealer[];

        const currentIndex = indicators.findIndex((w) => w.name === currentId);

        if (currentIndex >= 0) {
            self.css = `margin-left: ${L_PADDING + (currentIndex * WS_WIDTH)}px`;
        }
    };

    const highlight = (
        <box
            className="button active"

            valign={Gtk.Align.CENTER}
            halign={Gtk.Align.START}

            setup={(self) => {
                self.hook(Hyprland, 'notify::focused-workspace', updateHighlight);
            }}
        />
    ) as Widget.Box;

    let workspaces: Widget.Revealer[] = [];

    return (
        <box>
            <overlay overlay={highlight}>
                <box
                    setup={(self) => {
                        const refresh = () => {
                            (self.children as Widget.Revealer[]).forEach((rev) => {
                                rev.reveal_child = false;
                            });

                            workspaces.forEach((ws) => {
                                ws.reveal_child = true;
                            });
                        };

                        const updateWorkspaces = () => {
                            Hyprland.get_workspaces().forEach((ws) => {
                                const currentWs = (self.children as Widget.Revealer[])
                                    .find((ch) => ch.name === ws.id.toString());

                                if (!currentWs && ws.id > 0) {
                                    self.add(Workspace({ id: ws.id }));
                                }
                            });

                            // Make sure the order is correct
                            workspaces.forEach((workspace, i) => {
                                (workspace.get_parent() as Widget.Box)
                                    .reorder_child(workspace, i);
                            });
                        };

                        const updateAll = () => {
                            workspaces = (self.children as Widget.Revealer[])
                                .filter((ch) => {
                                    return Hyprland.get_workspaces().find((ws) => {
                                        return ws.id.toString() === ch.name;
                                    });
                                })
                                .sort((a, b) => parseInt(a.name ?? '0') - parseInt(b.name ?? '0'));

                            updateWorkspaces();
                            refresh();
                        };

                        updateAll();
                        self.hook(Hyprland, 'event', updateAll);
                    }}
                />
            </overlay>
        </box>
    );
};

App.start({
    instanceName: 'hyprland-segfault',
    main() {
        return (
            <window
                exclusivity={Astal.Exclusivity.EXCLUSIVE}
                anchor={
                    Astal.WindowAnchor.TOP |
                    Astal.WindowAnchor.LEFT |
                    Astal.WindowAnchor.RIGHT
                }
            >
                <box hexpand halign={Gtk.Align.START}>
                    <Workspaces />
                    <CurrentClient />
                </box>

            </window>
        );
    },
});
matt1432 commented 12 hours ago

It takes a few seconds or minutes for it to crash. I usually just spam close and open terminals, have a window change its title and change workspaces a few times and it'll crash

Joao-Queiroga commented 10 hours ago

I think the error is in the get_client (at least for me). Because when I comented this line self.toggleClassName("occupied", (hypr.get_workspace(i)?.get_clients().length || 0) > 0). the program stopped crashing