fix(ags): clipboard logic

This commit is contained in:
2025-04-27 15:35:41 +02:00
parent 54b69f0aac
commit 97130dbe27
5 changed files with 124 additions and 101 deletions

View File

@ -17,55 +17,61 @@ function ClipboardEntry({ id, content }: Entry) {
execAsync([SHELL, "-c", `cliphist decode ${id} | wl-copy`]);
}}
>
<box>
<label
cssClasses={["content"]}
xalign={0}
label={content}
/>
</box>
<label
cssClasses={["content"]}
xalign={0}
label={content}
/>
</button>
}
async function getClipboardHistory() {
async function getClipboardHistory(history: Variable<Entry[]>) {
try {
const ids = await execAsync([SHELL, "-c", "cliphist list | awk '{print $1}'"])
.then(it => it.split("\n"));
const contents = await execAsync([SHELL, "-c", "cliphist list | awk '{$1=\"\"; print}'"])
.then(it => it.split("\n"));
return ids
history.set(ids
.map((id, index) => ({ id, content: contents[index] }))
.filter(({ content }) => content && content.length > 0);
.filter(({ content }) => content && content.length > 0));
} catch {
return [];
history.set([]);
}
}
export default async function Clipboard(_monitor_id: number) {
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor;
const history = await getClipboardHistory();
const hypr = Hyprland.get_default();
const history: Variable<Entry[]> = Variable([]);
const toSkip = Variable(0);
const list = bind(toSkip).as(count => limit(
skip(history, count),
const list = Variable.derive([
bind(history),
bind(toSkip)
], (history, count) => limit(
skip(history, count),
10
));
const isEmpty = bind(list).as(list => list.length === 0);
const onScroll = (dy: number) => {
const value = toSkip.get();
if (dy < 0) {
if ((value - 10) < 0) return;
toSkip.set(value - 10)
} else {
if ((value + 10) > history.length) return;
if ((value + 10) > history.get().length) return;
toSkip.set(value + 10)
}
};
const setup = (self: Gtk.Window) => self.connect('notify::visible', async () => {
await getClipboardHistory(history)
})
return <window
visible={false}
monitor={bind(hypr, "focusedMonitor").as(monitor => monitor.id)}
@ -74,22 +80,35 @@ export default async function Clipboard(_monitor_id: number) {
keymode={Astal.Keymode.EXCLUSIVE}
anchor={TOP | BOTTOM | LEFT | RIGHT}
onKeyPressed={hideWindow}
application={App}>
<box hexpand={false} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
<box cssClasses={["clipboard"]} vertical>
<box spacing={6} vertical onScroll={(_, __, dy) => onScroll(dy)}>
{list.as(list => list.map(({ id, content }) => (
<ClipboardEntry id={id} content={content} />
)))}
</box>
<box
halign={Gtk.Align.CENTER}
cssClasses={["not-found"]}
vertical
visible={list.as(it => it.length === 0)}>
<image iconName="system-search-symbolic" />
<label label="No match found" />
</box>
application={App}
setup={setup}>
<box hexpand={false} cssClasses={["clipboard"]} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
<box spacing={6} vertical onScroll={(_, __, dy) => onScroll(dy)}>
{bind(list).as(list => list.map(({ id, content }) => (
<ClipboardEntry id={id} content={content} />
)))}
<button
cssClasses={["entry", "primary"]}
visible={isEmpty.as(empty => !empty)}
onClicked={() => {
execAsync([SHELL, "-c", "cliphist wipe"])
history.set([])
}}
>
<label
cssClasses={["content"]}
xalign={0}
label="Wipe clipboard history"
/>
</button>
</box>
<box
halign={Gtk.Align.CENTER}
cssClasses={["not-found"]}
vertical
visible={isEmpty}>
<image iconName="system-search-symbolic" />
<label label="No match found" />
</box>
</box>
</window>

View File

@ -12,8 +12,8 @@ function AppButton({ app }: { app: Apps.Application }) {
cssClasses={["application"]}
onClicked={() => {
hide();
app.launch();
}}
app.launch();
}}
>
<box>
<image iconName={app.iconName} />
@ -37,7 +37,7 @@ function AppButton({ app }: { app: Apps.Application }) {
export default function Launcher(_monitor_id: number) {
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor;
const hypr = Hyprland.get_default();
const hypr = Hyprland.get_default();
const apps = new Apps.Apps();
const query = Variable("")
@ -56,26 +56,24 @@ export default function Launcher(_monitor_id: number) {
anchor={TOP | BOTTOM | LEFT | RIGHT}
onKeyPressed={hideWindow}
application={App}>
<box hexpand={false} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
<box cssClasses={["launcher"]} vertical>
<entry
placeholderText="Search"
onChanged={self => query.set(self.text)}
onActivate={onEnter}
/>
<box spacing={6} vertical>
{list.as(list => list.map(app => (
<AppButton app={app} />
)))}
</box>
<box
halign={Gtk.Align.CENTER}
cssClasses={["not-found"]}
vertical
visible={list.as(l => l.length === 0)}>
<image iconName="system-search-symbolic" />
<label label="No match found" />
</box>
<box hexpand={false} cssClasses={["launcher"]} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
<entry
placeholderText="Search"
onChanged={self => query.set(self.text)}
onActivate={onEnter}
/>
<box spacing={6} vertical>
{list.as(list => list.map(app => (
<AppButton app={app} />
)))}
</box>
<box
halign={Gtk.Align.CENTER}
cssClasses={["not-found"]}
vertical
visible={list.as(l => l.length === 0)}>
<image iconName="system-search-symbolic" />
<label label="No match found" />
</box>
</box>
</window>