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

@ -8,12 +8,20 @@
border: none; border: none;
margin-bottom: 6px; margin-bottom: 6px;
&:focus { &:focus {
outline: 2px solid $color0; outline: 2px solid $color0;
} }
} }
.entry.primary {
background: $color3;
&:hover>.content {
color: $color3;
}
}
.entry { .entry {
padding: 10px; padding: 10px;
background: $color5; background: $color5;
@ -22,17 +30,16 @@
&: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;
}
} }
} }
@ -47,4 +54,4 @@
margin-bottom: 8px; margin-bottom: 8px;
} }
} }
} }

View File

@ -8,7 +8,7 @@
border: none; border: none;
margin-bottom: 6px; margin-bottom: 6px;
&:focus { &:focus {
outline: 2px solid $color0; outline: 2px solid $color0;
} }
@ -22,8 +22,9 @@
&:hover { &:hover {
background: $color0; background: $color0;
box>.name, box>.description { box>.name,
box>.description {
color: $color5; color: $color5;
} }
} }
@ -32,17 +33,15 @@
margin-right: 12px; margin-right: 12px;
} }
box { .name {
.name { color: $color0;
color: $color0; font-size: 14px;
font-size: 14px; font-weight: 600;
font-weight: 600; }
}
.description { .description {
color: $color0; color: $color0;
font-size: 12px; font-size: 12px;
}
} }
} }
@ -57,4 +56,4 @@
margin-bottom: 8px; margin-bottom: 8px;
} }
} }
} }

View File

@ -17,55 +17,61 @@ 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(
skip(history, count), const list = Variable.derive([
bind(history),
bind(toSkip)
], (history, count) => limit(
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();
if (dy < 0) { if (dy < 0) {
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,22 +80,35 @@ 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} />
)))} )))}
</box> <button
<box cssClasses={["entry", "primary"]}
halign={Gtk.Align.CENTER} visible={isEmpty.as(empty => !empty)}
cssClasses={["not-found"]} onClicked={() => {
vertical execAsync([SHELL, "-c", "cliphist wipe"])
visible={list.as(it => it.length === 0)}> history.set([])
<image iconName="system-search-symbolic" /> }}
<label label="No match found" /> >
</box> <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>
</box> </box>
</window> </window>

View File

@ -12,8 +12,8 @@ function AppButton({ app }: { app: Apps.Application }) {
cssClasses={["application"]} cssClasses={["application"]}
onClicked={() => { onClicked={() => {
hide(); hide();
app.launch(); app.launch();
}} }}
> >
<box> <box>
<image iconName={app.iconName} /> <image iconName={app.iconName} />
@ -37,7 +37,7 @@ function AppButton({ app }: { app: Apps.Application }) {
export default function Launcher(_monitor_id: number) { export default function Launcher(_monitor_id: number) {
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor; const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor;
const hypr = Hyprland.get_default(); const hypr = Hyprland.get_default();
const apps = new Apps.Apps(); const apps = new Apps.Apps();
const query = Variable("") const query = Variable("")
@ -56,26 +56,24 @@ 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)} onActivate={onEnter}
onActivate={onEnter} />
/> <box spacing={6} vertical>
<box spacing={6} vertical> {list.as(list => list.map(app => (
{list.as(list => list.map(app => ( <AppButton app={app} />
<AppButton app={app} /> )))}
)))} </box>
</box> <box
<box halign={Gtk.Align.CENTER}
halign={Gtk.Align.CENTER} cssClasses={["not-found"]}
cssClasses={["not-found"]} vertical
vertical visible={list.as(l => l.length === 0)}>
visible={list.as(l => l.length === 0)}> <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>