fix(ags): clipboard logic

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

View File

@ -1,26 +1,26 @@
// SCSS Variables // SCSS Variables
// Generated by 'wal' // Generated by 'wal'
$wallpaper: "/home/sadorowo/images/wallpapers/youtube_Sheri_142.jpg"; $wallpaper: "/home/sadorowo/images/wallpapers/youtube_Sheri_170.jpg";
// Special // Special
$background: #f9f7f5; $background: #f9f6f3;
$foreground: #4C3A47; $foreground: #251F23;
$cursor: #4C3A47; $cursor: #251F23;
// Colors // Colors
$color0: #f9f7f5; $color0: #f9f6f3;
$color1: #B77E8E; $color1: #AB8772;
$color2: #6E8EAF; $color2: #D69768;
$color3: #138DCF; $color3: #727188;
$color4: #609DD4; $color4: #897A85;
$color5: #A196AD; $color5: #79869C;
$color6: #D6ABA7; $color6: #A29597;
$color7: #4C3A47; $color7: #251F23;
$color8: #847974; $color8: #82766c;
$color9: #B77E8E; $color9: #AB8772;
$color10: #6E8EAF; $color10: #D69768;
$color11: #138DCF; $color11: #727188;
$color12: #609DD4; $color12: #897A85;
$color13: #A196AD; $color13: #79869C;
$color14: #D6ABA7; $color14: #A29597;
$color15: #4C3A47; $color15: #251F23;

View File

@ -14,6 +14,14 @@
} }
} }
.entry.primary {
background: $color3;
&:hover>.content {
color: $color3;
}
}
.entry { .entry {
padding: 10px; padding: 10px;
background: $color5; background: $color5;
@ -23,18 +31,17 @@
&:hover { &:hover {
background: $color0; background: $color0;
box>.content { >.content {
color: $color5; color: $color5;
} }
} }
box {
.content { .content {
color: $color0; color: $color0;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
} }
} }
}
.not-found { .not-found {
background: $color5; background: $color5;

View File

@ -23,7 +23,8 @@
&:hover { &:hover {
background: $color0; background: $color0;
box>.name, box>.description { box>.name,
box>.description {
color: $color5; color: $color5;
} }
} }
@ -32,7 +33,6 @@
margin-right: 12px; margin-right: 12px;
} }
box {
.name { .name {
color: $color0; color: $color0;
font-size: 14px; font-size: 14px;
@ -44,7 +44,6 @@
font-size: 12px; font-size: 12px;
} }
} }
}
.not-found { .not-found {
background: $color5; background: $color5;

View File

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

View File

@ -56,8 +56,7 @@ export default function Launcher(_monitor_id: number) {
anchor={TOP | BOTTOM | LEFT | RIGHT} anchor={TOP | BOTTOM | LEFT | RIGHT}
onKeyPressed={hideWindow} onKeyPressed={hideWindow}
application={App}> application={App}>
<box hexpand={false} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}> <box hexpand={false} cssClasses={["launcher"]} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
<box cssClasses={["launcher"]} vertical>
<entry <entry
placeholderText="Search" placeholderText="Search"
onChanged={self => query.set(self.text)} onChanged={self => query.set(self.text)}
@ -77,6 +76,5 @@ export default function Launcher(_monitor_id: number) {
<label label="No match found" /> <label label="No match found" />
</box> </box>
</box> </box>
</box>
</window> </window>
} }