Compare commits
12 Commits
2f106a527e
...
ryo
| Author | SHA1 | Date | |
|---|---|---|---|
| d1a9dc907f | |||
| 272a5ff4be | |||
| 186f6ddc82 | |||
| fa9367c43a | |||
| c8d6711466 | |||
| f50cc9928f | |||
| b42ad1fa8f | |||
| e14707ec48 | |||
| 523af750b0 | |||
| e05dab3ed6 | |||
| f9e65c6b5b | |||
| cd8a39fc9f |
@@ -1,5 +1,9 @@
|
|||||||
# colorshell
|
# colorshell
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> **This is a fork of [retrozinndev/colorshell](https://github.com/retrozinndev/colorshell)**
|
||||||
|
> This repository contains custom modifications for use with NixOS. For the original project, please visit the [upstream repository](https://github.com/retrozinndev/colorshell).
|
||||||
|
|
||||||
> [!note]
|
> [!note]
|
||||||
> My personal dotfiles are now on [retrozinndev/Hyprland-Dots](https://github.com/retrozinndev/Hyprland-Dots)
|
> My personal dotfiles are now on [retrozinndev/Hyprland-Dots](https://github.com/retrozinndev/Hyprland-Dots)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ if uwsm check is-active; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $1 =~ [.]desktop$ ]]; then
|
if [[ $1 =~ [.]desktop$ ]]; then
|
||||||
gtk-launch $@
|
gio launch $@
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
colorshell = pkgs.callPackage ./nix/package.nix { inherit inputs'; };
|
colorshell = pkgs.callPackage ./nix/colorshell.nix { inherit inputs'; };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages = {
|
packages = {
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
{
|
||||||
|
inputs',
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
stdenvNoCC,
|
||||||
|
moreutils,
|
||||||
|
pnpm_10,
|
||||||
|
buildNpmPackage,
|
||||||
|
wrapGAppsHook4,
|
||||||
|
gobject-introspection,
|
||||||
|
glib,
|
||||||
|
gjs,
|
||||||
|
libadwaita,
|
||||||
|
pywal16,
|
||||||
|
dart-sass,
|
||||||
|
psmisc,
|
||||||
|
socat,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
packageJSON = lib.importJSON ../package.json;
|
||||||
|
pname = packageJSON.name;
|
||||||
|
version = packageJSON.version;
|
||||||
|
|
||||||
|
# Cleaned sources from this repository
|
||||||
|
src = lib.fileset.toSource {
|
||||||
|
root = ../.;
|
||||||
|
fileset = lib.fileset.difference ../. (
|
||||||
|
lib.fileset.unions [
|
||||||
|
../flake.nix
|
||||||
|
../flake.lock
|
||||||
|
(lib.fileset.maybeMissing ../result)
|
||||||
|
(lib.fileset.maybeMissing ../build)
|
||||||
|
./.
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Derivation building just the gresources file
|
||||||
|
colorshellResources = stdenv.mkDerivation {
|
||||||
|
pname = "${pname}-resources.gresource";
|
||||||
|
inherit version;
|
||||||
|
|
||||||
|
inherit src;
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
glib
|
||||||
|
];
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
glib-compile-resources resources.gresource.xml \
|
||||||
|
--sourcedir ./resources \
|
||||||
|
--target resources.gresource
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
cp resources.gresource $out
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Cleaned sources, with FHS paths patched out.
|
||||||
|
colorshellSrc = stdenvNoCC.mkDerivation {
|
||||||
|
pname = "${pname}-src";
|
||||||
|
inherit version;
|
||||||
|
|
||||||
|
inherit src;
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
# Copy the ags JS lib from the Nix store into the source tree so pnpm can
|
||||||
|
# treat it as a local file: dependency (no /nix/store path inside pnpm).
|
||||||
|
cp -R ${inputs'.ags.packages.ags.jsPackage} ags-js-lib
|
||||||
|
|
||||||
|
# Point the devDependency at the local copy instead of the FHS path.
|
||||||
|
substituteInPlace package.json \
|
||||||
|
--replace-fail "file:/usr/share/ags/js" "file:./ags-js-lib"
|
||||||
|
|
||||||
|
# Update the lockfile to reference the local copy as well.
|
||||||
|
# We need to keep specifiers in sync with package.json to satisfy
|
||||||
|
# pnpm's frozen-lockfile check.
|
||||||
|
substituteInPlace pnpm-lock.yaml \
|
||||||
|
--replace-fail "file:/usr/share/ags/js" "file:./ags-js-lib" \
|
||||||
|
--replace-fail "../../../../usr/share/ags/js" "./ags-js-lib"
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir $out
|
||||||
|
cp -rp * $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
buildNpmPackage (finalAttrs: {
|
||||||
|
inherit pname version;
|
||||||
|
|
||||||
|
src = colorshellSrc;
|
||||||
|
sourceRoot = "${finalAttrs.src.name}";
|
||||||
|
|
||||||
|
npmConfigHook = pnpm_10.configHook;
|
||||||
|
npmDeps = finalAttrs.pnpmDeps;
|
||||||
|
pnpmDeps = pnpm_10.fetchDeps {
|
||||||
|
inherit (finalAttrs)
|
||||||
|
pname
|
||||||
|
version
|
||||||
|
src
|
||||||
|
sourceRoot
|
||||||
|
;
|
||||||
|
|
||||||
|
fetcherVersion = 2;
|
||||||
|
# Hash updated after local pnpmDeps build
|
||||||
|
hash = "sha256-m/aPNvv26r0DUvRUR4TL2GwwAHKvEIkc8Nvlm/jpnPc=";
|
||||||
|
|
||||||
|
# fetcher version 2 fails if there are no *-exec files in the output
|
||||||
|
preFixup = ''
|
||||||
|
touch $out/.dummy-exec
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
wrapGAppsHook4
|
||||||
|
gobject-introspection
|
||||||
|
inputs'.ags.packages.default
|
||||||
|
moreutils
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
glib
|
||||||
|
gjs
|
||||||
|
libadwaita
|
||||||
|
inputs'.astal.packages.astal4
|
||||||
|
inputs'.astal.packages.apps
|
||||||
|
inputs'.astal.packages.auth
|
||||||
|
inputs'.astal.packages.battery
|
||||||
|
inputs'.astal.packages.bluetooth
|
||||||
|
inputs'.astal.packages.hyprland
|
||||||
|
inputs'.astal.packages.io
|
||||||
|
inputs'.astal.packages.mpris
|
||||||
|
inputs'.astal.packages.network
|
||||||
|
inputs'.astal.packages.notifd
|
||||||
|
inputs'.astal.packages.tray
|
||||||
|
inputs'.astal.packages.wireplumber
|
||||||
|
];
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
# Allow incremental or repeated builds: don't fail if ./build already exists
|
||||||
|
mkdir -p build
|
||||||
|
outPath=./build/${packageJSON.name}
|
||||||
|
ags bundle ./src/app.ts $outPath \
|
||||||
|
--gtk 4 \
|
||||||
|
--root ./src \
|
||||||
|
--define "DEVEL=false" \
|
||||||
|
--define "COLORSHELL_VERSION='${finalAttrs.version}'" \
|
||||||
|
--define "GRESOURCES_FILE='${colorshellResources}'"
|
||||||
|
|
||||||
|
# add socket-communication support on executable
|
||||||
|
{
|
||||||
|
head -n1 $outPath
|
||||||
|
sed '1{/^#!.*$/d}' ${../scripts/socket.sh}
|
||||||
|
cat "$outPath" | sed '/^#!.*$/d'
|
||||||
|
} | sponge $outPath
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp -rp build/${packageJSON.name} $out/bin/
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
preFixup = ''
|
||||||
|
gappsWrapperArgs+=(
|
||||||
|
--prefix PATH : ${
|
||||||
|
lib.makeBinPath [
|
||||||
|
# runtime executables
|
||||||
|
pywal16 # provides `wal` for colorshell's wallpaper module
|
||||||
|
dart-sass
|
||||||
|
glib
|
||||||
|
psmisc
|
||||||
|
socat
|
||||||
|
]
|
||||||
|
}
|
||||||
|
--prefix GI_TYPELIB_PATH : "${
|
||||||
|
lib.makeSearchPath "lib/girepository-1.0" (with inputs'.astal.packages; [
|
||||||
|
astal4
|
||||||
|
apps
|
||||||
|
auth
|
||||||
|
battery
|
||||||
|
bluetooth
|
||||||
|
hyprland
|
||||||
|
io
|
||||||
|
mpris
|
||||||
|
network
|
||||||
|
notifd
|
||||||
|
tray
|
||||||
|
wireplumber
|
||||||
|
])
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
|
||||||
|
passthru = {
|
||||||
|
resources = colorshellResources;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
lib,
|
lib,
|
||||||
stdenv,
|
stdenv,
|
||||||
stdenvNoCC,
|
stdenvNoCC,
|
||||||
|
moreutils,
|
||||||
pnpm_10,
|
pnpm_10,
|
||||||
buildNpmPackage,
|
buildNpmPackage,
|
||||||
wrapGAppsHook4,
|
wrapGAppsHook4,
|
||||||
@@ -10,6 +11,8 @@
|
|||||||
glib,
|
glib,
|
||||||
gjs,
|
gjs,
|
||||||
libadwaita,
|
libadwaita,
|
||||||
|
dart-sass,
|
||||||
|
socat,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
packageJSON = lib.importJSON ../package.json;
|
packageJSON = lib.importJSON ../package.json;
|
||||||
@@ -94,7 +97,7 @@ buildNpmPackage (finalAttrs: {
|
|||||||
;
|
;
|
||||||
|
|
||||||
fetcherVersion = 2;
|
fetcherVersion = 2;
|
||||||
hash = "sha256-m/aPNvv26r0DUvRUR4TL2GwwAHKvEIkc8Nvlm/jpnPc=";
|
hash = "sha256-Z5JP7hPEjLY9wGnWe6kM6T1qk3UUSlJnoxdDqS/ksnw=";
|
||||||
|
|
||||||
# fetcher version 2 fails if there are no *-exec files in the output
|
# fetcher version 2 fails if there are no *-exec files in the output
|
||||||
preFixup = ''
|
preFixup = ''
|
||||||
@@ -106,6 +109,7 @@ buildNpmPackage (finalAttrs: {
|
|||||||
wrapGAppsHook4
|
wrapGAppsHook4
|
||||||
gobject-introspection
|
gobject-introspection
|
||||||
inputs'.ags.packages.default
|
inputs'.ags.packages.default
|
||||||
|
moreutils
|
||||||
];
|
];
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
@@ -129,25 +133,42 @@ buildNpmPackage (finalAttrs: {
|
|||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
runHook preBuild
|
runHook preBuild
|
||||||
|
|
||||||
mkdir -p $out/bin
|
mkdir build
|
||||||
ags bundle ./src/app.ts $out/bin/${packageJSON.name} \
|
outPath=./build/${packageJSON.name}
|
||||||
|
ags bundle ./src/app.ts $outPath \
|
||||||
--gtk 4 \
|
--gtk 4 \
|
||||||
--root ./src \
|
--root ./src \
|
||||||
--define "DEVEL=false" \
|
--define "DEVEL=false" \
|
||||||
--define "COLORSHELL_VERSION='${finalAttrs.version}'" \
|
--define "COLORSHELL_VERSION='${finalAttrs.version}'" \
|
||||||
--define "GRESOURCES_FILE='${colorshellResources}'"
|
--define "GRESOURCES_FILE='${colorshellResources}'"
|
||||||
|
|
||||||
|
# add socket-communication support on executable
|
||||||
|
{
|
||||||
|
head -n1 $outPath
|
||||||
|
sed '1{/^#!.*$/d}' ${../scripts/socket.sh}
|
||||||
|
cat "$outPath" | sed '/^#!.*$/d'
|
||||||
|
} | sponge $outPath
|
||||||
|
|
||||||
runHook postBuild
|
runHook postBuild
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# the above buildPhase installs for us
|
installPhase = ''
|
||||||
dontInstall = true;
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp -rp build/${packageJSON.name} $out/bin/
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
preFixup = ''
|
preFixup = ''
|
||||||
gappsWrapperArgs+=(
|
gappsWrapperArgs+=(
|
||||||
--prefix PATH : ${
|
--prefix PATH : ${
|
||||||
lib.makeBinPath [
|
lib.makeBinPath [
|
||||||
# runtime executables
|
# runtime executables
|
||||||
|
dart-sass
|
||||||
|
glib
|
||||||
|
socat
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -7,6 +7,9 @@
|
|||||||
padding: 28px;
|
padding: 28px;
|
||||||
background: colors.$bg-translucent;
|
background: colors.$bg-translucent;
|
||||||
border-radius: $radius $radius 0 0;
|
border-radius: $radius $radius 0 0;
|
||||||
|
max-width: 1600px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
& entry {
|
& entry {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -22,29 +25,46 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
& flowbox {
|
& flowbox {
|
||||||
padding: 16px 24px;
|
padding: 16px 36px;
|
||||||
|
|
||||||
& > flowboxchild {
|
& > flowboxchild {
|
||||||
& > button {
|
& > button {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
color.change($color: colors.$bg-primary, $alpha: 0.9),
|
||||||
|
color.change($color: colors.$bg-secondary, $alpha: 0.7)
|
||||||
|
);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
& image {
|
& image {
|
||||||
-gtk-icon-size: 64px;
|
-gtk-icon-size: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
& label {
|
& label.app-name {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
|
font-size: 16px;
|
||||||
text-shadow: 1px 1px 1px rgba(colors.$bg-primary, .2);
|
text-shadow: 1px 1px 1px rgba(colors.$bg-primary, .2);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus > button,
|
&:focus > button,
|
||||||
&:selected > button,
|
&:selected > button {
|
||||||
& > button:hover {
|
background: linear-gradient(
|
||||||
background-color: rgba($color: colors.$bg-secondary, $alpha: .5);
|
135deg,
|
||||||
|
color.change($color: colors.$bg-secondary, $alpha: 0.95),
|
||||||
|
color.change($color: colors.$bg-tertiary, $alpha: 0.8)
|
||||||
|
);
|
||||||
|
border-color: colors.$bg-tertiary;
|
||||||
|
box-shadow: 0 0 0 1px colors.$bg-tertiary;
|
||||||
|
|
||||||
|
& label.app-name {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@use "sass:color";
|
||||||
@use "./colors";
|
@use "./colors";
|
||||||
|
|
||||||
.runner .popup-window-container {
|
.runner .popup-window-container {
|
||||||
@@ -29,16 +30,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& scrolledwindow {
|
|
||||||
margin: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
& list {
|
& list {
|
||||||
|
padding: 0 12px;
|
||||||
& .result {
|
& .result {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: colors.$bg-primary;
|
background: linear-gradient(
|
||||||
margin: 2px 0;
|
135deg,
|
||||||
border-radius: 14px;
|
color.change($color: colors.$bg-primary, $alpha: 0.9),
|
||||||
|
color.change($color: colors.$bg-secondary, $alpha: 0.7)
|
||||||
|
);
|
||||||
|
margin: 6px 0;
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
& image {
|
& image {
|
||||||
-gtk-icon-size: 28px;
|
-gtk-icon-size: 28px;
|
||||||
@@ -47,7 +50,7 @@
|
|||||||
|
|
||||||
& .title {
|
& .title {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 16px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .description {
|
& .description {
|
||||||
@@ -57,17 +60,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
& > *:selected .result,
|
& > *:selected .result,
|
||||||
& > *:active .result,
|
& > *:active .result {
|
||||||
& > *:hover .result {
|
background: linear-gradient(
|
||||||
background: colors.$bg-secondary;
|
135deg,
|
||||||
}
|
color.change($color: colors.$bg-secondary, $alpha: 0.95),
|
||||||
|
color.change($color: colors.$bg-tertiary, $alpha: 0.8)
|
||||||
|
);
|
||||||
|
|
||||||
& > *:first-child {
|
border-color: colors.$bg-tertiary;
|
||||||
margin-top: 12px;
|
box-shadow: 0 0 0 1px colors.$bg-tertiary;
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
& .title {
|
||||||
margin-bottom: 0;
|
font-weight: 700;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-13
@@ -43,19 +43,7 @@ sh ./scripts/build.sh -o "${outdir:-./build/release}" -b -r "${gresource_file:-\
|
|||||||
if [[ $socket_support ]]; then
|
if [[ $socket_support ]]; then
|
||||||
echo "[info] adding socket communication support"
|
echo "[info] adding socket communication support"
|
||||||
script="\
|
script="\
|
||||||
#!/usr/bin/bash
|
`cat ./scripts/colorshell-socket-interface.sh`
|
||||||
|
|
||||||
if gdbus introspect --session \\
|
|
||||||
--dest io.github.retrozinndev.colorshell \\
|
|
||||||
--object-path /io/github/retrozinndev/colorshell > /dev/null 2>&1; then
|
|
||||||
|
|
||||||
if command -v socat > /dev/null 2>&1; then
|
|
||||||
echo \"\$@\" | socat - \"\${XDG_RUNTIME_DIR:-/run/user/\$(id -u)}/colorshell.sock\"
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo \"[warn] \`socat\` not installed, falling back to remote instance communication\"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
`cat "${outdir:-./build/release}/colorshell" | sed -e 's/^#.*//'`" # remove shebang
|
`cat "${outdir:-./build/release}/colorshell" | sed -e 's/^#.*//'`" # remove shebang
|
||||||
|
|
||||||
echo -en "$script" > "${outdir:-./build/release}/colorshell"
|
echo -en "$script" > "${outdir:-./build/release}/colorshell"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if gdbus introspect --session \
|
||||||
|
--dest io.github.retrozinndev.colorshell \
|
||||||
|
--object-path /io/github/retrozinndev/colorshell > /dev/null 2>&1; then
|
||||||
|
|
||||||
|
if command -v socat > /dev/null 2>&1; then
|
||||||
|
echo "$@" | socat - "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/colorshell.sock"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "[warn] \`socat\` not installed, falling back to remote instance communication"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
+11
-2
@@ -5,10 +5,19 @@ file="${1:-./build/colorshell}"
|
|||||||
function start() {
|
function start() {
|
||||||
if Is_running; then
|
if Is_running; then
|
||||||
echo "[info] killing previous instance"
|
echo "[info] killing previous instance"
|
||||||
colorshell quit || killall gjs
|
colorshell quit || killall gjs 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
echo "[info] starting"
|
echo "[info] starting"
|
||||||
exec "$file"
|
|
||||||
|
# Always run through nix develop to ensure proper environment variables are set
|
||||||
|
# This is needed because the manually built executable doesn't have wrapGAppsHook
|
||||||
|
if command -v nix > /dev/null 2>&1; then
|
||||||
|
# Use nix develop -c to run with proper environment
|
||||||
|
# The -c flag runs the command in the devshell environment
|
||||||
|
exec nix develop -c bash -c "exec \"$file\" \"\$@\"" -- "$@"
|
||||||
|
else
|
||||||
|
exec "$file" "$@"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ -f $file ]]; then
|
if [[ -f $file ]]; then
|
||||||
|
|||||||
+67
-14
@@ -67,7 +67,7 @@ export class Shell extends Adw.Application {
|
|||||||
super({
|
super({
|
||||||
applicationId: "io.github.retrozinndev.colorshell",
|
applicationId: "io.github.retrozinndev.colorshell",
|
||||||
flags: Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
|
flags: Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
|
||||||
version: COLORSHELL_VERSION ?? "0.0.0-unknown",
|
version: (typeof COLORSHELL_VERSION !== "undefined" ? COLORSHELL_VERSION : "0.0.0-unknown"),
|
||||||
});
|
});
|
||||||
|
|
||||||
setConsoleLogDomain("Colorshell");
|
setConsoleLogDomain("Colorshell");
|
||||||
@@ -168,21 +168,74 @@ you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster re
|
|||||||
private init(): void {
|
private init(): void {
|
||||||
// load gresource from build-defined path
|
// load gresource from build-defined path
|
||||||
try {
|
try {
|
||||||
const gresourcesPath: string = GRESOURCES_FILE.startsWith('/') ? GRESOURCES_FILE : (GRESOURCES_FILE.split('/').filter(s =>
|
// Handle missing GRESOURCES_FILE define (when running via AGS Home Manager)
|
||||||
s !== ""
|
if (typeof GRESOURCES_FILE === "undefined" || !GRESOURCES_FILE) {
|
||||||
).map(path => {
|
// Try to find gresource in common locations
|
||||||
// support environment variables at runtime
|
// When symlinked via Home Manager, configDir is ~/.config/ags -> ../colorshell/src
|
||||||
if(/^\$/.test(path)) {
|
// We need to resolve the actual path of the colorshell directory
|
||||||
const env = GLib.getenv(path.replace(/^\$/, ""));
|
const configDir = GLib.get_user_config_dir();
|
||||||
if(env === null)
|
const agsConfigFile = Gio.File.new_for_path(`${configDir}/ags/app.ts`);
|
||||||
throw new Error(`Couldn't get environment variable: ${path}`);
|
let colorshellBuildPath: string | null = null;
|
||||||
|
|
||||||
return env;
|
// Try to resolve the symlink to find the actual colorshell directory
|
||||||
|
// When Home Manager symlinks ~/.config/ags -> ../colorshell/src,
|
||||||
|
// we need to go up one level from src to find the colorshell root
|
||||||
|
try {
|
||||||
|
// Get the real path of app.ts (resolves symlinks)
|
||||||
|
const appTsFile = Gio.File.new_for_path(`${configDir}/ags/app.ts`);
|
||||||
|
if (appTsFile.query_exists(null)) {
|
||||||
|
const realPath = appTsFile.get_path()!;
|
||||||
|
// realPath will be something like /home/olivier/NixOS-New/colorshell/src/app.ts
|
||||||
|
// Go up from src/ to get colorshell root, then to build/
|
||||||
|
const srcDir = GLib.path_get_dirname(realPath);
|
||||||
|
const colorshellDir = GLib.path_get_dirname(srcDir);
|
||||||
|
colorshellBuildPath = `${colorshellDir}/build/resources.gresource`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// If symlink resolution fails, try absolute paths
|
||||||
}
|
}
|
||||||
return path;
|
|
||||||
}).join('/'));
|
const possiblePaths = [
|
||||||
this.#gresource = Gio.Resource.load(gresourcesPath);
|
colorshellBuildPath,
|
||||||
Gio.resources_register(this.#gresource);
|
`${GLib.get_home_dir()}/NixOS-New/colorshell/build/resources.gresource`, // Absolute path (most reliable)
|
||||||
|
`${configDir}/../colorshell/build/resources.gresource`, // Relative to ~/.config/ags
|
||||||
|
`${GLib.get_user_config_dir()}/colorshell/build/resources.gresource`,
|
||||||
|
`${GLib.get_home_dir()}/.config/colorshell/build/resources.gresource`,
|
||||||
|
].filter(p => p !== null) as string[];
|
||||||
|
|
||||||
|
let gresourcesPath: string | null = null;
|
||||||
|
for (const path of possiblePaths) {
|
||||||
|
const file = Gio.File.new_for_path(path);
|
||||||
|
if (file.query_exists(null)) {
|
||||||
|
gresourcesPath = path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gresourcesPath) {
|
||||||
|
console.warn("Colorshell: GRESOURCES_FILE not defined and couldn't find gresource file. Icons may not be available.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#gresource = Gio.Resource.load(gresourcesPath);
|
||||||
|
Gio.resources_register(this.#gresource);
|
||||||
|
} else {
|
||||||
|
const gresourcesPath: string = GRESOURCES_FILE.startsWith('/') ? GRESOURCES_FILE : (GRESOURCES_FILE.split('/').filter(s =>
|
||||||
|
s !== ""
|
||||||
|
).map(path => {
|
||||||
|
// support environment variables at runtime
|
||||||
|
if(/^\$/.test(path)) {
|
||||||
|
const env = GLib.getenv(path.replace(/^\$/, ""));
|
||||||
|
if(env === null)
|
||||||
|
throw new Error(`Couldn't get environment variable: ${path}`);
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}).join('/'));
|
||||||
|
this.#gresource = Gio.Resource.load(gresourcesPath);
|
||||||
|
Gio.resources_register(this.#gresource);
|
||||||
|
}
|
||||||
|
|
||||||
// add icons
|
// add icons
|
||||||
Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!)
|
Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!)
|
||||||
|
|||||||
+3
-3
@@ -44,8 +44,8 @@ export namespace Cli {
|
|||||||
name: "version",
|
name: "version",
|
||||||
alias: "v",
|
alias: "v",
|
||||||
help: "print the current colorshell version",
|
help: "print the current colorshell version",
|
||||||
onCalled: () => `colorshell by retrozinndev, version ${COLORSHELL_VERSION
|
onCalled: () => `colorshell by retrozinndev, version ${(typeof COLORSHELL_VERSION !== "undefined" ? COLORSHELL_VERSION : "unknown")
|
||||||
}${DEVEL ? "(devel)" : ""}`
|
}${(typeof DEVEL !== "undefined" && DEVEL) ? "(devel)" : ""}`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -110,7 +110,7 @@ export namespace Cli {
|
|||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
rootScope = scope;
|
rootScope = scope;
|
||||||
DEVEL && modules.push(devel);
|
(typeof DEVEL !== "undefined" && DEVEL) && modules.push(devel);
|
||||||
|
|
||||||
scope.run(() => {
|
scope.run(() => {
|
||||||
if(communicationMethod instanceof Gio.SocketService) {
|
if(communicationMethod instanceof Gio.SocketService) {
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ const generalConfigDefaults = {
|
|||||||
position_v: "top",
|
position_v: "top",
|
||||||
/** dismisses notification popup when unhovered after hovering
|
/** dismisses notification popup when unhovered after hovering
|
||||||
* @default false */
|
* @default false */
|
||||||
dismiss_after_unhover: false
|
dismiss_on_unhover: false
|
||||||
},
|
},
|
||||||
|
|
||||||
night_light: {
|
night_light: {
|
||||||
|
|||||||
Vendored
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
declare const SRC: string
|
declare const SRC: string
|
||||||
declare const DEVEL: boolean;
|
declare const DEVEL: boolean | undefined;
|
||||||
declare const GRESOURCES_FILE: string;
|
declare const GRESOURCES_FILE: string | undefined;
|
||||||
declare const COLORSHELL_VERSION: string;
|
declare const COLORSHELL_VERSION: string | undefined;
|
||||||
|
|
||||||
declare module "inline:*" {
|
declare module "inline:*" {
|
||||||
const content: string
|
const content: string
|
||||||
|
|||||||
+2
-1
@@ -5,8 +5,9 @@ import AstalApps from "gi://AstalApps";
|
|||||||
import AstalHyprland from "gi://AstalHyprland";
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
|
|
||||||
|
|
||||||
|
// Check if uwsm exists and is active, handling errors gracefully
|
||||||
export const uwsmIsActive: boolean = await execAsync(
|
export const uwsmIsActive: boolean = await execAsync(
|
||||||
"uwsm check is-active"
|
"sh -c 'which uwsm > /dev/null 2>&1 && uwsm check is-active'"
|
||||||
).then(() => true).catch(() => false);
|
).then(() => true).catch(() => false);
|
||||||
const astalApps: AstalApps.Apps = new AstalApps.Apps();
|
const astalApps: AstalApps.Apps = new AstalApps.Apps();
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Audio Controls:
|
|||||||
|
|
||||||
Media Controls:
|
Media Controls:
|
||||||
media: manage colorshell's active player, see "media help".
|
media: manage colorshell's active player, see "media help".
|
||||||
${DEVEL ? `
|
${(typeof DEVEL !== "undefined" && DEVEL) ? `
|
||||||
Development Tools:
|
Development Tools:
|
||||||
dev: tools to help debugging colorshell
|
dev: tools to help debugging colorshell
|
||||||
` : ""}
|
` : ""}
|
||||||
@@ -60,8 +60,8 @@ export function handleArguments(cmd: RemoteCaller, args: Array<string>): number
|
|||||||
|
|
||||||
case "version":
|
case "version":
|
||||||
case "v":
|
case "v":
|
||||||
cmd.print_literal(`colorshell by retrozinndev, version ${COLORSHELL_VERSION
|
cmd.print_literal(`colorshell by retrozinndev, version ${(typeof COLORSHELL_VERSION !== "undefined" ? COLORSHELL_VERSION : "unknown")
|
||||||
}${DEVEL ? " (devel)" : ""}\nhttps://github.com/retrozinndev/colorshell`);
|
}${(typeof DEVEL !== "undefined" && DEVEL) ? " (devel)" : ""}\nhttps://github.com/retrozinndev/colorshell`);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
case "dev":
|
case "dev":
|
||||||
|
|||||||
@@ -133,8 +133,7 @@ class Config<K extends string, V = any> extends GObject.Object {
|
|||||||
public bindProperty(path: string, expectType: "number"): Accessor<number>;
|
public bindProperty(path: string, expectType: "number"): Accessor<number>;
|
||||||
public bindProperty(path: string, expectType: "string"): Accessor<string>;
|
public bindProperty(path: string, expectType: "string"): Accessor<string>;
|
||||||
public bindProperty(path: string, expectType: "object"): Accessor<object>;
|
public bindProperty(path: string, expectType: "object"): Accessor<object>;
|
||||||
public bindProperty(path: string, expectType: "any"): Accessor<any>;
|
public bindProperty(path: string, expectType?: "any"): Accessor<any>;
|
||||||
public bindProperty(path: string, expectType: undefined): Accessor<any>;
|
|
||||||
|
|
||||||
public bindProperty(propertyPath: string, expectType?: ValueTypes): Accessor<boolean|number|string|object|any> {
|
public bindProperty(propertyPath: string, expectType?: ValueTypes): Accessor<boolean|number|string|object|any> {
|
||||||
return new Accessor(() => this.getProperty(propertyPath, expectType as never), (callback: () => void) => {
|
return new Accessor(() => this.getProperty(propertyPath, expectType as never), (callback: () => void) => {
|
||||||
@@ -147,8 +146,7 @@ class Config<K extends string, V = any> extends GObject.Object {
|
|||||||
public getProperty(path: string, expectType: "number"): number;
|
public getProperty(path: string, expectType: "number"): number;
|
||||||
public getProperty(path: string, expectType: "string"): string;
|
public getProperty(path: string, expectType: "string"): string;
|
||||||
public getProperty(path: string, expectType: "object"): object;
|
public getProperty(path: string, expectType: "object"): object;
|
||||||
public getProperty(path: string, expectType: "any"): any;
|
public getProperty(path: string, expectType?: "any"): any;
|
||||||
public getProperty(path: string, expectType: undefined): any;
|
|
||||||
|
|
||||||
public getProperty(path: string, expectType?: ValueTypes): boolean|number|string|object|any {
|
public getProperty(path: string, expectType?: ValueTypes): boolean|number|string|object|any {
|
||||||
return this._getProperty(path, this.#entries, expectType);
|
return this._getProperty(path, this.#entries, expectType);
|
||||||
@@ -158,8 +156,7 @@ class Config<K extends string, V = any> extends GObject.Object {
|
|||||||
public getPropertyDefault(path: string, expectType: "number"): number;
|
public getPropertyDefault(path: string, expectType: "number"): number;
|
||||||
public getPropertyDefault(path: string, expectType: "string"): string;
|
public getPropertyDefault(path: string, expectType: "string"): string;
|
||||||
public getPropertyDefault(path: string, expectType: "object"): object;
|
public getPropertyDefault(path: string, expectType: "object"): object;
|
||||||
public getPropertyDefault(path: string, expectType: "any"): any;
|
public getPropertyDefault(path: string, expectType?: "any"): any;
|
||||||
public getPropertyDefault(path: string, expectType: undefined): any;
|
|
||||||
|
|
||||||
public getPropertyDefault(path: string, expectType?: ValueTypes): boolean|number|string|object|any {
|
public getPropertyDefault(path: string, expectType?: ValueTypes): boolean|number|string|object|any {
|
||||||
return this._getProperty(path, this.defaults, expectType);
|
return this._getProperty(path, this.defaults, expectType);
|
||||||
|
|||||||
@@ -120,7 +120,13 @@ export class NightLight extends GObject.Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public applyIdentity(): void {
|
public applyIdentity(): void {
|
||||||
this.dispatch("identity");
|
try {
|
||||||
|
this.dispatch("identity");
|
||||||
|
} catch (e) {
|
||||||
|
// hyprsunset not available, skip
|
||||||
|
console.warn("Night Light: hyprsunset not available, cannot apply identity");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!this.#identity) {
|
if(!this.#identity) {
|
||||||
this.#identity = true;
|
this.#identity = true;
|
||||||
@@ -133,7 +139,13 @@ export class NightLight extends GObject.Object {
|
|||||||
private dispatch(call: "identity"): string;
|
private dispatch(call: "identity"): string;
|
||||||
|
|
||||||
private dispatch(call: "temperature"|"gamma"|"identity", val?: number): string {
|
private dispatch(call: "temperature"|"gamma"|"identity", val?: number): string {
|
||||||
return exec(`hyprctl hyprsunset ${call}${val != null ? ` ${val}` : ""}`);
|
try {
|
||||||
|
return exec(`hyprctl hyprsunset ${call}${val != null ? ` ${val}` : ""}`);
|
||||||
|
} catch (e) {
|
||||||
|
// hyprsunset not available, return empty string
|
||||||
|
console.warn(`Night Light: hyprsunset not available, skipping ${call} command`);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async dispatchAsync(call: "temperature", val: number): Promise<string>;
|
private async dispatchAsync(call: "temperature", val: number): Promise<string>;
|
||||||
|
|||||||
+128
-52
@@ -4,7 +4,7 @@ import GObject, { register, getter, gtype, property, setter } from "ags/gobject"
|
|||||||
|
|
||||||
import Gio from "gi://Gio?version=2.0";
|
import Gio from "gi://Gio?version=2.0";
|
||||||
import GLib from "gi://GLib?version=2.0";
|
import GLib from "gi://GLib?version=2.0";
|
||||||
import { createSubscription, encoder } from "./utils";
|
import { createSubscription } from "./utils";
|
||||||
import { Notifications } from "./notifications";
|
import { Notifications } from "./notifications";
|
||||||
import { generalConfig } from "../config";
|
import { generalConfig } from "../config";
|
||||||
import { createRoot, getScope, Scope } from "ags";
|
import { createRoot, getScope, Scope } from "ags";
|
||||||
@@ -70,7 +70,7 @@ class Wallpaper extends GObject.Object {
|
|||||||
@setter(String)
|
@setter(String)
|
||||||
set wallpaper(newValue: string) { this.setWallpaper(newValue); }
|
set wallpaper(newValue: string) { this.setWallpaper(newValue); }
|
||||||
|
|
||||||
public get wallpapersPath() { return this.#wallpapersPath; }
|
get wallpapersPath() { return this.#wallpapersPath; }
|
||||||
|
|
||||||
@property(gtype<WallpaperPositioning>(String))
|
@property(gtype<WallpaperPositioning>(String))
|
||||||
positioning: WallpaperPositioning = "cover";
|
positioning: WallpaperPositioning = "cover";
|
||||||
@@ -98,6 +98,10 @@ class Wallpaper extends GObject.Object {
|
|||||||
generalConfig.bindProperty("wallpaper.color_mode", "string"),
|
generalConfig.bindProperty("wallpaper.color_mode", "string"),
|
||||||
() => {
|
() => {
|
||||||
const mode = generalConfig.getProperty("wallpaper.color_mode", "string");
|
const mode = generalConfig.getProperty("wallpaper.color_mode", "string");
|
||||||
|
|
||||||
|
if(this.colorMode === mode)
|
||||||
|
return;
|
||||||
|
|
||||||
if(!mode || (mode !== "darken" && mode !== "lighten")) {
|
if(!mode || (mode !== "darken" && mode !== "lighten")) {
|
||||||
Notifications.getDefault().sendNotification({
|
Notifications.getDefault().sendNotification({
|
||||||
appName: "colorshell",
|
appName: "colorshell",
|
||||||
@@ -118,6 +122,9 @@ class Wallpaper extends GObject.Object {
|
|||||||
const positioning = generalConfig
|
const positioning = generalConfig
|
||||||
.getProperty("wallpaper.positioning", "string") as WallpaperPositioning;
|
.getProperty("wallpaper.positioning", "string") as WallpaperPositioning;
|
||||||
|
|
||||||
|
if(this.positioning === positioning)
|
||||||
|
return;
|
||||||
|
|
||||||
if(!positioning || (positioning !== "contain" &&
|
if(!positioning || (positioning !== "contain" &&
|
||||||
positioning !== "cover" &&
|
positioning !== "cover" &&
|
||||||
positioning !== "tile")) {
|
positioning !== "tile")) {
|
||||||
@@ -125,7 +132,7 @@ class Wallpaper extends GObject.Object {
|
|||||||
Notifications.getDefault().sendNotification({
|
Notifications.getDefault().sendNotification({
|
||||||
appName: "colorshell",
|
appName: "colorshell",
|
||||||
summary: "Couldn't update wallpaper position",
|
summary: "Couldn't update wallpaper position",
|
||||||
body: "Invalid position value. Possible values are: \"cover\", \"contain\" or \"tile\""
|
body: "Invalid position value. Possible values are: \"cover\"(default), \"contain\" or \"tile\""
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -154,60 +161,98 @@ class Wallpaper extends GObject.Object {
|
|||||||
return this.instance;
|
return this.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private writeChanges(): void {
|
private writeChanges(): Promise<void> {
|
||||||
this.#hyprpaperFile.replace_async(null, false,
|
return new Promise((resolve, reject) => {
|
||||||
Gio.FileCreateFlags.REPLACE_DESTINATION,
|
try {
|
||||||
GLib.PRIORITY_DEFAULT, null, (_, result) => {
|
const content = `# This file was automatically generated by colorshell
|
||||||
const res = this.#hyprpaperFile.replace_finish(result);
|
|
||||||
if(!res) {
|
preload = ${this.#wallpaper}
|
||||||
console.error(`Wallpaper: an error occurred when trying to replace the hyprpaper file`);
|
splash = ${this.#splash}
|
||||||
|
wallpaper = , ${this.positioning === "cover" ? "" : `${this.positioning}:`}${this.#wallpaper}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Use synchronous file writing for reliability
|
||||||
|
const filePath = this.#hyprpaperFile.get_path();
|
||||||
|
if(!filePath) {
|
||||||
|
reject(new Error("Could not get hyprpaper file path"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// success
|
// Ensure directory exists
|
||||||
res.write_bytes_async(encoder.encode(`# This file was automatically generated by color-shell
|
const parentDir = this.#hyprpaperFile.get_parent();
|
||||||
|
if(parentDir && !parentDir.query_exists(null)) {
|
||||||
|
parentDir.make_directory_with_parents(null);
|
||||||
|
}
|
||||||
|
|
||||||
preload = ${this.#wallpaper}
|
// Write file synchronously using GLib
|
||||||
splash = ${this.#splash}
|
const success = GLib.file_set_contents(filePath, content);
|
||||||
wallpaper = , ${this.#wallpaper}`.split('\n').map(str => str.trimStart()).join('\n')),
|
if(success) {
|
||||||
GLib.PRIORITY_DEFAULT, null, (_, asyncRes) => {
|
resolve();
|
||||||
if(_!.write_finish(asyncRes)) res.flush(null);
|
} else {
|
||||||
res.close(null);
|
reject(new Error("Failed to write hyprpaper config file"));
|
||||||
}
|
}
|
||||||
);
|
} catch (e: any) {
|
||||||
|
reject(new Error(`Failed to write config file: ${e.message}`));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getData(): WalData {
|
public getData(): WalData {
|
||||||
const content = readFile(`${GLib.getenv("XDG_CACHE_HOME")}/wal/colors.json`);
|
const cacheHome = GLib.getenv("XDG_CACHE_HOME") || `${GLib.get_home_dir()}/.cache`;
|
||||||
|
const content = readFile(`${cacheHome}/wal/colors.json`);
|
||||||
return JSON.parse(content) as WalData;
|
return JSON.parse(content) as WalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getWallpaper(): Promise<string|undefined> {
|
public async getWallpaper(): Promise<string|undefined> {
|
||||||
return await execAsync("hyprctl hyprpaper listactive").then(stdout => {
|
return await execAsync("sh -c \"hyprctl hyprpaper listactive | tail -n 1\"").then(stdout => {
|
||||||
const lineSplit = stdout.split('\n');
|
|
||||||
stdout = lineSplit[lineSplit.length - 1];
|
|
||||||
|
|
||||||
const loaded = stdout.split('=')[1]?.trim();
|
const loaded = stdout.split('=')[1]?.trim();
|
||||||
|
|
||||||
if(!loaded)
|
if(!loaded)
|
||||||
console.warn(`Wallpaper: Couldn't get wallpaper. There is(are) no loaded wallpaper(s)`);
|
console.warn(`Wallpaper: Couldn't get wallpaper. There is(are) no loaded wallpaper(s)`);
|
||||||
|
|
||||||
return loaded;
|
return loaded;
|
||||||
}).catch((err: Error) => {
|
}).catch((e: Error) => {
|
||||||
console.error(`Wallpaper: Couldn't get wallpaper. Stderr: \n${err.message}`);
|
console.error(`Wallpaper: Couldn't get wallpaper. Stderr: \n${e.message}`);
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public reloadColors(): void {
|
public reloadColors(): void {
|
||||||
execAsync(`wal -t --cols16 ${this.colorMode} -i "${this.#wallpaper}"`).then(() => {
|
const cacheHome = GLib.getenv("XDG_CACHE_HOME") || `${GLib.get_home_dir()}/.cache`;
|
||||||
|
const colorsKittyPath = `${cacheHome}/wal/colors-kitty.conf`;
|
||||||
|
const kittyConfigPath = `${GLib.get_user_config_dir()}/kitty/kitty.conf`;
|
||||||
|
|
||||||
|
const runWal = (extraArgs: string = "") =>
|
||||||
|
execAsync(`wal -t --cols16 ${this.colorMode} ${extraArgs} -i "${this.#wallpaper}"`);
|
||||||
|
|
||||||
|
// First try default backend; if it fails (e.g. some images on aarch64),
|
||||||
|
// fall back to a more forgiving backend like "colorz".
|
||||||
|
runWal().catch((e: Error) => {
|
||||||
|
console.error(`Wallpaper: Couldn't update shell colors with default backend. Stderr: ${e.message}`);
|
||||||
|
console.log("Wallpaper: Falling back to pywal backend 'colorz'");
|
||||||
|
return runWal("--backend colorz");
|
||||||
|
}).then(() => {
|
||||||
console.log("Wallpaper: reloaded shell colors");
|
console.log("Wallpaper: reloaded shell colors");
|
||||||
|
|
||||||
|
// First, try to set colors on all existing kitty instances
|
||||||
|
execAsync(`kitty @ set-colors --all ${colorsKittyPath}`).then(() => {
|
||||||
|
console.log("Wallpaper: reloaded colors in existing kitty instances");
|
||||||
|
}).catch((e: Error) => {
|
||||||
|
// It's okay if this fails (e.g., no kitty instances running)
|
||||||
|
console.log(`Wallpaper: Couldn't reload kitty colors in existing instances: ${e.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then, update the configured colors for future kitty instances
|
||||||
|
// This is critical - it tells kitty to use these colors for new windows
|
||||||
|
execAsync(`kitty @ set-colors --configured ${colorsKittyPath}`).then(() => {
|
||||||
|
console.log("Wallpaper: configured colors for future kitty instances");
|
||||||
|
}).catch((e: Error) => {
|
||||||
|
// If no kitty instances are running, we can't set configured colors
|
||||||
|
// In this case, new instances should still pick up colors from the include directive
|
||||||
|
console.log(`Wallpaper: Couldn't set configured colors (new instances will use include directive): ${e.message}`);
|
||||||
|
});
|
||||||
}).catch((e: Error) => {
|
}).catch((e: Error) => {
|
||||||
console.error(`Wallpaper: Couldn't update shell colors. Stderr: ${e.message}`);
|
console.error(`Wallpaper: Couldn't update shell colors even with fallback backend. Stderr: ${e.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,37 +260,68 @@ class Wallpaper extends GObject.Object {
|
|||||||
if(this.wallpaper.trim() === "")
|
if(this.wallpaper.trim() === "")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await execAsync(`hyprctl hyprpaper wallpaper \", ${this.positioning}:${this.wallpaper}\"`);
|
const wallpaperPath = this.#wallpaper.trim();
|
||||||
this.reloadColors();
|
|
||||||
write && this.writeChanges();
|
try {
|
||||||
|
// Write config file first if needed
|
||||||
|
if(write) {
|
||||||
|
await this.writeChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload all current wallpapers
|
||||||
|
await execAsync(`hyprctl hyprpaper unload all`).catch(() => {
|
||||||
|
// Ignore errors - this is usually fine
|
||||||
|
});
|
||||||
|
|
||||||
|
// Preload the new wallpaper
|
||||||
|
await execAsync(`hyprctl hyprpaper preload "${wallpaperPath}"`);
|
||||||
|
|
||||||
|
// Set wallpaper on all monitors
|
||||||
|
await execAsync(`hyprctl hyprpaper wallpaper ", ${wallpaperPath}"`);
|
||||||
|
|
||||||
|
// Note: We don't need to reload or restart hyprpaper here
|
||||||
|
// The preload and wallpaper commands should apply the change immediately
|
||||||
|
// The config file is written for persistence across hyprpaper restarts
|
||||||
|
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(`Wallpaper: Error reloading wallpaper: ${e.message}`);
|
||||||
|
console.error(`Wallpaper: Stack trace: ${e.stack}`);
|
||||||
|
Notifications.getDefault().sendNotification({
|
||||||
|
appName: "colorshell",
|
||||||
|
summary: "Failed to set wallpaper",
|
||||||
|
body: `Error: ${e.message}`
|
||||||
|
});
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public setWallpaper(path: string|Gio.File, write: boolean = true): void {
|
public setWallpaper(path: string|Gio.File, write: boolean = true): void {
|
||||||
path = typeof path === "string" ? path : path.peek_path()!;
|
path = typeof path === "string" ? path : path.peek_path()!;
|
||||||
|
|
||||||
execAsync("hyprctl hyprpaper unload all").then(() =>
|
if(!GLib.file_test(path, GLib.FileTest.EXISTS)) {
|
||||||
execAsync(`hyprctl hyprpaper preload ${path}`).then(() =>
|
console.error("Wallpaper: file does not exist, skipped");
|
||||||
execAsync(`hyprctl hyprpaper wallpaper \", ${this.positioning}:${path}\"`).then(() => {
|
return;
|
||||||
this.#wallpaper = path;
|
}
|
||||||
this.reloadColors();
|
|
||||||
write && this.writeChanges();
|
this.#wallpaper = path;
|
||||||
}).catch((e: Error) => {
|
this.reloadWallpaper(write).catch((e: Error) => {
|
||||||
console.error(`Wallpaper: Couldn't set wallpaper. Stderr: ${e.message}`);
|
console.error(`Wallpaper: Couldn't set wallpaper. Stderr: ${e.message}`);
|
||||||
})
|
|
||||||
).catch((e: Error) => {
|
|
||||||
console.error(`Wallpaper: Couldn't preload image. Stderr: ${e.message}`);
|
|
||||||
})
|
|
||||||
).catch((e: Error) => {
|
|
||||||
console.error(`Wallpaper: Couldn't unload images from memory. Stderr: ${e.message}`);
|
|
||||||
});
|
});
|
||||||
|
this.reloadColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async pickWallpaper(): Promise<string|undefined> {
|
public async pickWallpaper(): Promise<string|undefined> {
|
||||||
return (await execAsync(`zenity --file-selection`).then(wall => {
|
return (await execAsync(`zenity --file-selection`).then(wall => {
|
||||||
if(!wall.trim()) return undefined;
|
const trimmedWall = wall.trim();
|
||||||
|
if(!trimmedWall) return undefined;
|
||||||
|
|
||||||
this.setWallpaper(wall);
|
// Ensure path is absolute
|
||||||
return wall;
|
const absolutePath = GLib.path_is_absolute(trimmedWall)
|
||||||
|
? trimmedWall
|
||||||
|
: GLib.build_filenamev([GLib.get_current_dir(), trimmedWall]);
|
||||||
|
|
||||||
|
this.setWallpaper(absolutePath);
|
||||||
|
return absolutePath;
|
||||||
}).catch((e: Error) => {
|
}).catch((e: Error) => {
|
||||||
console.error(`Wallpaper: Couldn't pick wallpaper, is \`zenity\` installed? Stderr: ${e.message}`);
|
console.error(`Wallpaper: Couldn't pick wallpaper, is \`zenity\` installed? Stderr: ${e.message}`);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
+36
-1
@@ -276,11 +276,14 @@ export function openRunner(props: RunnerProps, placeholders?: Array<Result>): As
|
|||||||
props.height ??= 420;
|
props.height ??= 420;
|
||||||
|
|
||||||
let clickTimeout: GLib.Source|undefined;
|
let clickTimeout: GLib.Source|undefined;
|
||||||
|
let lastMouseX: number|null = null;
|
||||||
|
let lastMouseY: number|null = null;
|
||||||
|
let lastKeyboardNavTime: number = 0;
|
||||||
|
|
||||||
if(!instance)
|
if(!instance)
|
||||||
instance = Windows.getDefault().createWindowForFocusedMonitor((mon, root) =>
|
instance = Windows.getDefault().createWindowForFocusedMonitor((mon, root) =>
|
||||||
<PopupWindow namespace={"runner"} monitor={mon} widthRequest={props.width}
|
<PopupWindow namespace={"runner"} monitor={mon} widthRequest={props.width}
|
||||||
heightRequest={props.height} exclusivity={Astal.Exclusivity.IGNORE} halign={Gtk.Align.CENTER}
|
exclusivity={Astal.Exclusivity.IGNORE} halign={Gtk.Align.CENTER}
|
||||||
marginTop={(AstalHyprland.get_default().get_monitor(mon)?.height / 2) - (props.height! / 2)}
|
marginTop={(AstalHyprland.get_default().get_monitor(mon)?.height / 2) - (props.height! / 2)}
|
||||||
valign={Gtk.Align.START} hexpand orientation={Gtk.Orientation.VERTICAL}
|
valign={Gtk.Align.START} hexpand orientation={Gtk.Orientation.VERTICAL}
|
||||||
$={() => {
|
$={() => {
|
||||||
@@ -302,12 +305,14 @@ export function openRunner(props: RunnerProps, placeholders?: Array<Result>): As
|
|||||||
case Gdk.KEY_Up:
|
case Gdk.KEY_Up:
|
||||||
selectPreviousItem(listbox);
|
selectPreviousItem(listbox);
|
||||||
gtkEntry?.grab_focus();
|
gtkEntry?.grab_focus();
|
||||||
|
lastKeyboardNavTime = Date.now();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case Gdk.KEY_Right:
|
case Gdk.KEY_Right:
|
||||||
case Gdk.KEY_Down:
|
case Gdk.KEY_Down:
|
||||||
selectNextItem(listbox);
|
selectNextItem(listbox);
|
||||||
gtkEntry?.grab_focus();
|
gtkEntry?.grab_focus();
|
||||||
|
lastKeyboardNavTime = Date.now();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,6 +379,36 @@ export function openRunner(props: RunnerProps, placeholders?: Array<Result>): As
|
|||||||
child.closeOnClick &&
|
child.closeOnClick &&
|
||||||
Runner.close();
|
Runner.close();
|
||||||
}
|
}
|
||||||
|
}} $={(self) => {
|
||||||
|
// Hover-based selection: only triggers when the mouse actually moves
|
||||||
|
const motion = Gtk.EventControllerMotion.new();
|
||||||
|
self.add_controller(motion);
|
||||||
|
|
||||||
|
motion.connect("motion", (_controller, x, y) => {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// While user is actively navigating with keyboard,
|
||||||
|
// don't let hover steal selection
|
||||||
|
if(lastKeyboardNavTime && now - lastKeyboardNavTime < 200)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// First motion: just record pointer position, don't change selection
|
||||||
|
if(lastMouseX === null && lastMouseY === null) {
|
||||||
|
lastMouseX = x;
|
||||||
|
lastMouseY = y;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore synthetic events that don't actually move the pointer
|
||||||
|
if(x === lastMouseX && y === lastMouseY)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lastMouseX = x;
|
||||||
|
lastMouseY = y;
|
||||||
|
|
||||||
|
const row = self.get_row_at_y(y);
|
||||||
|
row && self.select_row(row as Gtk.ListBoxRow);
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Gtk.ScrolledWindow>
|
</Gtk.ScrolledWindow>
|
||||||
|
|||||||
@@ -9,12 +9,19 @@ import GObject from "ags/gobject";
|
|||||||
import AstalNotifd from "gi://AstalNotifd";
|
import AstalNotifd from "gi://AstalNotifd";
|
||||||
import Pango from "gi://Pango?version=1.0";
|
import Pango from "gi://Pango?version=1.0";
|
||||||
import GLib from "gi://GLib?version=2.0";
|
import GLib from "gi://GLib?version=2.0";
|
||||||
|
import { generalConfig } from "../config";
|
||||||
|
|
||||||
|
|
||||||
function getNotificationImage(notif: AstalNotifd.Notification|HistoryNotification): (string|undefined) {
|
function getNotificationImage(notif: AstalNotifd.Notification|HistoryNotification): (string|undefined) {
|
||||||
const img = notif.image || notif.appIcon;
|
// AstalNotifd.Notification uses snake_case properties (app_icon, image),
|
||||||
|
// while our HistoryNotification uses camelCase (appIcon, image).
|
||||||
|
const anyNotif = notif as any;
|
||||||
|
const img: string | undefined =
|
||||||
|
anyNotif.image ||
|
||||||
|
anyNotif.app_icon ||
|
||||||
|
anyNotif.appIcon;
|
||||||
|
|
||||||
if(!img || !img.includes('/'))
|
if (typeof img !== "string" || !img.includes("/"))
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
||||||
return pathToURI(img);
|
return pathToURI(img);
|
||||||
@@ -51,9 +58,23 @@ export function NotificationWidget({ notification, actionClicked, holdOnHover, s
|
|||||||
|
|
||||||
<Gtk.EventControllerMotion onEnter={() => holdOnHover &&
|
<Gtk.EventControllerMotion onEnter={() => holdOnHover &&
|
||||||
Notifications.getDefault().holdNotification(notification.id)
|
Notifications.getDefault().holdNotification(notification.id)
|
||||||
} onLeave={() => holdOnHover &&
|
} onLeave={() => {
|
||||||
Notifications.getDefault().releaseNotification(notification.id)
|
if(!holdOnHover)
|
||||||
}
|
return;
|
||||||
|
|
||||||
|
const dismissOnUnhover = generalConfig
|
||||||
|
.getProperty("notifications.dismiss_on_unhover", "boolean");
|
||||||
|
|
||||||
|
if(dismissOnUnhover) {
|
||||||
|
setTimeout(() =>
|
||||||
|
Notifications.getDefault().removeNotification(notification.id),
|
||||||
|
600);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Notifications.getDefault().releaseNotification(notification.id);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Gtk.GestureClick onReleased={(gesture) =>
|
<Gtk.GestureClick onReleased={(gesture) =>
|
||||||
gesture.get_current_button() === Gdk.BUTTON_PRIMARY &&
|
gesture.get_current_button() === Gdk.BUTTON_PRIMARY &&
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export const AppsWindow = (mon: number) => {
|
|||||||
return <PopupWindow namespace="apps-window" layer={Astal.Layer.OVERLAY}
|
return <PopupWindow namespace="apps-window" layer={Astal.Layer.OVERLAY}
|
||||||
exclusivity={Astal.Exclusivity.IGNORE} monitor={mon} marginTop={64}
|
exclusivity={Astal.Exclusivity.IGNORE} monitor={mon} marginTop={64}
|
||||||
class={"apps-window"} orientation={Gtk.Orientation.VERTICAL}
|
class={"apps-window"} orientation={Gtk.Orientation.VERTICAL}
|
||||||
cssBackgroundWindow="background: rgba(0, 0, 0, .2);"
|
cssBackgroundWindow="background: rgba(0, 0, 0, .2);" halign={Gtk.Align.FILL}
|
||||||
|
valign={Gtk.Align.FILL} hexpand vexpand
|
||||||
actionKeyPressed={(self, key) => {
|
actionKeyPressed={(self, key) => {
|
||||||
const entry = getPopupWindowContainer(self).get_first_child()!
|
const entry = getPopupWindowContainer(self).get_first_child()!
|
||||||
.get_first_child()!.get_first_child()! as Gtk.SearchEntry;
|
.get_first_child()!.get_first_child()! as Gtk.SearchEntry;
|
||||||
@@ -36,8 +37,8 @@ export const AppsWindow = (mon: number) => {
|
|||||||
|
|
||||||
entry.grab_focus();
|
entry.grab_focus();
|
||||||
}}>
|
}}>
|
||||||
<Gtk.Box hexpand={false} halign={Gtk.Align.CENTER}>
|
<Gtk.Box hexpand halign={Gtk.Align.CENTER}>
|
||||||
<Gtk.SearchEntry hexpand={false} onSearchChanged={(self) => {
|
<Gtk.SearchEntry hexpand onSearchChanged={(self) => {
|
||||||
setResults(getAstalApps().fuzzy_query(self.text.trim()));
|
setResults(getAstalApps().fuzzy_query(self.text.trim()));
|
||||||
}} onStopSearch={(self) => (self.get_root() as Astal.Window)?.close()} />
|
}} onStopSearch={(self) => (self.get_root() as Astal.Window)?.close()} />
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
@@ -46,9 +47,11 @@ export const AppsWindow = (mon: number) => {
|
|||||||
hscrollbarPolicy={Gtk.PolicyType.NEVER} overlayScrolling
|
hscrollbarPolicy={Gtk.PolicyType.NEVER} overlayScrolling
|
||||||
propagateNaturalHeight={false} hexpand vexpand>
|
propagateNaturalHeight={false} hexpand vexpand>
|
||||||
|
|
||||||
<Gtk.Box hexpand={false} vexpand={false}>
|
<Gtk.Box hexpand vexpand>
|
||||||
<Gtk.FlowBox rowSpacing={60} columnSpacing={60} activateOnSingleClick
|
<Gtk.FlowBox orientation={Gtk.Orientation.HORIZONTAL}
|
||||||
minChildrenPerLine={1} homogeneous onChildActivated={(_, child) =>
|
rowSpacing={32} columnSpacing={32} activateOnSingleClick
|
||||||
|
minChildrenPerLine={4} maxChildrenPerLine={10} hexpand homogeneous
|
||||||
|
onChildActivated={(_, child) =>
|
||||||
child.get_child()!.activate() // pass activation to button
|
child.get_child()!.activate() // pass activation to button
|
||||||
}>
|
}>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const Bar = (mon: number) => {
|
|||||||
halign={Gtk.Align.START} spacing={widgetSpacing}
|
halign={Gtk.Align.START} spacing={widgetSpacing}
|
||||||
$type="start">
|
$type="start">
|
||||||
|
|
||||||
<Apps />
|
<Apps monitor={mon} />
|
||||||
<Workspaces />
|
<Workspaces />
|
||||||
<FocusedClient />
|
<FocusedClient />
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
@@ -29,14 +29,14 @@ export const Bar = (mon: number) => {
|
|||||||
spacing={widgetSpacing} halign={Gtk.Align.CENTER}
|
spacing={widgetSpacing} halign={Gtk.Align.CENTER}
|
||||||
$type="center">
|
$type="center">
|
||||||
|
|
||||||
<Clock />
|
<Clock monitor={mon} />
|
||||||
<Media />
|
<Media monitor={mon} />
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
<Gtk.Box class={"widgets-right"} homogeneous={false}
|
<Gtk.Box class={"widgets-right"} homogeneous={false}
|
||||||
spacing={widgetSpacing} halign={Gtk.Align.END}
|
spacing={widgetSpacing} halign={Gtk.Align.END}
|
||||||
$type="end">
|
$type="end">
|
||||||
<Tray />
|
<Tray />
|
||||||
<Status />
|
<Status monitor={mon} />
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
</Gtk.CenterBox>
|
</Gtk.CenterBox>
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { createBinding } from "ags";
|
|||||||
import { tr } from "../../../i18n/intl";
|
import { tr } from "../../../i18n/intl";
|
||||||
|
|
||||||
|
|
||||||
export const Apps = () =>
|
export const Apps = ({ monitor }: { monitor: number }) =>
|
||||||
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((openWindows) =>
|
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((openWindows) =>
|
||||||
`apps ${Object.hasOwn(openWindows, "apps-window") ? "open" : ""}`
|
`apps ${Object.hasOwn(openWindows, "apps-window") ? "open" : ""}`
|
||||||
)} iconName={"applications-other-symbolic"} halign={Gtk.Align.CENTER}
|
)} iconName={"applications-other-symbolic"} halign={Gtk.Align.CENTER}
|
||||||
hexpand tooltipText={tr("apps")} onClicked={() =>
|
hexpand tooltipText={tr("apps")} onClicked={() =>
|
||||||
Windows.getDefault().open("apps-window")}
|
Windows.getDefault().open("apps-window", false, monitor)}
|
||||||
/>;
|
/>;
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import { time } from "../../../modules/utils";
|
|||||||
import { generalConfig } from "../../../config";
|
import { generalConfig } from "../../../config";
|
||||||
|
|
||||||
|
|
||||||
export const Clock = () =>
|
export const Clock = ({ monitor }: { monitor: number }) =>
|
||||||
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((wins) =>
|
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((wins) =>
|
||||||
`clock ${wins.includes("center-window") ? "open" : ""}`)}
|
`clock ${wins.includes("center-window") ? "open" : ""}`)}
|
||||||
onClicked={() => Windows.getDefault().toggle("center-window")}
|
onClicked={() => Windows.getDefault().toggle("center-window", monitor)}
|
||||||
label={time((dt) => dt.format(
|
label={time((dt) => dt.format(
|
||||||
generalConfig.getProperty("clock.date_format", "string"))
|
generalConfig.getProperty("clock.date_format", "string"))
|
||||||
?? "An error occurred"
|
?? "An error occurred"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import AstalMpris from "gi://AstalMpris";
|
|||||||
import Pango from "gi://Pango?version=1.0";
|
import Pango from "gi://Pango?version=1.0";
|
||||||
|
|
||||||
|
|
||||||
export const Media = () =>
|
export const Media = ({ monitor }: { monitor: number }) =>
|
||||||
<Gtk.Box class={"media"} visible={createBinding(Player.getDefault(), "player").as(p => p.available)}>
|
<Gtk.Box class={"media"} visible={createBinding(Player.getDefault(), "player").as(p => p.available)}>
|
||||||
<Gtk.EventControllerScroll $={(self) => {
|
<Gtk.EventControllerScroll $={(self) => {
|
||||||
self.set_flags(Gtk.EventControllerScrollFlags.VERTICAL)
|
self.set_flags(Gtk.EventControllerScrollFlags.VERTICAL)
|
||||||
@@ -42,7 +42,7 @@ export const Media = () =>
|
|||||||
return true;
|
return true;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Gtk.GestureClick onReleased={() => Windows.getDefault().toggle("center-window")} />
|
<Gtk.GestureClick onReleased={() => Windows.getDefault().toggle("center-window", monitor)} />
|
||||||
<Gtk.EventControllerMotion onEnter={(self) => {
|
<Gtk.EventControllerMotion onEnter={(self) => {
|
||||||
const revealer = self.get_widget()!.get_last_child() as Gtk.Revealer;
|
const revealer = self.get_widget()!.get_last_child() as Gtk.Revealer;
|
||||||
revealer.set_reveal_child(true);
|
revealer.set_reveal_child(true);
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import AstalNetwork from "gi://AstalNetwork";
|
|||||||
import AstalWp from "gi://AstalWp";
|
import AstalWp from "gi://AstalWp";
|
||||||
|
|
||||||
|
|
||||||
export const Status = () =>
|
export const Status = ({ monitor }: { monitor: number }) =>
|
||||||
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((openWins) =>
|
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((openWins) =>
|
||||||
openWins.includes("control-center") ?
|
openWins.includes("control-center") ?
|
||||||
"open status"
|
"open status"
|
||||||
: "status"
|
: "status"
|
||||||
)} onClicked={() => Windows.getDefault().toggle("control-center")}>
|
)} onClicked={() => Windows.getDefault().toggle("control-center", monitor)}>
|
||||||
|
|
||||||
<Gtk.Box>
|
<Gtk.Box>
|
||||||
<Gtk.Box class={"volume-indicators"} spacing={5}>
|
<Gtk.Box class={"volume-indicators"} spacing={5}>
|
||||||
|
|||||||
+53
-10
@@ -17,8 +17,10 @@ import AstalHyprland from "gi://AstalHyprland";
|
|||||||
export type WindowInstance = { instance?: Astal.Window, connections: Array<number> };
|
export type WindowInstance = { instance?: Astal.Window, connections: Array<number> };
|
||||||
export type WindowData = {
|
export type WindowData = {
|
||||||
create: () => (Astal.Window | Array<Astal.Window>);
|
create: () => (Astal.Window | Array<Astal.Window>);
|
||||||
|
createForMonitor?: (mon: number) => Astal.Window;
|
||||||
instance?: WindowInstance | Array<WindowInstance>;
|
instance?: WindowInstance | Array<WindowInstance>;
|
||||||
status?: "open" | "closed";
|
status?: "open" | "closed";
|
||||||
|
preferredMonitor?: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -41,12 +43,12 @@ export class Windows extends GObject.Object {
|
|||||||
#scope!: ReturnType<typeof getScope>;
|
#scope!: ReturnType<typeof getScope>;
|
||||||
#windows: Record<string, WindowData> = {
|
#windows: Record<string, WindowData> = {
|
||||||
"bar": { create: this.createWindowForMonitors(Bar) },
|
"bar": { create: this.createWindowForMonitors(Bar) },
|
||||||
"osd": { create: this.createWindowForFocusedMonitor(OSD), },
|
"osd": { create: this.createWindowForFocusedMonitor(OSD), createForMonitor: this.createWindowForMonitor(OSD) },
|
||||||
"control-center": { create: this.createWindowForFocusedMonitor(ControlCenter), },
|
"control-center": { create: this.createWindowForFocusedMonitor(ControlCenter), createForMonitor: this.createWindowForMonitor(ControlCenter) },
|
||||||
"center-window": { create: this.createWindowForFocusedMonitor(CenterWindow), },
|
"center-window": { create: this.createWindowForFocusedMonitor(CenterWindow), createForMonitor: this.createWindowForMonitor(CenterWindow) },
|
||||||
"logout-menu": { create: this.createWindowForFocusedMonitor(LogoutMenu), },
|
"logout-menu": { create: this.createWindowForFocusedMonitor(LogoutMenu), createForMonitor: this.createWindowForMonitor(LogoutMenu) },
|
||||||
"floating-notifications": { create: this.createWindowForFocusedMonitor(FloatingNotifications), },
|
"floating-notifications": { create: this.createWindowForFocusedMonitor(FloatingNotifications), createForMonitor: this.createWindowForMonitor(FloatingNotifications) },
|
||||||
"apps-window": { create: this.createWindowForFocusedMonitor(AppsWindow) }
|
"apps-window": { create: this.createWindowForFocusedMonitor(AppsWindow), createForMonitor: this.createWindowForMonitor(AppsWindow) }
|
||||||
};
|
};
|
||||||
|
|
||||||
@signal(String) windowOpen(_name: string) {}
|
@signal(String) windowOpen(_name: string) {}
|
||||||
@@ -244,6 +246,29 @@ export class Windows extends GObject.Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a window instance for a specific monitor
|
||||||
|
* @param create generates the window. use provided monitor number in the returned window
|
||||||
|
* @returns a function that when called with a monitor ID, returns a Astal.Window instance
|
||||||
|
*/
|
||||||
|
public createWindowForMonitor(create: (mon: number, scope: ReturnType<typeof getScope>) => GObject.Object|Astal.Window): ((mon: number) => Astal.Window) {
|
||||||
|
return (mon: number) => {
|
||||||
|
return createRoot((dispose) => {
|
||||||
|
const scope = getScope();
|
||||||
|
const instance = create(mon, scope) as Astal.Window;
|
||||||
|
const connection = instance.connect("close-request", () => dispose());
|
||||||
|
|
||||||
|
this.#scope.onMount(dispose)
|
||||||
|
scope.onCleanup(() =>
|
||||||
|
GObject.signal_handler_is_connected(instance, connection) &&
|
||||||
|
instance.disconnect(connection)
|
||||||
|
);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public addWindow(name: string, create: () => Astal.Window|Array<Astal.Window>): void {
|
public addWindow(name: string, create: () => Astal.Window|Array<Astal.Window>): void {
|
||||||
this.#windows[name] = { create };
|
this.#windows[name] = { create };
|
||||||
}
|
}
|
||||||
@@ -264,7 +289,7 @@ export class Windows extends GObject.Object {
|
|||||||
return this.openWindows.includes(name);
|
return this.openWindows.includes(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public open(name: string, ignoreOpenStatus: boolean = false): void {
|
public open(name: string, ignoreOpenStatus: boolean = false, monitor?: number | null): void {
|
||||||
if(this.isOpen(name) && !ignoreOpenStatus) return;
|
if(this.isOpen(name) && !ignoreOpenStatus) return;
|
||||||
|
|
||||||
const window = this.#windows[name];
|
const window = this.#windows[name];
|
||||||
@@ -273,8 +298,22 @@ export class Windows extends GObject.Object {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store preferred monitor if provided
|
||||||
|
if(monitor !== undefined) {
|
||||||
|
window.preferredMonitor = monitor;
|
||||||
|
}
|
||||||
|
|
||||||
this.#windows[name].status = "open";
|
this.#windows[name].status = "open";
|
||||||
const windowInstance = window.create();
|
|
||||||
|
// Use createForMonitor if monitor is specified and available, otherwise use default create
|
||||||
|
let windowInstance: Astal.Window | Array<Astal.Window>;
|
||||||
|
if(monitor !== null && monitor !== undefined && window.createForMonitor) {
|
||||||
|
windowInstance = window.createForMonitor(monitor);
|
||||||
|
} else if(window.preferredMonitor !== null && window.preferredMonitor !== undefined && window.createForMonitor) {
|
||||||
|
windowInstance = window.createForMonitor(window.preferredMonitor);
|
||||||
|
} else {
|
||||||
|
windowInstance = window.create();
|
||||||
|
}
|
||||||
|
|
||||||
if(Array.isArray(windowInstance)) {
|
if(Array.isArray(windowInstance)) {
|
||||||
window.instance = windowInstance.map(wi => {
|
window.instance = windowInstance.map(wi => {
|
||||||
@@ -309,8 +348,12 @@ export class Windows extends GObject.Object {
|
|||||||
this.notify("open-windows");
|
this.notify("open-windows");
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggle(name: string): void {
|
public toggle(name: string, monitor?: number | null): void {
|
||||||
this.isOpen(name) ? this.close(name) : this.open(name);
|
if(this.isOpen(name)) {
|
||||||
|
this.close(name);
|
||||||
|
} else {
|
||||||
|
this.open(name, false, monitor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeAll(): void {
|
public closeAll(): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user