add: clipboard history widget

This commit is contained in:
Franek 2025-04-26 21:48:58 +02:00
parent 8deb06278f
commit 54b69f0aac
7 changed files with 191 additions and 7 deletions

View File

@ -6,8 +6,9 @@ import Hyprland from "gi://AstalHyprland";
import QuickSettings from "widget/quick_settings/quick_settings"; import QuickSettings from "widget/quick_settings/quick_settings";
import BluetoothWindow from "widget/quick_settings/bluetooth"; import BluetoothWindow from "widget/quick_settings/bluetooth";
import Clipboard from "widget/launcher/clipboard";
import Launcher from "widget/launcher/launcher"; import Launcher from "widget/launcher/launcher";
import BatteryInfo from "@/widget/bar/battery_info"; import BatteryInfo from "widget/bar/battery_info";
import Bar from "widget/bar/bar"; import Bar from "widget/bar/bar";
const hypr = Hyprland.get_default(); const hypr = Hyprland.get_default();
@ -17,7 +18,8 @@ const components = [
Launcher, Launcher,
QuickSettings, QuickSettings,
BluetoothWindow, BluetoothWindow,
BatteryInfo BatteryInfo,
Clipboard
]; ];
const setupBars = async (monitor_id: number) => { const setupBars = async (monitor_id: number) => {

View File

@ -9,8 +9,8 @@ export const hideWindow = (self: Gtk.Window, keyval: number) => {
const keys = ["quick_settings"].includes(self.name) const keys = ["quick_settings"].includes(self.name)
? [Gdk.KEY_Escape] ? [Gdk.KEY_Escape]
: [ : [
Gdk.KEY_Escape, Gdk.KEY_Escape,
Gdk.KEY_Super_L, Gdk.KEY_Super_L,
Gdk.KEY_Super_R Gdk.KEY_Super_R
]; ];
@ -21,4 +21,12 @@ export const openOnButton = (event: Gdk.ButtonEvent, keyval: number) => (action:
if (event.get_button() !== keyval) return; if (event.get_button() !== keyval) return;
action(); action();
} }
export const limit = <T>(list: T[], max: number) => list.filter((_, i) => i <= (max - 1));
export const skip = <T>(list: T[], items: number) => list.filter((_, i) => {
if (items < 0) items = 0;
if (items > (list.length - 1)) items = list.length;
return i > (items - 1);
});

26
ags/styles/colors.scss Normal file
View File

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

View File

@ -0,0 +1,50 @@
@use '../colors' as *;
.clipboard {
entry {
background: $color5;
color: $color0;
padding: 8px 12px;
border: none;
margin-bottom: 6px;
&:focus {
outline: 2px solid $color0;
}
}
.entry {
padding: 10px;
background: $color5;
border-radius: 8px;
transition: background 0.2s ease;
&:hover {
background: $color0;
box>.content {
color: $color5;
}
}
box {
.content {
color: $color0;
font-size: 14px;
font-weight: 600;
}
}
}
.not-found {
background: $color5;
color: $color0;
font-size: 14px;
padding: 10px;
border-radius: 8px;
image {
margin-bottom: 8px;
}
}
}

View File

@ -1,4 +1,5 @@
@forward '_bar'; @forward '_bar';
@forward '_launcher'; @forward '_launcher';
@forward '_clipboard';
@forward '_battery_info'; @forward '_battery_info';
@forward '_quick_settings'; @forward '_quick_settings';

View File

@ -0,0 +1,96 @@
import { Variable, bind, execAsync } from "astal";
import { App, Astal, Gtk } from "astal/gtk4";
import { hideWindow, limit, skip } from "@lib/utils";
import { SHELL } from "@/settings.json";
import Hyprland from "gi://AstalHyprland";
const hide = () => App.get_window("clipboard")?.hide();
type Entry = { id: string, content: string };
function ClipboardEntry({ id, content }: Entry) {
return <button
cssClasses={["entry"]}
onClicked={() => {
hide();
execAsync([SHELL, "-c", `cliphist decode ${id} | wl-copy`]);
}}
>
<box>
<label
cssClasses={["content"]}
xalign={0}
label={content}
/>
</box>
</button>
}
async function getClipboardHistory() {
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
.map((id, index) => ({ id, content: contents[index] }))
.filter(({ content }) => content && content.length > 0);
} catch {
return [];
}
}
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 toSkip = Variable(0);
const list = bind(toSkip).as(count => limit(
skip(history, count),
10
));
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;
toSkip.set(value + 10)
}
};
return <window
visible={false}
monitor={bind(hypr, "focusedMonitor").as(monitor => monitor.id)}
name={"clipboard"}
cssClasses={["clipboard"]}
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>
</box>
</box>
</window>
}

View File

@ -44,7 +44,7 @@ bindd = $mainMod + Shift, 8, Move to workspace 8, movetoworkspace
bindd = $mainMod + Shift, 9, Move to workspace 9, movetoworkspace, 9 bindd = $mainMod + Shift, 9, Move to workspace 9, movetoworkspace, 9
bindd = $mainMod + Shift, 0, Move to workspace 10, movetoworkspace, 10 bindd = $mainMod + Shift, 0, Move to workspace 10, movetoworkspace, 10
bindd = $mainMod, Tab, Toggle workspace overview, overview:toggle bindd = $mainMod, Tab, Toggle workspace overview, overview:toggle
bindd = $mainMod, S, Toggle special workspace, togglespecialworkspace, magic bindd = $mainMod, S, Toggle special workspace, togglespecialworkspace, magic
bindd = $mainMod + Shift, S, Move to special workspace, movetoworkspace, special:magic bindd = $mainMod + Shift, S, Move to special workspace, movetoworkspace, special:magic
@ -57,7 +57,8 @@ bindmd = $mainMod, mouse:273, Resize window, resizewindow
bindd = , PRINT, Take partial screenshot, exec, hyprshot -zm region -o $screenshot_dir bindd = , PRINT, Take partial screenshot, exec, hyprshot -zm region -o $screenshot_dir
bindd = $mainMod, PRINT, Take fullscreen screenshot, exec, hyprshot -zm output -o $screenshot_dir bindd = $mainMod, PRINT, Take fullscreen screenshot, exec, hyprshot -zm output -o $screenshot_dir
bindd = $mainMod, Space, Open app launcher, exec, ags toggle launcher bindd = $mainMod, Space, Open app launcher, exec, ags toggle launcher
bindd = $mainMod, V, Open clipboard history, exec, ags toggle clipboard
bindd = $mainMod + Shift, T, Toggle bar, exec, ~/.config/hypr/scripts/toggle-bar-visibility.sh bindd = $mainMod + Shift, T, Toggle bar, exec, ~/.config/hypr/scripts/toggle-bar-visibility.sh
bindd = $mainMod, SUPER_L, Open quick settings, exec, ags toggle quick_settings bindd = $mainMod, SUPER_L, Open quick settings, exec, ags toggle quick_settings
bindd = Ctrl+Shift, R, Reload AGS, exec, ags quit; ags run --gtk4 & bindd = Ctrl+Shift, R, Reload AGS, exec, ags quit; ags run --gtk4 &