const { Box, Entry, Icon, Label, ListBox, Scrollable } = Widget; const { execAsync } = Utils; const Hyprland = await Service.import('hyprland'); import { Fzf, FzfResultItem } from 'fzf'; import Gtk from 'gi://Gtk?version=3.0'; import CursorBox from '../misc/cursorbox.ts'; import PopupWindow from '../misc/popup.ts'; import { Monitor } from 'types/service/hyprland'; const N_ITEMS = 30; const copyOldItem = (key: string | number): void => { execAsync([ 'bash', '-c', `cliphist list | grep ${key} | cliphist decode | wl-copy`, ]).then(() => { App.closeWindow('win-clipboard'); }); }; export default () => { let CopiedItems = [] as [string, number][]; let fzfResults: FzfResultItem<[string, number]>[]; const getKey = (r: Gtk.ListBoxRow) => parseInt(r.get_child()?.name ?? ''); const list = ListBox().on('row-activated', (_, row) => { copyOldItem(getKey(row)); }); const updateItems = () => { (list.get_children() as Gtk.ListBoxRow[]).forEach((r) => { r.changed(); }); }; const setSort = (text: string) => { if (text === '' || text === '-') { list.set_sort_func((row1, row2) => getKey(row2) - getKey(row1)); } else { const fzf = new Fzf(CopiedItems, { selector: (item) => item[0], tiebreakers: [(a, b) => b[1] - a[1]], }); fzfResults = fzf.find(text); list.set_sort_func((a, b) => { const row1 = fzfResults.find((f) => f.item[1] === getKey(a))?.score ?? 0; const row2 = fzfResults.find((f) => f.item[1] === getKey(b))?.score ?? 0; return row2 - row1; }); } updateItems(); }; const makeItem = (key: string, val: string) => { CopiedItems.push([val, parseInt(key)]); const widget = CursorBox({ class_name: 'item', name: key, on_primary_click_release: () => copyOldItem(key), child: Box({ children: [ val.startsWith('img:') ? Icon({ icon: val.replace('img:', ''), size: 100 * 2, }) : Label({ label: val, truncate: 'end', max_width_chars: 100, }), ], }), }); list.add(widget); widget.show_all(); updateItems(); }; // Decode old item: const decodeItem = (index: string) => { execAsync([ 'bash', '-c', `cliphist list | grep ${index} | cliphist decode`, ]).then((out) => { makeItem(index, out); }); }; const entry = Entry({ // Set some text so on-change works the first time text: '-', hexpand: true, on_change: ({ text }) => { if (text !== null) { setSort(text); } }, }); const on_open = () => { CopiedItems = []; entry.text = ''; execAsync('clipboard-manager').then(async(out) => { list.get_children()?.forEach((ch) => { ch.destroy(); }); const items = out.split('\n'); for (let i = 0; i < N_ITEMS; ++i) { if (items[i].includes('img')) { makeItem((items[i].match('[0-9]+') ?? [''])[0], items[i]); } else { decodeItem(items[i]); } } let x: number; let y: number; const monitor = (JSON.parse(await Hyprland.messageAsync('j/monitors')) as Monitor[]) .find((m) => m.focused) as Monitor; switch (monitor.transform) { case 1: x = monitor.x - (monitor.height / 2); y = monitor.y - (monitor.width / 2); break; case 2: x = monitor.x - (monitor.width / 2); y = monitor.y - (monitor.height / 2); break; case 3: x = monitor.x + (monitor.height / 2); y = monitor.y + (monitor.width / 2); break; default: x = monitor.x + (monitor.width / 2); y = monitor.y + (monitor.height / 2); break; } await Hyprland.messageAsync(`dispatch movecursor ${x} ${y}`); entry.grab_focus(); }).catch(console.log); }; on_open(); return PopupWindow({ name: 'clipboard', keymode: 'on-demand', on_open, child: Box({ class_name: 'clipboard', vertical: true, children: [ Box({ class_name: 'header', children: [ Icon('preferences-system-search-symbolic'), entry, ], }), Scrollable({ hscroll: 'never', vscroll: 'automatic', child: Box({ vertical: true, children: [list], }), }), ], }), }); };