🔧 chore: better implementation of the media copy link feature, fix Cliboard.copyAsync() always returning an error

This commit is contained in:
retrozinndev
2025-09-01 20:51:03 -03:00
parent 082a1aface
commit 8b96ba9bf7
5 changed files with 137 additions and 87 deletions
+5 -2
View File
@@ -134,11 +134,14 @@ $color-hover: colors.$bg-primary;
margin-right: $spacing; margin-right: $spacing;
} }
& .media-controls { & .buttons {
margin-left: $spacing;
}
& .button-row {
border-top-right-radius: 12px; border-top-right-radius: 12px;
border-bottom-right-radius: 12px; border-bottom-right-radius: 12px;
padding: 4px 0; padding: 4px 0;
margin-left: $spacing;
& > button image { & > button image {
margin: 0; margin: 0;
+1 -1
View File
@@ -52,7 +52,7 @@
} }
& .bottom { & .bottom {
& .controls { & .button-row {
margin-top: 9px; margin-top: 9px;
& button { & button {
+1 -1
View File
@@ -102,7 +102,7 @@ class Clipboard extends GObject.Object {
const stderr = Gio.DataInputStream.new(proc.get_stderr_pipe()!); const stderr = Gio.DataInputStream.new(proc.get_stderr_pipe()!);
if(!proc.wait_check()) { if(!proc.wait_check(null)) {
try { try {
const [err, ] = stderr.read_upto('\x00', -1); const [err, ] = stderr.read_upto('\x00', -1);
console.error(`Clipboard: An error occurred while copying text. Stderr: ${err}`); console.error(`Clipboard: An error occurred while copying text. Stderr: ${err}`);
+29 -25
View File
@@ -80,31 +80,35 @@ export const Media = () => {
revealChild={false}> revealChild={false}>
<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={"buttons"} spacing={4}>
<Gtk.Button class={"link"} iconName={"edit-paste-symbolic"} <Gtk.Box class={"extra button-row"}>
visible={variableToBoolean(accessMediaUrl(player.get()))} <Gtk.Button class={"link"} iconName={"edit-paste-symbolic"}
tooltipText={"Copy link to Clipboard"} onClicked={() => { visible={variableToBoolean(accessMediaUrl(player.get()))}
const url = accessMediaUrl(player.get()).get(); tooltipText={"Copy link to Clipboard"} onClicked={() => {
url && Clipboard.getDefault().copyAsync(url); const url = accessMediaUrl(player.get()).get();
}} url && Clipboard.getDefault().copyAsync(url);
/> }}
<Gtk.Button class={"previous"} iconName={"media-skip-backward-symbolic"} />
tooltipText={"Previous"} onClicked={() => </Gtk.Box>
player.get().canGoPrevious && player.get().previous()} <Gtk.Box class={"media-controls button-row"}>
/> <Gtk.Button class={"previous"} iconName={"media-skip-backward-symbolic"}
<Gtk.Button class={"play-pause"} iconName={createBinding(player.get(), "playbackStatus").as(status => tooltipText={"Previous"} onClicked={() =>
status === AstalMpris.PlaybackStatus.PAUSED ? player.get().canGoPrevious && player.get().previous()}
"media-playback-start-symbolic" />
: "media-playback-pause-symbolic")} <Gtk.Button class={"play-pause"} iconName={createBinding(player.get(), "playbackStatus").as(status =>
tooltipText={ status === AstalMpris.PlaybackStatus.PAUSED ?
createBinding(player.get(), "playbackStatus").as(status => "media-playback-start-symbolic"
status === AstalMpris.PlaybackStatus.PAUSED ? "Play" : "Pause") : "media-playback-pause-symbolic")}
} onClicked={() => player.get().play_pause()} tooltipText={
/> createBinding(player.get(), "playbackStatus").as(status =>
<Gtk.Button class={"next"} iconName={"media-skip-forward-symbolic"} status === AstalMpris.PlaybackStatus.PAUSED ? "Play" : "Pause")
tooltipText={"Next"} onClicked={() => player.get().canGoNext && } onClicked={() => player.get().play_pause()}
player.get().next()} />
/> <Gtk.Button class={"next"} iconName={"media-skip-forward-symbolic"}
tooltipText={"Next"} onClicked={() => player.get().canGoNext &&
player.get().next()}
/>
</Gtk.Box>
</Gtk.Box>} </Gtk.Box>}
</With> </With>
</Gtk.Revealer> </Gtk.Revealer>
+101 -58
View File
@@ -1,19 +1,18 @@
import { timeout } from "ags/time"; import { createBinding, For } from "ags";
import { register } from "ags/gobject";
import { Astal, Gtk } from "ags/gtk4"; import { Astal, Gtk } from "ags/gtk4";
import { Clipboard } from "../../modules/clipboard"; import { Clipboard } from "../../modules/clipboard";
import { accessMediaUrl } from "../../modules/media"; import { accessMediaUrl } from "../../modules/media";
import { player, setPlayer } from "../../modules/media"; import { player, setPlayer } from "../../modules/media";
import { createBinding, For } from "ags";
import { pathToURI, variableToBoolean } from "../../modules/utils"; import { pathToURI, variableToBoolean } from "../../modules/utils";
import AstalMpris from "gi://AstalMpris"; import AstalMpris from "gi://AstalMpris";
import AstalIO from "gi://AstalIO";
import Pango from "gi://Pango?version=1.0"; import Pango from "gi://Pango?version=1.0";
import Adw from "gi://Adw?version=1"; import Adw from "gi://Adw?version=1";
import { register } from "ags/gobject"; import GLib from "gi://GLib?version=2.0";
let dragTimer: (AstalIO.Time|undefined); let dragTimer: (GLib.Source|undefined);
export const BigMedia = () => { export const BigMedia = () => {
const availablePlayers = createBinding(AstalMpris.get_default(), "players").as(pls => const availablePlayers = createBinding(AstalMpris.get_default(), "players").as(pls =>
@@ -47,6 +46,7 @@ export const BigMedia = () => {
@register({ GTypeName: "PlayerWidget" }) @register({ GTypeName: "PlayerWidget" })
class PlayerWidget extends Gtk.Box { class PlayerWidget extends Gtk.Box {
#player!: AstalMpris.Player; #player!: AstalMpris.Player;
#copyClickTimeout?: GLib.Source;
get player() { return this.#player; } get player() { return this.#player; }
@@ -98,15 +98,17 @@ class PlayerWidget extends Gtk.Box {
return; return;
if(!dragTimer) { if(!dragTimer) {
dragTimer = timeout(200, () => dragTimer = setTimeout(() =>
player.position = Math.floor(value)); player.position = Math.floor(value)
, 200);
return; return;
} }
dragTimer.cancel(); dragTimer.destroy();
dragTimer = timeout(200, () => dragTimer = setTimeout(() =>
player.position = Math.floor(value)); player.position = Math.floor(value)
, 200);
}} }}
/> />
</Gtk.Box> as Gtk.Box </Gtk.Box> as Gtk.Box
@@ -123,58 +125,99 @@ class PlayerWidget extends Gtk.Box {
})} $type="start" })} $type="start"
/> />
<Gtk.Box class={"controls button-row"} $type="center"> <Gtk.Box spacing={4} $type="center">
<Gtk.Button class={"link"} iconName={"edit-paste-symbolic"} <Gtk.Box class={"extra button-row"}>
tooltipText={"Copy link to clipboard"} <Gtk.Button class={"link"}
visible={variableToBoolean(accessMediaUrl(player))} tooltipText={"Copy link to clipboard"}
onClicked={() => { visible={variableToBoolean(accessMediaUrl(player))}
const url = accessMediaUrl(player).get(); onClicked={(self) => {
url && Clipboard.getDefault().copyAsync(url); const url = accessMediaUrl(player).get();
}} // a widget that supports adding multiple icons and allows switching
/> // through them would be pretty nice!! (i'll probably do this later)
<Gtk.Button class={"shuffle"} visible={createBinding(player, "shuffleStatus").as(status => url &&
status !== AstalMpris.Shuffle.UNSUPPORTED)} iconName={ Clipboard.getDefault().copyAsync(url).then(() => {
createBinding(player, "shuffleStatus").as(status => status === AstalMpris.Shuffle.ON ? if(this.#copyClickTimeout && !this.#copyClickTimeout.is_destroyed())
"media-playlist-shuffle-symbolic" this.#copyClickTimeout.destroy();
: "media-playlist-consecutive-symbolic")} tooltipText={
createBinding(player, "shuffleStatus").as(status => status === AstalMpris.Shuffle.ON ?
"Shuffle"
: "No shuffle")} onClicked={() => player.shuffle()}
/>
<Gtk.Button class={"previous"} iconName={"media-skip-backward-symbolic"}
tooltipText={"Previous"} onClicked={() => player.canGoPrevious && player.previous()}
/>
<Gtk.Button class={"play-pause"} tooltipText={
createBinding(player, "playbackStatus").as(status =>
status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play")}
iconName={createBinding(player, "playbackStatus").as(status =>
status === AstalMpris.PlaybackStatus.PLAYING ?
"media-playback-pause-symbolic"
: "media-playback-start-symbolic")} onClicked={() => player.play_pause()}
/>
<Gtk.Button class={"next"} iconName={"media-skip-forward-symbolic"}
tooltipText={"Next"} onClicked={() => player.canGoNext && player.next()}
/>
<Gtk.Button class={"repeat"} iconName={createBinding(player, "loopStatus").as(status => {
if(status === AstalMpris.Loop.TRACK)
return "media-playlist-repeat-song-symbolic";
if(status === AstalMpris.Loop.PLAYLIST) (self.get_child() as Gtk.Stack).set_visible_child_name("done-icon");
return "media-playlist-repeat-symbolic"; this.#copyClickTimeout = setTimeout(() => {
(self.get_child() as Gtk.Stack).set_visible_child_name("copy-icon");
this.#copyClickTimeout!.destroy();
this.#copyClickTimeout = undefined;
}, 1100);
}).catch(() => {
if(this.#copyClickTimeout && !this.#copyClickTimeout.is_destroyed())
this.#copyClickTimeout.destroy();
return "loop-arrow-symbolic"; (self.get_child() as Gtk.Stack).set_visible_child_name("error-icon");
})} visible={createBinding(player, "loopStatus").as(status => this.#copyClickTimeout = setTimeout(() => {
status !== AstalMpris.Loop.UNSUPPORTED)} (self.get_child() as Gtk.Stack).set_visible_child_name("copy-icon");
tooltipText={createBinding(player, "loopStatus").as(status => { this.#copyClickTimeout!.destroy();
if(status === AstalMpris.Loop.TRACK) this.#copyClickTimeout = undefined;
return "Loop song"; }, 900);
});
}}>
if(status === AstalMpris.Loop.PLAYLIST) <Gtk.Stack transitionType={Gtk.StackTransitionType.CROSSFADE}
return "Loop playlist"; transitionDuration={340}>
return "No loop"; <Gtk.StackPage name={"copy-icon"} child={
})} onClicked={() => player.loop()} <Gtk.Image iconName={"edit-paste-symbolic"} /> as Gtk.Widget
/> } />
<Gtk.StackPage name={"done-icon"} child={
<Gtk.Image iconName={"object-select-symbolic"} /> as Gtk.Widget
} />
<Gtk.StackPage name={"error-icon"} child={
<Gtk.Image iconName={"window-close-symbolic"} /> as Gtk.Widget
} />
</Gtk.Stack>
</Gtk.Button>
</Gtk.Box>
<Gtk.Box class={"media-controls button-row"}>
<Gtk.Button class={"shuffle"} visible={createBinding(player, "shuffleStatus").as(status =>
status !== AstalMpris.Shuffle.UNSUPPORTED)} iconName={
createBinding(player, "shuffleStatus").as(status => status === AstalMpris.Shuffle.ON ?
"media-playlist-shuffle-symbolic"
: "media-playlist-consecutive-symbolic")} tooltipText={
createBinding(player, "shuffleStatus").as(status => status === AstalMpris.Shuffle.ON ?
"Shuffle"
: "No shuffle")} onClicked={() => player.shuffle()}
/>
<Gtk.Button class={"previous"} iconName={"media-skip-backward-symbolic"}
tooltipText={"Previous"} onClicked={() => player.canGoPrevious && player.previous()}
/>
<Gtk.Button class={"play-pause"} tooltipText={
createBinding(player, "playbackStatus").as(status =>
status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play")}
iconName={createBinding(player, "playbackStatus").as(status =>
status === AstalMpris.PlaybackStatus.PLAYING ?
"media-playback-pause-symbolic"
: "media-playback-start-symbolic")} onClicked={() => player.play_pause()}
/>
<Gtk.Button class={"next"} iconName={"media-skip-forward-symbolic"}
tooltipText={"Next"} onClicked={() => player.canGoNext && player.next()}
/>
<Gtk.Button class={"repeat"} iconName={createBinding(player, "loopStatus").as(status => {
if(status === AstalMpris.Loop.TRACK)
return "media-playlist-repeat-song-symbolic";
if(status === AstalMpris.Loop.PLAYLIST)
return "media-playlist-repeat-symbolic";
return "loop-arrow-symbolic";
})} visible={createBinding(player, "loopStatus").as(status =>
status !== AstalMpris.Loop.UNSUPPORTED)}
tooltipText={createBinding(player, "loopStatus").as(status => {
if(status === AstalMpris.Loop.TRACK)
return "Loop song";
if(status === AstalMpris.Loop.PLAYLIST)
return "Loop playlist";
return "No loop";
})} onClicked={() => player.loop()}
/>
</Gtk.Box>
</Gtk.Box> </Gtk.Box>
<Gtk.Label class={"length"} xalign={1} yalign={0} <Gtk.Label class={"length"} xalign={1} yalign={0}
halign={Gtk.Align.END} label={createBinding(player, "length").as(len => { /* bananananananana */ halign={Gtk.Align.END} label={createBinding(player, "length").as(len => { /* bananananananana */