✨ feat(ags/runner): add plugin to control media from runner with prefix :
This commit is contained in:
+4
-2
@@ -13,6 +13,7 @@ import { Runner } from "./runner/Runner";
|
|||||||
import { PluginApps } from "./runner/plugins/apps";
|
import { PluginApps } from "./runner/plugins/apps";
|
||||||
import { PluginShell } from "./runner/plugins/shell";
|
import { PluginShell } from "./runner/plugins/shell";
|
||||||
import { PluginWebSearch } from "./runner/plugins/websearch";
|
import { PluginWebSearch } from "./runner/plugins/websearch";
|
||||||
|
import { PluginMedia } from "./runner/plugins/media";
|
||||||
|
|
||||||
|
|
||||||
let osdTimer: (Time|undefined);
|
let osdTimer: (Time|undefined);
|
||||||
@@ -20,13 +21,14 @@ let osdTimer: (Time|undefined);
|
|||||||
const runnerPlugins: Array<Runner.Plugin> = [
|
const runnerPlugins: Array<Runner.Plugin> = [
|
||||||
PluginApps,
|
PluginApps,
|
||||||
PluginShell,
|
PluginShell,
|
||||||
PluginWebSearch
|
PluginWebSearch,
|
||||||
|
PluginMedia
|
||||||
];
|
];
|
||||||
|
|
||||||
App.start({
|
App.start({
|
||||||
instanceName: "astal",
|
instanceName: "astal",
|
||||||
requestHandler(request: string, response: (result: any) => void) {
|
requestHandler(request: string, response: (result: any) => void) {
|
||||||
console.log(`[LOG] Arguments received: ${request}`);
|
// console.log(`[LOG] Arguments received: ${request}`);
|
||||||
response(handleArguments(request));
|
response(handleArguments(request));
|
||||||
},
|
},
|
||||||
main() {
|
main() {
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { bind, Variable } from "astal";
|
||||||
|
import { ResultWidget, ResultWidgetProps } from "../../widget/runner/ResultWidget";
|
||||||
|
import { Runner } from "../Runner";
|
||||||
|
import AstalMpris from "gi://AstalMpris";
|
||||||
|
|
||||||
|
export const PluginMedia = {
|
||||||
|
prefix: ":",
|
||||||
|
handle() {
|
||||||
|
const player = AstalMpris.get_default().players[0];
|
||||||
|
|
||||||
|
if(!player) return new ResultWidget({
|
||||||
|
icon: "folder-music-symbolic",
|
||||||
|
title: "Couldn't find any players",
|
||||||
|
description: "No media / player found with mpris"
|
||||||
|
} as ResultWidgetProps);
|
||||||
|
return [
|
||||||
|
new ResultWidget({
|
||||||
|
icon: bind(player, "playbackStatus").as((status) => status === AstalMpris.PlaybackStatus.PLAYING ?
|
||||||
|
"media-playback-pause-symbolic"
|
||||||
|
: "media-playback-start-symbolic"),
|
||||||
|
title: Variable.derive([
|
||||||
|
bind(player, "title"),
|
||||||
|
bind(player, "artist"),
|
||||||
|
bind(player, "playbackStatus")
|
||||||
|
], (title, artist, status) => `${ status === AstalMpris.PlaybackStatus.PLAYING ?
|
||||||
|
"Pause" : "Play"
|
||||||
|
} ${title} | ${artist}`)(),
|
||||||
|
onClick: () => player && player.play_pause()
|
||||||
|
} as ResultWidgetProps),
|
||||||
|
new ResultWidget({
|
||||||
|
icon: "media-skip-backward-symbolic",
|
||||||
|
title: Variable.derive([
|
||||||
|
bind(player, "title"),
|
||||||
|
bind(player, "artist")
|
||||||
|
], (title, artist) =>
|
||||||
|
`Go Previous ${ title ? title : player.busName }${ artist ? ` | ${artist}` : "" }`
|
||||||
|
)(),
|
||||||
|
onClick: () => player && player.canGoPrevious && player.previous()
|
||||||
|
} as ResultWidgetProps),
|
||||||
|
new ResultWidget({
|
||||||
|
icon: "media-skip-forward-symbolic",
|
||||||
|
title: Variable.derive([
|
||||||
|
bind(player, "title"),
|
||||||
|
bind(player, "artist")
|
||||||
|
], (title, artist) =>
|
||||||
|
`Go Next ${ title ? title : player.busName }${ artist ? ` | ${artist}` : "" }`
|
||||||
|
)(),
|
||||||
|
onClick: () => player && player.canGoNext && player.next()
|
||||||
|
} as ResultWidgetProps)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
} as Runner.Plugin;
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Astal } from "astal/gtk3";
|
import { Astal } from "astal/gtk3";
|
||||||
|
|
||||||
import AstalApps from "gi://AstalApps";
|
import AstalApps from "gi://AstalApps";
|
||||||
import AstalHyprland from "gi://AstalHyprland";
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
|
|
||||||
|
|
||||||
const astalApps: AstalApps.Apps = new AstalApps.Apps();
|
const astalApps: AstalApps.Apps = new AstalApps.Apps();
|
||||||
let appsList: Array<AstalApps.Application> = astalApps.get_list();
|
let appsList: Array<AstalApps.Application> = astalApps.get_list();
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { Gtk } from "astal/gtk3";
|
import { Gtk } from "astal/gtk3";
|
||||||
import { Windows } from "../windows";
|
|
||||||
import { restartInstance } from "./reload-handler";
|
|
||||||
import { Wireplumber } from "./volume";
|
import { Wireplumber } from "./volume";
|
||||||
|
import { Windows } from "../windows";
|
||||||
|
|
||||||
|
import { restartInstance } from "./reload-handler";
|
||||||
import { startRunnerDefault } from "../runner/Runner";
|
import { startRunnerDefault } from "../runner/Runner";
|
||||||
import { showWorkspaceNumbers } from "../widget/bar/Workspaces";
|
import { showWorkspaceNumbers } from "../widget/bar/Workspaces";
|
||||||
import { timeout } from "astal";
|
import { timeout } from "astal";
|
||||||
|
|
||||||
|
|
||||||
export function handleArguments(request: string): any {
|
export function handleArguments(request: string): any {
|
||||||
const args: Array<string> = request.split(" ");
|
const args: Array<string> = request.split(" ");
|
||||||
switch(args[0]) {
|
switch(args[0]) {
|
||||||
@@ -30,8 +33,10 @@ export function handleArguments(request: string): any {
|
|||||||
return "Opening runner..."
|
return "Opening runner..."
|
||||||
|
|
||||||
case "show-ws-numbers":
|
case "show-ws-numbers":
|
||||||
showWorkspaceNumbers.set(true);
|
if(!showWorkspaceNumbers.get()) {
|
||||||
timeout(2000, () => showWorkspaceNumbers.set(false));
|
showWorkspaceNumbers.set(true);
|
||||||
|
timeout(2200, () => showWorkspaceNumbers.set(false));
|
||||||
|
}
|
||||||
return "Showing numbers";
|
return "Showing numbers";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -156,7 +161,7 @@ Options:
|
|||||||
reload: creates a new astal instance and removes this one.
|
reload: creates a new astal instance and removes this one.
|
||||||
volume: wireplumber volume controller, see "volume help".
|
volume: wireplumber volume controller, see "volume help".
|
||||||
runner: open the application runner.
|
runner: open the application runner.
|
||||||
(show|hide)-ws-numbers: show or hide workspace numbers in bar.
|
show-ws-numbers: show or hide workspace numbers in bar.
|
||||||
help, -h, --help: shows this help message.
|
help, -h, --help: shows this help message.
|
||||||
|
|
||||||
2025 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License.
|
2025 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AstalIO, GObject, property, register, signal, timeout } from "astal";
|
import { AstalIO, GObject, property, register, signal, timeout } from "astal";
|
||||||
import AstalNotifd from "gi://AstalNotifd";
|
import AstalNotifd from "gi://AstalNotifd";
|
||||||
|
|
||||||
export const
|
export let
|
||||||
NOTIFICATION_TIMEOUT_URGENT: number = 0,
|
NOTIFICATION_TIMEOUT_URGENT: number = 0,
|
||||||
NOTIFICATION_TIMEOUT_NORMAL: number = 4000,
|
NOTIFICATION_TIMEOUT_NORMAL: number = 4000,
|
||||||
NOTIFICATION_TIMEOUT_LOW: number = 2000;
|
NOTIFICATION_TIMEOUT_LOW: number = 2000;
|
||||||
@@ -22,7 +22,7 @@ class Notifications extends GObject.Object {
|
|||||||
|
|
||||||
#notifications: Array<AstalNotifd.Notification> = [];
|
#notifications: Array<AstalNotifd.Notification> = [];
|
||||||
#history: Array<HistoryNotification> = [];
|
#history: Array<HistoryNotification> = [];
|
||||||
#connections: Array<number>;
|
#connections: Array<number> = [];
|
||||||
#historyLimit: number = 10;
|
#historyLimit: number = 10;
|
||||||
|
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class Notifications extends GObject.Object {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.#connections = [
|
this.#connections.push(
|
||||||
AstalNotifd.get_default().connect("notified", (notifd, id) => {
|
AstalNotifd.get_default().connect("notified", (notifd, id) => {
|
||||||
const notification = notifd.get_notification(id);
|
const notification = notifd.get_notification(id);
|
||||||
const notifTimeout = notification.urgency === AstalNotifd.Urgency.LOW ?
|
const notifTimeout = notification.urgency === AstalNotifd.Urgency.LOW ?
|
||||||
@@ -106,7 +106,7 @@ class Notifications extends GObject.Object {
|
|||||||
this.removeNotification(id);
|
this.removeNotification(id);
|
||||||
this.addHistory(notifd.get_notification(id));
|
this.addHistory(notifd.get_notification(id));
|
||||||
})
|
})
|
||||||
];
|
);
|
||||||
|
|
||||||
this.run_dispose = () => {
|
this.run_dispose = () => {
|
||||||
super.run_dispose();
|
super.run_dispose();
|
||||||
@@ -128,8 +128,10 @@ class Notifications extends GObject.Object {
|
|||||||
this.#history.length === this.#historyLimit &&
|
this.#history.length === this.#historyLimit &&
|
||||||
this.removeHistory(this.#history[this.#history.length - 1]);
|
this.removeHistory(this.#history[this.#history.length - 1]);
|
||||||
|
|
||||||
const newArray = this.#history.length > 0 ? this.#history.reverse().filter((item) => item.id !== notif.id) : [];
|
this.#history.map((notifb, i) =>
|
||||||
newArray.push({
|
notifb.id === notif.id && this.#history.splice(i, 1));
|
||||||
|
|
||||||
|
this.#history.unshift({
|
||||||
id: notif.id,
|
id: notif.id,
|
||||||
appName: notif.appName,
|
appName: notif.appName,
|
||||||
body: notif.body,
|
body: notif.body,
|
||||||
@@ -138,21 +140,18 @@ class Notifications extends GObject.Object {
|
|||||||
time: notif.time,
|
time: notif.time,
|
||||||
image: notif.image ? notif.image : undefined
|
image: notif.image ? notif.image : undefined
|
||||||
} as HistoryNotification);
|
} as HistoryNotification);
|
||||||
this.#history = newArray.reverse();
|
|
||||||
this.notify("history");
|
this.notify("history");
|
||||||
this.emit("history-added", this.#history[0]);
|
this.emit("history-added", this.#history[0]);
|
||||||
onAdded && onAdded(notif);
|
onAdded && onAdded(notif);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearHistory(): void {
|
public clearHistory(): void {
|
||||||
for(let i = 0; i < this.history.length; i++) {
|
this.#history.reverse().map((notif) => {
|
||||||
const notif = this.history[this.history.length-1];
|
this.#history.pop()
|
||||||
|
this.emit("history-removed", notif.id);
|
||||||
if(this.#history.pop()) {
|
this.notify("history");
|
||||||
this.emit("history-removed", notif.id);
|
});
|
||||||
this.notify("history");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeHistory(notif: (HistoryNotification|number)): void {
|
public removeHistory(notif: (HistoryNotification|number)): void {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { execAsync, GLib, GObject, property, register, signal } from "astal";
|
|||||||
import { Connectable } from "astal/binding";
|
import { Connectable } from "astal/binding";
|
||||||
import { Gdk } from "astal/gtk3";
|
import { Gdk } from "astal/gtk3";
|
||||||
import { getDateTime } from "./time";
|
import { getDateTime } from "./time";
|
||||||
|
import { getUserDirs } from "./utils";
|
||||||
|
|
||||||
@register({ GTypeName: "ScreenRecording" })
|
@register({ GTypeName: "ScreenRecording" })
|
||||||
class Recording extends GObject.Object implements Connectable {
|
class Recording extends GObject.Object implements Connectable {
|
||||||
@@ -17,11 +18,13 @@ class Recording extends GObject.Object implements Connectable {
|
|||||||
|
|
||||||
#recording: boolean = false;
|
#recording: boolean = false;
|
||||||
#path: string = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_VIDEOS) || `${GLib.get_home_dir()}/Recordings`;
|
#path: string = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_VIDEOS) || `${GLib.get_home_dir()}/Recordings`;
|
||||||
|
|
||||||
/** Default extension: mp4(h264) */
|
/** Default extension: mp4(h264) */
|
||||||
#extension: string = "mp4";
|
#extension: string = "mp4";
|
||||||
#recordAudio: boolean = false;
|
#recordAudio: boolean = false;
|
||||||
#monitor: (number|null) = null;
|
#monitor: (number|null) = null;
|
||||||
#area: (Gdk.Rectangle|null) = null;
|
#area: (Gdk.Rectangle|null) = null;
|
||||||
|
#pid: (number|null) = null;
|
||||||
|
|
||||||
@property(Boolean)
|
@property(Boolean)
|
||||||
public get recording() { return this.#recording; }
|
public get recording() { return this.#recording; }
|
||||||
@@ -67,19 +70,21 @@ class Recording extends GObject.Object implements Connectable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public startRecording(monitor?: number, area?: Gdk.Rectangle) {
|
public startRecording(monitor?: number, area?: Gdk.Rectangle) {
|
||||||
const output = `${getDateTime().get().format("%Y-%m-%d-%H%M%S")}_rec.${this.extension}`;
|
if(this.#recording)
|
||||||
|
throw new Error("Screen Recording is already running!");
|
||||||
|
|
||||||
|
const output = `${getDateTime().get().format("%Y-%m-%d-%H%M%S")}_rec.${this.extension || "mp4"}`;
|
||||||
this.#recording = true;
|
this.#recording = true;
|
||||||
this.emit("started");
|
this.emit("started");
|
||||||
execAsync([
|
execAsync([
|
||||||
"wf-recorder",
|
`sh ${ GLib.get_user_config_dir()}/ags/scripts/sh/recording.sh`,
|
||||||
`${Boolean(area) ?
|
`${ area ? `-g ${area?.x || 0},${area?.y || 0} ${area?.width || 1}x${area?.height || 1}` : "" }`,
|
||||||
`-g ${area?.x || 0},${area?.y || 0} ${area?.width || 1}x${area?.height || 1}`
|
|
||||||
: ""}`,
|
|
||||||
`-f ${output}`
|
`-f ${output}`
|
||||||
]).then(() => {
|
]).then(async (stdout: string) => {
|
||||||
this.emit("stopped", `${this.path}/${output}`);
|
const pid: number = Number.parseInt(
|
||||||
this.#recording = false;
|
(await execAsync(`echo ${stdout} | head -n 1`)).split(':')[1]);
|
||||||
this.notify("recording");
|
|
||||||
|
this.#pid = pid;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
wf-recorder $@ &
|
||||||
|
echo "PID: $!"
|
||||||
|
exit 0
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { register } from "astal";
|
import { Binding, register } from "astal";
|
||||||
import { Gtk, Widget } from "astal/gtk3";
|
import { Gtk, Widget } from "astal/gtk3";
|
||||||
import { Runner } from "../../runner/Runner";
|
import { Runner } from "../../runner/Runner";
|
||||||
|
|
||||||
export { ResultWidget, ResultWidgetProps };
|
export { ResultWidget, ResultWidgetProps };
|
||||||
|
|
||||||
type ResultWidgetProps = {
|
type ResultWidgetProps = {
|
||||||
icon?: string;
|
icon?: string | Binding<string | undefined>;
|
||||||
title: string;
|
title: string | Binding<string | undefined>;
|
||||||
description?: string;
|
description?: string | Binding<string | undefined>;
|
||||||
closeOnClick?: boolean;
|
closeOnClick?: boolean;
|
||||||
setup?: () => void;
|
setup?: () => void;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@@ -16,7 +16,7 @@ type ResultWidgetProps = {
|
|||||||
@register({ GTypeName: "ResultWidget" })
|
@register({ GTypeName: "ResultWidget" })
|
||||||
class ResultWidget extends Widget.Box {
|
class ResultWidget extends Widget.Box {
|
||||||
public readonly onClick: (() => void);
|
public readonly onClick: (() => void);
|
||||||
public readonly icon: (string|undefined);
|
public readonly icon: (string | Binding<string|undefined> | undefined);
|
||||||
public readonly setup: ((() => void)|undefined);
|
public readonly setup: ((() => void)|undefined);
|
||||||
public readonly closeOnClick: boolean = true;
|
public readonly closeOnClick: boolean = true;
|
||||||
|
|
||||||
@@ -55,7 +55,9 @@ class ResultWidget extends Widget.Box {
|
|||||||
} as Widget.LabelProps),
|
} as Widget.LabelProps),
|
||||||
new Widget.Label({
|
new Widget.Label({
|
||||||
className: "description",
|
className: "description",
|
||||||
visible: Boolean(props.description),
|
visible: (props.description instanceof Binding) ?
|
||||||
|
props.description.as(Boolean)
|
||||||
|
: Boolean(props.description),
|
||||||
truncate: true,
|
truncate: true,
|
||||||
xalign: 0,
|
xalign: 0,
|
||||||
label: props.description || ""
|
label: props.description || ""
|
||||||
|
|||||||
Reference in New Issue
Block a user