diff --git a/src/app.ts b/src/app.ts index bf5fede..cdafb52 100644 --- a/src/app.ts +++ b/src/app.ts @@ -16,7 +16,6 @@ import { Runner } from "./runner/Runner"; import { Windows } from "./windows"; import { Notifications } from "./scripts/notifications"; import { Wallpaper } from "./scripts/wallpaper"; - import { Stylesheet } from "./scripts/stylesheet"; import { Clipboard } from "./scripts/clipboard"; import { Config } from "./scripts/config"; @@ -30,6 +29,7 @@ import GObject, { register } from "ags/gobject"; import AstalNotifd from "gi://AstalNotifd"; import GLib from "gi://GLib?version=2.0"; import Gio from "gi://Gio?version=2.0"; +import Adw from "gi://Adw?version=1"; const runnerPlugins: Array = [ @@ -43,6 +43,8 @@ const runnerPlugins: Array = [ const defaultWindows: Array = []; +Adw.init(); + @register({ GTypeName: "Shell" }) export class Shell extends Gtk.Application { private static instance: Shell; @@ -100,18 +102,33 @@ export class Shell extends Gtk.Application { vfunc_command_line(cmd: Gio.ApplicationCommandLine): number { const args = cmd.get_arguments(); + args.splice(0, 1); // remove executable if(cmd.isRemote) { - cmd.print_literal(handleArguments(args)); - cmd.done(); - return 0; + try { + const res = handleArguments(cmd, args); + cmd.done(); + cmd.set_exit_status(res); + return res; + } catch(_e) { + const e = _e as Error; + cmd.printerr_literal(`Error: something went wrong! Stderr: ${e.message}\n${e.stack}`); + cmd.done(); + return 1; + } + } else { + if(args[1]) { + printerr("Error: colorshell not running. Try to clean-run before using arguments"); + return 1; + } + + this.main(); } - this.main(args); return 0; } - private main(_args: Array): void { + private main(): void { this.#loop = GLib.MainLoop.new(null, false); const connections = new Map | number>(); @@ -157,6 +174,11 @@ export class Shell extends Gtk.Application { this.#loop.run(); } + + quit(): void { + this.#loop.is_running() && this.#loop.quit(); + super.quit(); + } } diff --git a/src/scripts/arg-handler.ts b/src/scripts/arg-handler.ts index 449467e..0f1f8d4 100644 --- a/src/scripts/arg-handler.ts +++ b/src/scripts/arg-handler.ts @@ -6,264 +6,15 @@ import { Runner } from "../runner/Runner"; import { showWorkspaceNumber } from "../widget/bar/Workspaces"; import { playSystemBell } from "./utils"; import { player, setPlayer } from "../widget/bar/Media"; -import { generalConfig } from "../app"; +import { generalConfig, Shell } from "../app"; import AstalIO from "gi://AstalIO"; import AstalMpris from "gi://AstalMpris"; +import Gio from "gi://Gio?version=2.0"; -let wsTimeout: (AstalIO.Time|undefined); - -export function handleArguments(args: Array): any { - switch(args[0]) { - case "help": - case "h": - return getHelp(); - - case "open": - case "close": - case "toggle": - case "windows": - case "reopen": - return handleWindowArgs(args); - - case "volume": - return handleVolumeArgs(args); - - case "media": - return handleMediaArgs(args); - - case "reload": - restartInstance(); - return "Restarting instance..."; - - case "runner": - !Runner.instance ? - Runner.openDefault(args[1] || undefined) - : Runner.close(); - - return `Opening runner${args[1] ? ` with predefined text: "${args[1]}"` : ""}`; - - case "peek-workspace-num": - if(wsTimeout) - return "Workspace numbers are already showing"; - - showWorkspaceNumber(true); - wsTimeout = timeout(Number.parseInt(args[1]) || 2200, () => { - showWorkspaceNumber(false); - wsTimeout = undefined; - }); - return "Toggled workspace numbers"; - - default: - return "Error: command not found! try checking help"; - } -} - -function handleMediaArgs(args: Array): string { - if(/h|help/.test(args[1])) - return ` -Manage colorshell's active player - -Options: - play: resume/start active player's media. - pause: pause the active player. - play-pause: toggle play/pause on active player. - stop: stop the active player's media. - previous: go back to previous media if player supports it. - next: jump to next media if player supports it. - bus-name: get active player's mpris bus name. - list: show available players with their bus name. - select bus_name: change the active player, where bus_name is - the desired player's mpris bus name(with the mediaplayer2 prefix). -`.trim(); - - const activePlayer: AstalMpris.Player|undefined = player.get().available ? - player.get() - : undefined; - const players = AstalMpris.get_default().players.filter(pl => pl.available); - - if(!activePlayer) - return `Error: no active player found! try playing some media first` - - switch(args[1]) { - case "play": - activePlayer.play(); - return "Playing"; - - case "list": - return `Available players:\n${players.map(pl => { - let playbackStatusStr: string; - switch(pl.playbackStatus) { - case AstalMpris.PlaybackStatus.PAUSED: - playbackStatusStr = "paused"; - break; - case AstalMpris.PlaybackStatus.PLAYING: - playbackStatusStr = "playing"; - break; - default: - playbackStatusStr = "stopped"; - break; - } - - return ` ${pl.busName}: ${playbackStatusStr}`; - }).join('\n')}`; - - case "pause": - activePlayer.pause(); - return "Paused"; - - case "play-pause": - activePlayer.play_pause(); - return activePlayer?.playbackStatus === AstalMpris.PlaybackStatus.PAUSED ? - "Toggled play" - : "Toggled pause"; - - case "stop": - activePlayer.stop(); - return "Stopped!"; - - case "previous": - activePlayer.canGoPrevious && activePlayer.previous(); - return activePlayer.canGoPrevious ? - "Back to previous" - : "Player does not support this command"; - - case "next": - activePlayer.canGoNext && activePlayer.next(); - return activePlayer.canGoNext ? - "Jump to next" - : "Player does not support this command"; - - case "bus-name": - return activePlayer.busName; - - case "select": - if(!args[2] || !players.filter(pl => pl.busName == args[2])?.[0]) - return `Error: either no player was specified or the player with specified bus name does not exist/is not available!`; - - setPlayer(players.filter(pl => pl.busName === args[2])[0]); - return `Done setting player to \`${args[2]}\`!` - } - - return "Error: couldn't handle media arguments, try checking `media help`"; -} - -function handleWindowArgs(args: Array): string { - switch(args[0]) { - case "reopen": - Windows.getDefault().reopen(); - return "Reopening all open windows"; - - case "windows": - return Object.keys(Windows.getDefault().windows).map(name => - `${name}: ${Windows.getDefault().isOpen(name) ? "open" : "closed" }`).join('\n'); - } - - const specifiedWindow: string = args[1]; - - if(!specifiedWindow) - return "Error: window argument not specified!"; - - if(!Windows.getDefault().hasWindow(specifiedWindow)) - return `Error: "${specifiedWindow}" not found on window list! Make sure to add new windows to the system before using them`; - - switch(args[0]) { - case "open": - if(!Windows.getDefault().isOpen(specifiedWindow)) { - Windows.getDefault().open(specifiedWindow); - return `Opening window with name "${args[1]}"`; - } - - return `Window is already open, ignored`; - - case "close": - if(Windows.getDefault().isOpen(specifiedWindow)) { - Windows.getDefault().close(specifiedWindow); - return `Closing window with name "${args[1]}"`; - } - - return `Window is already closed, ignored`; - - case "toggle": - if(!Windows.getDefault().isOpen(specifiedWindow)) { - Windows.getDefault().open(specifiedWindow); - return `Toggle opening window "${args[1]}"`; - } - - Windows.getDefault().close(specifiedWindow); - return `Toggle closing window "${args[1]}"`; - } - - return "Couldn't handle window management arguments"; -} - -function handleVolumeArgs(args: Array) { - if(!args[1]) - return `Please specify what you want to do!\n\n${volumeHelp()}`; - - if(/^(sink|source)(\-increase|\-decrease|\-set)$/.test(args[1]) && !args[2]) - return `You forgot to add a value to be set!`; - - if(Number.isNaN(Number.parseFloat(args[2])) && Number.isSafeInteger(Number.parseFloat(args[2]))) - return `Argument "${args[2]} is not a valid number! Please use integers"`; - - const command: Array = args[1].split('-'); - - if(args[1] === "help") - return volumeHelp(); - - switch(command[1]) { - case "set": - command[0] === "sink" ? - Wireplumber.getDefault().setSinkVolume(Number.parseInt(args[2])) - : Wireplumber.getDefault().setSourceVolume(Number.parseInt(args[2])) - return `Done! Set ${command[0]} volume to ${args[2]}`; - - case "mute": - command[0] === "sink" ? - Wireplumber.getDefault().toggleMuteSink() - : Wireplumber.getDefault().toggleMuteSource() - - return `Done toggling mute!`; - - case "increase": - command[0] === "sink" ? - Wireplumber.getDefault().increaseSinkVolume(Number.parseInt(args[2])) - : Wireplumber.getDefault().increaseSourceVolume(Number.parseInt(args[2])) - - generalConfig.getProperty("misc.play_bell_on_volume_change", "boolean") === true && - playSystemBell(); - - return `Done increasing volume by ${args[2]}`; - - case "decrease": - command[0] === "sink" ? - Wireplumber.getDefault().decreaseSinkVolume(Number.parseInt(args[2])) - : Wireplumber.getDefault().decreaseSourceVolume(Number.parseInt(args[2])) - - generalConfig.getProperty("misc.play_bell_on_volume_change", "boolean") === true && - playSystemBell(); - - return `Done decreasing volume to ${args[2]}`; - } - - return `Couldn't resolve arguments! "${args.join(' ').replace(new RegExp(`^${args[0]}`), "")}"`; - - function volumeHelp(): string { - return ` -Control speaker and microphone volumes -Options: - (sink|source)-set [number]: set speaker/microphone volume. - (sink|source)-mute: toggle mute for the speaker/microphone device. - (sink|source)-increase [number]: increases speaker/microphone volume. - (sink|source)-decrease [number]: decreases speaker/microphone volume. -`.trim(); - } -} - -function getHelp(): string { - return `Manage Astal Windows and do more stuff. From retrozinndev's colorshell, +let wsTimeout: AstalIO.Time|undefined; +const help = `Manage Astal Windows and do more stuff. From retrozinndev's colorshell, made using Astal Libraries, AGS and Gnim by Aylur. Window Management: @@ -273,6 +24,7 @@ made using Astal Libraries, AGS and Gnim by Aylur. windows: list shell windows and their respective status. reload: quit this instance and start a new one. reopen: restart all open-windows. + quit: exit the main instance of the shell. Audio Controls: volume: speaker and microphone volume controller, see "volume help". @@ -288,4 +40,325 @@ made using Astal Libraries, AGS and Gnim by Aylur. 2025 (c) retrozinndev's colorshell, licensed under the MIT License. https://github.com/retrozinndev/colorshell `.split('\n').map(l => l.replace(/^ {8}/, "")).join('\n'); + +export function handleArguments(cmd: Gio.ApplicationCommandLine, args: Array): number { + switch(args[0]) { + case "help": + case "h": + cmd.print_literal(help); + return 0; + + case "open": + case "close": + case "toggle": + case "windows": + case "reopen": + return handleWindowArgs(cmd, args); + + case "volume": + return handleVolumeArgs(cmd, args); + + case "media": + return handleMediaArgs(cmd, args); + + case "reload": + restartInstance(); + cmd.print_literal("Restarting instance..."); + return 0; + + case "quit": + try { + Shell.getDefault().quit(); + cmd.print_literal("Quitting main instance..."); + } catch(_e) { + const e = _e as Error; + cmd.printerr_literal(`Error: couldn't quit instance. Stderr: ${e.message}\n${e.stack}`); + return 1; + } + return 0; + + case "runner": + !Runner.instance ? + Runner.openDefault(args[1] || undefined) + : Runner.close(); + + cmd.print_literal(`Opening runner${args[1] ? ` with predefined text: "${args[1]}"` : ""}`); + return 0; + + case "peek-workspace-num": + if(wsTimeout) { + cmd.print_literal("Workspace numbers are already showing"); + return 0; + } + + showWorkspaceNumber(true); + wsTimeout = timeout(Number.parseInt(args[1]) || 2200, () => { + showWorkspaceNumber(false); + wsTimeout = undefined; + }); + cmd.print_literal("Toggled workspace numbers"); + return 0; + } + + cmd.printerr_literal("Error: command not found! try checking help"); + return 1; +} + +function handleMediaArgs(cmd: Gio.ApplicationCommandLine, args: Array): number { + if(/h|help/.test(args[1])) { + const mediaHelp = ` +Manage colorshell's active player + +Options: + play: resume/start active player's media. + pause: pause the active player. + play-pause: toggle play/pause on active player. + stop: stop the active player's media. + previous: go back to previous media if player supports it. + next: jump to next media if player supports it. + bus-name: get active player's mpris bus name. + list: show available players with their bus name. + select bus_name: change the active player, where bus_name is + the desired player's mpris bus name(with the mediaplayer2 prefix). +`.trim(); + cmd.print_literal(mediaHelp); + return 0; + } + + const activePlayer: AstalMpris.Player|undefined = player.get().available ? + player.get() + : undefined; + const players = AstalMpris.get_default().players.filter(pl => pl.available); + + if(!activePlayer) { + cmd.printerr_literal(`Error: no active player found! try playing some media first`); + return 1; + } + + switch(args[1]) { + case "play": + activePlayer.play(); + cmd.print_literal("Playing"); + return 0; + + case "list": + cmd.print_literal(`Available players:\n${players.map(pl => { + let playbackStatusStr: string; + switch(pl.playbackStatus) { + case AstalMpris.PlaybackStatus.PAUSED: + playbackStatusStr = "paused"; + break; + case AstalMpris.PlaybackStatus.PLAYING: + playbackStatusStr = "playing"; + break; + default: + playbackStatusStr = "stopped"; + break; + } + + return ` ${pl.busName}: ${playbackStatusStr}`; + }).join('\n')}`); + return 0; + + case "pause": + activePlayer.pause(); + cmd.print_literal("Paused"); + return 0; + + case "play-pause": + activePlayer.play_pause(); + cmd.print_literal( + activePlayer?.playbackStatus === AstalMpris.PlaybackStatus.PAUSED ? + "Toggled play" + : "Toggled pause" + ); + return 0; + + case "stop": + activePlayer.stop(); + cmd.print_literal("Stopped!"); + return 0; + + case "previous": + activePlayer.canGoPrevious && activePlayer.previous(); + cmd.print_literal( + activePlayer.canGoPrevious ? + "Back to previous" + : "Player does not support this command" + ); + return 0; + + case "next": + activePlayer.canGoNext && activePlayer.next(); + cmd.print_literal( + activePlayer.canGoNext ? + "Jump to next" + : "Player does not support this command" + ); + return 0; + + case "bus-name": + cmd.print_literal(activePlayer.busName); + return 0; + + case "select": + if(!args[2] || !players.filter(pl => pl.busName == args[2])?.[0]) { + cmd.printerr_literal(`Error: either no player was specified or the player with \ +specified bus name does not exist/is not available!`); + + return 1; + } + + setPlayer(players.filter(pl => pl.busName === args[2])[0]); + cmd.print_literal(`Done setting player to \`${args[2]}\`!`); + return 0; + } + + cmd.printerr_literal("Error: couldn't handle media arguments, try checking `media help`"); + return 1; +} + +function handleWindowArgs(cmd: Gio.ApplicationCommandLine, args: Array): number { + switch(args[0]) { + case "reopen": + Windows.getDefault().reopen(); + cmd.print_literal("Reopening all open windows"); + return 0; + + case "windows": + cmd.print_literal( + Object.keys(Windows.getDefault().windows).map(name => + `${name}: ${Windows.getDefault().isOpen(name) ? + "open" + : "closed"}` + ).join('\n') + ); + return 0; + } + + const specifiedWindow: string = args[1]; + + if(!specifiedWindow) { + cmd.printerr_literal("Error: window argument not specified!"); + return 1; + } + + if(!Windows.getDefault().hasWindow(specifiedWindow)) { + cmd.printerr_literal( + `Error: "${specifiedWindow}" not found on window list! Make sure to add new windows to the system before using them` + ); + return 1; + } + + switch(args[0]) { + case "open": + if(!Windows.getDefault().isOpen(specifiedWindow)) { + Windows.getDefault().open(specifiedWindow); + cmd.print_literal(`Opening window with name "${args[1]}"`); + return 0; + } + + cmd.print_literal(`Window is already open, ignored`); + return 0; + + case "close": + if(Windows.getDefault().isOpen(specifiedWindow)) { + Windows.getDefault().close(specifiedWindow); + cmd.print_literal(`Closing window with name "${args[1]}"`); + return 0; + } + + cmd.print_literal(`Window is already closed, ignored`); + return 0; + + case "toggle": + if(!Windows.getDefault().isOpen(specifiedWindow)) { + Windows.getDefault().open(specifiedWindow); + cmd.print_literal(`Toggle opening window "${args[1]}"`); + return 0; + } + + Windows.getDefault().close(specifiedWindow); + cmd.print_literal(`Toggle closing window "${args[1]}"`); + return 0; + } + + cmd.printerr_literal("Couldn't handle window management arguments"); + return 1; +} + +function handleVolumeArgs(cmd: Gio.ApplicationCommandLine, args: Array): number { + if(!args[1]) { + cmd.printerr_literal(`Error: please specify what to do! see \`volume help\``); + return 1; + } + + if(/^(sink|source)[-](increase|decrease|set)$/.test(args[1]) && !args[2]) { + cmd.printerr_literal(`Error: you forgot to set a value`); + return 1; + } + + if(Number.isNaN(Number.parseFloat(args[2]))) { + cmd.printerr_literal(`Error: argument "${args[2]} is not a valid number! Please use integers"`); + return 1; + } + + const command: Array = args[1].split('-'); + + if(/h|help/.test(args[1])) { + cmd.print_literal(` +Control speaker and microphone volumes +Options: + (sink|source)-set [number]: set speaker/microphone volume. + (sink|source)-mute: toggle mute for the speaker/microphone device. + (sink|source)-increase [number]: increases speaker/microphone volume. + (sink|source)-decrease [number]: decreases speaker/microphone volume. +`.trim()); + + return 0; + } + + switch(command[1]) { + case "set": + command[0] === "sink" ? + Wireplumber.getDefault().setSinkVolume(Number.parseInt(args[2])) + : Wireplumber.getDefault().setSourceVolume(Number.parseInt(args[2])) + cmd.print_literal(`Done! Set ${command[0]} volume to ${args[2]}`); + return 0; + + case "mute": + command[0] === "sink" ? + Wireplumber.getDefault().toggleMuteSink() + : Wireplumber.getDefault().toggleMuteSource() + + cmd.print_literal(`Done toggling mute!`); + return 0; + + case "increase": + command[0] === "sink" ? + Wireplumber.getDefault().increaseSinkVolume(Number.parseInt(args[2])) + : Wireplumber.getDefault().increaseSourceVolume(Number.parseInt(args[2])) + + generalConfig.getProperty("misc.play_bell_on_volume_change", "boolean") === true && + playSystemBell(); + + cmd.print_literal(`Done increasing volume by ${args[2]}`); + return 0; + + case "decrease": + command[0] === "sink" ? + Wireplumber.getDefault().decreaseSinkVolume(Number.parseInt(args[2])) + : Wireplumber.getDefault().decreaseSourceVolume(Number.parseInt(args[2])) + + generalConfig.getProperty("misc.play_bell_on_volume_change", "boolean") === true && + playSystemBell(); + + cmd.print_literal(`Done decreasing volume to ${args[2]}`); + return 0; + } + + cmd.printerr_literal(`Error: couldn't resolve arguments! "${args.join(' ') + .replace(new RegExp(`^${args[0]}`), "")}"`); + + return 1; } diff --git a/src/scripts/reload-handler.ts b/src/scripts/reload-handler.ts index e5e43fd..18161f2 100644 --- a/src/scripts/reload-handler.ts +++ b/src/scripts/reload-handler.ts @@ -1,27 +1,15 @@ -import { monitorFile } from "ags/file"; -import { execAsync } from "ags/process"; import { uwsmIsActive } from "./apps"; import Gio from "gi://Gio?version=2.0"; +import { Shell } from "../app"; -const monitoringPaths = [ "./scripts", "./window", "./app.ts", "env.d.ts" ]; - export function restartInstance(): void { - execAsync(`astal -q "colorshell"`); Gio.Subprocess.new( ( uwsmIsActive ? - [ "uwsm", "app", "--", "ags", "run" ] - : [ "ags", "run" ]), + [ "uwsm", "app", "--", "colorshell" ] + : [ "colorshell" ]), Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE ); -} - -export function monitorPaths(): void { - monitoringPaths.map((path: string) => { - monitorFile( - path, - () => restartInstance() - ) - }); + Shell.getDefault().quit(); } diff --git a/src/style/_functions.scss b/src/style/_functions.scss deleted file mode 100644 index 2f0382d..0000000 --- a/src/style/_functions.scss +++ /dev/null @@ -1,14 +0,0 @@ -@use "sass:color"; - - -/** - * GTK3 only supports sRGB color space, unfortunately - */ -@function toRGB($color) { - @return rgba( - color.channel($color, "red"), - color.channel($color, "green"), - color.channel($color, "blue"), - color.alpha($color) - ); -}