From f793e546f68d11150869dfb7f35b6b21f300d256 Mon Sep 17 00:00:00 2001 From: matt1432 Date: Wed, 10 Jul 2024 15:14:44 -0400 Subject: [PATCH] refactor(ags clipboard): make a clipboard service --- modules/ags/config/.eslintrc.json | 10 ++- modules/ags/config/services/clipboard.ts | 106 +++++++++++++++++++++++ modules/ags/config/ts/clipboard/main.ts | 76 ++++++---------- 3 files changed, 142 insertions(+), 50 deletions(-) create mode 100644 modules/ags/config/services/clipboard.ts diff --git a/modules/ags/config/.eslintrc.json b/modules/ags/config/.eslintrc.json index 441dfbe5..b9b975bb 100644 --- a/modules/ags/config/.eslintrc.json +++ b/modules/ags/config/.eslintrc.json @@ -72,7 +72,15 @@ "prefer-template": ["warn"], "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-unused-vars": ["warn", { + "args": "all", + "argsIgnorePattern": "^_", + "caughtErrors": "all", + "caughtErrorsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "ignoreRestSiblings": true + }], "@typescript-eslint/no-unsafe-declaration-merging": "off", "@stylistic/array-bracket-newline": ["warn", "consistent"], diff --git a/modules/ags/config/services/clipboard.ts b/modules/ags/config/services/clipboard.ts new file mode 100644 index 00000000..a3ec28ad --- /dev/null +++ b/modules/ags/config/services/clipboard.ts @@ -0,0 +1,106 @@ +const { execAsync } = Utils; + + +// TODO: add wl-paste --watch to not have to get history every time +const MAX_CLIPS = 100; + +class Clipboard extends Service { + static { + Service.register(this, { + 'clip-added': ['string'], + 'history-searched': [], + }, { + clips: ['jsobject'], + }); + } + + private _clips_left = 0; + private _clips: Map = new Map(); + + get clips() { + return this._clips; + } + + + private _decrementClipsLeft() { + if (--this._clips_left === 0) { + this.emit('history-searched'); + // FIXME: this is necessary when not putting a cap on clip amount + // exec(`prlimit --pid ${exec('pgrep ags')} --nofile=1024:`); + } + } + + private async _decodeItem(index: string): Promise { + try { + const decodedItem = await execAsync([ + 'bash', '-c', `cliphist list | grep ${index} | cliphist decode`, + ]); + + this._decrementClipsLeft(); + + return decodedItem; + } + catch (error) { + console.error(error); + this._decrementClipsLeft(); + + return null; + } + } + + public copyOldItem(key: string | number): void { + execAsync([ + 'bash', '-c', `cliphist list | grep ${key} | cliphist decode | wl-copy`, + ]); + } + + public getHistory() { + this._clips = new Map(); + + // FIXME: this is necessary when not putting a cap on clip amount + // exec(`prlimit --pid ${exec('pgrep ags')} --nofile=10024:`); + + // This command comes from '../../clipboard/script.sh' + execAsync('clipboard-manager') + .then((out) => { + const rawClips = out.split('\n'); + + this._clips_left = Math.min(rawClips.length - 1, MAX_CLIPS); + + rawClips.forEach(async(clip, i) => { + if (i > MAX_CLIPS) { + return; + } + + if (clip.includes('img')) { + this._decrementClipsLeft(); + this._clips.set( + parseInt((clip.match('[0-9]+') ?? [''])[0]), + { + clip, + isImage: true, + }, + ); + } + else { + const decodedClip = await this._decodeItem(clip); + + if (decodedClip) { + this._clips.set( + parseInt(clip), + { + clip: decodedClip, + isImage: false, + }, + ); + } + } + }); + }) + .catch((error) => console.error(error)); + } +} + +const clipboard = new Clipboard(); + +export default clipboard; diff --git a/modules/ags/config/ts/clipboard/main.ts b/modules/ags/config/ts/clipboard/main.ts index 6387605f..72a7f46a 100644 --- a/modules/ags/config/ts/clipboard/main.ts +++ b/modules/ags/config/ts/clipboard/main.ts @@ -1,40 +1,33 @@ const { Box, Icon, Label } = Widget; -const { execAsync } = Utils; import { Fzf, FzfResultItem } from 'fzf'; import Gtk from 'gi://Gtk?version=3.0'; +import Clipboard from '../../services/clipboard.ts'; import CursorBox from '../misc/cursorbox.ts'; import SortedList from '../misc/sorted-list.ts'; -const N_ITEMS = 30; - -const copyOldItem = (key: string | number): void => { - execAsync([ - 'bash', '-c', `cliphist list | grep ${key} | cliphist decode | wl-copy`, - ]); - App.closeWindow('win-clipboard'); -}; - export default () => { - let CopiedItems = [] as [string, number][]; - let fzfResults: FzfResultItem<[string, number]>[]; + let fzfResults: FzfResultItem<[number, { clip: string, isImage: boolean }]>[]; - const getKey = (r: Gtk.ListBoxRow) => parseInt(r.get_child()?.name ?? ''); - - const makeItem = (list: Gtk.ListBox, key: string, val: string) => { - CopiedItems.push([val, parseInt(key)]); + const getKey = (r: Gtk.ListBoxRow): number => parseInt(r.get_child()?.name ?? '0'); + const makeItem = ( + list: Gtk.ListBox, + key: number, + val: string, + isImage: boolean, + ): void => { const widget = CursorBox({ class_name: 'item', - name: key, + name: key.toString(), - on_primary_click_release: () => copyOldItem(key), + on_primary_click_release: () => Clipboard.copyOldItem(key), child: Box({ children: [ - val.startsWith('img:') ? + isImage ? Icon({ icon: val.replace('img:', ''), size: 100 * 2, @@ -53,41 +46,26 @@ export default () => { widget.show_all(); }; - // Decode old item: - const decodeItem = (list: Gtk.ListBox, index: string) => { - execAsync([ - 'bash', '-c', `cliphist list | grep ${index} | cliphist decode`, - ]).then((out) => { - makeItem(list, index, out); - }); - }; return SortedList({ name: 'clipboard', class_name: 'clipboard', transition: 'slide top', - on_select: (r) => copyOldItem(getKey(r)), + on_select: (r) => Clipboard.copyOldItem(getKey(r)), init_rows: (list) => { - CopiedItems = []; + Clipboard.getHistory(); - execAsync('clipboard-manager').then((out) => { - list.get_children()?.forEach((ch) => { - ch.destroy(); + const connectId = Clipboard.connect('history-searched', () => { + list.get_children().forEach((row) => { + row.destroy(); }); - - const items = out.split('\n'); - - for (let i = 0; i < N_ITEMS; ++i) { - if (items[i].includes('img')) { - makeItem(list, (items[i].match('[0-9]+') ?? [''])[0], items[i]); - } - else { - decodeItem(list, items[i]); - } - } - }).catch(console.error); + Clipboard.clips.forEach((clip, key) => { + makeItem(list, key, clip.clip, clip.isImage); + }); + Clipboard.disconnect(connectId); + }); }, set_sort: (text, list) => { @@ -95,16 +73,16 @@ export default () => { list.set_sort_func((row1, row2) => getKey(row2) - getKey(row1)); } else { - const fzf = new Fzf(CopiedItems, { - selector: (item) => item[0], + const fzf = new Fzf([...Clipboard.clips.entries()], { + selector: ([_key, { clip }]) => clip, - tiebreakers: [(a, b) => b[1] - a[1]], + tiebreakers: [(a, b) => b[0] - a[0]], }); 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; + const row1 = fzfResults.find((f) => f.item[0] === getKey(a))?.score ?? 0; + const row2 = fzfResults.find((f) => f.item[0] === getKey(b))?.score ?? 0; return row2 - row1; });