💥 fix(clipboard): issues with special characters on Clipboard.copyAsync()
This commit is contained in:
+28
-17
@@ -1,17 +1,12 @@
|
|||||||
|
import { timeout } from "ags/time";
|
||||||
|
import { monitorFile, readFile } from "ags/file";
|
||||||
|
import { execAsync } from "ags/process";
|
||||||
|
import GObject, { getter, register, signal } from "ags/gobject";
|
||||||
|
|
||||||
import AstalIO from "gi://AstalIO";
|
import AstalIO from "gi://AstalIO";
|
||||||
import GLib from "gi://GLib?version=2.0";
|
import GLib from "gi://GLib?version=2.0";
|
||||||
import Gio from "gi://Gio?version=2.0";
|
import Gio from "gi://Gio?version=2.0";
|
||||||
|
|
||||||
import GObject, { getter, register, signal } from "ags/gobject";
|
|
||||||
import { timeout } from "ags/time";
|
|
||||||
import { monitorFile, readFile } from "ags/file";
|
|
||||||
import { execAsync } from "ags/process";
|
|
||||||
|
|
||||||
|
|
||||||
interface ClipboardSignals extends GObject.Object.SignalSignatures {
|
|
||||||
copied: Clipboard["copied"];
|
|
||||||
wiped: Clipboard["wiped"];
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum ClipboardItemType {
|
export enum ClipboardItemType {
|
||||||
TEXT = 0,
|
TEXT = 0,
|
||||||
@@ -38,7 +33,10 @@ export { Clipboard };
|
|||||||
class Clipboard extends GObject.Object {
|
class Clipboard extends GObject.Object {
|
||||||
private static instance: Clipboard;
|
private static instance: Clipboard;
|
||||||
|
|
||||||
declare $signals: ClipboardSignals;
|
declare $signals: GObject.Object.SignalSignatures & {
|
||||||
|
"copied": Clipboard["copied"];
|
||||||
|
"wiped": Clipboard["wiped"];
|
||||||
|
};
|
||||||
|
|
||||||
#dbFile: Gio.File;
|
#dbFile: Gio.File;
|
||||||
#dbMonitor: Gio.FileMonitor;
|
#dbMonitor: Gio.FileMonitor;
|
||||||
@@ -50,7 +48,6 @@ class Clipboard extends GObject.Object {
|
|||||||
@signal(GObject.TYPE_JSOBJECT) copied(_item: object) {}
|
@signal(GObject.TYPE_JSOBJECT) copied(_item: object) {}
|
||||||
@signal() wiped() {};
|
@signal() wiped() {};
|
||||||
|
|
||||||
|
|
||||||
@getter(Array)
|
@getter(Array)
|
||||||
public get history() { return this.#history; }
|
public get history() { return this.#history; }
|
||||||
|
|
||||||
@@ -97,11 +94,25 @@ class Clipboard extends GObject.Object {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async copyAsync(content: string): Promise<void> {
|
public async copyAsync(content: string): Promise<boolean> {
|
||||||
await execAsync(`wl-copy "${content}"`).catch((err: Gio.IOErrorEnum) => {
|
const proc = Gio.Subprocess.new(
|
||||||
console.error(`Clipboard: Couldn't copy text using wl-copy. Stderr:\n\t${err.message
|
["wl-copy", content],
|
||||||
} | Stack:\n\t\t${err.stack}`);
|
Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE
|
||||||
});
|
);
|
||||||
|
|
||||||
|
const stderr = Gio.DataInputStream.new(proc.get_stderr_pipe()!);
|
||||||
|
|
||||||
|
if(!proc.wait_check()) {
|
||||||
|
try {
|
||||||
|
const [err, ] = stderr.read_upto('\x00', -1);
|
||||||
|
console.error(`Clipboard: An error occurred while copying text. Stderr: ${err}`);
|
||||||
|
} catch(_) {
|
||||||
|
console.error(`Clipboard: An error occurred while copying text and shell couldn't read \
|
||||||
|
stderr for more info.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return proc.get_exit_status() === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async selectItem(itemToSelect: number|ClipboardItem): Promise<boolean> {
|
public async selectItem(itemToSelect: number|ClipboardItem): Promise<boolean> {
|
||||||
|
|||||||
+14
-1
@@ -1,4 +1,5 @@
|
|||||||
import { createRoot, createState, onCleanup } from "ags";
|
import { Accessor, createConnection, createRoot, createState, onCleanup } from "ags";
|
||||||
|
import { decoder } from "./utils";
|
||||||
|
|
||||||
import GObject from "ags/gobject";
|
import GObject from "ags/gobject";
|
||||||
import AstalMpris from "gi://AstalMpris";
|
import AstalMpris from "gi://AstalMpris";
|
||||||
@@ -54,6 +55,18 @@ export function initPlayer(): void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function accessMediaUrl(player: AstalMpris.Player): Accessor<string|undefined> {
|
||||||
|
return createConnection(player.get_meta("xesam:url"),
|
||||||
|
[player, "notify::metadata", () => player.get_meta("xesam:url")]
|
||||||
|
).as(url => {
|
||||||
|
const byteString = url?.get_data_as_bytes();
|
||||||
|
|
||||||
|
return byteString ?
|
||||||
|
decoder.decode(byteString.toArray())
|
||||||
|
: undefined;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function disposePlayer(): void {
|
export function disposePlayer(): void {
|
||||||
if(disposeFun) {
|
if(disposeFun) {
|
||||||
disposeFun();
|
disposeFun();
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ export function escapeUnintendedMarkup(input: string): string {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function escapeSpecialCharacters(str: string): string {
|
||||||
|
return str.replace(/[\\^$.*?()[\]{}|]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
export function getChildren(widget: Gtk.Widget): Array<Gtk.Widget> {
|
export function getChildren(widget: Gtk.Widget): Array<Gtk.Widget> {
|
||||||
const firstChild = widget.get_first_child(),
|
const firstChild = widget.get_first_child(),
|
||||||
children: Array<Gtk.Widget> = [];
|
children: Array<Gtk.Widget> = [];
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Accessor, createBinding, createConnection, onCleanup, With } from "ags";
|
import { createBinding, onCleanup, With } from "ags";
|
||||||
import { Gtk } from "ags/gtk4";
|
import { Gtk } from "ags/gtk4";
|
||||||
import { Separator } from "../Separator";
|
import { Separator } from "../Separator";
|
||||||
import { Windows } from "../../windows";
|
import { Windows } from "../../windows";
|
||||||
import { Clipboard } from "../../modules/clipboard";
|
import { Clipboard } from "../../modules/clipboard";
|
||||||
import { decoder, getPlayerIconFromBusName, variableToBoolean } from "../../modules/utils";
|
import { getPlayerIconFromBusName, variableToBoolean } from "../../modules/utils";
|
||||||
import { player, setPlayer } from "../../modules/media";
|
import { accessMediaUrl, player, setPlayer } from "../../modules/media";
|
||||||
|
|
||||||
import GObject from "ags/gobject";
|
import GObject from "ags/gobject";
|
||||||
import AstalMpris from "gi://AstalMpris";
|
import AstalMpris from "gi://AstalMpris";
|
||||||
@@ -105,9 +105,9 @@ export const Media = () => {
|
|||||||
<With value={player(pl => pl.available)}>
|
<With value={player(pl => pl.available)}>
|
||||||
{(available: boolean) => available && <Gtk.Box class={"media-controls button-row"}>
|
{(available: boolean) => available && <Gtk.Box class={"media-controls button-row"}>
|
||||||
<Gtk.Button class={"link"} iconName={"edit-paste-symbolic"}
|
<Gtk.Button class={"link"} iconName={"edit-paste-symbolic"}
|
||||||
visible={variableToBoolean(getMediaUrl(player.get()))}
|
visible={variableToBoolean(accessMediaUrl(player.get()))}
|
||||||
tooltipText={"Copy link to Clipboard"} onClicked={() => {
|
tooltipText={"Copy link to Clipboard"} onClicked={() => {
|
||||||
const url = getMediaUrl(player.get()).get();
|
const url = accessMediaUrl(player.get()).get();
|
||||||
url && Clipboard.getDefault().copyAsync(url);
|
url && Clipboard.getDefault().copyAsync(url);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -133,15 +133,3 @@ export const Media = () => {
|
|||||||
</Gtk.Revealer>
|
</Gtk.Revealer>
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMediaUrl(player: AstalMpris.Player): Accessor<string|undefined> {
|
|
||||||
return createConnection(player.get_meta("xesam:url"),
|
|
||||||
[player, "notify::metadata", () => player.get_meta("xesam:url")]
|
|
||||||
).as(url => {
|
|
||||||
const byteString = url?.get_data_as_bytes();
|
|
||||||
|
|
||||||
return byteString ?
|
|
||||||
decoder.decode(byteString.toArray())
|
|
||||||
: undefined;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { timeout } from "ags/time";
|
import { timeout } from "ags/time";
|
||||||
import { Astal, Gtk } from "ags/gtk4";
|
import { Astal, Gtk } from "ags/gtk4";
|
||||||
import { Clipboard } from "../../modules/clipboard";
|
import { Clipboard } from "../../modules/clipboard";
|
||||||
import { getMediaUrl } from "../bar/Media";
|
import { accessMediaUrl } from "../../modules/media";
|
||||||
import { player, setPlayer } from "../../modules/media";
|
import { player, setPlayer } from "../../modules/media";
|
||||||
import { createBinding, For } from "ags";
|
import { createBinding, For } from "ags";
|
||||||
import { pathToURI, variableToBoolean } from "../../modules/utils";
|
import { pathToURI, variableToBoolean } from "../../modules/utils";
|
||||||
@@ -126,9 +126,9 @@ class PlayerWidget extends Gtk.Box {
|
|||||||
<Gtk.Box class={"controls button-row"} $type="center">
|
<Gtk.Box class={"controls button-row"} $type="center">
|
||||||
<Gtk.Button class={"link"} iconName={"edit-paste-symbolic"}
|
<Gtk.Button class={"link"} iconName={"edit-paste-symbolic"}
|
||||||
tooltipText={"Copy link to clipboard"}
|
tooltipText={"Copy link to clipboard"}
|
||||||
visible={variableToBoolean(getMediaUrl(player))}
|
visible={variableToBoolean(accessMediaUrl(player))}
|
||||||
onClicked={() => {
|
onClicked={() => {
|
||||||
const url = getMediaUrl(player).get();
|
const url = accessMediaUrl(player).get();
|
||||||
url && Clipboard.getDefault().copyAsync(url);
|
url && Clipboard.getDefault().copyAsync(url);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user