forked from Shinonome/caelestia-cli
Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 62e7911864 | |||
| 6f8e5849cb | |||
| 54f7611437 | |||
| 2eda287a80 | |||
| 4263e5f809 | |||
| 70ce21f798 | |||
| 3f57cd71d1 | |||
| ad962cb572 | |||
| 3319d2ca19 | |||
| c20bc567a4 | |||
| d7b7d2ae04 | |||
| 12abcf2336 | |||
| d6c1e13246 | |||
| 597780ba78 | |||
| 47730a22b9 | |||
| f4fee9c3d5 | |||
| 67942d1d7a | |||
| fc09d2fcd3 | |||
| 351ebb60c6 | |||
| 8bc7e495af | |||
| 35b10394b6 | |||
| 46a9516f72 | |||
| 7467089ebe | |||
| eb18d29056 | |||
| 59d1bb532a | |||
| a9ab4a02d5 | |||
| 683172ed65 | |||
| 4782799b55 | |||
| db1e0da5bb | |||
| 6cdfe72e8b | |||
| 78be122d0c | |||
| 838749ef0a | |||
| 3802bccc6f | |||
| 12f0d51862 | |||
| 202f687dde | |||
| 99142f11ad | |||
| f2a9bf2490 | |||
| c72223a7e6 | |||
| 3e19fd6919 | |||
| febcc5662a | |||
| f280b9cbb6 | |||
| b22ab08a37 | |||
| ebca5f8557 | |||
| 63e2132830 | |||
| 1cd8cae2d9 | |||
| e325129f7a | |||
| 651efcd137 | |||
| 0df89887a0 | |||
| e24656da0e | |||
| c9c1be183a | |||
| 6023a37064 | |||
| d3881bfc26 | |||
| d727836cc9 | |||
| 5b34ef0061 | |||
| c3631cd35b | |||
| 386ccf3729 | |||
| 1fcfb83fba | |||
| d15d5c4399 | |||
| caf26e7c5b | |||
| ff38a8c5cf | |||
| 9489f0d4f6 | |||
| 7027ea5442 | |||
| f541e99d07 | |||
| ff6ca32b11 | |||
| ae8deb35a7 | |||
| 50646cd565 | |||
| fed8cc5800 | |||
| 84e16c9968 | |||
| 1d5ba89573 | |||
| b4ea0f6db6 | |||
| 981f686a3c | |||
| 43fb0cfc35 | |||
| 46e05afc56 | |||
| d8037819f0 | |||
| 06a7102490 | |||
| 2bde2ddfbf | |||
| 882adb2c6c | |||
| 13a2d46d08 | |||
| beabe2683c | |||
| 3deb726278 | |||
| 465c200c83 |
@@ -0,0 +1,20 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
There are only a few rules:
|
||||||
|
- Follow the commit convention as follows:
|
||||||
|
- The name of the commit should be `module: change`
|
||||||
|
- Try to be consistent with the module names; you can look at existing commits for the module names I use
|
||||||
|
- If there is more than one change, the change in the commit name should be the most impactful change
|
||||||
|
- Put other changes in the description
|
||||||
|
- Format your code
|
||||||
|
- Just try to follow the code style of the rest of the code and ensure that there is:
|
||||||
|
- no trailing whitespace on any lines
|
||||||
|
- a single space between operators
|
||||||
|
- No AI slop allowed
|
||||||
|
- AI readme/docs slop = instant block
|
||||||
|
- PLEASE TEST YOUR PRS
|
||||||
|
- I can't believe I have to put this here, but please test your PRs before submitting them
|
||||||
|
- Your PR must not break anything currently existing, or specify in the description if it does
|
||||||
|
- PR descriptions should be descriptive
|
||||||
|
- Please explain what the PR does and how to use it in your PR description
|
||||||
|
- Also include any breaking changes and/or side effects of the PR
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: soramanew
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: soramane
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
|
polar: # Replace with a single Polar username
|
||||||
|
buy_me_a_coffee: soramane
|
||||||
|
thanks_dev: # Replace with a single thanks.dev username
|
||||||
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest a new feature
|
||||||
|
labels: ["enhancement"]
|
||||||
|
type: "Feature"
|
||||||
|
title: "[FEATURE] "
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "NOTE: Please write in **English**."
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "What would you like to be added?"
|
||||||
|
description: "Can be a suggestion for an existing feature. You can suggest a widget, minor user interaction changes.. whatever."
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "How will it help?"
|
||||||
|
description: "It's helpful to include examples (like in your use case)."
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Extra info"
|
||||||
|
description: "If you want a new widget, a pic of the inspiration (if available) would be awesome."
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
name: Issue
|
||||||
|
description: Report an issue with the dots
|
||||||
|
labels: ["bug"]
|
||||||
|
type: "Bug"
|
||||||
|
title: "[BUG] "
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "**Welcome to submit a new issue!**\n- It takes only 3 steps, so please be patient :)\n- Tip: If your issue is not a feature request and is not an issue with the dots (e.g. \"how do I use X feature\"), please use [Discussions](https://github.com/caelestia-dots/shell/discussions) instead."
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: "Step 1. Before you submit"
|
||||||
|
description: "Hint: The 2nd and 3rd checkbox is **not** forcely required as you may have failed to do so."
|
||||||
|
options:
|
||||||
|
- label: I have read the above instructions and am sure that this is supposed to be posted here.
|
||||||
|
required: true
|
||||||
|
- label: I've successfully updated to the latest versions following the [updating guide](https://github.com/caelestia-dots/caelestia?tab=readme-ov-file#updating).
|
||||||
|
required: false # Not required cuz user may have failed to do so
|
||||||
|
- label: I've successfully updated the system packages to the latest.
|
||||||
|
required: false # Not required cuz user may have failed to do so
|
||||||
|
- label: I've ticked the checkboxes without reading their contents
|
||||||
|
required: false # Obviously
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Step 2. Version info"
|
||||||
|
description: "Run `caelestia -v` and paste the result below."
|
||||||
|
value: "<details><summary>Version info</summary>\n\n```\n<!-- Run `caelestia -v` and paste the result here! -->\n```\n\n</details>"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
**Tips for the following Step 3**
|
||||||
|
1. Use `LANG=C LC_ALL=C` to get the output of a command in English, eg. `LANG=C LC_ALL=C date` displays time in English.
|
||||||
|
2. If it throws errors, **PLEASE**, attach logs and describe in detail if possible.
|
||||||
|
- The CLI failed to run? Simply post the output below.
|
||||||
|
- Installation failed? Run installation again for logs.
|
||||||
|
- You may use more code blocks when needed.
|
||||||
|
3. In case you are confused, the `<details>`, `<summary>`, `</summary>`, `</details>` are HTML tags for folding the logs (typically very long) inside. Please do not touch them (unless you know what you are doing).
|
||||||
|
4. If the logs are suuuuuuper long, consider using an online pastebin service instead.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Step 3. Describe the issue"
|
||||||
|
value: "\n<!-- Firsly describe your issue here! -->\n\n<details><summary>Logs</summary>\n\n```\n<!-- Put your log content here!-->\n```\n\n</details>"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Reminder
|
||||||
|
options:
|
||||||
|
- label: I agree that it's usually impossible for others to help me without my logs.
|
||||||
|
required: true
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
name: Create release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Clean stale artifacts
|
||||||
|
run: git clean -dfx
|
||||||
|
|
||||||
|
- name: Setup python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Install build
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install build
|
||||||
|
|
||||||
|
- name: Create packages
|
||||||
|
run: python -m build
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: dist/*
|
||||||
|
generate_release_notes: true
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
name: Update emojis
|
name: Update emojis
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * 0"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update:
|
update:
|
||||||
@@ -28,11 +28,11 @@ jobs:
|
|||||||
pip install .
|
pip install .
|
||||||
|
|
||||||
- name: Fetch emojis
|
- name: Fetch emojis
|
||||||
run: ./run.sh emoji -f
|
run: ./bin/caelestia emoji -f
|
||||||
|
|
||||||
- name: Check for changes
|
- name: Check for changes
|
||||||
id: check
|
id: check
|
||||||
run: echo modified=$(test -n "$(git status --porcelain)" && echo 'true' || echo 'false') >> $GITHUB_OUTPUT
|
run: echo modified=$(git diff --exit-code src/caelestia/data/emojis.txt &>/dev/null && echo 'false' || echo 'true') >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Commit and push changes
|
- name: Commit and push changes
|
||||||
if: steps.check.outputs.modified == 'true'
|
if: steps.check.outputs.modified == 'true'
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
name: Update flake inputs
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-flake:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: nixbuild/nix-quick-install-action@v31
|
||||||
|
with:
|
||||||
|
nix_conf: |
|
||||||
|
keep-env-derivations = true
|
||||||
|
keep-outputs = true
|
||||||
|
|
||||||
|
- name: Restore and save Nix store
|
||||||
|
uses: nix-community/cache-nix-action@v6
|
||||||
|
with:
|
||||||
|
# restore and save a cache using this key
|
||||||
|
primary-key: nix-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||||
|
# if there's no cache hit, restore a cache by this prefix
|
||||||
|
restore-prefixes-first-match: nix-
|
||||||
|
# collect garbage until the Nix store size (in bytes) is at most this number
|
||||||
|
# before trying to save a new cache
|
||||||
|
# 1G = 1073741824
|
||||||
|
gc-max-store-size-linux: 1G
|
||||||
|
# do purge caches
|
||||||
|
purge: true
|
||||||
|
# purge all versions of the cache
|
||||||
|
purge-prefixes: nix-
|
||||||
|
# created more than this number of seconds ago
|
||||||
|
purge-created: 0
|
||||||
|
# or, last accessed more than this number of seconds ago
|
||||||
|
# relative to the start of the `Post Restore and save Nix store` phase
|
||||||
|
purge-last-accessed: 0
|
||||||
|
# except any version with the key that is the same as the `primary-key`
|
||||||
|
purge-primary-key: never
|
||||||
|
|
||||||
|
- name: Update flake inputs
|
||||||
|
run: nix flake update
|
||||||
|
|
||||||
|
- name: Attempt to build flake
|
||||||
|
run: nix build '.#with-shell'
|
||||||
|
|
||||||
|
- name: Test modules
|
||||||
|
run: |
|
||||||
|
result/bin/caelestia -v
|
||||||
|
result/bin/caelestia -h
|
||||||
|
result/bin/caelestia toggle -h
|
||||||
|
result/bin/caelestia scheme -h
|
||||||
|
result/bin/caelestia scheme list
|
||||||
|
result/bin/caelestia scheme get
|
||||||
|
result/bin/caelestia scheme set -n gruvbox -f hard -m dark -v content
|
||||||
|
result/bin/caelestia screenshot -h
|
||||||
|
result/bin/caelestia record -h
|
||||||
|
result/bin/caelestia clipboard -h
|
||||||
|
result/bin/caelestia emoji -h
|
||||||
|
result/bin/caelestia emoji
|
||||||
|
result/bin/caelestia wallpaper -h
|
||||||
|
result/bin/caelestia resizer -h
|
||||||
|
|
||||||
|
- name: Test graphical stuff
|
||||||
|
env:
|
||||||
|
XDG_RUNTIME_DIR: /home/runner/runtime
|
||||||
|
WLR_BACKENDS: headless
|
||||||
|
WLR_LIBINPUT_NO_DEVICES: 1
|
||||||
|
WAYLAND_DISPLAY: wayland-1
|
||||||
|
GTK_USE_PORTAL: 0
|
||||||
|
run: |
|
||||||
|
mkdir $XDG_RUNTIME_DIR
|
||||||
|
chown $USER $XDG_RUNTIME_DIR
|
||||||
|
chmod 0700 $XDG_RUNTIME_DIR
|
||||||
|
|
||||||
|
nix profile install 'nixpkgs#sway'
|
||||||
|
sway &
|
||||||
|
sleep 3 # Give Sway some time to start
|
||||||
|
result/bin/caelestia shell -d
|
||||||
|
sleep 3 # Give the shell some time to start (and die)
|
||||||
|
|
||||||
|
# Test CLI graphical modules
|
||||||
|
result/bin/caelestia clipboard &
|
||||||
|
result/bin/caelestia emoji -p &
|
||||||
|
result/bin/caelestia shell -s
|
||||||
|
result/bin/caelestia shell drawers list
|
||||||
|
result/bin/caelestia shell mpris list
|
||||||
|
result/bin/caelestia shell notifs clear
|
||||||
|
|
||||||
|
pgrep .quickshell-wra # Fail job if shell died
|
||||||
|
result/bin/caelestia shell -k
|
||||||
|
killall sway # Screw using IPC
|
||||||
|
|
||||||
|
- name: Check for changes
|
||||||
|
id: check
|
||||||
|
run: echo modified=$(git diff --exit-code flake.lock &>/dev/null && echo 'false' || echo 'true') >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Commit and push changes
|
||||||
|
if: steps.check.outputs.modified == 'true'
|
||||||
|
uses: EndBug/add-and-commit@v9
|
||||||
|
with:
|
||||||
|
add: flake.lock
|
||||||
|
default_author: github_actions
|
||||||
|
message: "[CI] chore: update flake"
|
||||||
@@ -11,9 +11,8 @@ The main control script for the Caelestia dotfiles.
|
|||||||
- [`app2unit`](https://github.com/Vladimir-csp/app2unit) - launching apps
|
- [`app2unit`](https://github.com/Vladimir-csp/app2unit) - launching apps
|
||||||
- [`wl-clipboard`](https://github.com/bugaevc/wl-clipboard) - copying to clipboard
|
- [`wl-clipboard`](https://github.com/bugaevc/wl-clipboard) - copying to clipboard
|
||||||
- [`slurp`](https://github.com/emersion/slurp) - selecting an area
|
- [`slurp`](https://github.com/emersion/slurp) - selecting an area
|
||||||
- [`wl-screenrec`](https://github.com/russelltg/wl-screenrec) - screen recording
|
- [`gpu-screen-recorder`](https://git.dec05eba.com/gpu-screen-recorder/about) - screen recording
|
||||||
- `glib2` - closing notifications
|
- `glib2` - closing notifications
|
||||||
- `libpulse` - getting audio device
|
|
||||||
- [`cliphist`](https://github.com/sentriz/cliphist) - clipboard history
|
- [`cliphist`](https://github.com/sentriz/cliphist) - clipboard history
|
||||||
- [`fuzzel`](https://codeberg.org/dnkl/fuzzel) - clipboard history/emoji picker
|
- [`fuzzel`](https://codeberg.org/dnkl/fuzzel) - clipboard history/emoji picker
|
||||||
|
|
||||||
@@ -21,18 +20,52 @@ The main control script for the Caelestia dotfiles.
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Package manager (recommended)
|
### Arch linux
|
||||||
|
|
||||||
The cli is available from the AUR as `caelestia-cli-git`. To install it you can use
|
The CLI is available from the AUR as `caelestia-cli`. You can install it with an AUR helper
|
||||||
an AUR helper like [`yay`](https://github.com/Jguer/yay), or manually download the
|
like [`yay`](https://github.com/Jguer/yay) or manually downloading the PKGBUILD and running `makepkg -si`.
|
||||||
PKGBUILD and run `makepkg -si`.
|
|
||||||
|
|
||||||
e.g. using yay
|
A package following the latest commit also exists as `caelestia-cli-git`. This is bleeding edge
|
||||||
|
and likely to be unstable/have bugs. Regular users are recommended to use the stable package
|
||||||
|
(`caelestia-cli`).
|
||||||
|
|
||||||
|
### Nix
|
||||||
|
|
||||||
|
You can run the CLI directly via `nix run`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yay -S caelestia-cli-git
|
nix run github:caelestia-dots/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or add it to your system configuration:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
|
caelestia-cli = {
|
||||||
|
url = "github:caelestia-dots/cli";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The package is available as `caelestia-cli.packages.<system>.default`, which can be added to your
|
||||||
|
`environment.systemPackages`, `users.users.<username>.packages`, `home.packages` if using home-manager,
|
||||||
|
or a devshell. The CLI can then be used via the `caelestia` command.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> The default package does not have the shell enabled by default, which is required for full functionality.
|
||||||
|
> To enable the shell, use the `with-shell` package. This is the recommended installation method, as
|
||||||
|
> the CLI exposes the shell via the `shell` subcommand, meaning there is no need for the shell package
|
||||||
|
> to be exposed.
|
||||||
|
|
||||||
|
For home-manager, you can also use the Caelestia's home manager module (explained in
|
||||||
|
[configuring](https://github.com/caelestia-dots/shell?tab=readme-ov-file#home-manager-module)) that
|
||||||
|
installs and configures the shell and the CLI.
|
||||||
|
|
||||||
### Manual installation
|
### Manual installation
|
||||||
|
|
||||||
Install all [dependencies](#dependencies), then install
|
Install all [dependencies](#dependencies), then install
|
||||||
@@ -44,7 +77,7 @@ Install all [dependencies](#dependencies), then install
|
|||||||
e.g. via an AUR helper (yay)
|
e.g. via an AUR helper (yay)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yay -S libnotify swappy grim dart-sass app2unit wl-clipboard slurp wl-screenrec glib2 libpulse cliphist fuzzel python-build python-installer python-hatch python-hatch-vcs
|
yay -S libnotify swappy grim dart-sass app2unit wl-clipboard slurp gpu-screen-recorder glib2 cliphist fuzzel python-build python-installer python-hatch python-hatch-vcs
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, clone the repo, `cd` into it, build the wheel via `python -m build --wheel`
|
Now, clone the repo, `cd` into it, build the wheel via `python -m build --wheel`
|
||||||
@@ -66,24 +99,91 @@ All subcommands/options can be explored via the help flag.
|
|||||||
|
|
||||||
```
|
```
|
||||||
$ caelestia -h
|
$ caelestia -h
|
||||||
usage: caelestia [-h] COMMAND ...
|
usage: caelestia [-h] [-v] COMMAND ...
|
||||||
|
|
||||||
Main control script for the Caelestia dotfiles
|
Main control script for the Caelestia dotfiles
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
|
-v, --version print the current version
|
||||||
|
|
||||||
subcommands:
|
subcommands:
|
||||||
valid subcommands
|
valid subcommands
|
||||||
|
|
||||||
COMMAND the subcommand to run
|
COMMAND the subcommand to run
|
||||||
shell start or message the shell
|
shell start or message the shell
|
||||||
toggle toggle a special workspace
|
toggle toggle a special workspace
|
||||||
scheme manage the colour scheme
|
scheme manage the colour scheme
|
||||||
screenshot take a screenshot
|
screenshot take a screenshot
|
||||||
record start a screen recording
|
record start a screen recording
|
||||||
clipboard open clipboard history
|
clipboard open clipboard history
|
||||||
emoji emoji/glyph utilities
|
emoji emoji/glyph utilities
|
||||||
wallpaper manage the wallpaper
|
wallpaper manage the wallpaper
|
||||||
pip picture in picture utilities
|
resizer window resizer daemon
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Configuring
|
||||||
|
|
||||||
|
All configuration options are in `~/.config/caelestia/cli.json`.
|
||||||
|
|
||||||
|
<details><summary>Example configuration</summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"theme": {
|
||||||
|
"enableTerm": true,
|
||||||
|
"enableHypr": true,
|
||||||
|
"enableDiscord": true,
|
||||||
|
"enableSpicetify": true,
|
||||||
|
"enableFuzzel": true,
|
||||||
|
"enableBtop": true,
|
||||||
|
"enableGtk": true,
|
||||||
|
"enableQt": true
|
||||||
|
},
|
||||||
|
"toggles": {
|
||||||
|
"communication": {
|
||||||
|
"discord": {
|
||||||
|
"enable": true,
|
||||||
|
"match": [{ "class": "discord" }],
|
||||||
|
"command": ["discord"],
|
||||||
|
"move": true
|
||||||
|
},
|
||||||
|
"whatsapp": {
|
||||||
|
"enable": true,
|
||||||
|
"match": [{ "class": "whatsapp" }],
|
||||||
|
"move": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"music": {
|
||||||
|
"spotify": {
|
||||||
|
"enable": true,
|
||||||
|
"match": [{ "class": "Spotify" }, { "initialTitle": "Spotify" }, { "initialTitle": "Spotify Free" }],
|
||||||
|
"command": ["spicetify", "watch", "-s"],
|
||||||
|
"move": true
|
||||||
|
},
|
||||||
|
"feishin": {
|
||||||
|
"enable": true,
|
||||||
|
"match": [{ "class": "feishin" }],
|
||||||
|
"move": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sysmon": {
|
||||||
|
"btop": {
|
||||||
|
"enable": true,
|
||||||
|
"match": [{ "class": "btop", "title": "btop", "workspace": { "name": "special:sysmon" } }],
|
||||||
|
"command": ["foot", "-a", "btop", "-T", "btop", "fish", "-C", "exec btop"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"todo": {
|
||||||
|
"todoist": {
|
||||||
|
"enable": true,
|
||||||
|
"match": [{ "class": "Todoist" }],
|
||||||
|
"command": ["todoist"],
|
||||||
|
"move": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
# Utility script for running caelestia
|
# Utility script for running caelestia
|
||||||
|
|
||||||
cd "$(dirname $0)/src" || exit
|
cd "$(dirname $0)/../src" || exit
|
||||||
|
|
||||||
python -m caelestia "$@"
|
python -m caelestia "$@"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
set -l seen '__fish_seen_subcommand_from'
|
set -l seen '__fish_seen_subcommand_from'
|
||||||
set -l has_opt '__fish_contains_opt'
|
set -l has_opt '__fish_contains_opt'
|
||||||
|
|
||||||
set -l commands shell toggle scheme screenshot record clipboard emoji-picker wallpaper pip
|
set -l commands shell toggle scheme screenshot record clipboard emoji-picker wallpaper resizer
|
||||||
set -l not_seen "not $seen $commands"
|
set -l not_seen "not $seen $commands"
|
||||||
|
|
||||||
# Disable file completions
|
# Disable file completions
|
||||||
@@ -19,7 +19,7 @@ complete -c caelestia -n $not_seen -a 'record' -d 'Start a screen recording'
|
|||||||
complete -c caelestia -n $not_seen -a 'clipboard' -d 'Open clipboard history'
|
complete -c caelestia -n $not_seen -a 'clipboard' -d 'Open clipboard history'
|
||||||
complete -c caelestia -n $not_seen -a 'emoji' -d 'Emoji/glyph utilities'
|
complete -c caelestia -n $not_seen -a 'emoji' -d 'Emoji/glyph utilities'
|
||||||
complete -c caelestia -n $not_seen -a 'wallpaper' -d 'Manage the wallpaper'
|
complete -c caelestia -n $not_seen -a 'wallpaper' -d 'Manage the wallpaper'
|
||||||
complete -c caelestia -n $not_seen -a 'pip' -d 'Picture in picture utilities'
|
complete -c caelestia -n $not_seen -a 'resizer' -d 'Window resizer'
|
||||||
|
|
||||||
# Shell
|
# Shell
|
||||||
set -l commands mpris drawers wallpaper notifs
|
set -l commands mpris drawers wallpaper notifs
|
||||||
@@ -58,7 +58,7 @@ set -l not_seen "$seen shell && $seen drawers && not $seen $commands"
|
|||||||
complete -c caelestia -n $not_seen -a 'list' -d 'List togglable drawers'
|
complete -c caelestia -n $not_seen -a 'list' -d 'List togglable drawers'
|
||||||
complete -c caelestia -n $not_seen -a 'toggle' -d 'Toggle a drawer'
|
complete -c caelestia -n $not_seen -a 'toggle' -d 'Toggle a drawer'
|
||||||
|
|
||||||
set -l commands (caelestia shell drawers list)
|
set -l commands (caelestia shell drawers list 2> /dev/null)
|
||||||
complete -c caelestia -n "$seen shell && $seen drawers && $seen toggle && not $seen $commands" -a "$commands" -d 'drawer'
|
complete -c caelestia -n "$seen shell && $seen drawers && $seen toggle && not $seen $commands" -a "$commands" -d 'drawer'
|
||||||
|
|
||||||
set -l commands list get set
|
set -l commands list get set
|
||||||
@@ -121,5 +121,7 @@ complete -c caelestia -n "$seen wallpaper" -s 'N' -l 'no-smart' -d 'Disable smar
|
|||||||
complete -c caelestia -n "$seen emoji" -s 'p' -l 'picker' -d 'Open emoji/glyph picker'
|
complete -c caelestia -n "$seen emoji" -s 'p' -l 'picker' -d 'Open emoji/glyph picker'
|
||||||
complete -c caelestia -n "$seen emoji" -s 'f' -l 'fetch' -d 'Fetch emoji/glyph data from remote'
|
complete -c caelestia -n "$seen emoji" -s 'f' -l 'fetch' -d 'Fetch emoji/glyph data from remote'
|
||||||
|
|
||||||
# Pip
|
# Resizer
|
||||||
complete -c caelestia -n "$seen pip" -s 'd' -l 'daemon' -d 'Start in daemon mode'
|
complete -c caelestia -n "$seen resizer" -s 'd' -l 'daemon' -d 'Start in daemon mode'
|
||||||
|
complete -c caelestia -n "$seen resizer" -a 'pip' -d 'Quick pip mode'
|
||||||
|
complete -c caelestia -n "$seen resizer" -a 'active' -d 'Select the active window'
|
||||||
|
|||||||
+5
-5
@@ -12,13 +12,13 @@
|
|||||||
dart-sass,
|
dart-sass,
|
||||||
grim,
|
grim,
|
||||||
fuzzel,
|
fuzzel,
|
||||||
wl-screenrec,
|
gpu-screen-recorder,
|
||||||
dconf,
|
dconf,
|
||||||
killall,
|
killall,
|
||||||
caelestia-shell,
|
caelestia-shell,
|
||||||
withShell ? false,
|
withShell ? false,
|
||||||
discordBin ? "discord",
|
discordBin ? "discord",
|
||||||
qtctStyle ? "Fusion",
|
qtctStyle ? "Darkly",
|
||||||
}:
|
}:
|
||||||
python3.pkgs.buildPythonApplication {
|
python3.pkgs.buildPythonApplication {
|
||||||
pname = "caelestia-cli";
|
pname = "caelestia-cli";
|
||||||
@@ -50,7 +50,7 @@ python3.pkgs.buildPythonApplication {
|
|||||||
dart-sass
|
dart-sass
|
||||||
grim
|
grim
|
||||||
fuzzel
|
fuzzel
|
||||||
wl-screenrec
|
gpu-screen-recorder
|
||||||
dconf
|
dconf
|
||||||
killall
|
killall
|
||||||
]
|
]
|
||||||
@@ -70,9 +70,9 @@ python3.pkgs.buildPythonApplication {
|
|||||||
--replace-fail 'discord' ${discordBin} \
|
--replace-fail 'discord' ${discordBin} \
|
||||||
--replace-fail 'todoist' 'todoist.desktop'
|
--replace-fail 'todoist' 'todoist.desktop'
|
||||||
|
|
||||||
# Use config style instead of fusion
|
# Use config style instead of darkly
|
||||||
substituteInPlace src/caelestia/data/templates/qtct.conf \
|
substituteInPlace src/caelestia/data/templates/qtct.conf \
|
||||||
--replace-fail 'Fusion' '${qtctStyle}'
|
--replace-fail 'Darkly' '${qtctStyle}'
|
||||||
'';
|
'';
|
||||||
|
|
||||||
postInstall = "installShellCompletion completions/caelestia.fish";
|
postInstall = "installShellCompletion completions/caelestia.fish";
|
||||||
|
|||||||
Generated
+11
-60
@@ -1,67 +1,19 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"app2unit": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1752557494,
|
|
||||||
"narHash": "sha256-GIcH+k321WBUl//gBypaJkLRMrKDemcSpzADJoyUdec=",
|
|
||||||
"owner": "soramanew",
|
|
||||||
"repo": "app2unit",
|
|
||||||
"rev": "574d764446997e30218a29a6b9871fb1b9c6554d",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "soramanew",
|
|
||||||
"repo": "app2unit",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"caelestia-cli": {
|
|
||||||
"inputs": {
|
|
||||||
"app2unit": [
|
|
||||||
"caelestia-shell",
|
|
||||||
"app2unit"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"caelestia-shell",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1752566000,
|
|
||||||
"narHash": "sha256-xaSDZXvZtuM+88PsmfTDWv6+VxN5cOsT/5/czsk3xgI=",
|
|
||||||
"owner": "caelestia-dots",
|
|
||||||
"repo": "cli",
|
|
||||||
"rev": "b1019d11924d1bc9440f457ddf94fc0d8a230ff4",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "caelestia-dots",
|
|
||||||
"repo": "cli",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"caelestia-shell": {
|
"caelestia-shell": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"app2unit": [
|
"caelestia-cli": [],
|
||||||
"app2unit"
|
|
||||||
],
|
|
||||||
"caelestia-cli": "caelestia-cli",
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"quickshell": "quickshell"
|
"quickshell": "quickshell"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752637099,
|
"lastModified": 1757726711,
|
||||||
"narHash": "sha256-08oPnEGYkuU7Vqa4F7rOi4E9j2Drigm3DxdOA+/mgF4=",
|
"narHash": "sha256-nihMIyW+IN01jLH+XhRDJ4V/9ulD/iqi0dvA7gYlclA=",
|
||||||
"owner": "caelestia-dots",
|
"owner": "caelestia-dots",
|
||||||
"repo": "shell",
|
"repo": "shell",
|
||||||
"rev": "19431534c954f763eb095dd131fd0b19ff74837b",
|
"rev": "a57dd9343a2643f73f3994dc230b824617f89ecf",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -72,11 +24,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752480373,
|
"lastModified": 1757487488,
|
||||||
"narHash": "sha256-JHQbm+OcGp32wAsXTE/FLYGNpb+4GLi5oTvCxwSoBOA=",
|
"narHash": "sha256-zwE/e7CuPJUWKdvvTCB7iunV4E/+G0lKfv4kk/5Izdg=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "62e0f05ede1da0d54515d4ea8ce9c733f12d9f08",
|
"rev": "ab0f3607a6c7486ea22229b92ed2d355f1482ee0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -94,11 +46,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752631407,
|
"lastModified": 1756981260,
|
||||||
"narHash": "sha256-dLDtKxh1VabwLxv5xbjI+oRkDyqWEKGITU+0dEaaW28=",
|
"narHash": "sha256-GhuD9QVimjynHI0OOyZsqJsnlXr2orowh9H+HYz4YMs=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "4d8055f1cd9924bcace59405894b8879633eb83d",
|
"rev": "6eb12551baf924f8fdecdd04113863a754259c34",
|
||||||
"revCount": 638,
|
"revCount": 672,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
||||||
},
|
},
|
||||||
@@ -109,7 +61,6 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"app2unit": "app2unit",
|
|
||||||
"caelestia-shell": "caelestia-shell",
|
"caelestia-shell": "caelestia-shell",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,10 @@
|
|||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
|
|
||||||
app2unit = {
|
|
||||||
url = "github:soramanew/app2unit";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
|
|
||||||
caelestia-shell = {
|
caelestia-shell = {
|
||||||
url = "github:caelestia-dots/shell";
|
url = "github:caelestia-dots/shell";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
inputs.app2unit.follows = "app2unit";
|
inputs.caelestia-cli.follows = "";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,20 +26,15 @@
|
|||||||
packages = forAllSystems (pkgs: rec {
|
packages = forAllSystems (pkgs: rec {
|
||||||
caelestia-cli = pkgs.callPackage ./default.nix {
|
caelestia-cli = pkgs.callPackage ./default.nix {
|
||||||
rev = self.rev or self.dirtyRev;
|
rev = self.rev or self.dirtyRev;
|
||||||
app2unit = inputs.app2unit.packages.${pkgs.system}.default;
|
|
||||||
caelestia-shell = inputs.caelestia-shell.packages.${pkgs.system}.default;
|
caelestia-shell = inputs.caelestia-shell.packages.${pkgs.system}.default;
|
||||||
};
|
};
|
||||||
|
with-shell = caelestia-cli.override {withShell = true;};
|
||||||
default = caelestia-cli;
|
default = caelestia-cli;
|
||||||
});
|
});
|
||||||
|
|
||||||
devShells = forAllSystems (pkgs: {
|
devShells = forAllSystems (pkgs: {
|
||||||
default = pkgs.mkShellNoCC {
|
default = pkgs.mkShellNoCC {
|
||||||
inputsFrom = [self.packages.${pkgs.system}.caelestia-cli];
|
packages = [self.packages.${pkgs.system}.with-shell];
|
||||||
packages = [
|
|
||||||
(pkgs.writeShellScriptBin "caelestia" ''
|
|
||||||
cd src && python -m caelestia "$@"
|
|
||||||
'')
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,3 +16,13 @@ caelestia = "caelestia:main"
|
|||||||
|
|
||||||
[tool.hatch.version]
|
[tool.hatch.version]
|
||||||
source = "vcs"
|
source = "vcs"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.sdist]
|
||||||
|
only-include = [
|
||||||
|
"src",
|
||||||
|
"completions",
|
||||||
|
"README.md"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 120
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky cadcff
|
|||||||
sapphire aec7ff
|
sapphire aec7ff
|
||||||
blue a6baff
|
blue a6baff
|
||||||
lavender bfcaff
|
lavender bfcaff
|
||||||
|
klink 6685d1
|
||||||
|
klinkSelection 6585d1
|
||||||
|
kvisited 7276dd
|
||||||
|
kvisitedSelection 7276dd
|
||||||
|
knegative 8e70ff
|
||||||
|
knegativeSelection 8e70ff
|
||||||
|
kneutral c794ff
|
||||||
|
kneutralSelection c794ff
|
||||||
|
kpositive 54afff
|
||||||
|
kpositiveSelection 54afff
|
||||||
text e4e1e7
|
text e4e1e7
|
||||||
subtext1 c6c5d1
|
subtext1 c6c5d1
|
||||||
subtext0 8f909a
|
subtext0 8f909a
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky 0082b6
|
|||||||
sapphire 037ba6
|
sapphire 037ba6
|
||||||
blue 005e90
|
blue 005e90
|
||||||
lavender 0077b7
|
lavender 0077b7
|
||||||
|
klink 2e8fc3
|
||||||
|
klinkSelection 308fc4
|
||||||
|
kvisited 2584d6
|
||||||
|
kvisitedSelection 2984d7
|
||||||
|
knegative 607eff
|
||||||
|
knegativeSelection 607eff
|
||||||
|
kneutral c794ff
|
||||||
|
kneutralSelection c794ff
|
||||||
|
kpositive 00b8de
|
||||||
|
kpositiveSelection 00b8df
|
||||||
text 191c1e
|
text 191c1e
|
||||||
subtext1 41484e
|
subtext1 41484e
|
||||||
subtext0 6e757c
|
subtext0 6e757c
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky ccdbff
|
|||||||
sapphire b1c6ff
|
sapphire b1c6ff
|
||||||
blue aab9ff
|
blue aab9ff
|
||||||
lavender c2c9ff
|
lavender c2c9ff
|
||||||
|
klink 6a84d1
|
||||||
|
klinkSelection 6a84d1
|
||||||
|
kvisited 7775dc
|
||||||
|
kvisitedSelection 7775dc
|
||||||
|
knegative 946dff
|
||||||
|
knegativeSelection 946dff
|
||||||
|
kneutral c794ff
|
||||||
|
kneutralSelection c794ff
|
||||||
|
kpositive 5daeff
|
||||||
|
kpositiveSelection 5eaeff
|
||||||
text e4e1e7
|
text e4e1e7
|
||||||
subtext1 c6c5d1
|
subtext1 c6c5d1
|
||||||
subtext0 90909a
|
subtext0 90909a
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky d0daff
|
|||||||
sapphire b7c5ff
|
sapphire b7c5ff
|
||||||
blue b0b8ff
|
blue b0b8ff
|
||||||
lavender c7c8ff
|
lavender c7c8ff
|
||||||
|
klink 7382d2
|
||||||
|
klinkSelection 7382d2
|
||||||
|
kvisited 8172da
|
||||||
|
kvisitedSelection 8172da
|
||||||
|
knegative a167ff
|
||||||
|
knegativeSelection a167ff
|
||||||
|
kneutral ca92ff
|
||||||
|
kneutralSelection c992ff
|
||||||
|
kpositive 60adff
|
||||||
|
kpositiveSelection 60adff
|
||||||
text e5e1e7
|
text e5e1e7
|
||||||
subtext1 c8c5d1
|
subtext1 c8c5d1
|
||||||
subtext0 918f9a
|
subtext0 918f9a
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
primary_paletteKeyColor 33653E
|
||||||
|
secondary_paletteKeyColor 1B4E2A
|
||||||
|
tertiary_paletteKeyColor 376942
|
||||||
|
neutral_paletteKeyColor 1E1E26
|
||||||
|
neutral_variant_paletteKeyColor 23252D
|
||||||
|
background 23262D
|
||||||
|
onBackground 02200B
|
||||||
|
surface 050505
|
||||||
|
surfaceDim 1E1E24
|
||||||
|
surfaceBright 1E1E24
|
||||||
|
surfaceContainerLowest 0a0a0b
|
||||||
|
surfaceContainerLow 0a0a0b
|
||||||
|
surfaceContainer 0a0a0b
|
||||||
|
surfaceContainerHigh 050505
|
||||||
|
surfaceContainerHighest 0f1210
|
||||||
|
onSurface F5F5F6
|
||||||
|
surfaceVariant 0a0a0b
|
||||||
|
onSurfaceVariant c9c9c9
|
||||||
|
inverseSurface 0a0a0b
|
||||||
|
inverseOnSurface ACACAC
|
||||||
|
outline 838383
|
||||||
|
outlineVariant 1E1E25
|
||||||
|
shadow 000000
|
||||||
|
scrim 000000
|
||||||
|
surfaceTint 24BD5C
|
||||||
|
primary 24BD5C
|
||||||
|
onPrimary 091f11
|
||||||
|
primaryContainer 0f1210
|
||||||
|
onPrimaryContainer 24BD5C
|
||||||
|
inversePrimary 24BD5C
|
||||||
|
secondary 24BD5C
|
||||||
|
onSecondary 043a14
|
||||||
|
secondaryContainer 0f1210
|
||||||
|
onSecondaryContainer F4F3F5
|
||||||
|
tertiary 32653E
|
||||||
|
onTertiary F5F4F6
|
||||||
|
tertiaryContainer 1E1E25
|
||||||
|
onTertiaryContainer F5F5F6
|
||||||
|
error c66e73
|
||||||
|
onError F5F4F6
|
||||||
|
errorContainer 893034
|
||||||
|
onErrorContainer F5F4F6
|
||||||
|
primaryFixed 24BD5C
|
||||||
|
primaryFixedDim 24BD5C
|
||||||
|
onPrimaryFixed F5F4F6
|
||||||
|
onPrimaryFixedVariant F5F4F6
|
||||||
|
secondaryFixed 24BD5C
|
||||||
|
secondaryFixedDim 24BD5C
|
||||||
|
onSecondaryFixed F5F4F6
|
||||||
|
onSecondaryFixedVariant F5F4F6
|
||||||
|
tertiaryFixed 24BD5C
|
||||||
|
tertiaryFixedDim 24BD5C
|
||||||
|
onTertiaryFixed F5F4F6
|
||||||
|
onTertiaryFixedVariant F5F4F6
|
||||||
|
term0 343434
|
||||||
|
term1 23B65A
|
||||||
|
term2 43ff88
|
||||||
|
term3 7cfcab
|
||||||
|
term4 78c19f
|
||||||
|
term5 7ae9a7
|
||||||
|
term6 80deb2
|
||||||
|
term7 ccdcd6
|
||||||
|
term8 9aa59f
|
||||||
|
term9 cdff9e
|
||||||
|
term10 00f608
|
||||||
|
term11 c9fff3
|
||||||
|
term12 a4c7cd
|
||||||
|
term13 a5f7a2
|
||||||
|
term14 87f1b5
|
||||||
|
term15 ffffff
|
||||||
|
rosewater f4f0fa
|
||||||
|
flamingo dfe0f5
|
||||||
|
pink bdffd4
|
||||||
|
mauve 73fa90
|
||||||
|
red 8affab
|
||||||
|
maroon abf0c5
|
||||||
|
peach a9daac
|
||||||
|
yellow d0f9f4
|
||||||
|
green 8af797
|
||||||
|
teal a0f9aa
|
||||||
|
sky cefb97
|
||||||
|
sapphire 85ef77
|
||||||
|
blue 65eea0
|
||||||
|
lavender 90f79e
|
||||||
|
text e0e3e4
|
||||||
|
subtext1 bec8cc
|
||||||
|
subtext0 889296
|
||||||
|
overlay2 767f83
|
||||||
|
overlay1 646c6f
|
||||||
|
overlay0 535a5d
|
||||||
|
surface2 43494b
|
||||||
|
surface1 33383a
|
||||||
|
surface0 212627
|
||||||
|
base 101415
|
||||||
|
mantle 101415
|
||||||
|
crust 0f1314
|
||||||
|
success B5CCBA
|
||||||
|
onSuccess 213528
|
||||||
|
successContainer 374B3E
|
||||||
|
onSuccessContainer D1E9D6
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
primary_paletteKeyColor 33653E
|
||||||
|
secondary_paletteKeyColor 1B4E2A
|
||||||
|
tertiary_paletteKeyColor 376942
|
||||||
|
neutral_paletteKeyColor 1E1E26
|
||||||
|
neutral_variant_paletteKeyColor 23252D
|
||||||
|
background 23262D
|
||||||
|
onBackground 02200B
|
||||||
|
surface 1E1E24
|
||||||
|
surfaceDim 1E1E24
|
||||||
|
surfaceBright 1E1E24
|
||||||
|
surfaceContainerLowest 23262C
|
||||||
|
surfaceContainerLow 23262C
|
||||||
|
surfaceContainer 23262C
|
||||||
|
surfaceContainerHigh 1b1d22
|
||||||
|
surfaceContainerHighest 232C29
|
||||||
|
onSurface F5F5F6
|
||||||
|
surfaceVariant 23262C
|
||||||
|
onSurfaceVariant c9c9c9
|
||||||
|
inverseSurface 23262C
|
||||||
|
inverseOnSurface ACACAC
|
||||||
|
outline 979797
|
||||||
|
outlineVariant 1E1E25
|
||||||
|
shadow 000000
|
||||||
|
scrim 000000
|
||||||
|
surfaceTint 24BD5C
|
||||||
|
primary 24BD5C
|
||||||
|
onPrimary 091f11
|
||||||
|
primaryContainer 232c29
|
||||||
|
onPrimaryContainer 24BD5C
|
||||||
|
inversePrimary 24BD5C
|
||||||
|
secondary 24BD5C
|
||||||
|
onSecondary 043a14
|
||||||
|
secondaryContainer 232c29
|
||||||
|
onSecondaryContainer F4F3F5
|
||||||
|
tertiary 32653E
|
||||||
|
onTertiary F5F4F6
|
||||||
|
tertiaryContainer 1E1E25
|
||||||
|
onTertiaryContainer F5F5F6
|
||||||
|
error c66e73
|
||||||
|
onError F5F4F6
|
||||||
|
errorContainer 893034
|
||||||
|
onErrorContainer F5F4F6
|
||||||
|
primaryFixed 24BD5C
|
||||||
|
primaryFixedDim 24BD5C
|
||||||
|
onPrimaryFixed F5F4F6
|
||||||
|
onPrimaryFixedVariant F5F4F6
|
||||||
|
secondaryFixed 24BD5C
|
||||||
|
secondaryFixedDim 24BD5C
|
||||||
|
onSecondaryFixed F5F4F6
|
||||||
|
onSecondaryFixedVariant F5F4F6
|
||||||
|
tertiaryFixed 24BD5C
|
||||||
|
tertiaryFixedDim 24BD5C
|
||||||
|
onTertiaryFixed F5F4F6
|
||||||
|
onTertiaryFixedVariant F5F4F6
|
||||||
|
term0 343434
|
||||||
|
term1 23B65A
|
||||||
|
term2 43ff88
|
||||||
|
term3 7cfcab
|
||||||
|
term4 78c19f
|
||||||
|
term5 7ae9a7
|
||||||
|
term6 80deb2
|
||||||
|
term7 ccdcd6
|
||||||
|
term8 9aa59f
|
||||||
|
term9 cdff9e
|
||||||
|
term10 00f608
|
||||||
|
term11 c9fff3
|
||||||
|
term12 a4c7cd
|
||||||
|
term13 a5f7a2
|
||||||
|
term14 87f1b5
|
||||||
|
term15 ffffff
|
||||||
|
rosewater f4f0fa
|
||||||
|
flamingo dfe0f5
|
||||||
|
pink bdffd4
|
||||||
|
mauve 73fa90
|
||||||
|
red 8affab
|
||||||
|
maroon abf0c5
|
||||||
|
peach a9daac
|
||||||
|
yellow d0f9f4
|
||||||
|
green 8af797
|
||||||
|
teal a0f9aa
|
||||||
|
sky cefb97
|
||||||
|
sapphire 85ef77
|
||||||
|
blue 65eea0
|
||||||
|
lavender 90f79e
|
||||||
|
text e0e3e4
|
||||||
|
subtext1 bec8cc
|
||||||
|
subtext0 889296
|
||||||
|
overlay2 767f83
|
||||||
|
overlay1 646c6f
|
||||||
|
overlay0 535a5d
|
||||||
|
surface2 43494b
|
||||||
|
surface1 33383a
|
||||||
|
surface0 212627
|
||||||
|
base 101415
|
||||||
|
mantle 101415
|
||||||
|
crust 0f1314
|
||||||
|
success B5CCBA
|
||||||
|
onSuccess 213528
|
||||||
|
successContainer 374B3E
|
||||||
|
onSuccessContainer D1E9D6
|
||||||
@@ -82,6 +82,16 @@ sky 97e7fb
|
|||||||
sapphire 77d4ef
|
sapphire 77d4ef
|
||||||
blue 65c9ee
|
blue 65c9ee
|
||||||
lavender 90d6f7
|
lavender 90d6f7
|
||||||
|
klink 0093b4
|
||||||
|
klinkSelection 0093b3
|
||||||
|
kvisited 0089bf
|
||||||
|
kvisitedSelection 0089be
|
||||||
|
knegative 607eff
|
||||||
|
knegativeSelection 607eff
|
||||||
|
kneutral 34c359
|
||||||
|
kneutralSelection 34c359
|
||||||
|
kpositive 00bbc7
|
||||||
|
kpositiveSelection 00bbc7
|
||||||
text e0e3e4
|
text e0e3e4
|
||||||
subtext1 bec8cc
|
subtext1 bec8cc
|
||||||
subtext0 889296
|
subtext0 889296
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky 4b882e
|
|||||||
sapphire 5d7c2e
|
sapphire 5d7c2e
|
||||||
blue 00664e
|
blue 00664e
|
||||||
lavender 00816c
|
lavender 00816c
|
||||||
|
klink 559652
|
||||||
|
klinkSelection 559652
|
||||||
|
kvisited c06b00
|
||||||
|
kvisitedSelection c06b00
|
||||||
|
knegative a78300
|
||||||
|
knegativeSelection a78300
|
||||||
|
kneutral c7a900
|
||||||
|
kneutralSelection c7a900
|
||||||
|
kpositive a0b31d
|
||||||
|
kpositiveSelection a1b31c
|
||||||
text 1c1c16
|
text 1c1c16
|
||||||
subtext1 494739
|
subtext1 494739
|
||||||
subtext0 777565
|
subtext0 777565
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky 94e8f6
|
|||||||
sapphire 74d5e9
|
sapphire 74d5e9
|
||||||
blue 5fcae8
|
blue 5fcae8
|
||||||
lavender 8cd7f3
|
lavender 8cd7f3
|
||||||
|
klink 0094ac
|
||||||
|
klinkSelection 0094ab
|
||||||
|
kvisited 008bb6
|
||||||
|
kvisitedSelection 008bb5
|
||||||
|
knegative 607eff
|
||||||
|
knegativeSelection 607eff
|
||||||
|
kneutral 34c359
|
||||||
|
kneutralSelection 34c359
|
||||||
|
kpositive 00bcbf
|
||||||
|
kpositiveSelection 00bcbd
|
||||||
text e0e3e4
|
text e0e3e4
|
||||||
subtext1 bec8ca
|
subtext1 bec8ca
|
||||||
subtext0 889394
|
subtext0 889394
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky 4b882e
|
|||||||
sapphire 657b26
|
sapphire 657b26
|
||||||
blue 00664e
|
blue 00664e
|
||||||
lavender 00816c
|
lavender 00816c
|
||||||
|
klink 559652
|
||||||
|
klinkSelection 559652
|
||||||
|
kvisited c06b00
|
||||||
|
kvisitedSelection c06b00
|
||||||
|
knegative ae8000
|
||||||
|
knegativeSelection ae8000
|
||||||
|
kneutral d1a500
|
||||||
|
kneutralSelection d0a500
|
||||||
|
kpositive adaf00
|
||||||
|
kpositiveSelection adaf00
|
||||||
text 1d1c15
|
text 1d1c15
|
||||||
subtext1 4a4738
|
subtext1 4a4738
|
||||||
subtext0 797564
|
subtext0 797564
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky e1df87
|
|||||||
sapphire b3d27e
|
sapphire b3d27e
|
||||||
blue ffa2bd
|
blue ffa2bd
|
||||||
lavender ffbcbb
|
lavender ffbcbb
|
||||||
|
klink bf6ba0
|
||||||
|
klinkSelection bf6ba0
|
||||||
|
kvisited cc6232
|
||||||
|
kvisitedSelection cc6232
|
||||||
|
knegative d66a00
|
||||||
|
knegativeSelection d66900
|
||||||
|
kneutral ff8d00
|
||||||
|
kneutralSelection ff8d06
|
||||||
|
kpositive de9d00
|
||||||
|
kpositiveSelection df9d00
|
||||||
text ece0d9
|
text ece0d9
|
||||||
subtext1 d6c3b5
|
subtext1 d6c3b5
|
||||||
subtext0 9f8e81
|
subtext0 9f8e81
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky 4b882e
|
|||||||
sapphire 6a7a22
|
sapphire 6a7a22
|
||||||
blue 00664e
|
blue 00664e
|
||||||
lavender c2484e
|
lavender c2484e
|
||||||
|
klink 559652
|
||||||
|
klinkSelection 559652
|
||||||
|
kvisited c06b00
|
||||||
|
kvisitedSelection c06b00
|
||||||
|
knegative b27f00
|
||||||
|
knegativeSelection b27f00
|
||||||
|
kneutral d5a300
|
||||||
|
kneutralSelection d5a300
|
||||||
|
kpositive b3ae00
|
||||||
|
kpositiveSelection b3ae00
|
||||||
text 1d1b15
|
text 1d1b15
|
||||||
subtext1 4b4738
|
subtext1 4b4738
|
||||||
subtext0 7a7464
|
subtext0 7a7464
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky c4ddff
|
|||||||
sapphire a4caff
|
sapphire a4caff
|
||||||
blue 9abdff
|
blue 9abdff
|
||||||
lavender b7ccff
|
lavender b7ccff
|
||||||
|
klink 5689ce
|
||||||
|
klinkSelection 5689ce
|
||||||
|
kvisited 5f7bdd
|
||||||
|
kvisitedSelection 5f7bdd
|
||||||
|
knegative 7877ff
|
||||||
|
knegativeSelection 7878ff
|
||||||
|
kneutral c794ff
|
||||||
|
kneutralSelection c794ff
|
||||||
|
kpositive 13b3ff
|
||||||
|
kpositiveSelection 0db3ff
|
||||||
text e3e2e7
|
text e3e2e7
|
||||||
subtext1 c4c6d0
|
subtext1 c4c6d0
|
||||||
subtext0 8e909a
|
subtext0 8e909a
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky c2deff
|
|||||||
sapphire a1caff
|
sapphire a1caff
|
||||||
blue 97beff
|
blue 97beff
|
||||||
lavender b5cdff
|
lavender b5cdff
|
||||||
|
klink 5389ce
|
||||||
|
klinkSelection 5489ce
|
||||||
|
kvisited 5b7cdd
|
||||||
|
kvisitedSelection 5c7bdd
|
||||||
|
knegative 7479ff
|
||||||
|
knegativeSelection 7578ff
|
||||||
|
kneutral c794ff
|
||||||
|
kneutralSelection c794ff
|
||||||
|
kpositive 00b4fd
|
||||||
|
kpositiveSelection 00b4fe
|
||||||
text e3e2e7
|
text e3e2e7
|
||||||
subtext1 c3c6d0
|
subtext1 c3c6d0
|
||||||
subtext0 8d919a
|
subtext0 8d919a
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky 4b882e
|
|||||||
sapphire 6d791e
|
sapphire 6d791e
|
||||||
blue 00664e
|
blue 00664e
|
||||||
lavender c2484e
|
lavender c2484e
|
||||||
|
klink 559652
|
||||||
|
klinkSelection 559652
|
||||||
|
kvisited c06b00
|
||||||
|
kvisitedSelection c06b00
|
||||||
|
knegative b47d00
|
||||||
|
knegativeSelection b57d00
|
||||||
|
kneutral d8a200
|
||||||
|
kneutralSelection d9a200
|
||||||
|
kpositive b7ac00
|
||||||
|
kpositiveSelection b8ac00
|
||||||
text 1e1b15
|
text 1e1b15
|
||||||
subtext1 4c4638
|
subtext1 4c4638
|
||||||
subtext0 7b7464
|
subtext0 7b7464
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky d3d9ff
|
|||||||
sapphire bdc3ff
|
sapphire bdc3ff
|
||||||
blue b7b6ff
|
blue b7b6ff
|
||||||
lavender ccc6ff
|
lavender ccc6ff
|
||||||
|
klink 7b80d1
|
||||||
|
klinkSelection 7b80d1
|
||||||
|
kvisited 8a6fd7
|
||||||
|
kvisitedSelection 8a6fd7
|
||||||
|
knegative ac62fa
|
||||||
|
knegativeSelection ac62fa
|
||||||
|
kneutral d48dff
|
||||||
|
kneutralSelection d48eff
|
||||||
|
kpositive 60adff
|
||||||
|
kpositiveSelection 60adff
|
||||||
text e5e1e7
|
text e5e1e7
|
||||||
subtext1 c9c4d0
|
subtext1 c9c4d0
|
||||||
subtext0 938f9a
|
subtext0 938f9a
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky d2d9ff
|
|||||||
sapphire bbc4ff
|
sapphire bbc4ff
|
||||||
blue b5b6ff
|
blue b5b6ff
|
||||||
lavender cbc7ff
|
lavender cbc7ff
|
||||||
|
klink 7880d1
|
||||||
|
klinkSelection 7881d1
|
||||||
|
kvisited 8770d8
|
||||||
|
kvisitedSelection 8770d8
|
||||||
|
knegative a964fd
|
||||||
|
knegativeSelection a864fd
|
||||||
|
kneutral d08fff
|
||||||
|
kneutralSelection d090ff
|
||||||
|
kpositive 60adff
|
||||||
|
kpositiveSelection 60adff
|
||||||
text e5e1e7
|
text e5e1e7
|
||||||
subtext1 c9c5d0
|
subtext1 c9c5d0
|
||||||
subtext0 928f9a
|
subtext0 928f9a
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ sky cedaff
|
|||||||
sapphire b5c5ff
|
sapphire b5c5ff
|
||||||
blue aeb8ff
|
blue aeb8ff
|
||||||
lavender c6c8ff
|
lavender c6c8ff
|
||||||
|
klink 7083d2
|
||||||
|
klinkSelection 6f83d2
|
||||||
|
kvisited 7e73db
|
||||||
|
kvisitedSelection 7d73db
|
||||||
|
knegative 9d69ff
|
||||||
|
knegativeSelection 9b6aff
|
||||||
|
kneutral c794ff
|
||||||
|
kneutralSelection c794ff
|
||||||
|
kpositive 60adff
|
||||||
|
kpositiveSelection 60adff
|
||||||
text e5e1e7
|
text e5e1e7
|
||||||
subtext1 c7c5d1
|
subtext1 c7c5d1
|
||||||
subtext0 918f9a
|
subtext0 918f9a
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Cava Audio Visualizer Configuration Template
|
||||||
|
# Optimized for smooth and responsive visualization
|
||||||
|
|
||||||
|
[general]
|
||||||
|
# Number of bars (20-200) - fewer bars = better performance
|
||||||
|
bars = 64
|
||||||
|
# Framerate (1-144) - higher = smoother but more CPU intensive
|
||||||
|
framerate = 60
|
||||||
|
|
||||||
|
[input]
|
||||||
|
# Audio input method: pulse, alsa, fifo, or portaudio
|
||||||
|
method = pulse
|
||||||
|
# Audio device (leave as default for auto-detection)
|
||||||
|
source = auto
|
||||||
|
|
||||||
|
[output]
|
||||||
|
# Output method: ncurses, terminal, raw, or circle
|
||||||
|
method = ncurses
|
||||||
|
# Terminal color scheme
|
||||||
|
style = stereo
|
||||||
|
|
||||||
|
[color]
|
||||||
|
# Color gradient for bars using template variables
|
||||||
|
gradient = 1
|
||||||
|
gradient_count = 8
|
||||||
|
gradient_color_1 = '{{ $green }}'
|
||||||
|
gradient_color_2 = '{{ $teal }}'
|
||||||
|
gradient_color_3 = '{{ $sky }}'
|
||||||
|
gradient_color_4 = '{{ $sapphire }}'
|
||||||
|
gradient_color_5 = '{{ $blue }}'
|
||||||
|
gradient_color_6 = '{{ $lavender }}'
|
||||||
|
gradient_color_7 = '{{ $mauve }}'
|
||||||
|
gradient_color_8 = '{{ $maroon }}'
|
||||||
|
|
||||||
|
[smoothing]
|
||||||
|
# Noise reduction (0-100) - higher = smoother but less responsive
|
||||||
|
# 77 is default, 85 provides good balance for smooth visualization
|
||||||
|
noise_reduction = 85
|
||||||
|
|
||||||
|
# Monstercat smoothing (0 or 1) - adds smoothing between adjacent bars
|
||||||
|
monstercat = 1
|
||||||
|
|
||||||
|
# Wave effect (0 or 1) - creates wave-like motion across bars
|
||||||
|
waves = 0
|
||||||
|
|
||||||
|
# Gravity (0-200) - controls how fast bars fall
|
||||||
|
# 100 = normal gravity, 150 = faster fall, 50 = slower fall
|
||||||
|
gravity = 120
|
||||||
|
|
||||||
|
[eq]
|
||||||
|
# Equalizer settings for frequency response
|
||||||
|
# Lower frequencies tend to be louder, so reduce them slightly
|
||||||
|
1 = 0.8
|
||||||
|
2 = 0.9
|
||||||
|
3 = 1.0
|
||||||
|
4 = 1.1
|
||||||
|
5 = 1.2
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# HTOP Color Configuration Template
|
||||||
|
# This template generates a custom htoprc configuration with themed colors
|
||||||
|
# Colors are defined using terminal color codes (0-255) or RGB hex values
|
||||||
|
|
||||||
|
# htoprc configuration with custom colors
|
||||||
|
fields=0 48 17 18 38 39 40 2 46 47 49 1
|
||||||
|
sort_key=46
|
||||||
|
sort_direction=-1
|
||||||
|
tree_sort_key=0
|
||||||
|
tree_sort_direction=1
|
||||||
|
hide_kernel_threads=1
|
||||||
|
hide_userland_threads=0
|
||||||
|
shadow_other_users=0
|
||||||
|
show_thread_names=0
|
||||||
|
show_program_path=1
|
||||||
|
highlight_base_name=0
|
||||||
|
highlight_deleted_exe=1
|
||||||
|
highlight_megabytes=1
|
||||||
|
highlight_threads=1
|
||||||
|
highlight_changes=0
|
||||||
|
highlight_changes_delay_secs=5
|
||||||
|
find_comm_in_cmdline=1
|
||||||
|
strip_exe_from_cmdline=1
|
||||||
|
show_merged_command=0
|
||||||
|
tree_view=0
|
||||||
|
tree_view_always_by_pid=0
|
||||||
|
all_branches_collapsed=0
|
||||||
|
header_margin=1
|
||||||
|
detailed_cpu_time=0
|
||||||
|
cpu_count_from_one=0
|
||||||
|
show_cpu_usage=1
|
||||||
|
show_cpu_frequency=0
|
||||||
|
show_cpu_temperature=0
|
||||||
|
degree_fahrenheit=0
|
||||||
|
update_process_names=0
|
||||||
|
account_guest_in_cpu_meter=0
|
||||||
|
color_scheme=6
|
||||||
|
|
||||||
|
# Custom color definitions using template variables
|
||||||
|
# Main interface colors
|
||||||
|
color_background={{ $surface }}
|
||||||
|
color_text={{ $onSurface }}
|
||||||
|
color_highlight={{ $primary }}
|
||||||
|
color_selected={{ $surfaceContainer }}
|
||||||
|
|
||||||
|
# CPU meter colors (gradient)
|
||||||
|
color_cpu_low={{ $green }}
|
||||||
|
color_cpu_med={{ $yellow }}
|
||||||
|
color_cpu_high={{ $red }}
|
||||||
|
|
||||||
|
# Memory meter colors
|
||||||
|
color_mem_used={{ $blue }}
|
||||||
|
color_mem_buffers={{ $teal }}
|
||||||
|
color_mem_cache={{ $sapphire }}
|
||||||
|
color_mem_available={{ $green }}
|
||||||
|
|
||||||
|
# Process list colors
|
||||||
|
color_process_normal={{ $onSurface }}
|
||||||
|
color_process_running={{ $green }}
|
||||||
|
color_process_sleeping={{ $outline }}
|
||||||
|
color_process_zombie={{ $red }}
|
||||||
|
color_process_stopped={{ $yellow }}
|
||||||
|
|
||||||
|
# Header and border colors
|
||||||
|
color_header={{ $onSurfaceVariant }}
|
||||||
|
color_border={{ $outline }}
|
||||||
|
color_separator={{ $outlineVariant }}
|
||||||
|
|
||||||
|
# Function key colors
|
||||||
|
color_function_key={{ $tertiary }}
|
||||||
|
color_function_desc={{ $onSurface }}
|
||||||
|
|
||||||
|
# Tree view colors
|
||||||
|
color_tree_line={{ $outline }}
|
||||||
|
color_tree_collapsed={{ $primary }}
|
||||||
|
color_tree_expanded={{ $secondary }}
|
||||||
|
|
||||||
|
# Load average colors
|
||||||
|
color_load_low={{ $green }}
|
||||||
|
color_load_med={{ $yellow }}
|
||||||
|
color_load_high={{ $red }}
|
||||||
|
|
||||||
|
# Priority colors
|
||||||
|
color_priority_high={{ $red }}
|
||||||
|
color_priority_normal={{ $onSurface }}
|
||||||
|
color_priority_low={{ $outline }}
|
||||||
|
|
||||||
|
# Swap meter colors
|
||||||
|
color_swap_used={{ $maroon }}
|
||||||
|
color_swap_cache={{ $peach }}
|
||||||
|
|
||||||
|
# Temperature colors (if enabled)
|
||||||
|
color_temp_cool={{ $green }}
|
||||||
|
color_temp_warm={{ $yellow }}
|
||||||
|
color_temp_hot={{ $red }}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# NVTOP Color Configuration Template
|
||||||
|
# Format: color_name = RGB_HEX_VALUE
|
||||||
|
# Colors must be specified as 6-digit hex values without # prefix
|
||||||
|
|
||||||
|
# Background colors
|
||||||
|
background = {{ $surface }}
|
||||||
|
selected_bg = {{ $surfaceContainer }}
|
||||||
|
header_bg = {{ $surfaceVariant }}
|
||||||
|
|
||||||
|
# Text colors
|
||||||
|
text = {{ $onSurface }}
|
||||||
|
selected_text = {{ $primary }}
|
||||||
|
header_text = {{ $onSurfaceVariant }}
|
||||||
|
inactive_text = {{ $outline }}
|
||||||
|
|
||||||
|
# GPU utilization colors (gradient from low to high)
|
||||||
|
gpu_util_low = {{ $green }}
|
||||||
|
gpu_util_med = {{ $yellow }}
|
||||||
|
gpu_util_high = {{ $red }}
|
||||||
|
|
||||||
|
# Memory usage colors
|
||||||
|
memory_low = {{ $teal }}
|
||||||
|
memory_med = {{ $sapphire }}
|
||||||
|
memory_high = {{ $blue }}
|
||||||
|
|
||||||
|
# Temperature colors (cool to hot)
|
||||||
|
temp_cool = {{ $green }}
|
||||||
|
temp_warm = {{ $yellow }}
|
||||||
|
temp_hot = {{ $red }}
|
||||||
|
|
||||||
|
# Power usage colors
|
||||||
|
power_low = {{ $green }}
|
||||||
|
power_med = {{ $peach }}
|
||||||
|
power_high = {{ $maroon }}
|
||||||
|
|
||||||
|
# Process list colors
|
||||||
|
process_normal = {{ $onSurface }}
|
||||||
|
process_highlight = {{ $primary }}
|
||||||
|
process_killed = {{ $red }}
|
||||||
|
|
||||||
|
# Border and separator colors
|
||||||
|
border = {{ $outline }}
|
||||||
|
separator = {{ $outlineVariant }}
|
||||||
|
|
||||||
|
# Chart and graph colors
|
||||||
|
chart_line = {{ $tertiary }}
|
||||||
|
chart_fill = {{ $surfaceContainer }}
|
||||||
|
|
||||||
|
# Status indicators
|
||||||
|
status_ok = {{ $green }}
|
||||||
|
status_warning = {{ $yellow }}
|
||||||
|
status_error = {{ $red }}
|
||||||
|
status_info = {{ $blue }}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
[ColorScheme]
|
|
||||||
active_colors = {{ $onSurface }}, {{ $surfaceContainer }}, {{ $surfaceContainerHighest }}, {{ $surfaceContainerHigh }}, {{ $surfaceContainerLowest }}, {{ $surfaceContainerLow }}, {{ $onSurface }}, {{ $onSurface }}, {{ $onSurface }}, {{ $surface }}, {{ $surfaceContainer }}, {{ $shadow }}, {{ $primaryContainer }}, {{ $onPrimaryContainer }}, {{ $secondary }}, {{ $primary }}, {{ $surface }}, {{ $scrim }}, {{ $surfaceContainer }}, {{ $onSurface }}, {{ $secondary }}
|
|
||||||
inactive_colors = {{ $onSurface }}, {{ $surfaceContainer }}, {{ $surfaceContainerHighest }}, {{ $surfaceContainerHigh }}, {{ $surfaceContainerLowest }}, {{ $surfaceContainerLow }}, {{ $onSurface }}, {{ $onSurface }}, {{ $onSurface }}, {{ $surface }}, {{ $surfaceContainer }}, {{ $shadow }}, {{ $primaryContainer }}, {{ $onPrimaryContainer }}, {{ $secondary }}, {{ $primary }}, {{ $surface }}, {{ $scrim }}, {{ $surfaceContainer }}, {{ $onSurface }}, {{ $secondary }}
|
|
||||||
disabled_colors = {{ $outline }}, {{ $surface }}, {{ $surfaceContainerHigh }}, {{ $surfaceContainer }}, {{ $surfaceContainerLow }}, {{ $surfaceContainer }}, {{ $outline }}, {{ $onSurfaceVariant }}, {{ $onSurfaceVariant }}, {{ $surface }}, {{ $surfaceContainer }}, {{ $shadow }}, {{ $surfaceContainerHigh }}, {{ $onSurface }}, {{ $onSurfaceVariant }}, {{ $onSurface }}, {{ $surface }}, {{ $scrim }}, {{ $surfaceContainer }}, {{ $onSurface }}, {{ $secondary }}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[Appearance]
|
[Appearance]
|
||||||
color_scheme_path={{ $config }}/colors/caelestia.conf
|
color_scheme_path={{ $config }}/colors/caelestia.colors
|
||||||
custom_palette=true
|
custom_palette=true
|
||||||
icon_theme=Papirus-{{ $mode }}
|
icon_theme=Papirus-{{ $mode }}
|
||||||
standard_dialogs=default
|
standard_dialogs=default
|
||||||
style=Fusion
|
style=Darkly
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
[ColorEffects:Disabled]
|
||||||
|
Color={{ $surfaceContainer }}
|
||||||
|
ColorAmount=0.5
|
||||||
|
ColorEffect=3
|
||||||
|
ContrastAmount=0
|
||||||
|
ContrastEffect=0
|
||||||
|
IntensityAmount=0
|
||||||
|
IntensityEffect=0
|
||||||
|
|
||||||
|
[ColorEffects:Inactive]
|
||||||
|
ChangeSelectionColor=true
|
||||||
|
Color={{ $surfaceContainerLowest }}
|
||||||
|
ColorAmount=0.025
|
||||||
|
ColorEffect=0
|
||||||
|
ContrastAmount=0.1
|
||||||
|
ContrastEffect=0
|
||||||
|
Enable=true
|
||||||
|
IntensityAmount=0
|
||||||
|
IntensityEffect=0
|
||||||
|
|
||||||
|
[Colors:Button]
|
||||||
|
BackgroundAlternate={{ $surfaceVariant }}
|
||||||
|
BackgroundNormal={{ $surfaceContainerHigh }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $onSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurface }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:Complementary]
|
||||||
|
BackgroundAlternate={{ $surface }}
|
||||||
|
BackgroundNormal={{ $surfaceContainer }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $inverseSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurfaceVariant }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:Header]
|
||||||
|
BackgroundAlternate={{ $surfaceContainer }}
|
||||||
|
BackgroundNormal={{ $surfaceContainer }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $inverseSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurfaceVariant }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:Header][Inactive]
|
||||||
|
BackgroundAlternate={{ $surfaceContainer }}
|
||||||
|
BackgroundNormal={{ $surfaceContainer }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $inverseSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurfaceVariant }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:Selection]
|
||||||
|
BackgroundAlternate={{ $primary }}
|
||||||
|
BackgroundNormal={{ $primary }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $secondary }}
|
||||||
|
ForegroundActive={{ $onPrimary }}
|
||||||
|
ForegroundInactive={{ $onPrimary }}
|
||||||
|
ForegroundLink={{ $klinkSelection }}
|
||||||
|
ForegroundNegative={{ $knegativeSelection }}
|
||||||
|
ForegroundNeutral={{ $kneutralSelection }}
|
||||||
|
ForegroundNormal={{ $onPrimary }}
|
||||||
|
ForegroundPositive={{ $kpositiveSelection }}
|
||||||
|
ForegroundVisited={{ $kvisitedSelection }}
|
||||||
|
|
||||||
|
[Colors:Tooltip]
|
||||||
|
BackgroundAlternate={{ $surfaceVariant }}
|
||||||
|
BackgroundNormal={{ $surfaceContainer }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $onSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurface }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:View]
|
||||||
|
BackgroundAlternate={{ $surfaceContainer }}
|
||||||
|
BackgroundNormal={{ $surfaceDim }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
#-----------------------------------------------
|
||||||
|
DecorationHover={{ $inversePrimary }}
|
||||||
|
ForegroundActive={{ $inverseSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurface }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:Window]
|
||||||
|
BackgroundAlternate={{ $surfaceVariant }}
|
||||||
|
BackgroundNormal={{ $surfaceContainer }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $klink }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
#--- Window titles, context icons
|
||||||
|
ForegroundNormal={{ $onSurfaceVariant }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[General]
|
||||||
|
ColorScheme=Caelestia
|
||||||
|
Name=Caelestia
|
||||||
|
shadeSortColumn=true
|
||||||
|
|
||||||
|
[KDE]
|
||||||
|
contrast=4
|
||||||
|
|
||||||
|
[WM]
|
||||||
|
activeBackground={{ $surfaceContainerHighest }}
|
||||||
|
activeBlend=252,252,252
|
||||||
|
activeForeground={{ $onSurface }}
|
||||||
|
inactiveBackground={{ $secondaryContainer }}
|
||||||
|
inactiveBlend=161,169,177
|
||||||
|
inactiveForeground={{ $onSecondaryContainer }}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
[ColorEffects:Disabled]
|
||||||
|
Color={{ $surfaceContainer }}
|
||||||
|
ColorAmount=0.5
|
||||||
|
ColorEffect=3
|
||||||
|
ContrastAmount=0
|
||||||
|
ContrastEffect=0
|
||||||
|
IntensityAmount=0
|
||||||
|
IntensityEffect=0
|
||||||
|
|
||||||
|
[ColorEffects:Inactive]
|
||||||
|
ChangeSelectionColor=true
|
||||||
|
Color={{ $surfaceContainerLowest }}
|
||||||
|
ColorAmount=0.025
|
||||||
|
ColorEffect=0
|
||||||
|
ContrastAmount=0.1
|
||||||
|
ContrastEffect=0
|
||||||
|
Enable=false
|
||||||
|
IntensityAmount=0
|
||||||
|
IntensityEffect=0
|
||||||
|
|
||||||
|
[Colors:Button]
|
||||||
|
BackgroundAlternate={{ $surfaceVariant }}
|
||||||
|
BackgroundNormal={{ $surfaceContainerHigh }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $onSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurface }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:Complementary]
|
||||||
|
BackgroundAlternate={{ $surface }}
|
||||||
|
BackgroundNormal={{ $surfaceContainer }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $inverseSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurfaceVariant }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:Header]
|
||||||
|
BackgroundAlternate={{ $surfaceContainer }}
|
||||||
|
BackgroundNormal={{ $surfaceContainer }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $inverseSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurfaceVariant }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:Header][Inactive]
|
||||||
|
BackgroundAlternate={{ $surfaceContainer }}
|
||||||
|
BackgroundNormal={{ $surfaceContainer }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $inverseSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurfaceVariant }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:Selection]
|
||||||
|
BackgroundAlternate={{ $primary }}
|
||||||
|
BackgroundNormal={{ $primary }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $secondary }}
|
||||||
|
ForegroundActive={{ $onPrimary }}
|
||||||
|
ForegroundInactive={{ $onPrimary }}
|
||||||
|
ForegroundLink={{ $klinkSelection }}
|
||||||
|
ForegroundNegative={{ $knegativeSelection }}
|
||||||
|
ForegroundNeutral={{ $kneutralSelection }}
|
||||||
|
ForegroundNormal={{ $onPrimary }}
|
||||||
|
ForegroundPositive={{ $kpositiveSelection }}
|
||||||
|
ForegroundVisited={{ $kvisitedSelection }}
|
||||||
|
|
||||||
|
[Colors:Tooltip]
|
||||||
|
BackgroundAlternate={{ $surfaceVariant }}
|
||||||
|
BackgroundNormal={{ $surfaceContainer }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $onSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurface }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:View]
|
||||||
|
BackgroundAlternate={{ $surfaceContainer }}
|
||||||
|
BackgroundNormal={{ $surfaceBright }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
#-----------------------------------------------
|
||||||
|
DecorationHover={{ $secondaryFixed }}
|
||||||
|
ForegroundActive={{ $inverseSurface }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
ForegroundNormal={{ $onSurface }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[Colors:Window]
|
||||||
|
BackgroundAlternate={{ $surfaceVariant }}
|
||||||
|
BackgroundNormal={{ $surfaceContainer }}
|
||||||
|
DecorationFocus={{ $primary }}
|
||||||
|
DecorationHover={{ $primary }}
|
||||||
|
ForegroundActive={{ $klink }}
|
||||||
|
ForegroundInactive={{ $outline }}
|
||||||
|
ForegroundLink={{ $klink }}
|
||||||
|
ForegroundNegative={{ $knegative }}
|
||||||
|
ForegroundNeutral={{ $kneutral }}
|
||||||
|
#--- Window titles, context icons
|
||||||
|
ForegroundNormal={{ $onSurfaceVariant }}
|
||||||
|
ForegroundPositive={{ $kpositive }}
|
||||||
|
ForegroundVisited={{ $kvisited }}
|
||||||
|
|
||||||
|
[General]
|
||||||
|
ColorScheme=Caelestia
|
||||||
|
Name=Caelestia
|
||||||
|
shadeSortColumn=true
|
||||||
|
|
||||||
|
[KDE]
|
||||||
|
contrast=4
|
||||||
|
|
||||||
|
[WM]
|
||||||
|
activeBackground={{ $surfaceContainerHighest }}
|
||||||
|
activeBlend=227,229,231
|
||||||
|
activeForeground={{ $onSurface }}
|
||||||
|
inactiveBackground={{ $secondaryContainer }}
|
||||||
|
inactiveBlend=239,240,241
|
||||||
|
inactiveForeground={{ $onSurfaceVariant }}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: 'Caelestia Theme'
|
||||||
|
accent: '{{ $primary }}'
|
||||||
|
background: '{{ $surface }}'
|
||||||
|
foreground: '{{ $onSurface }}'
|
||||||
|
details: {{ $warp_mode }}
|
||||||
|
cursor: '{{ $secondary }}'
|
||||||
|
terminal_colors:
|
||||||
|
normal:
|
||||||
|
black: '{{ $term0 }}'
|
||||||
|
red: '{{ $term1 }}'
|
||||||
|
green: '{{ $term2 }}'
|
||||||
|
yellow: '{{ $term3 }}'
|
||||||
|
blue: '{{ $term4 }}'
|
||||||
|
magenta: '{{ $term5 }}'
|
||||||
|
cyan: '{{ $term6 }}'
|
||||||
|
white: '{{ $term7 }}'
|
||||||
|
bright:
|
||||||
|
black: '{{ $term8 }}'
|
||||||
|
red: '{{ $term9 }}'
|
||||||
|
green: '{{ $term10 }}'
|
||||||
|
yellow: '{{ $term11 }}'
|
||||||
|
blue: '{{ $term12 }}'
|
||||||
|
magenta: '{{ $term13 }}'
|
||||||
|
cyan: '{{ $term14 }}'
|
||||||
|
white: '{{ $term15 }}'
|
||||||
+24
-14
@@ -1,6 +1,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from caelestia.subcommands import clipboard, emoji, pip, record, scheme, screenshot, shell, toggle, wallpaper
|
from caelestia.subcommands import clipboard, emoji, record, resizer, scheme, screenshot, shell, toggle, wallpaper
|
||||||
from caelestia.utils.paths import wallpapers_dir
|
from caelestia.utils.paths import wallpapers_dir
|
||||||
from caelestia.utils.scheme import get_scheme_names, scheme_variants
|
from caelestia.utils.scheme import get_scheme_names, scheme_variants
|
||||||
from caelestia.utils.wallpaper import get_wallpaper
|
from caelestia.utils.wallpaper import get_wallpaper
|
||||||
@@ -22,19 +22,13 @@ def parse_args() -> (argparse.ArgumentParser, argparse.Namespace):
|
|||||||
shell_parser.add_argument("-d", "--daemon", action="store_true", help="start the shell detached")
|
shell_parser.add_argument("-d", "--daemon", action="store_true", help="start the shell detached")
|
||||||
shell_parser.add_argument("-s", "--show", action="store_true", help="print all shell IPC commands")
|
shell_parser.add_argument("-s", "--show", action="store_true", help="print all shell IPC commands")
|
||||||
shell_parser.add_argument("-l", "--log", action="store_true", help="print the shell log")
|
shell_parser.add_argument("-l", "--log", action="store_true", help="print the shell log")
|
||||||
shell_parser.add_argument(
|
shell_parser.add_argument("-k", "--kill", action="store_true", help="kill the shell")
|
||||||
"--log-rules",
|
shell_parser.add_argument("--log-rules", metavar="RULES", help="log rules to apply")
|
||||||
default="quickshell.dbus.properties.warning=false;quickshell.dbus.dbusmenu.warning=false;quickshell.service.notifications.warning=false;quickshell.service.sni.host.warning=false;qt.qpa.wayland.textinput.warning=false",
|
|
||||||
metavar="RULES",
|
|
||||||
help="log rules to apply",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create parser for toggle opts
|
# Create parser for toggle opts
|
||||||
toggle_parser = command_parser.add_parser("toggle", help="toggle a special workspace")
|
toggle_parser = command_parser.add_parser("toggle", help="toggle a special workspace")
|
||||||
toggle_parser.set_defaults(cls=toggle.Command)
|
toggle_parser.set_defaults(cls=toggle.Command)
|
||||||
toggle_parser.add_argument(
|
toggle_parser.add_argument("workspace", help="the workspace to toggle")
|
||||||
"workspace", choices=["communication", "music", "sysmon", "specialws", "todo"], help="the workspace to toggle"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create parser for scheme opts
|
# Create parser for scheme opts
|
||||||
scheme_parser = command_parser.add_parser("scheme", help="manage the colour scheme")
|
scheme_parser = command_parser.add_parser("scheme", help="manage the colour scheme")
|
||||||
@@ -76,6 +70,7 @@ def parse_args() -> (argparse.ArgumentParser, argparse.Namespace):
|
|||||||
record_parser.set_defaults(cls=record.Command)
|
record_parser.set_defaults(cls=record.Command)
|
||||||
record_parser.add_argument("-r", "--region", nargs="?", const="slurp", help="record a region")
|
record_parser.add_argument("-r", "--region", nargs="?", const="slurp", help="record a region")
|
||||||
record_parser.add_argument("-s", "--sound", action="store_true", help="record audio")
|
record_parser.add_argument("-s", "--sound", action="store_true", help="record audio")
|
||||||
|
record_parser.add_argument("-p", "--pause", action="store_true", help="pause/resume the recording")
|
||||||
|
|
||||||
# Create parser for clipboard opts
|
# Create parser for clipboard opts
|
||||||
clipboard_parser = command_parser.add_parser("clipboard", help="open clipboard history")
|
clipboard_parser = command_parser.add_parser("clipboard", help="open clipboard history")
|
||||||
@@ -112,9 +107,24 @@ def parse_args() -> (argparse.ArgumentParser, argparse.Namespace):
|
|||||||
help="do not automatically change the scheme mode based on wallpaper colour",
|
help="do not automatically change the scheme mode based on wallpaper colour",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create parser for pip opts
|
# Create parser for resizer opts
|
||||||
pip_parser = command_parser.add_parser("pip", help="picture in picture utilities")
|
resizer_parser = command_parser.add_parser("resizer", help="window resizer daemon")
|
||||||
pip_parser.set_defaults(cls=pip.Command)
|
resizer_parser.set_defaults(cls=resizer.Command)
|
||||||
pip_parser.add_argument("-d", "--daemon", action="store_true", help="start the daemon")
|
resizer_parser.add_argument("-d", "--daemon", action="store_true", help="start the resizer daemon")
|
||||||
|
resizer_parser.add_argument(
|
||||||
|
"pattern",
|
||||||
|
nargs="?",
|
||||||
|
help="pattern to match against windows ('active' for current window only, 'pip' for quick pip mode)",
|
||||||
|
)
|
||||||
|
resizer_parser.add_argument(
|
||||||
|
"match_type",
|
||||||
|
nargs="?",
|
||||||
|
metavar="match_type",
|
||||||
|
choices=["titleContains", "titleExact", "titleRegex", "initialTitle"],
|
||||||
|
help="type of pattern matching (titleContains,titleExact,titleRegex,initialTitle)",
|
||||||
|
)
|
||||||
|
resizer_parser.add_argument("width", nargs="?", help="width to resize to")
|
||||||
|
resizer_parser.add_argument("height", nargs="?", help="height to resize to")
|
||||||
|
resizer_parser.add_argument("actions", nargs="?", help="comma-separated actions to apply (float,center,pip)")
|
||||||
|
|
||||||
return parser, parser.parse_args()
|
return parser, parser.parse_args()
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import re
|
|
||||||
import socket
|
|
||||||
from argparse import Namespace
|
|
||||||
|
|
||||||
from caelestia.utils import hypr
|
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
|
||||||
args: Namespace
|
|
||||||
|
|
||||||
def __init__(self, args: Namespace) -> None:
|
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def run(self) -> None:
|
|
||||||
if self.args.daemon:
|
|
||||||
self.daemon()
|
|
||||||
else:
|
|
||||||
win = hypr.message("activewindow")
|
|
||||||
if win["floating"]:
|
|
||||||
self.handle_window(win["address"], win["workspace"]["name"])
|
|
||||||
|
|
||||||
def handle_window(self, address: str, ws: str) -> None:
|
|
||||||
mon_id = next(w for w in hypr.message("workspaces") if w["name"] == ws)["monitorID"]
|
|
||||||
mon = next(m for m in hypr.message("monitors") if m["id"] == mon_id)
|
|
||||||
width, height = next(c for c in hypr.message("clients") if c["address"] == address)["size"]
|
|
||||||
|
|
||||||
scale_factor = mon["height"] / 4 / height
|
|
||||||
scaled_win_size = f"{int(width * scale_factor)} {int(height * scale_factor)}"
|
|
||||||
off = min(mon["width"], mon["height"]) * 0.03
|
|
||||||
move_to = f"{int(mon['width'] - off - width * scale_factor)} {int(mon['height'] - off - height * scale_factor)}"
|
|
||||||
|
|
||||||
hypr.dispatch("resizewindowpixel", "exact", f"{scaled_win_size},address:{address}")
|
|
||||||
hypr.dispatch("movewindowpixel", "exact", f"{move_to},address:{address}")
|
|
||||||
|
|
||||||
def daemon(self) -> None:
|
|
||||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
|
||||||
sock.connect(hypr.socket2_path)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
data = sock.recv(4096).decode()
|
|
||||||
if data.startswith("openwindow>>"):
|
|
||||||
address, ws, cls, title = data[12:].split(",")
|
|
||||||
if re.match(r"^[Pp]icture(-| )in(-| )[Pp]icture$", title):
|
|
||||||
self.handle_window(f"0x{address}", ws)
|
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
|
import json
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from caelestia.utils.notify import notify
|
from caelestia.utils.notify import close_notification, notify
|
||||||
from caelestia.utils.paths import recording_notif_path, recording_path, recordings_dir
|
from caelestia.utils.paths import recording_notif_path, recording_path, recordings_dir
|
||||||
|
|
||||||
|
RECORDER = "gpu-screen-recorder"
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
args: Namespace
|
args: Namespace
|
||||||
@@ -14,69 +19,83 @@ class Command:
|
|||||||
self.args = args
|
self.args = args
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
proc = subprocess.run(["pidof", "wl-screenrec"])
|
if self.args.pause:
|
||||||
if proc.returncode == 0:
|
subprocess.run(["pkill", "-USR2", "-f", RECORDER], stdout=subprocess.DEVNULL)
|
||||||
|
elif self.proc_running():
|
||||||
self.stop()
|
self.stop()
|
||||||
else:
|
else:
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def start(self) -> None:
|
def proc_running(self) -> bool:
|
||||||
args = []
|
return subprocess.run(["pidof", RECORDER], stdout=subprocess.DEVNULL).returncode == 0
|
||||||
|
|
||||||
|
def intersects(self, a: tuple[int, int, int, int], b: tuple[int, int, int, int]) -> bool:
|
||||||
|
return a[0] < b[0] + b[2] and a[0] + a[2] > b[0] and a[1] < b[1] + b[3] and a[1] + a[3] > b[1]
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
args = ["-w"]
|
||||||
|
|
||||||
|
monitors = json.loads(subprocess.check_output(["hyprctl", "monitors", "-j"]))
|
||||||
if self.args.region:
|
if self.args.region:
|
||||||
if self.args.region == "slurp":
|
if self.args.region == "slurp":
|
||||||
region = subprocess.check_output(["slurp"], text=True)
|
region = subprocess.check_output(["slurp", "-f", "%wx%h+%x+%y"], text=True)
|
||||||
else:
|
else:
|
||||||
region = self.args.region
|
region = self.args.region.strip()
|
||||||
args += ["-g", region.strip()]
|
args += ["region", "-region", region]
|
||||||
|
|
||||||
|
m = re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)", region)
|
||||||
|
if not m:
|
||||||
|
raise ValueError(f"Invalid region: {region}")
|
||||||
|
|
||||||
|
w, h, x, y = map(int, m.groups())
|
||||||
|
r = x, y, w, h
|
||||||
|
max_rr = 0
|
||||||
|
for monitor in monitors:
|
||||||
|
if self.intersects((monitor["x"], monitor["y"], monitor["width"], monitor["height"]), r):
|
||||||
|
rr = round(monitor["refreshRate"])
|
||||||
|
max_rr = max(max_rr, rr)
|
||||||
|
args += ["-f", str(max_rr)]
|
||||||
|
else:
|
||||||
|
focused_monitor = next(monitor for monitor in monitors if monitor["focused"])
|
||||||
|
if focused_monitor:
|
||||||
|
args += [focused_monitor["name"], "-f", str(round(focused_monitor["refreshRate"]))]
|
||||||
|
|
||||||
if self.args.sound:
|
if self.args.sound:
|
||||||
sources = subprocess.check_output(["pactl", "list", "short", "sources"], text=True).splitlines()
|
args += ["-a", "default_output"]
|
||||||
for source in sources:
|
|
||||||
if "RUNNING" in source:
|
|
||||||
args += ["--audio", "--audio-device", source.split()[1]]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise ValueError("No audio source found")
|
|
||||||
|
|
||||||
recording_path.parent.mkdir(parents=True, exist_ok=True)
|
recording_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen([RECORDER, *args, "-o", str(recording_path)], start_new_session=True)
|
||||||
["wl-screenrec", *args, "--codec", "hevc", "-f", recording_path],
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
text=True,
|
|
||||||
start_new_session=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Send notif if proc hasn't ended after a small delay
|
notif = notify("-p", "Recording started", "Recording...")
|
||||||
time.sleep(0.1)
|
recording_notif_path.write_text(notif)
|
||||||
if proc.poll() is None:
|
|
||||||
notif = notify("-p", "Recording started", "Recording...")
|
try:
|
||||||
recording_notif_path.write_text(notif)
|
if proc.wait(1) != 0:
|
||||||
else:
|
close_notification(notif)
|
||||||
notify("Recording failed", f"Recording failed to start: {proc.communicate()[1]}")
|
notify(
|
||||||
|
"Recording failed",
|
||||||
|
"An error occurred attempting to start recorder. "
|
||||||
|
f"Command `{' '.join(proc.args)}` failed with exit code {proc.returncode}",
|
||||||
|
)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
pass
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
subprocess.run(["pkill", "wl-screenrec"])
|
# Start killing recording process
|
||||||
|
subprocess.run(["pkill", "-f", RECORDER], stdout=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
# Wait for recording to finish to avoid corrupted video file
|
||||||
|
while self.proc_running():
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
# Move to recordings folder
|
# Move to recordings folder
|
||||||
new_path = recordings_dir / f"recording_{datetime.now().strftime('%Y%m%d_%H-%M-%S')}.mp4"
|
new_path = recordings_dir / f"recording_{datetime.now().strftime('%Y%m%d_%H-%M-%S')}.mp4"
|
||||||
recordings_dir.mkdir(exist_ok=True, parents=True)
|
recordings_dir.mkdir(exist_ok=True, parents=True)
|
||||||
recording_path.rename(new_path)
|
shutil.move(recording_path, new_path)
|
||||||
|
|
||||||
# Close start notification
|
# Close start notification
|
||||||
try:
|
try:
|
||||||
notif = recording_notif_path.read_text()
|
close_notification(recording_notif_path.read_text())
|
||||||
subprocess.run(
|
|
||||||
[
|
|
||||||
"gdbus",
|
|
||||||
"call",
|
|
||||||
"--session",
|
|
||||||
"--dest=org.freedesktop.Notifications",
|
|
||||||
"--object-path=/org/freedesktop/Notifications",
|
|
||||||
"--method=org.freedesktop.Notifications.CloseNotification",
|
|
||||||
notif,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,464 @@
|
|||||||
|
import json
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
from argparse import Namespace
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from caelestia.utils import hypr
|
||||||
|
from caelestia.utils.logging import log_message
|
||||||
|
from caelestia.utils.paths import user_config_path
|
||||||
|
|
||||||
|
|
||||||
|
class WindowRule:
|
||||||
|
def __init__(self, name: str, match_type: str, width: str, height: str, actions: list[str]):
|
||||||
|
self.name = name
|
||||||
|
self.match_type = match_type
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.actions = actions
|
||||||
|
|
||||||
|
|
||||||
|
class Command:
|
||||||
|
def __init__(self, args: Namespace) -> None:
|
||||||
|
self.args = args
|
||||||
|
self.timeout_tracker: dict[str, float] = {}
|
||||||
|
self.window_rules = self._load_window_rules()
|
||||||
|
|
||||||
|
def _load_window_rules(self) -> list[WindowRule]:
|
||||||
|
default_rules = [
|
||||||
|
WindowRule("(Bitwarden", "titleContains", "20%", "54%", ["float", "center"]),
|
||||||
|
WindowRule("Sign in - Google Accounts", "titleContains", "35%", "65%", ["float", "center"]),
|
||||||
|
WindowRule("oauth", "titleContains", "30%", "60%", ["float", "center"]),
|
||||||
|
WindowRule("^[Pp]icture(-| )in(-| )[Pp]icture$", "titleRegex", "", "", ["pip"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
config = json.loads(user_config_path.read_text())
|
||||||
|
if "resizer" in config and "rules" in config["resizer"]:
|
||||||
|
rules = []
|
||||||
|
for rule_config in config["resizer"]["rules"]:
|
||||||
|
rules.append(
|
||||||
|
WindowRule(
|
||||||
|
rule_config["name"],
|
||||||
|
rule_config["matchType"],
|
||||||
|
rule_config["width"],
|
||||||
|
rule_config["height"],
|
||||||
|
rule_config["actions"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return rules
|
||||||
|
except (json.JSONDecodeError, KeyError):
|
||||||
|
log_message("ERROR: invalid config")
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return default_rules
|
||||||
|
|
||||||
|
def _is_rate_limited(self, key: str) -> bool:
|
||||||
|
current_time = time.time()
|
||||||
|
last_time = self.timeout_tracker.get(key, 0)
|
||||||
|
|
||||||
|
if current_time < last_time + 1:
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.timeout_tracker[key] = current_time
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_window_info(self, window_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
try:
|
||||||
|
clients = hypr.message("clients")
|
||||||
|
if isinstance(clients, list):
|
||||||
|
for client in clients:
|
||||||
|
if isinstance(client, dict) and client.get("address") == f"0x{window_id}":
|
||||||
|
return client
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _apply_pip_action(self, window_id: str) -> None:
|
||||||
|
try:
|
||||||
|
address = f"0x{window_id}"
|
||||||
|
clients_result = hypr.message("clients")
|
||||||
|
if not isinstance(clients_result, list):
|
||||||
|
return
|
||||||
|
|
||||||
|
window = None
|
||||||
|
for c in clients_result:
|
||||||
|
if isinstance(c, dict) and c.get("address") == address:
|
||||||
|
window = c
|
||||||
|
break
|
||||||
|
|
||||||
|
if not window or not isinstance(window, dict) or not window.get("floating", False):
|
||||||
|
return
|
||||||
|
|
||||||
|
workspaces_result = hypr.message("workspaces")
|
||||||
|
if not isinstance(workspaces_result, list):
|
||||||
|
return
|
||||||
|
|
||||||
|
workspace_info = window.get("workspace")
|
||||||
|
if not isinstance(workspace_info, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
workspace_name = workspace_info.get("name")
|
||||||
|
workspace = None
|
||||||
|
for w in workspaces_result:
|
||||||
|
if isinstance(w, dict) and w.get("name") == workspace_name:
|
||||||
|
workspace = w
|
||||||
|
break
|
||||||
|
|
||||||
|
if not workspace or not isinstance(workspace, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
monitors_result = hypr.message("monitors")
|
||||||
|
if not isinstance(monitors_result, list):
|
||||||
|
return
|
||||||
|
|
||||||
|
monitor_id = workspace.get("monitorID")
|
||||||
|
monitor = None
|
||||||
|
for m in monitors_result:
|
||||||
|
if isinstance(m, dict) and m.get("id") == monitor_id:
|
||||||
|
monitor = m
|
||||||
|
break
|
||||||
|
|
||||||
|
if not monitor or not isinstance(monitor, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
window_size = window.get("size")
|
||||||
|
if not isinstance(window_size, list) or len(window_size) < 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
width, height = window_size[0], window_size[1]
|
||||||
|
if not isinstance(width, (int, float)) or not isinstance(height, (int, float)):
|
||||||
|
return
|
||||||
|
|
||||||
|
monitor_height = monitor.get("height")
|
||||||
|
monitor_width = monitor.get("width")
|
||||||
|
monitor_scale = monitor.get("scale")
|
||||||
|
monitor_x = monitor.get("x")
|
||||||
|
monitor_y = monitor.get("y")
|
||||||
|
|
||||||
|
if not all(isinstance(x, (int, float)) for x in [monitor_height, monitor_width, monitor_scale, monitor_x, monitor_y]):
|
||||||
|
return
|
||||||
|
|
||||||
|
monitor_height = monitor_height / monitor_scale
|
||||||
|
monitor_width = monitor_width / monitor_scale
|
||||||
|
|
||||||
|
scale_factor = monitor_height / 4 / height
|
||||||
|
scaled_width = int(width * scale_factor)
|
||||||
|
scaled_height = int(height * scale_factor)
|
||||||
|
|
||||||
|
# Ensure minimum reasonable size
|
||||||
|
min_width = 200
|
||||||
|
min_height = 150
|
||||||
|
scaled_width = max(scaled_width, min_width)
|
||||||
|
scaled_height = max(scaled_height, min_height)
|
||||||
|
|
||||||
|
# Use offset to ensure window stays on screen with some margin
|
||||||
|
offset = min(monitor_width, monitor_height) * 0.03
|
||||||
|
|
||||||
|
# Position in bottom-right corner with offset
|
||||||
|
move_x = monitor_x + monitor_width - scaled_width - offset
|
||||||
|
move_y = monitor_y + monitor_height - scaled_height - offset
|
||||||
|
|
||||||
|
command1 = f"dispatch resizewindowpixel exact {scaled_width} {scaled_height},address:{address}"
|
||||||
|
command2 = f"dispatch movewindowpixel exact {int(move_x)} {int(move_y)},address:{address}"
|
||||||
|
hypr.batch(command1, command2)
|
||||||
|
|
||||||
|
log_message(
|
||||||
|
f"Applied PiP action to window {address}: {scaled_width}x{scaled_height} at ({move_x}, {move_y})"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log_message(f"ERROR: Failed to apply PiP action to window 0x{window_id}: {e}")
|
||||||
|
|
||||||
|
def _apply_window_actions(self, window_id: str, width: str, height: str, actions: list[str]) -> bool:
|
||||||
|
dispatch_commands = []
|
||||||
|
|
||||||
|
if "float" in actions:
|
||||||
|
window_info = self._get_window_info(window_id)
|
||||||
|
if window_info and not window_info.get("floating", False):
|
||||||
|
dispatch_commands.append(f"dispatch togglefloating address:0x{window_id}")
|
||||||
|
|
||||||
|
if "pip" in actions:
|
||||||
|
self._apply_pip_action(window_id)
|
||||||
|
return True
|
||||||
|
|
||||||
|
dispatch_commands.append(f"dispatch resizewindowpixel exact {width} {height},address:0x{window_id}")
|
||||||
|
|
||||||
|
if "center" in actions:
|
||||||
|
dispatch_commands.append("dispatch centerwindow")
|
||||||
|
|
||||||
|
try:
|
||||||
|
hypr.batch(*dispatch_commands)
|
||||||
|
log_message(f"Applied actions to window 0x{window_id}: {width} x {height} ({', '.join(actions)})")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log_message(f"ERROR: Failed to apply window actions for window 0x{window_id}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _match_window_rule(self, window_title: str, initial_title: str) -> WindowRule | None:
|
||||||
|
for rule in self.window_rules:
|
||||||
|
if rule.match_type == "initialTitle":
|
||||||
|
if initial_title == rule.name:
|
||||||
|
return rule
|
||||||
|
elif rule.match_type == "titleContains":
|
||||||
|
if rule.name in window_title:
|
||||||
|
return rule
|
||||||
|
elif rule.match_type == "titleExact":
|
||||||
|
if window_title == rule.name:
|
||||||
|
return rule
|
||||||
|
elif rule.match_type == "titleRegex":
|
||||||
|
try:
|
||||||
|
if re.search(rule.name, window_title):
|
||||||
|
return rule
|
||||||
|
except re.error:
|
||||||
|
log_message(f"ERROR: Invalid regex pattern in rule '{rule.name}'")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _handle_window_event(self, event: str) -> None:
|
||||||
|
if event.startswith("windowtitle"):
|
||||||
|
self._handle_title_event(event)
|
||||||
|
elif event.startswith("openwindow"):
|
||||||
|
self._handle_open_event(event)
|
||||||
|
|
||||||
|
def _handle_title_event(self, event: str) -> None:
|
||||||
|
try:
|
||||||
|
# Handle both >> and >>> separators (different Hyprland versions)
|
||||||
|
if ">>>" in event:
|
||||||
|
window_id = event.split(">>>")[1].split(",")[0]
|
||||||
|
else:
|
||||||
|
window_id = event.split(">>")[1].split(",")[0]
|
||||||
|
|
||||||
|
# Remove any leading > characters
|
||||||
|
window_id = window_id.lstrip(">")
|
||||||
|
|
||||||
|
if not all(c in "0123456789abcdefABCDEF" for c in window_id):
|
||||||
|
log_message(f"ERROR: Invalid window ID format: {window_id}")
|
||||||
|
return
|
||||||
|
|
||||||
|
window_info = self._get_window_info(window_id)
|
||||||
|
if not window_info:
|
||||||
|
return
|
||||||
|
|
||||||
|
window_title = window_info.get("title", "")
|
||||||
|
initial_title = window_info.get("initialTitle", "")
|
||||||
|
|
||||||
|
log_message(f"DEBUG: Window 0x{window_id} - Title: '{window_title}' | Initial: '{initial_title}'")
|
||||||
|
|
||||||
|
rule = self._match_window_rule(window_title, initial_title)
|
||||||
|
if rule:
|
||||||
|
if self._is_rate_limited(window_id):
|
||||||
|
log_message(f"Rate limited: skipping window 0x{window_id}")
|
||||||
|
return
|
||||||
|
|
||||||
|
log_message(f"Matched rule '{rule.name}' for window 0x{window_id}")
|
||||||
|
self._apply_window_actions(window_id, rule.width, rule.height, rule.actions)
|
||||||
|
|
||||||
|
except (IndexError, ValueError) as e:
|
||||||
|
log_message(f"ERROR: Failed to parse window title event: {e}")
|
||||||
|
|
||||||
|
def _handle_open_event(self, event: str) -> None:
|
||||||
|
try:
|
||||||
|
# Handle both >> and >>> separators
|
||||||
|
if "openwindow>>>" in event:
|
||||||
|
data = event[13:] # Remove "openwindow>>>"
|
||||||
|
else:
|
||||||
|
data = event[12:] # Remove "openwindow>>"
|
||||||
|
|
||||||
|
window_id, workspace, window_class, title = data.split(",", 3)
|
||||||
|
|
||||||
|
# Remove any leading > characters
|
||||||
|
window_id = window_id.lstrip(">")
|
||||||
|
|
||||||
|
if not all(c in "0123456789abcdefABCDEF" for c in window_id):
|
||||||
|
log_message(f"ERROR: Invalid window ID format: {window_id}")
|
||||||
|
return
|
||||||
|
|
||||||
|
log_message(f"DEBUG: New window 0x{window_id} - Title: '{title}' | Class: '{window_class}'")
|
||||||
|
|
||||||
|
rule = self._match_window_rule(title, title)
|
||||||
|
if rule:
|
||||||
|
if self._is_rate_limited(window_id):
|
||||||
|
log_message(f"Rate limited: skipping window 0x{window_id}")
|
||||||
|
return
|
||||||
|
|
||||||
|
log_message(f"Matched rule '{rule.name}' for new window 0x{window_id}")
|
||||||
|
self._apply_window_actions(window_id, rule.width, rule.height, rule.actions)
|
||||||
|
|
||||||
|
except (IndexError, ValueError) as e:
|
||||||
|
log_message(f"ERROR: Failed to parse window open event: {e}")
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
if self.args.daemon:
|
||||||
|
self._run_daemon()
|
||||||
|
elif hasattr(self.args, "pattern") and self.args.pattern == "pip":
|
||||||
|
self._run_pip_mode()
|
||||||
|
elif all(
|
||||||
|
hasattr(self.args, attr) and getattr(self.args, attr)
|
||||||
|
for attr in ["pattern", "match_type", "width", "height", "actions"]
|
||||||
|
):
|
||||||
|
self._run_active_mode()
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"Resizer daemon - use --daemon to start, 'pip' for quick pip mode, or provide pattern, match_type, width, height, and actions for active mode"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_pip_mode(self) -> None:
|
||||||
|
"""Quick pip mode - applies pip action to the active window if it's floating"""
|
||||||
|
try:
|
||||||
|
active_window_result = hypr.message("activewindow")
|
||||||
|
if not isinstance(active_window_result, dict) or not active_window_result.get("address"):
|
||||||
|
print("ERROR: No active window found")
|
||||||
|
return
|
||||||
|
|
||||||
|
address = active_window_result.get("address", "")
|
||||||
|
if not isinstance(address, str) or not address.startswith("0x"):
|
||||||
|
print("ERROR: Invalid window address")
|
||||||
|
return
|
||||||
|
|
||||||
|
window_id = address[2:] # Remove "0x" prefix
|
||||||
|
window_title = active_window_result.get("title", "")
|
||||||
|
|
||||||
|
if not active_window_result.get("floating", False):
|
||||||
|
print(f"Window '{window_title}' is not floating. PIP only works on floating windows.")
|
||||||
|
print("Try making it floating first with: hyprctl dispatch togglefloating")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Applying PIP to active window: '{window_title}'")
|
||||||
|
self._apply_pip_action(window_id)
|
||||||
|
print("PIP applied successfully")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: Failed to apply PIP to active window: {e}")
|
||||||
|
|
||||||
|
def _run_active_mode(self) -> None:
|
||||||
|
try:
|
||||||
|
# Create a temporary rule from command line arguments
|
||||||
|
actions = self.args.actions.split(",") if self.args.actions else []
|
||||||
|
temp_rule = WindowRule(self.args.pattern, self.args.match_type, self.args.width, self.args.height, actions)
|
||||||
|
|
||||||
|
# Special case: "active" pattern means only target the currently active window
|
||||||
|
if temp_rule.name.lower() == "active":
|
||||||
|
self._apply_to_active_window(temp_rule)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Find all windows that match the pattern
|
||||||
|
matching_windows = self._find_matching_windows(temp_rule)
|
||||||
|
|
||||||
|
if not matching_windows:
|
||||||
|
print(f"No windows found matching pattern '{temp_rule.name}' with match type '{temp_rule.match_type}'")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Found {len(matching_windows)} matching window(s)")
|
||||||
|
|
||||||
|
# Apply rule to all matching windows
|
||||||
|
success_count = 0
|
||||||
|
for window in matching_windows:
|
||||||
|
window_id = window["address"][2:] # Remove "0x" prefix
|
||||||
|
window_title = window.get("title", "")
|
||||||
|
|
||||||
|
print(f"Applying rule to window 0x{window_id}: '{window_title}'")
|
||||||
|
success = self._apply_window_actions(window_id, temp_rule.width, temp_rule.height, temp_rule.actions)
|
||||||
|
if success:
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
print(f"Successfully applied rule to {success_count}/{len(matching_windows)} windows")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: Failed to apply rule: {e}")
|
||||||
|
|
||||||
|
def _apply_to_active_window(self, temp_rule: WindowRule) -> None:
|
||||||
|
"""Apply rule only to the currently active window"""
|
||||||
|
try:
|
||||||
|
active_window_result = hypr.message("activewindow")
|
||||||
|
if not isinstance(active_window_result, dict) or not active_window_result.get("address"):
|
||||||
|
print("ERROR: No active window found")
|
||||||
|
return
|
||||||
|
|
||||||
|
window_title = active_window_result.get("title", "")
|
||||||
|
address = active_window_result.get("address", "")
|
||||||
|
if not isinstance(address, str) or not address.startswith("0x"):
|
||||||
|
print("ERROR: Invalid window address")
|
||||||
|
return
|
||||||
|
|
||||||
|
window_id = address[2:] # Remove "0x" prefix
|
||||||
|
|
||||||
|
print(f"Applying rule to active window 0x{window_id}: '{window_title}'")
|
||||||
|
success = self._apply_window_actions(window_id, temp_rule.width, temp_rule.height, temp_rule.actions)
|
||||||
|
if success:
|
||||||
|
print("Rule applied successfully")
|
||||||
|
else:
|
||||||
|
print("Failed to apply rule")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: Failed to apply rule to active window: {e}")
|
||||||
|
|
||||||
|
def _find_matching_windows(self, temp_rule: WindowRule) -> list:
|
||||||
|
"""Find all windows that match the given rule pattern"""
|
||||||
|
try:
|
||||||
|
clients_result = hypr.message("clients")
|
||||||
|
if not isinstance(clients_result, list):
|
||||||
|
return []
|
||||||
|
|
||||||
|
matching_windows = []
|
||||||
|
for window in clients_result:
|
||||||
|
if not isinstance(window, dict):
|
||||||
|
continue
|
||||||
|
|
||||||
|
window_title = window.get("title", "")
|
||||||
|
initial_title = window.get("initialTitle", "")
|
||||||
|
|
||||||
|
# Check if window matches the pattern
|
||||||
|
matches = False
|
||||||
|
if temp_rule.match_type == "initialTitle":
|
||||||
|
matches = initial_title == temp_rule.name
|
||||||
|
elif temp_rule.match_type == "titleContains":
|
||||||
|
matches = temp_rule.name in window_title
|
||||||
|
elif temp_rule.match_type == "titleExact":
|
||||||
|
matches = window_title == temp_rule.name
|
||||||
|
elif temp_rule.match_type == "titleRegex":
|
||||||
|
try:
|
||||||
|
matches = bool(re.search(temp_rule.name, window_title))
|
||||||
|
except re.error:
|
||||||
|
print(f"ERROR: Invalid regex pattern '{temp_rule.name}'")
|
||||||
|
return []
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
matching_windows.append(window)
|
||||||
|
|
||||||
|
return matching_windows
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: Failed to find matching windows: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _run_daemon(self) -> None:
|
||||||
|
log_message("Hyprland window resizer started")
|
||||||
|
log_message(f"Loaded {len(self.window_rules)} window rules")
|
||||||
|
|
||||||
|
socket_path = Path(hypr.socket2_path)
|
||||||
|
if not socket_path.exists():
|
||||||
|
log_message(f"ERROR: Hyprland socket not found at {socket_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
||||||
|
sock.connect(hypr.socket2_path)
|
||||||
|
|
||||||
|
log_message("Connected to Hyprland socket, listening for events...")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = sock.recv(4096).decode()
|
||||||
|
if data:
|
||||||
|
for line in data.strip().split("\n"):
|
||||||
|
if line:
|
||||||
|
self._handle_window_event(line)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
log_message("Resizer daemon stopped")
|
||||||
|
except Exception as e:
|
||||||
|
log_message(f"ERROR: {e}")
|
||||||
@@ -17,12 +17,17 @@ class Command:
|
|||||||
elif self.args.log:
|
elif self.args.log:
|
||||||
# Print the log
|
# Print the log
|
||||||
self.print_log()
|
self.print_log()
|
||||||
|
elif self.args.kill:
|
||||||
|
# Kill the shell
|
||||||
|
self.shell("kill")
|
||||||
elif self.args.message:
|
elif self.args.message:
|
||||||
# Send a message
|
# Send a message
|
||||||
self.message(*self.args.message)
|
self.message(*self.args.message)
|
||||||
else:
|
else:
|
||||||
# Start the shell
|
# Start the shell
|
||||||
args = ["qs", "-c", "caelestia", "-n", "--log-rules", self.args.log_rules]
|
args = ["qs", "-c", "caelestia", "-n"]
|
||||||
|
if self.args.log_rules:
|
||||||
|
args.append("--log-rules", self.args.log_rules)
|
||||||
if self.args.daemon:
|
if self.args.daemon:
|
||||||
args.append("-d")
|
args.append("-d")
|
||||||
subprocess.run(args)
|
subprocess.run(args)
|
||||||
@@ -42,7 +47,10 @@ class Command:
|
|||||||
print(self.shell("ipc", "show"), end="")
|
print(self.shell("ipc", "show"), end="")
|
||||||
|
|
||||||
def print_log(self) -> None:
|
def print_log(self) -> None:
|
||||||
log = self.shell("log", "-r", self.args.log_rules)
|
if self.args.log_rules:
|
||||||
|
log = self.shell("log", "-r", self.args.log_rules)
|
||||||
|
else:
|
||||||
|
log = self.shell("log")
|
||||||
# FIXME: remove when logging rules are added/warning is removed
|
# FIXME: remove when logging rules are added/warning is removed
|
||||||
for line in log.splitlines():
|
for line in log.splitlines():
|
||||||
if self.filter_log(line):
|
if self.filter_log(line):
|
||||||
|
|||||||
@@ -1,18 +1,124 @@
|
|||||||
import subprocess
|
import json
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
|
from collections import ChainMap
|
||||||
|
|
||||||
from caelestia.utils import hypr
|
from caelestia.utils import hypr
|
||||||
|
from caelestia.utils.paths import user_config_path
|
||||||
|
|
||||||
|
|
||||||
|
def is_subset(superset, subset):
|
||||||
|
for key, value in subset.items():
|
||||||
|
if key not in superset:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if isinstance(value, dict):
|
||||||
|
if not is_subset(superset[key], value):
|
||||||
|
return False
|
||||||
|
|
||||||
|
elif isinstance(value, str):
|
||||||
|
if value not in superset[key]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
elif isinstance(value, list):
|
||||||
|
if not set(value) <= set(superset[key]):
|
||||||
|
return False
|
||||||
|
elif isinstance(value, set):
|
||||||
|
if not value <= superset[key]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
if not value == superset[key]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class DeepChainMap(ChainMap):
|
||||||
|
def __getitem__(self, key):
|
||||||
|
values = (mapping[key] for mapping in self.maps if key in mapping)
|
||||||
|
try:
|
||||||
|
first = next(values)
|
||||||
|
except StopIteration:
|
||||||
|
return self.__missing__(key)
|
||||||
|
if isinstance(first, dict):
|
||||||
|
return self.__class__(first, *values)
|
||||||
|
return first
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return repr(dict(self))
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
args: Namespace
|
args: Namespace
|
||||||
|
cfg: dict[str, dict[str, dict[str, any]]] | DeepChainMap
|
||||||
clients: list[dict[str, any]] = None
|
clients: list[dict[str, any]] = None
|
||||||
|
|
||||||
def __init__(self, args: Namespace) -> None:
|
def __init__(self, args: Namespace) -> None:
|
||||||
self.args = args
|
self.args = args
|
||||||
|
|
||||||
|
self.cfg = {
|
||||||
|
"communication": {
|
||||||
|
"discord": {
|
||||||
|
"enable": True,
|
||||||
|
"match": [{"class": "discord"}],
|
||||||
|
"command": ["discord"],
|
||||||
|
"move": True,
|
||||||
|
},
|
||||||
|
"whatsapp": {
|
||||||
|
"enable": True,
|
||||||
|
"match": [{"class": "whatsapp"}],
|
||||||
|
"move": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"music": {
|
||||||
|
"spotify": {
|
||||||
|
"enable": True,
|
||||||
|
"match": [{"class": "Spotify"}, {"initialTitle": "Spotify"}, {"initialTitle": "Spotify Free"}],
|
||||||
|
"command": ["spicetify", "watch", "-s"],
|
||||||
|
"move": True,
|
||||||
|
},
|
||||||
|
"feishin": {
|
||||||
|
"enable": True,
|
||||||
|
"match": [{"class": "feishin"}],
|
||||||
|
"move": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sysmon": {
|
||||||
|
"btop": {
|
||||||
|
"enable": True,
|
||||||
|
"match": [{"class": "btop", "title": "btop", "workspace": {"name": "special:sysmon"}}],
|
||||||
|
"command": ["foot", "-a", "btop", "-T", "btop", "fish", "-C", "exec btop"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"todo": {
|
||||||
|
"todoist": {
|
||||||
|
"enable": True,
|
||||||
|
"match": [{"class": "Todoist"}],
|
||||||
|
"command": ["todoist"],
|
||||||
|
"move": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
self.cfg = DeepChainMap(json.loads(user_config_path.read_text())["toggles"], self.cfg)
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError, KeyError):
|
||||||
|
pass
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
getattr(self, self.args.workspace)()
|
if self.args.workspace == "specialws":
|
||||||
|
self.specialws()
|
||||||
|
return
|
||||||
|
|
||||||
|
spawned = False
|
||||||
|
if self.args.workspace in self.cfg:
|
||||||
|
for client in self.cfg[self.args.workspace].values():
|
||||||
|
if "enable" in client and client["enable"] and self.handle_client_config(client):
|
||||||
|
spawned = True
|
||||||
|
|
||||||
|
if not spawned:
|
||||||
|
hypr.dispatch("togglespecialworkspace", self.args.workspace)
|
||||||
|
|
||||||
def get_clients(self) -> list[dict[str, any]]:
|
def get_clients(self) -> list[dict[str, any]]:
|
||||||
if self.clients is None:
|
if self.clients is None:
|
||||||
@@ -22,54 +128,33 @@ class Command:
|
|||||||
|
|
||||||
def move_client(self, selector: callable, workspace: str) -> None:
|
def move_client(self, selector: callable, workspace: str) -> None:
|
||||||
for client in self.get_clients():
|
for client in self.get_clients():
|
||||||
if selector(client):
|
if selector(client) and client["workspace"]["name"] != f"special:{workspace}":
|
||||||
hypr.dispatch("movetoworkspacesilent", f"special:{workspace},address:{client['address']}")
|
hypr.dispatch("movetoworkspacesilent", f"special:{workspace},address:{client['address']}")
|
||||||
|
|
||||||
def spawn_client(self, selector: callable, spawn: list[str]) -> bool:
|
def spawn_client(self, selector: callable, spawn: list[str]) -> bool:
|
||||||
exists = any(selector(client) for client in self.get_clients())
|
if (spawn[0].endswith(".desktop") or shutil.which(spawn[0])) and not any(
|
||||||
|
selector(client) for client in self.get_clients()
|
||||||
|
):
|
||||||
|
hypr.dispatch("exec", f"[workspace special:{self.args.workspace}] app2unit -- {shlex.join(spawn)}")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
if not exists:
|
def handle_client_config(self, client: dict[str, any]) -> bool:
|
||||||
subprocess.Popen(["app2unit", "--", *spawn], start_new_session=True)
|
def selector(c: dict[str, any]) -> bool:
|
||||||
|
# Each match is or, inside matches is and
|
||||||
|
for match in client["match"]:
|
||||||
|
if is_subset(c, match):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
return not exists
|
spawned = False
|
||||||
|
if "command" in client and client["command"]:
|
||||||
|
spawned = self.spawn_client(selector, client["command"])
|
||||||
|
if "move" in client and client["move"]:
|
||||||
|
self.move_client(selector, self.args.workspace)
|
||||||
|
|
||||||
def spawn_or_move(self, selector: callable, spawn: list[str], workspace: str) -> None:
|
return spawned
|
||||||
if not self.spawn_client(selector, spawn):
|
|
||||||
self.move_client(selector, workspace)
|
|
||||||
|
|
||||||
def communication(self) -> None:
|
|
||||||
self.spawn_or_move(lambda c: c["class"] == "discord", ["discord"], "communication")
|
|
||||||
self.move_client(lambda c: c["class"] == "whatsapp", "communication")
|
|
||||||
hypr.dispatch("togglespecialworkspace", "communication")
|
|
||||||
|
|
||||||
def music(self) -> None:
|
|
||||||
self.spawn_or_move(
|
|
||||||
lambda c: c["class"] == "Spotify" or c["initialTitle"] == "Spotify" or c["initialTitle"] == "Spotify Free",
|
|
||||||
["spicetify", "watch", "-s"],
|
|
||||||
"music",
|
|
||||||
)
|
|
||||||
self.move_client(lambda c: c["class"] == "feishin", "music")
|
|
||||||
hypr.dispatch("togglespecialworkspace", "music")
|
|
||||||
|
|
||||||
def sysmon(self) -> None:
|
|
||||||
self.spawn_client(
|
|
||||||
lambda c: c["class"] == "btop" and c["title"] == "btop" and c["workspace"]["name"] == "special:sysmon",
|
|
||||||
["foot", "-a", "btop", "-T", "btop", "fish", "-C", "exec btop"],
|
|
||||||
)
|
|
||||||
hypr.dispatch("togglespecialworkspace", "sysmon")
|
|
||||||
|
|
||||||
def todo(self) -> None:
|
|
||||||
self.spawn_or_move(lambda c: c["class"] == "Todoist", ["todoist"], "todo")
|
|
||||||
hypr.dispatch("togglespecialworkspace", "todo")
|
|
||||||
|
|
||||||
def specialws(self) -> None:
|
def specialws(self) -> None:
|
||||||
workspaces = hypr.message("workspaces")
|
special = next(m for m in hypr.message("monitors") if m["focused"])["specialWorkspace"]["name"]
|
||||||
on_special_ws = any(ws["name"] == "special:special" for ws in workspaces)
|
hypr.dispatch("togglespecialworkspace", special[8:] or "special")
|
||||||
toggle_ws = "special"
|
|
||||||
|
|
||||||
if not on_special_ws:
|
|
||||||
active_ws = hypr.message("activewindow")["workspace"]["name"]
|
|
||||||
if active_ws.startswith("special:"):
|
|
||||||
toggle_ws = active_ws[8:]
|
|
||||||
|
|
||||||
hypr.dispatch("togglespecialworkspace", toggle_ws)
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
class Colour:
|
||||||
|
_rgb_vals: tuple[int, ...]
|
||||||
|
_hex_vals: tuple[str, ...]
|
||||||
|
|
||||||
|
def __init__(self, hex: str):
|
||||||
|
hex = hex.ljust(8, "f")
|
||||||
|
self._hex_vals = tuple(hex[i : i + 2] for i in range(0, 7, 2))
|
||||||
|
self._rgb_vals = tuple(int(h, 16) for h in self._hex_vals)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hex(self) -> str:
|
||||||
|
return "".join(self._hex_vals[:-1])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hexalpha(self) -> str:
|
||||||
|
return "".join(self._hex_vals)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rgb(self) -> str:
|
||||||
|
return f"rgb({','.join(map(str, self._rgb_vals[:-1]))})"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rgbalpha(self) -> str:
|
||||||
|
return f"rgba({','.join(map(str, self._rgb_vals))})"
|
||||||
|
|
||||||
|
|
||||||
|
def get_dynamic_colours(colours: dict[str, str]) -> dict[str, Colour]:
|
||||||
|
return {name: Colour(code) for name, code in colours.items()}
|
||||||
@@ -27,3 +27,9 @@ def message(msg: str, json: bool = True) -> str | dict[str, any]:
|
|||||||
|
|
||||||
def dispatch(dispatcher: str, *args: list[any]) -> bool:
|
def dispatch(dispatcher: str, *args: list[any]) -> bool:
|
||||||
return message(f"dispatch {dispatcher} {' '.join(map(str, args))}".rstrip(), json=False) == "ok"
|
return message(f"dispatch {dispatcher} {' '.join(map(str, args))}".rstrip(), json=False) == "ok"
|
||||||
|
|
||||||
|
|
||||||
|
def batch(*msgs: list[str], json: bool = False) -> str | dict[str, any]:
|
||||||
|
if json:
|
||||||
|
msgs = (f"j/{m.strip()}" for m in msgs)
|
||||||
|
return message(f"[[BATCH]]{';'.join(msgs)}", json=False)
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from time import strftime
|
||||||
|
|
||||||
|
|
||||||
|
def log_message(message: str) -> None:
|
||||||
|
timestamp = strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
print(f"[{timestamp}] {message}")
|
||||||
|
|
||||||
|
|
||||||
|
def log_exception(func):
|
||||||
|
"""Log exceptions to stdout instead of raising
|
||||||
|
|
||||||
|
Used by the `apply_()` functions so that an exception, when applying
|
||||||
|
a theme, does not prevent the other themes from being applied.
|
||||||
|
"""
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
func(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
log_message(f'Error during execution of "{func.__name__}()": {str(e)}')
|
||||||
|
return wrapper
|
||||||
@@ -92,6 +92,14 @@ dark_catppuccin = [
|
|||||||
hex_to_hct("b4befe"),
|
hex_to_hct("b4befe"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
kcolours = [
|
||||||
|
{"name": "klink", "hct": hex_to_hct("2980b9")},
|
||||||
|
{"name": "kvisited", "hct": hex_to_hct("9b59b6")},
|
||||||
|
{"name": "knegative", "hct": hex_to_hct("da4453")},
|
||||||
|
{"name": "kneutral", "hct": hex_to_hct("f67400")},
|
||||||
|
{"name": "kpositive", "hct": hex_to_hct("27ae60")},
|
||||||
|
]
|
||||||
|
|
||||||
colour_names = [
|
colour_names = [
|
||||||
"rosewater",
|
"rosewater",
|
||||||
"flamingo",
|
"flamingo",
|
||||||
@@ -185,6 +193,14 @@ def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
|||||||
else:
|
else:
|
||||||
colours[colour_names[i]] = harmonize(hct, colours["primary_paletteKeyColor"], (-0.2 if light else 0.05))
|
colours[colour_names[i]] = harmonize(hct, colours["primary_paletteKeyColor"], (-0.2 if light else 0.05))
|
||||||
|
|
||||||
|
# KColours
|
||||||
|
for colour in kcolours:
|
||||||
|
colours[colour["name"]] = harmonize(colour["hct"], colours["primary"], 0.1)
|
||||||
|
colours[f"{colour['name']}Selection"] = harmonize(colour["hct"], colours["onPrimaryFixedVariant"], 0.1)
|
||||||
|
if scheme.variant == "monochrome":
|
||||||
|
colours[colour["name"]] = grayscale(colours[colour["name"]], light)
|
||||||
|
colours[f"{colour['name']}Selection"] = grayscale(colours[f"{colour['name']}Selection"], light)
|
||||||
|
|
||||||
if scheme.variant == "neutral":
|
if scheme.variant == "neutral":
|
||||||
for name, hct in colours.items():
|
for name, hct in colours.items():
|
||||||
colours[name].chroma -= 15
|
colours[name].chroma -= 15
|
||||||
|
|||||||
@@ -3,3 +3,18 @@ import subprocess
|
|||||||
|
|
||||||
def notify(*args: list[str]) -> str:
|
def notify(*args: list[str]) -> str:
|
||||||
return subprocess.check_output(["notify-send", "-a", "caelestia-cli", *args], text=True).strip()
|
return subprocess.check_output(["notify-send", "-a", "caelestia-cli", *args], text=True).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def close_notification(id: str) -> None:
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
"gdbus",
|
||||||
|
"call",
|
||||||
|
"--session",
|
||||||
|
"--dest=org.freedesktop.Notifications",
|
||||||
|
"--object-path=/org/freedesktop/Notifications",
|
||||||
|
"--method=org.freedesktop.Notifications.CloseNotification",
|
||||||
|
id,
|
||||||
|
],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,29 +9,34 @@ config_dir = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
|
|||||||
data_dir = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local/share"))
|
data_dir = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local/share"))
|
||||||
state_dir = Path(os.getenv("XDG_STATE_HOME", Path.home() / ".local/state"))
|
state_dir = Path(os.getenv("XDG_STATE_HOME", Path.home() / ".local/state"))
|
||||||
cache_dir = Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache"))
|
cache_dir = Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache"))
|
||||||
|
pictures_dir = Path(os.getenv("XDG_PICTURES_DIR", Path.home() / "Pictures"))
|
||||||
|
videos_dir = Path(os.getenv("XDG_VIDEOS_DIR", Path.home() / "Videos"))
|
||||||
|
|
||||||
c_config_dir = config_dir / "caelestia"
|
c_config_dir = config_dir / "caelestia"
|
||||||
c_data_dir = data_dir / "caelestia"
|
c_data_dir = data_dir / "caelestia"
|
||||||
c_state_dir = state_dir / "caelestia"
|
c_state_dir = state_dir / "caelestia"
|
||||||
c_cache_dir = cache_dir / "caelestia"
|
c_cache_dir = cache_dir / "caelestia"
|
||||||
|
|
||||||
|
user_config_path = c_config_dir / "cli.json"
|
||||||
cli_data_dir = Path(__file__).parent.parent / "data"
|
cli_data_dir = Path(__file__).parent.parent / "data"
|
||||||
templates_dir = cli_data_dir / "templates"
|
templates_dir = cli_data_dir / "templates"
|
||||||
|
user_templates_dir = c_config_dir / "templates"
|
||||||
|
theme_dir = c_state_dir / "theme"
|
||||||
|
|
||||||
scheme_path = c_state_dir / "scheme.json"
|
scheme_path = c_state_dir / "scheme.json"
|
||||||
scheme_data_dir = cli_data_dir / "schemes"
|
scheme_data_dir = cli_data_dir / "schemes"
|
||||||
scheme_cache_dir = c_cache_dir / "schemes"
|
scheme_cache_dir = c_cache_dir / "schemes"
|
||||||
|
|
||||||
wallpapers_dir = Path.home() / "Pictures/Wallpapers"
|
wallpapers_dir = os.getenv("CAELESTIA_WALLPAPERS_DIR", pictures_dir / "Wallpapers")
|
||||||
wallpaper_path_path = c_state_dir / "wallpaper/path.txt"
|
wallpaper_path_path = c_state_dir / "wallpaper/path.txt"
|
||||||
wallpaper_link_path = c_state_dir / "wallpaper/current"
|
wallpaper_link_path = c_state_dir / "wallpaper/current"
|
||||||
wallpaper_thumbnail_path = c_state_dir / "wallpaper/thumbnail.jpg"
|
wallpaper_thumbnail_path = c_state_dir / "wallpaper/thumbnail.jpg"
|
||||||
wallpapers_cache_dir = c_cache_dir / "wallpapers"
|
wallpapers_cache_dir = c_cache_dir / "wallpapers"
|
||||||
|
|
||||||
screenshots_dir = Path.home() / "Pictures/Screenshots"
|
screenshots_dir = os.getenv("CAELESTIA_SCREENSHOTS_DIR", pictures_dir / "Screenshots")
|
||||||
screenshots_cache_dir = c_cache_dir / "screenshots"
|
screenshots_cache_dir = c_cache_dir / "screenshots"
|
||||||
|
|
||||||
recordings_dir = Path.home() / "Videos/Recordings"
|
recordings_dir = os.getenv("CAELESTIA_RECORDINGS_DIR", videos_dir / "Recordings")
|
||||||
recording_path = c_state_dir / "record/recording.mp4"
|
recording_path = c_state_dir / "record/recording.mp4"
|
||||||
recording_notif_path = c_state_dir / "record/notifid.txt"
|
recording_notif_path = c_state_dir / "record/notifid.txt"
|
||||||
|
|
||||||
|
|||||||
+134
-14
@@ -1,7 +1,19 @@
|
|||||||
|
import json
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from caelestia.utils.paths import c_state_dir, config_dir, templates_dir
|
from caelestia.utils.colour import get_dynamic_colours
|
||||||
|
from caelestia.utils.logging import log_exception
|
||||||
|
from caelestia.utils.paths import (
|
||||||
|
c_state_dir,
|
||||||
|
config_dir,
|
||||||
|
data_dir,
|
||||||
|
templates_dir,
|
||||||
|
theme_dir,
|
||||||
|
user_config_path,
|
||||||
|
user_templates_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def gen_conf(colours: dict[str, str]) -> str:
|
def gen_conf(colours: dict[str, str]) -> str:
|
||||||
@@ -25,6 +37,25 @@ def gen_replace(colours: dict[str, str], template: Path, hash: bool = False) ->
|
|||||||
return template
|
return template
|
||||||
|
|
||||||
|
|
||||||
|
def gen_replace_dynamic(colours: dict[str, str], template: Path) -> str:
|
||||||
|
def fill_colour(match: re.Match) -> str:
|
||||||
|
data = match.group(1).strip().split(".")
|
||||||
|
if len(data) != 2:
|
||||||
|
return match.group()
|
||||||
|
col, form = data
|
||||||
|
if col not in colours_dyn or not hasattr(colours_dyn[col], form):
|
||||||
|
return match.group()
|
||||||
|
return getattr(colours_dyn[col], form)
|
||||||
|
|
||||||
|
# match atomic {{ . }} pairs
|
||||||
|
field = r"\{\{((?:(?!\{\{|\}\}).)*)\}\}"
|
||||||
|
colours_dyn = get_dynamic_colours(colours)
|
||||||
|
template_content = template.read_text()
|
||||||
|
template_filled = re.sub(field, fill_colour, template_content)
|
||||||
|
|
||||||
|
return template_filled
|
||||||
|
|
||||||
|
|
||||||
def c2s(c: str, *i: list[int]) -> str:
|
def c2s(c: str, *i: list[int]) -> str:
|
||||||
"""Hex to ANSI sequence (e.g. ffffff, 11 -> \x1b]11;rgb:ff/ff/ff\x1b\\)"""
|
"""Hex to ANSI sequence (e.g. ffffff, 11 -> \x1b]11;rgb:ff/ff/ff\x1b\\)"""
|
||||||
return f"\x1b]{';'.join(map(str, i))};rgb:{c[0:2]}/{c[2:4]}/{c[4:6]}\x1b\\"
|
return f"\x1b]{';'.join(map(str, i))};rgb:{c[0:2]}/{c[2:4]}/{c[4:6]}\x1b\\"
|
||||||
@@ -73,6 +104,7 @@ def write_file(path: Path, content: str) -> None:
|
|||||||
path.write_text(content)
|
path.write_text(content)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
def apply_terms(sequences: str) -> None:
|
def apply_terms(sequences: str) -> None:
|
||||||
state = c_state_dir / "sequences.txt"
|
state = c_state_dir / "sequences.txt"
|
||||||
state.parent.mkdir(parents=True, exist_ok=True)
|
state.parent.mkdir(parents=True, exist_ok=True)
|
||||||
@@ -81,14 +113,19 @@ def apply_terms(sequences: str) -> None:
|
|||||||
pts_path = Path("/dev/pts")
|
pts_path = Path("/dev/pts")
|
||||||
for pt in pts_path.iterdir():
|
for pt in pts_path.iterdir():
|
||||||
if pt.name.isdigit():
|
if pt.name.isdigit():
|
||||||
with pt.open("a") as f:
|
try:
|
||||||
f.write(sequences)
|
with pt.open("a") as f:
|
||||||
|
f.write(sequences)
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
def apply_hypr(conf: str) -> None:
|
def apply_hypr(conf: str) -> None:
|
||||||
write_file(config_dir / "hypr/scheme/current.conf", conf)
|
write_file(config_dir / "hypr/scheme/current.conf", conf)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
def apply_discord(scss: str) -> None:
|
def apply_discord(scss: str) -> None:
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
@@ -100,22 +137,39 @@ def apply_discord(scss: str) -> None:
|
|||||||
write_file(config_dir / client / "themes/caelestia.theme.css", conf)
|
write_file(config_dir / client / "themes/caelestia.theme.css", conf)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
def apply_spicetify(colours: dict[str, str], mode: str) -> None:
|
def apply_spicetify(colours: dict[str, str], mode: str) -> None:
|
||||||
template = gen_replace(colours, templates_dir / f"spicetify-{mode}.ini")
|
template = gen_replace(colours, templates_dir / f"spicetify-{mode}.ini")
|
||||||
write_file(config_dir / "spicetify/Themes/caelestia/color.ini", template)
|
write_file(config_dir / "spicetify/Themes/caelestia/color.ini", template)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
def apply_fuzzel(colours: dict[str, str]) -> None:
|
def apply_fuzzel(colours: dict[str, str]) -> None:
|
||||||
template = gen_replace(colours, templates_dir / "fuzzel.ini")
|
template = gen_replace(colours, templates_dir / "fuzzel.ini")
|
||||||
write_file(config_dir / "fuzzel/fuzzel.ini", template)
|
write_file(config_dir / "fuzzel/fuzzel.ini", template)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
def apply_btop(colours: dict[str, str]) -> None:
|
def apply_btop(colours: dict[str, str]) -> None:
|
||||||
template = gen_replace(colours, templates_dir / "btop.theme", hash=True)
|
template = gen_replace(colours, templates_dir / "btop.theme", hash=True)
|
||||||
write_file(config_dir / "btop/themes/caelestia.theme", template)
|
write_file(config_dir / "btop/themes/caelestia.theme", template)
|
||||||
subprocess.run(["killall", "-USR2", "btop"], stderr=subprocess.DEVNULL)
|
subprocess.run(["killall", "-USR2", "btop"], stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
|
def apply_nvtop(colours: dict[str, str]) -> None:
|
||||||
|
template = gen_replace(colours, templates_dir / "nvtop.colors", hash=True)
|
||||||
|
write_file(config_dir / "nvtop/nvtop.colors", template)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
|
def apply_htop(colours: dict[str, str]) -> None:
|
||||||
|
template = gen_replace(colours, templates_dir / "htop.theme", hash=True)
|
||||||
|
write_file(config_dir / "htop/htoprc", template)
|
||||||
|
subprocess.run(["killall", "-USR2", "htop"], stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
def apply_gtk(colours: dict[str, str], mode: str) -> None:
|
def apply_gtk(colours: dict[str, str], mode: str) -> None:
|
||||||
template = gen_replace(colours, templates_dir / "gtk.css", hash=True)
|
template = gen_replace(colours, templates_dir / "gtk.css", hash=True)
|
||||||
write_file(config_dir / "gtk-3.0/gtk.css", template)
|
write_file(config_dir / "gtk-3.0/gtk.css", template)
|
||||||
@@ -126,25 +180,91 @@ def apply_gtk(colours: dict[str, str], mode: str) -> None:
|
|||||||
subprocess.run(["dconf", "write", "/org/gnome/desktop/interface/icon-theme", f"'Papirus-{mode.capitalize()}'"])
|
subprocess.run(["dconf", "write", "/org/gnome/desktop/interface/icon-theme", f"'Papirus-{mode.capitalize()}'"])
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
def apply_qt(colours: dict[str, str], mode: str) -> None:
|
def apply_qt(colours: dict[str, str], mode: str) -> None:
|
||||||
template = gen_replace(colours, templates_dir / "qtcolors.conf", hash=True)
|
template = gen_replace(colours, templates_dir / f"qt{mode}.colors", hash=True)
|
||||||
write_file(config_dir / "qt5ct/colors/caelestia.conf", template)
|
write_file(config_dir / "qt5ct/colors/caelestia.colors", template)
|
||||||
write_file(config_dir / "qt6ct/colors/caelestia.conf", template)
|
write_file(config_dir / "qt6ct/colors/caelestia.colors", template)
|
||||||
|
|
||||||
qtct = (templates_dir / "qtct.conf").read_text()
|
qtct = (templates_dir / "qtct.conf").read_text()
|
||||||
qtct = qtct.replace("{{ $mode }}", mode.capitalize())
|
qtct = qtct.replace("{{ $mode }}", mode.capitalize())
|
||||||
|
|
||||||
for ver in 5, 6:
|
for ver in 5, 6:
|
||||||
conf = qtct.replace("{{ $config }}", str(config_dir / f"qt{ver}ct"))
|
conf = qtct.replace("{{ $config }}", str(config_dir / f"qt{ver}ct"))
|
||||||
|
|
||||||
|
if ver == 5:
|
||||||
|
conf += """
|
||||||
|
[Fonts]
|
||||||
|
fixed="Monospace,12,-1,5,50,0,0,0,0,0"
|
||||||
|
general="Sans Serif,12,-1,5,50,0,0,0,0,0"
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
conf += """
|
||||||
|
[Fonts]
|
||||||
|
fixed="Monospace,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"
|
||||||
|
general="Sans Serif,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"
|
||||||
|
"""
|
||||||
write_file(config_dir / f"qt{ver}ct/qt{ver}ct.conf", conf)
|
write_file(config_dir / f"qt{ver}ct/qt{ver}ct.conf", conf)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
|
def apply_warp(colours: dict[str, str], mode: str) -> None:
|
||||||
|
warp_mode = "darker" if mode == "dark" else "lighter"
|
||||||
|
|
||||||
|
template = gen_replace(colours, templates_dir / "warp.yaml", hash=True)
|
||||||
|
template = template.replace("{{ $warp_mode }}", warp_mode)
|
||||||
|
write_file(data_dir / "warp-terminal/themes/caelestia.yaml", template)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
|
def apply_cava(colours: dict[str, str]) -> None:
|
||||||
|
template = gen_replace(colours, templates_dir / "cava.conf", hash=True)
|
||||||
|
write_file(config_dir / "cava/config", template)
|
||||||
|
subprocess.run(["killall", "-USR2", "cava"], stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
|
@log_exception
|
||||||
|
def apply_user_templates(colours: dict[str, str]) -> None:
|
||||||
|
if not user_templates_dir.is_dir():
|
||||||
|
return
|
||||||
|
|
||||||
|
for file in user_templates_dir.iterdir():
|
||||||
|
if file.is_file():
|
||||||
|
content = gen_replace_dynamic(colours, file)
|
||||||
|
write_file(theme_dir / file.name, content)
|
||||||
|
|
||||||
|
|
||||||
def apply_colours(colours: dict[str, str], mode: str) -> None:
|
def apply_colours(colours: dict[str, str], mode: str) -> None:
|
||||||
apply_terms(gen_sequences(colours))
|
try:
|
||||||
apply_hypr(gen_conf(colours))
|
cfg = json.loads(user_config_path.read_text())["theme"]
|
||||||
apply_discord(gen_scss(colours))
|
except (FileNotFoundError, json.JSONDecodeError, KeyError):
|
||||||
apply_spicetify(colours, mode)
|
cfg = {}
|
||||||
apply_fuzzel(colours)
|
|
||||||
apply_btop(colours)
|
def check(key: str) -> bool:
|
||||||
apply_gtk(colours, mode)
|
return cfg[key] if key in cfg else True
|
||||||
apply_qt(colours, mode)
|
|
||||||
|
if check("enableTerm"):
|
||||||
|
apply_terms(gen_sequences(colours))
|
||||||
|
if check("enableHypr"):
|
||||||
|
apply_hypr(gen_conf(colours))
|
||||||
|
if check("enableDiscord"):
|
||||||
|
apply_discord(gen_scss(colours))
|
||||||
|
if check("enableSpicetify"):
|
||||||
|
apply_spicetify(colours, mode)
|
||||||
|
if check("enableFuzzel"):
|
||||||
|
apply_fuzzel(colours)
|
||||||
|
if check("enableBtop"):
|
||||||
|
apply_btop(colours)
|
||||||
|
if check("enableNvtop"):
|
||||||
|
apply_nvtop(colours)
|
||||||
|
if check("enableHtop"):
|
||||||
|
apply_htop(colours)
|
||||||
|
if check("enableGtk"):
|
||||||
|
apply_gtk(colours, mode)
|
||||||
|
if check("enableQt"):
|
||||||
|
apply_qt(colours, mode)
|
||||||
|
if check("enableWarp"):
|
||||||
|
apply_warp(colours, mode)
|
||||||
|
if check("enableCava"):
|
||||||
|
apply_cava(colours)
|
||||||
|
apply_user_templates(colours)
|
||||||
|
|||||||
@@ -1,30 +1,50 @@
|
|||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from caelestia.utils.paths import config_dir
|
from caelestia.utils.paths import config_dir
|
||||||
|
|
||||||
|
|
||||||
def print_version() -> None:
|
def print_version() -> None:
|
||||||
print("Packages:")
|
if shutil.which("pacman"):
|
||||||
pkgs = ["caelestia-shell-git", "caelestia-cli-git", "caelestia-meta"]
|
print("Packages:")
|
||||||
versions = subprocess.run(
|
pkgs = ["caelestia-shell", "caelestia-cli", "caelestia-meta"]
|
||||||
["pacman", "-Q", *pkgs], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True
|
versions = subprocess.run(
|
||||||
).stdout
|
["pacman", "-Q", *pkgs], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True
|
||||||
|
).stdout
|
||||||
|
|
||||||
for pkg in pkgs:
|
for pkg in pkgs:
|
||||||
if pkg not in versions:
|
if pkg not in versions:
|
||||||
print(f" {pkg} not installed")
|
print(f" {pkg} not installed")
|
||||||
print("\n".join(f" {pkg}" for pkg in versions.splitlines()))
|
print("\n".join(f" {pkg}" for pkg in versions.splitlines()))
|
||||||
|
else:
|
||||||
|
print("Packages: not on Arch")
|
||||||
|
|
||||||
caelestia_dir = (config_dir / "hypr").resolve().parent
|
print()
|
||||||
print("\nCaelestia:")
|
try:
|
||||||
caelestia_ver = subprocess.check_output(
|
caelestia_dir = (config_dir / "hypr").resolve().parent
|
||||||
["git", "--git-dir", caelestia_dir / ".git", "rev-list", "--format=%B", "--max-count=1", "HEAD"], text=True
|
caelestia_ver = subprocess.check_output(
|
||||||
)
|
["git", "--git-dir", caelestia_dir / ".git", "rev-list", "--format=%B", "--max-count=1", "HEAD"], text=True
|
||||||
print(" Last commit:", caelestia_ver.split()[1])
|
)
|
||||||
print(" Commit message:", *caelestia_ver.splitlines()[1:])
|
print("Caelestia:")
|
||||||
|
print(" Last commit:", caelestia_ver.split()[1])
|
||||||
|
print(" Commit message:", *caelestia_ver.splitlines()[1:])
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print("Caelestia: not installed")
|
||||||
|
|
||||||
print("\nQuickshell:")
|
print()
|
||||||
print(" ", subprocess.check_output(["qs", "--version"], text=True).strip())
|
try:
|
||||||
|
shell_ver = subprocess.check_output(["/usr/lib/caelestia/version", "-s"], text=True).strip()
|
||||||
|
print("Shell:")
|
||||||
|
print(" ", shell_ver)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print("Shell: version helper not available")
|
||||||
|
|
||||||
|
print()
|
||||||
|
if shutil.which("qs"):
|
||||||
|
print("Quickshell:")
|
||||||
|
print(" ", subprocess.check_output(["qs", "--version"], text=True).strip())
|
||||||
|
else:
|
||||||
|
print("Quickshell: not in PATH")
|
||||||
|
|
||||||
local_shell_dir = config_dir / "quickshell/caelestia"
|
local_shell_dir = config_dir / "quickshell/caelestia"
|
||||||
if local_shell_dir.exists():
|
if local_shell_dir.exists():
|
||||||
|
|||||||
@@ -160,4 +160,18 @@ def set_wallpaper(wall: Path | str, no_smart: bool) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def set_random(args: Namespace) -> None:
|
def set_random(args: Namespace) -> None:
|
||||||
set_wallpaper(random.choice(get_wallpapers(args)), args.no_smart)
|
wallpapers = get_wallpapers(args)
|
||||||
|
|
||||||
|
if not wallpapers:
|
||||||
|
raise ValueError("No valid wallpapers found")
|
||||||
|
|
||||||
|
try:
|
||||||
|
last_wall = wallpaper_path_path.read_text()
|
||||||
|
wallpapers.remove(Path(last_wall))
|
||||||
|
|
||||||
|
if not wallpapers:
|
||||||
|
raise ValueError("Only valid wallpaper is current")
|
||||||
|
except (FileNotFoundError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
set_wallpaper(random.choice(wallpapers), args.no_smart)
|
||||||
|
|||||||
Reference in New Issue
Block a user