This commit is contained in:
Nickolaj Jepsen 2025-02-20 22:50:06 +01:00
parent 2b7b63a18c
commit 638ef7093e
140 changed files with 307 additions and 121 deletions

49
modules/apps/firefox.nix Normal file
View file

@ -0,0 +1,49 @@
{
pkgsUnstable,
inputs,
pkgs,
...
}: let
nur = inputs.nur.legacyPackages.${pkgs.system};
extensions = nur.repos.rycee.firefox-addons;
in {
programs.firefox = {
enable = true;
package = pkgsUnstable.firefox;
};
fireproof.home-manager = {
programs.firefox = {
enable = true;
package = pkgsUnstable.firefox;
profiles.default = {
extensions = with extensions; [
# Privacy
ublock-origin
clearurls
libredirect
# Security
bitwarden
# Media
dearrow
sponsorblock
# Search
kagi-search
# Productivity
new-tab-override
# Social
reddit-enhancement-suite
];
settings = {
"browser.startup.homepage" = "https://flame.nickolaj.com";
};
};
};
};
}

52
modules/apps/ghostty.nix Normal file
View file

@ -0,0 +1,52 @@
{
pkgs,
config,
...
}: {
environment.systemPackages = with pkgs; [
ghostty
];
fireproof.home-manager = {
programs.ghostty = {
enable = true;
enableFishIntegration = config.programs.fish.enable;
settings = {
window-decoration = false;
theme = "fireproof";
font-size = 11;
font-family = "Hack Nerd Font";
window-inherit-font-size = false;
};
themes = {
fireproof = {
background = "1C1B1A";
cursor-color = "DAD8CE";
foreground = "DAD8CE";
palette = [
"0=#100F0F"
"1=#AF3029"
"2=#66800B"
"3=#AD8301"
"4=#205EA6"
"5=#A02F6F"
"6=#24837B"
"7=#DAD8CE"
"8=#878580"
"9=#D14D41"
"10=#879A39"
"11=#D0A215"
"12=#4385BE"
"13=#CE5D97"
"14=#3AA99F"
"15=#F2F0E5"
];
selection-background = "403E3C";
selection-foreground = "DAD8CE";
};
};
};
};
fireproof.default-apps = {
terminal = "ghostty";
};
}

5
modules/apps/pycharm.nix Normal file
View file

@ -0,0 +1,5 @@
{pkgsUnstable, ...}: {
environment.systemPackages = [
pkgsUnstable.jetbrains.pycharm-professional
];
}

22
modules/apps/spotify.nix Normal file
View file

@ -0,0 +1,22 @@
{
username,
pkgs,
...
}: {
environment.systemPackages = with pkgs; [
spotify
];
age.secrets.spotify-player = {
rekeyFile = ../../secrets/spotify-player.age;
path = "/home/${username}/.cache/spotify-player/credentials.json";
mode = "0600";
owner = username;
};
fireproof.home-manager = {
programs.spotify-player = {
enable = true;
};
};
}

View file

@ -0,0 +1,5 @@
{pkgsUnstable, ...}: {
environment.systemPackages = [
pkgsUnstable.sublime-merge
];
}

View file

@ -0,0 +1,9 @@
{
username,
pkgsUnstable,
...
}: {
virtualisation.virtualbox.host.enable = true;
virtualisation.virtualbox.host.package = pkgsUnstable.virtualbox;
users.extraGroups.vboxusers.members = [username];
}

90
modules/apps/vscode.nix Normal file
View file

@ -0,0 +1,90 @@
{
pkgsUnstable,
pkgs,
inputs,
lib,
...
}: let
# stable = inputs.nix-vscode-extensions.extensions.${pkgs.system}.vscode-marketplace-release;
vscode-extensions = inputs.nix-vscode-extensions.extensions.${pkgs.system};
vscodePackage = pkgsUnstable.vscode;
vscodeMarketplace = (vscode-extensions.forVSCodeVersion vscodePackage.version).vscode-marketplace;
vscodeMarketplaceRelease = (vscode-extensions.forVSCodeVersion vscodePackage.version).vscode-marketplace-release;
vscodePkgs = vscodeMarketplace // vscodeMarketplaceRelease; # Prefer release over pre-release
mkFormatter = formatter: languages: {
"[${lib.concatStringsSep "][" languages}]" = {
"editor.defaultFormatter" = formatter;
"editor.formatOnSave" = true;
};
};
in {
fireproof.home-manager = {
programs.vscode = {
enable = true;
package = vscodePackage;
enableUpdateCheck = true;
enableExtensionUpdateCheck = true;
userSettings = lib.mkMerge [
{
# General
"extensions.ignoreRecommendations" = true;
# Remote
"remote.SSH.useLocalServer" = false;
# AI
"github.copilot.editor.enableAutoCompletions" = true;
"github.copilot.enable" = {"*" = true;};
# Theme
"workbench.colorTheme" = "Darcula Theme from IntelliJ";
"window.titleBarStyle" = "custom";
# Keybindings
"workbench.commandPalette.experimental.suggestCommands" = true; # Emulates IntelliJ's "Search Everywhere"
# nix-ide
"nix.enableLanguageServer" = true;
"nix.serverPath" = lib.getExe pkgs.nil;
"nix.serverSettings" = {
nil.formatting.command = ["nix" "fmt" "--" "--"];
};
}
(mkFormatter "esbenp.prettier-vscode" ["json" "jsonc" "markdown" "css" "scss" "typescript" "typescriptreact" "html" "yaml"])
(mkFormatter "charliermarsh.ruff" ["python"])
];
extensions = with vscodePkgs; [
# Remote
ms-vscode-remote.remote-ssh
# AI
github.copilot
github.copilot-chat
# Python
ms-pyright.pyright
ms-python.python
charliermarsh.ruff
# JavaScript
dbaeumer.vscode-eslint
esbenp.prettier-vscode
# Nix
jnoortheen.nix-ide
# Other languages
nefrob.vscode-just-syntax
redhat.vscode-yaml
# Theme
trinm1709.dracula-theme-from-intellij
# Keybindings
k--kato.intellij-idea-keybindings
];
};
};
}

4
modules/base/boot.nix Normal file
View file

@ -0,0 +1,4 @@
_: {
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
}

View file

@ -0,0 +1,8 @@
{lib, ...}: {
options.fireproof.default-apps = {
terminal = lib.mkOption {
type = lib.types.str;
description = "The terminal to use";
};
};
}

5
modules/base/envvar.nix Normal file
View file

@ -0,0 +1,5 @@
{config, ...}: {
environment.variables = {
EDITOR = config.defaults.editor;
};
}

3
modules/base/ld.nix Normal file
View file

@ -0,0 +1,3 @@
_: {
programs.nix-ld.enable = true;
}

View file

@ -0,0 +1,22 @@
{
config,
hostname,
...
}: {
age.secrets.hosts-private = {
# Contains IP addresses that i have no business sharing
rekeyFile = ../../secrets/hosts-private.age;
};
# Inject the private hosts file, because setting networking.hostFiles doesn't work
system.activationScripts.hosts-private = ''
cat /etc/hosts > /etc/hosts.bak
rm /etc/hosts
cat /etc/hosts.bak "${config.age.secrets.hosts-private.path}" >> /etc/hosts
rm /etc/hosts.bak
'';
networking = {
hostName = hostname;
};
}

3
modules/base/nix.nix Normal file
View file

@ -0,0 +1,3 @@
_: {
nix.settings.experimental-features = "nix-command flakes";
}

20
modules/base/secrets.nix Normal file
View file

@ -0,0 +1,20 @@
{hostname, ...}: let
hostSecrets = ../../secrets/hosts + ("/" + hostname);
publicKey = builtins.readFile (hostSecrets + "/id_ed25519.pub");
in {
age.identityPaths = ["/etc/ssh/ssh_host_ed25519_key"];
age.rekey = {
storageMode = "local";
hostPubkey = publicKey;
masterIdentities = [
{
identity = ../../secrets/yubikey-identity.pub;
}
];
extraEncryptionPubkeys = [
"age1pzrfw28f8qvsk9g8p2stundf4ph466jut0g6q47sse76zljtqy9q2w32zr" # Backup key (bitwarden)
];
localStorageDir = hostSecrets + /.rekey;
generatedSecretsDir = hostSecrets;
};
}

View file

@ -0,0 +1,8 @@
{username, ...}: {
security.sudo.wheelNeedsPassword = false;
nix.settings.trusted-users = [
"root"
"@wheel"
username
];
}

56
modules/base/ssh.nix Normal file
View file

@ -0,0 +1,56 @@
{
config,
username,
hostname,
lib,
...
}: let
# Load all public keys from ../../secrets/hosts/*/id_ed25519.pub
allHosts = lib.attrNames (lib.filterAttrs (_: type: type == "directory") (builtins.readDir ../../secrets/hosts));
publicKeys = map (x: builtins.readFile (../../secrets/hosts + ("/" + x) + "/id_ed25519.pub")) allHosts;
in {
age.secrets.ssh-key = {
rekeyFile = ../../secrets/hosts + ("/" + hostname) + /id_ed25519.age;
path = "/home/" + username + "/.ssh/id_ed25519";
mode = "0600";
owner = username;
};
age.secrets.ssh-key-ao = {
rekeyFile = ../../secrets/ssh-key-ao.age;
mode = "0600";
owner = username;
};
fireproof.home-manager = {
home.file.".ssh/id_ed25519.pub".source = ../../secrets/hosts + ("/" + hostname) + "/id_ed25519.pub";
programs.ssh = {
enable = true;
forwardAgent = true;
matchBlocks = {
"*" = {
identityFile = "${config.age.secrets.ssh-key.path}";
};
# Work hostnames definded in ./networking.nix
"*.ao" = {
user = "nij";
identityFile = "${config.age.secrets.ssh-key-ao.path}";
};
"dev.ao".proxyJump = "bastion.ao";
"scw.ao".proxyJump = "bastion.ao";
"clickhouse.ao".user = "ubuntu";
"flex.ao" = {
hostname = "192.168.2.5";
proxyJump = "bastion.ao";
};
};
};
};
programs.ssh.startAgent = true;
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
settings.KbdInteractiveAuthentication = false;
};
users.users.${username}.openssh.authorizedKeys.keys = publicKeys;
}

3
modules/base/time.nix Normal file
View file

@ -0,0 +1,3 @@
_: {
time.timeZone = "Europe/Copenhagen";
}

36
modules/base/user.nix Normal file
View file

@ -0,0 +1,36 @@
{
lib,
options,
username,
config,
...
}:
with lib; let
inherit (config.age) secrets;
in {
options.fireproof = {
home-manager = lib.mkOption {
type = options.home-manager.users.type.functor.wrapped;
};
};
config = {
age.secrets.hashed-user-password.rekeyFile = ../../secrets/hashed-user-password.age;
users.users.${username} = {
isNormalUser = true;
extraGroups = ["wheel"];
# initialPassword = "password";
hashedPasswordFile = secrets.hashed-user-password.path;
};
home-manager = {
useUserPackages = true;
useGlobalPkgs = true;
};
home-manager.users.${username} = mkAliasDefinitions options.fireproof.home-manager;
# set the same version of home-manager as the system
fireproof.home-manager.home.stateVersion = "24.11";
system.stateVersion = "24.11";
};
}

View file

@ -0,0 +1,11 @@
import { App } from "astal/gtk4";
import main from "./src/main";
import css from "./src/main.scss";
App.start({
css,
icons: "./icons",
main: () => {
main();
},
});

View file

@ -0,0 +1,37 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": ["@girs/**"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"all": false
},
"correctness": {
"useJsxKeyInIterable": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}

View file

@ -0,0 +1,80 @@
{
inputs,
pkgs,
lib,
config,
...
}: let
primaryMonitorName =
if builtins.length config.monitors > 0
then (builtins.elemAt config.monitors 0).name
else "";
packageName = "fireproof-shell";
cfg = config.modules.astral;
package = inputs.ags.lib.bundle {
inherit pkgs;
src = ./.;
name = packageName;
gtk4 = true;
entry = "app.ts";
extraPackages = with inputs.ags.packages.${pkgs.system}; [
battery
bluetooth
hyprland
network
tray
notifd
mpris
wireplumber
];
};
in {
options = {
modules.astral.primaryMonitor = lib.mkOption {
type = lib.types.str;
default = primaryMonitorName;
example = "DP-1";
};
modules.astral.notificationIgnores = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = ["/^Spotify/"];
example = ["/^Spotify/"];
};
modules.astral.trayIgnore = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = ["/spotify/"];
example = ["/spotify/"];
};
};
config = {
environment.systemPackages = [package inputs.ags.packages.${pkgs.system}.agsFull];
fireproof.home-manager = {
systemd.user.services.astal = {
Unit = {
Description = "Astal";
Documentation = "https://github.com/Aylur/astal";
After = ["graphical-session.target"];
};
Service = {
ExecStart = "${package}/bin/${packageName}";
Restart = "on-failure";
KillMode = "mixed";
Slice = "app-graphical.slice";
Environment = [
"ASTRAL_PRIMARY_MONITOR=${cfg.primaryMonitor}"
"ASTRAL_NOTIFICATION_IGNORE=${lib.concatStringsSep "," cfg.notificationIgnores}"
"ASTRAL_TRAY_IGNORE=${lib.concatStringsSep "," cfg.trayIgnore}"
];
};
Install = {
WantedBy = ["graphical-session.target"];
};
};
};
};
}

21
modules/desktop/astal/env.d.ts vendored Normal file
View file

@ -0,0 +1,21 @@
declare const SRC: string;
declare module "inline:*" {
const content: string;
export default content;
}
declare module "*.scss" {
const content: string;
export default content;
}
declare module "*.blp" {
const content: string;
export default content;
}
declare module "*.css" {
const content: string;
export default content;
}

View file

@ -0,0 +1,5 @@
Icons from https://glyphs.fyi with manually set stroke-width @ ~10 and color @ #000000, common iconnames might need to be renamed to avoid conflicts.
```
```

View file

@ -0,0 +1,6 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.3806 39.2231C24.7855 30.9472 31.6236 24.3617 40 24.3617H71.3205C65.579 12.8849 53.715 5.00386 40.0099 5H39.9898C33.8575 5.00173 28.0937 6.58059 23.0821 9.35299L21.9742 9.99265C18.2855 12.2133 15.0397 15.0948 12.4008 18.4734L24.3806 39.2231Z" fill="#ffffff" />
<path d="M48.4853 26.8617C52.7897 29.6475 55.6383 34.4911 55.6383 40C55.6383 42.7418 54.9326 45.3188 53.6931 47.5594L37.8866 74.9373L37.8502 75H40C59.3299 75 75 59.33 75 40C75 35.3534 74.0946 30.9184 72.4505 26.8617H48.4853Z" fill="#ffffff" />
<path d="M47.1343 53.9199C44.9952 55.0184 42.57 55.6383 40 55.6383C34.3092 55.6383 29.3283 52.5985 26.5925 48.054L10.7987 20.6985C7.13411 26.2315 5 32.8666 5 40C5 52.7369 11.8035 63.8846 21.9746 70.0075C22.3394 70.2272 22.7085 70.4403 23.0819 70.6469C26.7526 72.6775 30.8267 74.0678 35.1536 74.6672L35.7236 73.6838L47.1343 53.9199Z" fill="#ffffff" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 52.6596C46.9917 52.6596 52.6596 46.9917 52.6596 40C52.6596 33.0083 46.9917 27.3404 40 27.3404C33.0084 27.3404 27.3405 33.0083 27.3405 40C27.3405 46.9917 33.0084 52.6596 40 52.6596Z" fill="#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,3 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
<path d="M68 40C68 55.464 55.464 68 40 68C24.536 68 12 55.464 12 40C12 24.536 24.536 12 40 12C55.464 12 68 24.536 68 40Z" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="10" />
</svg>

After

Width:  |  Height:  |  Size: 316 B

View file

@ -0,0 +1,3 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M68 40C68 55.464 55.464 68 40 68C24.536 68 12 55.464 12 40C12 24.536 24.536 12 40 12C55.464 12 68 24.536 68 40Z" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="10" />
</svg>

After

Width:  |  Height:  |  Size: 311 B

View file

@ -0,0 +1,4 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M67.9528 18.6014C67.0154 17.3863 65.9979 16.2339 64.9066 15.152C64.0401 14.2448 63.1115 13.3979 62.1273 12.6173C62.6936 13.1056 63.2336 13.6232 63.7448 14.1679C65.6598 16.1914 67.1159 18.5967 68.0158 21.2227C69.8433 26.7052 69.7243 33.5669 66.2345 38.956C64.297 41.9757 61.591 44.4374 58.3886 46.094C55.1861 47.7507 51.5999 48.5439 47.9898 48.3938C47.6741 48.3938 47.3557 48.3938 47.0355 48.3704C33.8997 47.5908 30.5886 34.2503 37.4228 28.4864C35.5794 28.0939 32.1168 28.8632 29.7059 32.4321C27.5424 35.6372 27.6648 40.5835 28.9999 44.0928C28.1468 42.3621 27.5676 40.5119 27.2825 38.6069C25.5531 27.0423 33.3959 17.1799 40.5879 14.7379C36.7081 11.382 26.986 11.6099 19.7502 16.881C15.4945 20.0311 12.3956 24.4725 10.9258 29.5285C11.3942 25.4257 12.6279 21.4448 14.5642 17.788C12.1418 19.0294 9.05712 22.9553 7.5349 26.5917C5.31948 32.1395 4.53824 38.1471 5.26209 44.0694C5.31458 44.5181 5.36182 44.966 5.42131 45.4113C6.32843 50.5834 8.4087 55.4843 11.5055 59.7451C14.6023 64.006 18.635 67.5155 23.2998 70.0098C27.9646 72.504 33.1402 73.9178 38.4368 74.1448C43.7333 74.3718 49.0129 73.406 53.8779 71.3201C58.7429 69.2343 63.0667 66.0828 66.5236 62.1027C69.9806 58.1229 72.4808 53.4181 73.8356 48.343C75.1907 43.2678 75.3652 37.9544 74.3462 32.8027C73.3272 27.6511 71.1412 22.7955 67.9528 18.6014Z" fill="#ffffff" />
<path d="M68.0158 21.2227C67.1159 18.5967 65.6598 16.1914 63.7448 14.1679C61.469 11.8197 58.7709 9.91221 55.7891 8.54328C53.2996 7.32819 50.6746 6.40681 47.968 5.79811C43.197 4.764 38.2598 4.73448 33.4764 5.71148C28.5047 6.75098 24.1332 8.88111 21.3679 11.544C23.5729 10.3105 25.9362 9.37805 28.3936 8.77197C33.1688 7.58374 38.1692 7.60361 42.9347 8.82973C47.7002 10.0559 52.0777 12.4489 55.6649 15.7887C57.1188 17.1539 58.3913 18.6966 59.452 20.3797C63.7344 27.2743 63.3293 35.9421 59.99 41.0539C57.5099 44.8516 52.1979 48.4171 47.2419 48.3756C50.9719 48.6542 54.7084 47.9256 58.0532 46.2672C61.398 44.6088 64.2256 42.0829 66.2346 38.9585C69.7243 33.5668 69.8433 26.7052 68.0158 21.2227Z" fill="#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,3 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.4634 36.467C4.5122 38.4182 4.5122 41.5818 6.4634 43.533L36.467 73.5366C38.4182 75.4878 41.5818 75.4878 43.533 73.5366L73.5366 43.533C75.4878 41.5818 75.4878 38.4182 73.5366 36.467L43.533 6.4634C41.5818 4.5122 38.4182 4.5122 36.467 6.4634L30.3248 12.6056L38.4248 20.7058C38.9224 20.5515 39.4513 20.4685 39.9996 20.4685C42.9336 20.4685 45.3121 22.8469 45.3121 25.781C45.3121 26.3293 45.229 26.8582 45.0748 27.3558L53.131 35.412C53.6459 35.2458 54.195 35.156 54.7652 35.156C57.6992 35.156 60.0777 37.5344 60.0777 40.4685C60.0777 43.4025 57.6992 45.781 54.7652 45.781C51.8312 45.781 49.4527 43.4025 49.4527 40.4685C49.4527 39.6884 49.6208 38.9477 49.9228 38.2805L42.4215 30.7791V50.192C44.1377 51.0726 45.3121 52.8599 45.3121 54.9216C45.3121 57.8556 42.9336 60.2341 39.9996 60.2341C37.0656 60.2341 34.6871 57.8556 34.6871 54.9216C34.6871 52.6476 36.1158 50.7073 38.1246 49.9494V30.7531C36.1158 29.9952 34.6871 28.0549 34.6871 25.781C34.6871 24.98 34.8643 24.2205 35.1817 23.5394L27.2865 15.644L6.4634 36.467Z" fill="#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,4 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M50 70.5C51.3807 70.5 52.5 69.3807 52.5 68C52.5 66.6193 51.3807 65.5 50 65.5V70.5ZM30 65.5C28.6193 65.5 27.5 66.6193 27.5 68C27.5 69.3807 28.6193 70.5 30 70.5V65.5ZM42.5 55C42.5 53.6193 41.3807 52.5 40 52.5C38.6193 52.5 37.5 53.6193 37.5 55H42.5ZM37.5 68C37.5 69.3807 38.6193 70.5 40 70.5C41.3807 70.5 42.5 69.3807 42.5 68H37.5ZM58.5 31C58.5 29.6193 57.3807 28.5 56 28.5C54.6193 28.5 53.5 29.6193 53.5 31L58.5 31ZM26.5 31C26.5 29.6193 25.3807 28.5 24 28.5C22.6193 28.5 21.5 29.6193 21.5 31H26.5ZM50 65.5H30V70.5H50V65.5ZM37.5 55L37.5 68H42.5L42.5 55H37.5ZM53.5 31V39H58.5V31L53.5 31ZM26.5 39V31H21.5V39H26.5ZM21.5 39C21.5 49.2173 29.7827 57.5 40 57.5V52.5C32.5442 52.5 26.5 46.4558 26.5 39H21.5ZM53.5 39C53.5 46.4558 47.4558 52.5 40 52.5V57.5C50.2173 57.5 58.5 49.2173 58.5 39H53.5Z" fill="#C2CCDE" />
<path d="M31 21C31 16.0294 35.0294 12 40 12C44.9706 12 49 16.0294 49 21V39C49 43.9706 44.9706 48 40 48C35.0294 48 31 43.9706 31 39L31 21Z" fill="#C2CCDE" stroke="#C2CCDE" stroke-linecap="round" stroke-linejoin="round" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,4 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M61.7637 40L17.7637 40" stroke="#ffffff" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" />
<path d="M39.7637 62L39.7637 18" stroke="#ffffff" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" />
</svg>

After

Width:  |  Height:  |  Size: 340 B

View file

@ -0,0 +1,4 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.541 5C27.0483 5 22.5956 9.31133 22.5956 14.6296V21.2963H39.8087V23.1481H14.9453C9.45268 23.1481 5 27.4595 5 32.7778L5 47.2222C5 52.5405 9.45269 56.8518 14.9454 56.8518H20.6831V48.7037C20.6831 43.3854 25.1357 39.0741 30.6284 39.0741H48.989C53.6367 39.0741 57.4043 35.426 57.4043 30.9259V14.6296C57.4043 9.31133 52.9517 5 47.459 5H32.541ZM30.2459 16.8518C32.1472 16.8518 33.6885 15.3595 33.6885 13.5185C33.6885 11.6776 32.1472 10.1852 30.2459 10.1852C28.3446 10.1852 26.8033 11.6776 26.8033 13.5185C26.8033 15.3595 28.3446 16.8518 30.2459 16.8518Z" fill="#ffffff" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.4589 75C52.9516 75 57.4043 70.6887 57.4043 65.3704V58.7037H40.1912V56.8519L65.0545 56.8519C70.5472 56.8519 74.9999 52.5406 74.9999 47.2223V32.7778C74.9999 27.4595 70.5472 23.1482 65.0545 23.1482H59.3168V31.2964C59.3168 36.6147 54.8641 40.926 49.3715 40.926L31.0108 40.926C26.3632 40.926 22.5955 44.574 22.5955 49.0741L22.5955 65.3704C22.5955 70.6887 27.0482 75 32.5409 75H47.4589ZM49.754 63.1482C47.8527 63.1482 46.3114 64.6406 46.3114 66.4815C46.3114 68.3225 47.8527 69.8148 49.754 69.8148C51.6553 69.8148 53.1966 68.3225 53.1966 66.4815C53.1966 64.6406 51.6553 63.1482 49.754 63.1482Z" fill="#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,3 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M38.1441 12.6217C38.8159 10.9488 41.1841 10.9488 41.8559 12.6217L47.8081 27.4439C48.0942 28.1563 48.7628 28.642 49.5288 28.694L65.4648 29.7745C67.2635 29.8965 67.9953 32.1489 66.6118 33.3047L54.3544 43.5459C53.7653 44.0381 53.5099 44.8241 53.6972 45.5686L57.5941 61.0586C58.0339 62.8069 56.1179 64.199 54.5911 63.2404L41.0634 54.7476C40.4132 54.3394 39.5868 54.3394 38.9366 54.7476L25.4089 63.2404C23.8821 64.199 21.9661 62.8069 22.4059 61.0586L26.3028 45.5686C26.4901 44.8241 26.2347 44.0381 25.6456 43.5459L13.3882 33.3047C12.0047 32.1489 12.7365 29.8965 14.5352 29.7745L30.4712 28.694C31.2372 28.642 31.9058 28.1563 32.1919 27.4439L38.1441 12.6217Z" fill="#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 784 B

View file

@ -0,0 +1,4 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 24L32.1592 39.2526C32.6067 39.6504 32.6067 40.3496 32.1592 40.7474L15 56" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7" />
<path d="M41 56H65" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" />
</svg>

After

Width:  |  Height:  |  Size: 382 B

View file

@ -0,0 +1,5 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M57.5045 7.79198C57.5045 5.59458 55.0442 5.59455 53.9507 5.86923C55.4815 4.66065 57.2767 4.95367 57.9145 5.3199L72.6979 12.5687C74.1065 13.2594 75.0001 14.6965 75.0001 16.2714V63.9578C75.0001 65.5538 74.0827 67.0062 72.6455 67.6858L58.7346 74.2634C57.7778 74.6754 55.7002 75.6917 53.9507 74.2634C56.1376 74.6754 57.3222 73.1189 57.5045 72.066V7.79198Z" fill="#ffffff" />
<path d="M54.128 5.82976C55.2886 5.60042 57.5045 5.70662 57.5045 7.79198L57.5044 24.2063L12.684 58.1133C11.9005 58.7059 10.798 58.6076 10.1307 57.8856L5.51133 52.8871C4.78766 52.104 4.83829 50.8783 5.62406 50.1582L53.9507 5.86923L54.128 5.82976Z" fill="#ffffff" />
<path d="M57.5044 55.927L12.684 22.02C11.9005 21.4274 10.798 21.5257 10.1307 22.2477L5.51133 27.2463C4.78766 28.0293 4.83829 29.255 5.62406 29.9752L53.9507 74.2634C56.1376 74.6754 57.3222 73.1189 57.5045 72.066L57.5044 55.927Z" fill="#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 999 B

1
modules/desktop/astal/node_modules/.bin/biome generated vendored Symbolic link
View file

@ -0,0 +1 @@
../@biomejs/biome/bin/biome

60
modules/desktop/astal/node_modules/.package-lock.json generated vendored Normal file
View file

@ -0,0 +1,60 @@
{
"name": "astal-shell",
"lockfileVersion": 3,
"requires": true,
"packages": {
"../../../../usr/share/astal/gjs": {
"name": "astal",
"license": "LGPL-2.1"
},
"node_modules/@biomejs/biome": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
"dev": true,
"hasInstallScript": true,
"license": "MIT OR Apache-2.0",
"bin": {
"biome": "bin/biome"
},
"engines": {
"node": ">=14.21.3"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/biome"
},
"optionalDependencies": {
"@biomejs/cli-darwin-arm64": "1.9.4",
"@biomejs/cli-darwin-x64": "1.9.4",
"@biomejs/cli-linux-arm64": "1.9.4",
"@biomejs/cli-linux-arm64-musl": "1.9.4",
"@biomejs/cli-linux-x64": "1.9.4",
"@biomejs/cli-linux-x64-musl": "1.9.4",
"@biomejs/cli-win32-arm64": "1.9.4",
"@biomejs/cli-win32-x64": "1.9.4"
}
},
"node_modules/@biomejs/cli-linux-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/astal": {
"resolved": "../../../../usr/share/astal/gjs",
"link": true
}
}
}

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2023 Biome Developers and Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Biome Developers and Contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,207 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-dark-transparent.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg">
<img alt="Shows the banner of Biome, with its logo and the phrase 'Biome - Toolchain of the web'." src="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg" width="700">
</picture>
</p>
<div align="center">
[![Discord chat][discord-badge]][discord-url]
[![CI on main][ci-badge]][ci-url]
[![npm version][npm-badge]][npm-url]
[![VSCode version][vscode-badge]][vscode-url]
[![Open VSX version][open-vsx-badge]][open-vsx-url]
[discord-badge]: https://badgen.net/discord/online-members/BypW39g6Yc?icon=discord&label=discord&color=green
[discord-url]: https://biomejs.dev/chat
[ci-badge]: https://github.com/biomejs/biome/actions/workflows/main.yml/badge.svg
[ci-url]: https://github.com/biomejs/biome/actions/workflows/main.yml
[npm-badge]: https://badgen.net/npm/v/@biomejs/biome?icon=npm&color=green&label=%40biomejs%2Fbiome
[npm-url]: https://www.npmjs.com/package/@biomejs/biome/v/latest
[vscode-badge]: https://badgen.net/vs-marketplace/v/biomejs.biome?label=vscode&icon=visualstudio&color=green
[vscode-url]: https://marketplace.visualstudio.com/items?itemName=biomejs.biome
[open-vsx-badge]: https://badgen.net/open-vsx/version/biomejs/biome?label=open-vsx&color=green
[open-vsx-url]: https://open-vsx.org/extension/biomejs/biome
</div>
<!-- Insert new entries lexicographically by language code.
For example given below is the same order as these files appear on page:
https://github.com/biomejs/biome/tree/main/packages/%40biomejs/biome -->
<div align="center">
हिन्दी | [English](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.md) | [繁體中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-TW.md) | [简体中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-CN.md) | [日本語](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.ja.md) | [Português do Brasil](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.pt-br.md) | [한글](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.kr.md)
</div>
**Biome** वेब[^1] परियोजना[^2]ओं के लिए एक प्रदर्शनकारी उपकरण-श्रृंखला[^3] है, इसका उद्देश्य उक्त परियोजना[^2]ओं के स्वास्थ्य को बनाए रखने के लिए डेवलपर[^4] उपकरण प्रदान करना है।
**Biome** *JavaScript*, *TypeScript*, *JSX* और *JSON* के लिए **एक [तेज़ स्वरूपक](./benchmark#formatting)[^5]** है जो **[*Prettier* के साथ ९७% अनुकूलता](https://console.algora.io/challenges/prettier)[^6]** स्कोर[^7] करता है।
**Biome *JavaScript*, *TypeScript* और *JSX* के लिए एक [प्रदर्शनकारी लिंटर](https://github.com/biomejs/biome/tree/main/benchmark#linting)[^8]** है जिसमें ESLint, typescript-eslint और [अन्य स्रोतों](https://github.com/biomejs/biome/discussions/3) से **[२७० से अधिक नियम](https://biomejs.dev/linter/rules/)** शामिल हैं। यह **विस्तृत[^9] और संदर्भिकृत[^10] निदान[^11]** आउटपुट[^12] करता है जो आपको अपना कोड[^13] बेहतर बनाने और एक बेहतर प्रोग्रामर[^14] बनने में मदद करता है!
**Biome** को शुरू से ही [संपादक](https://biomejs.dev/guides/integrate-in-editor/)[^15] [के भीतर अंतरक्रियात्मक](https://biomejs.dev/guides/integrate-in-editor/)[^16] [रूप से](https://biomejs.dev/guides/integrate-in-editor/) उपयोग करने के लिए डिज़ाइन[^17] किया गया है। यह आपके द्वारा लिखे जा रहे विकृत[^18] कोड[^13] को स्वरूप[^5] और लिंट[^8] कर सकता है।
### स्थापना[^19]
```shell
npm install --save-dev --save-exact @biomejs/biome
```
### प्रयोग[^20]
* फ़ाइलें[^21] स्वरूप[^5] करें
```shell
npx @biomejs/biome format --write ./src
```
* फ़ाइलें[^21] लिंट[^8] करें
```shell
npx @biomejs/biome lint ./src
```
* स्वरूप, लिंट आदि चलाएँ और सुरक्षित सुझाव लागू करें
```shell
npx @biomejs/biome check --write ./src
```
* CI वातावरण में सभी फ़ाइलों को स्वरूप, लिंट आदि के विरुद्ध जाँचें
```shell
npx @biomejs/biome ci ./src
```
यदि आप Biome को स्थापित[^19] किए बिना चलाना चाहते हैं, तो WebAssembly में संकलित[^22] [ऑनलाइन](https://biomejs.dev/playground/)[^23] [प्रयोगशाला](https://biomejs.dev/playground/)[^24] का उपयोग करें।
## दस्तावेज़ीकरण[^25]
Biome के बारे में अधिक जानने के लिए हमारे [मुखपृष्ठ][biomejs] पर जाएँ, या Biome का उपयोग शुरू करने के लिए सीधे [आरंभ करने की मार्गदर्शिका][आरंभ-करें][^26] पर जाएँ।
## Biome के बारे में और
**Biome** में उचित पूर्व-निर्धारन[^27] हैं और इसके लिए कॉन्फ़िगरेशन[^28] की आवश्यकता नहीं है।
**Biome** का लक्ष्य आधुनिक वेब[^1] विकास की [सभी मुख्य भाषाओं][भाषा-समर्थन] का समर्थन करना है।
**Biome** को कार्य करने के लिए [Node.js की आवश्यकता नहीं है।](https://biomejs.dev/guides/manual-installation/)
**Biome** में प्रथम-श्रेणी का LSP समर्थन है, जिसमें एक परिष्कृत[^29] पार्सर[^30] है जो स्रोत पाठ[^31] को पूर्ण निष्ठा और शीर्ष-स्तरीय त्रुटि[^32] पुनर्प्राप्ति[^33] में प्रस्तुत करता है।
**Biome** उन कार्यक्षमता[^34]ओं को एकीकृत करता है जो पहले अलग-अलग उपकरण थे। साझा आधार पर निर्माण करने से हमें कोड[^13] प्रोसेसिंग, त्रुटि[^32]यों को प्रदर्शित करने, समानांतर कार्य, कैशिंग[^35] और कॉन्फ़िगरेशन[^28] के लिए एक सुसंगत अनुभव प्रदान करने की अनुमति मिलती है।
हमारे [परियोजना दर्शनशास्र][biome-दर्शनशास्र] के बारे में और पढ़ें।
**Biome** [MIT लाइसेंस प्राप्त](https://github.com/biomejs/biome/tree/main/LICENSE-MIT) या [Apache 2.0 लाइसेंस प्राप्त](https://github.com/biomejs/biome/tree/main/LICENSE-APACHE) है और [योगदानकर्ता अनुबंध आचार संहिता](https://github.com/biomejs/biome/tree/main/CODE_OF_CONDUCT.md) के तहत संचालित है।
## वित्तपोषण[^36]
आप परियोजना[^2] को विभिन्न तरीकों से वित्तपोषित[^36] कर सकते हैं।
### परियोजना[^2] प्रायोजन[^37] और वित्तपोषण[^36]
आप [Open Collective](https://opencollective.com/biome) या [GitHub Sponsors](https://github.com/sponsors/biomejs) के माध्यम से परियोजना[^2] को प्रायोजित[^37] या वित्तपोषित[^36] कर सकते हैं।
Biome एक सरल प्रायोजन[^37] कार्यक्रम प्रदान करता है जो कंपनियों को विभिन्न डेवलपरों[^4] के बीच दृश्यता[^38] और मान्यता प्राप्त करने की अनुमति देता है।
### वित्तपोषण[^36] जारी करना
हम [Polar.sh](https://polar.sh/biomejs) का उपयोग उन विशिष्ट[^39] सुविधाओं के पक्ष में वोट[^40] करने और बढ़ावा देने के लिए करते हैं जिन्हें आप देखना और लागू करना चाहते हैं। हमारे बकाया कार्य[^41] की जाँच करें और हमारी मदद करें:
[![वित्तपोषण जारी करें](https://polar.sh/embed/fund-our-backlog.svg?org=biomejs)](https://polar.sh/biomejs/)
## प्रायोजक[^37]
### स्वर्ण प्रायोजक[^42]
### रजत प्रायोजक[^43]
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://l2beat.com/" target="_blank"><img src="https://images.opencollective.com/l2beat/c2b2a27/logo/256.png" height="100"></a>
</td>
</tr>
</tbody>
</table>
### कांस्य प्रायोजक[^44]
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.kanamekey.com" target="_blank"><img src="https://images.opencollective.com/kaname/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://nanabit.dev/" target="_blank"><img src="https://images.opencollective.com/nanabit/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://vital.io/" target="_blank"><img src="https://avatars.githubusercontent.com/u/25357309?s=200" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/" target="_blank"><img src="https://avatars.githubusercontent.com/u/132028505?s=200&v=4" width="80"></a>
</td>
</tr>
</tbody>
</table>
[biomejs]: https://biomejs.dev/
[biome-दर्शनशास्र]: https://biomejs.dev/internals/philosophy/
[भाषा-समर्थन]: https://biomejs.dev/internals/language-support/
[आरंभ-करें]: https://biomejs.dev/guides/getting-started/
## शब्द सूची
नीचे दिए गए तिरछे शब्द आगत शब्द हैं।
[^1]: *वेब* - web: the internet
[^2]: परियोजना - project
[^3]: उपकरण-श्रृंखला - toolchain
[^4]: *डेव/डेवलपर* - dev/developer
[^5]: स्वरूप/स्वरूपक - format/foramtter
[^6]: अनुकूल/अनुकूलता - compatible/compatibility
[^7]: *स्कोर* - score
[^8]: *लिंट/लिंटर* - lint/linter
[^9]: विस्तार/विस्तृत - detail/detailed
[^10]: संदर्भ/संदर्भिकृत - context/contextualized
[^11]: निदान - diagnosis
[^12]: *आउटपुट* - output
[^13]: *कोड* - code
[^14]: *प्रोग्रामर* - programmer
[^15]: संपादक - editor, the text editor: vscode, zed, etc.
[^16]: अंतरक्रिया/अंतरक्रियात्मक - interact/interactive
[^17]: *डिज़ाइन* - design
[^18]: विकृत - malformed
[^19]: स्थापित_करना/स्थापना - install/installation
[^20]: प्रयोग - usage
[^21]: *फ़ाइल* - file
[^22]: संकलित_करना/संकलित/संकलनकर्ता - compile/compiled/compiler
[^23]: *ऑनलाइन* - online
[^24]: प्रयोगशाला - laboratory
[^25]: दस्तावेज़/दस्तावेज़ीकरण - document/documentation
[^26]: मार्गदर्शिका - guide
[^27]: पूर्व-निर्धारित - default
[^28]: *कॉन्फ़िग/कॉन्फ़िगर/कॉन्फ़िगरेशन* - config/configure/configuration
[^29]: परिष्कृत - sophisticated
[^30]: *पार्सर* - parser
[^31]: पाठ - text
[^32]: त्रुटि - error
[^33]: पुनर्प्राप्ति - recovery
[^34]: कार्यक्षमता - functionality
[^35]: *कैश/कैशिंग* - cache/caching, ~~cash/cashing~~
[^36]: वित्तपोषित_करना/वित्तपोषण - fund/funding
[^37]: प्रायोजित/प्रायोजन - sponsor/sponsorship
[^38]: दृश्यता - visibility
[^39]: विशिष्ट - specific
[^40]: *वोट* - vote
[^41]: बकाया_कार्य - backlog
[^42]: स्वर्ण_प्रायोजक - Gold Sponsor
[^43]: रजत_प्रायोजक - Silver Sponsor
[^44]: कांस्य_प्रायोजक - Bronze Sponsor

View file

@ -0,0 +1,115 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-dark-transparent.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg">
<img alt="Biome - Toolchain of the web" src="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg" width="700">
</picture>
</p>
<div align="center">
[![Discord chat][discord-badge]][discord-url]
[![CI on main][ci-badge]][ci-url]
[![npm version][npm-badge]][npm-url]
[![VSCode version][vscode-badge]][vscode-url]
[![Open VSX version][open-vsx-badge]][open-vsx-url]
[discord-badge]: https://badgen.net/discord/online-members/BypW39g6Yc?icon=discord&label=discord&color=green
[discord-url]: https://biomejs.dev/chat
[ci-badge]: https://github.com/biomejs/biome/actions/workflows/main.yml/badge.svg
[ci-url]: https://github.com/biomejs/biome/actions/workflows/main.yml
[npm-badge]: https://badgen.net/npm/v/@biomejs/biome?icon=npm&color=green&label=%40biomejs%2Fbiome
[npm-url]: https://www.npmjs.com/package/@biomejs/biome/v/latest
[vscode-badge]: https://badgen.net/vs-marketplace/v/biomejs.biome?label=vscode&icon=visualstudio&color=green
[vscode-url]: https://marketplace.visualstudio.com/items?itemName=biomejs.biome
[open-vsx-badge]: https://badgen.net/open-vsx/version/biomejs/biome?label=open-vsx&color=green
[open-vsx-url]: https://open-vsx.org/extension/biomejs/biome
</div>
<!-- Insert new entries lexicographically by language code.
For example given below is the same order as these files appear on page:
https://github.com/biomejs/biome/tree/main/packages/%40biomejs/biome -->
<div align="center">
[हिन्दी](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.hi.md) | [English](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.md) | [繁體中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-TW.md) | [简体中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-CN.md) | 日本語 | [Português do Brasil](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.pt-br.md) | [한글](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.kr.md)
</div>
**Biome** はWebプロジェクトのための高性能なツールチェーンであり、プロジェクトの健全性を維持するための開発者ツールの提供を目的としています。
**Biome は _JavaScript_, _TypeScript_, _JSX_ そして _JSON_ 向けの[高速なFormatter](./benchmark#formatting)**であり、**[_Prettier_ との互換性は97%](https://console.algora.io/challenges/prettier)** を達成しています。
**Biome は _JavaScript_, _TypeScript_, _JSX_ のための[高性能なLinter](https://github.com/biomejs/biome/tree/main/benchmark#linting)** であり、ESLint, typescript-eslint, [その他のソース](https://github.com/biomejs/biome/discussions/3)から **[270以上のルール](https://biomejs.dev/linter/rules/)**を提供しています。Biome は**詳細で文脈に沿った結果を出力**するため、コードを改善し、より良いプログラマになるための手助けをします!
**Biome** は最初から[**エディタ内で対話的に**](https://biomejs.dev/ja/guides/integrate-in-editor/)使用できるように設計されています。
あなたがコードを書いているときに、形の崩れたコードを format と lint することができます。
### インストール
```shell
npm install --save-dev --save-exact @biomejs/biome
```
### 使い方
```shell
# ファイルをformatする
npx @biomejs/biome format --write ./src
# ファイルをlintする
npx @biomejs/biome lint ./src
# format、lintなどを実行し、安全な提案を適用する
npx @biomejs/biome check --write ./src
# CI環境では、すべてのファイルを対象にformatやlintをチェックする
npx @biomejs/biome ci ./src
```
Biome をインストールせずに試したい場合は、WebAssembly にコンパイルされた[オンラインのプレイグラウンド](https://biomejs.dev/playground/)を利用してください。
## ドキュメント
Biome についてもっと知るために[ホームページ][biomejs]をチェックするか、Biome を使い始めるために[はじめる][getting-started]に進んでください。
## Biome をもっと詳しく
**Biome** は理にかなったデフォルト設定を持ち、設定を必要としません。
**Biome** はモダンなウェブ開発における[全ての主要な言語][language-support]をサポートすることを目指しています。
**Biome** は動作するために Node.js を必要としません。
**Biome** はソーステキストの完全な表現力とエラー回復能力を持つ洗練されたParserによって、優れたLSPサポートを提供します。
**Biome** は以前は別々のツールで提供されていた機能を統合します。共通基盤を構築することで、コードの処理、エラーの表示、並列処理、キャッシュ、設定について統一的な体験を提供します。
興味のある方は、[プロジェクトの理念][biome-philosophy]もご覧ください。
**Biome** は [MIT ライセンス](https://github.com/biomejs/biome/tree/main/LICENSE-MIT)または [Apache 2.0 ライセンス](https://github.com/biomejs/biome/tree/main/LICENSE-APACHE)であり、[コントリビューター行動規範](https://github.com/biomejs/biome/tree/main/CODE_OF_CONDUCT.md)の下で管理されています。
## スポンサー
### ゴールドスポンサー
### ブロンズスポンサー
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.kanamekey.com" target="_blank"><img src="https://images.opencollective.com/kaname/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://nanabit.dev/" target="_blank"><img src="https://images.opencollective.com/nanabit/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
</tr>
</tbody>
</table>
[biomejs]: https://biomejs.dev/ja/
[biome-philosophy]: https://biomejs.dev/ja/internals/philosophy/
[language-support]: https://biomejs.dev/ja/internals/language-support/
[getting-started]: https://biomejs.dev/ja/guides/getting-started/

View file

@ -0,0 +1,158 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-dark-transparent.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg">
<img alt="Biome - Toolchain of the web" src="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg" width="700">
</picture>
</p>
<div align="center">
[![Discord chat][discord-badge]][discord-url]
[![CI on main][ci-badge]][ci-url]
[![npm version][npm-badge]][npm-url]
[![VSCode version][vscode-badge]][vscode-url]
[![Open VSX version][open-vsx-badge]][open-vsx-url]
[discord-badge]: https://badgen.net/discord/online-members/BypW39g6Yc?icon=discord&label=discord&color=green
[discord-url]: https://biomejs.dev/chat
[ci-badge]: https://github.com/biomejs/biome/actions/workflows/main.yml/badge.svg
[ci-url]: https://github.com/biomejs/biome/actions/workflows/main.yml
[npm-badge]: https://badgen.net/npm/v/@biomejs/biome?icon=npm&color=green&label=%40biomejs%2Fbiome
[npm-url]: https://www.npmjs.com/package/@biomejs/biome/v/latest
[vscode-badge]: https://badgen.net/vs-marketplace/v/biomejs.biome?label=vscode&icon=visualstudio&color=green
[vscode-url]: https://marketplace.visualstudio.com/items?itemName=biomejs.biome
[open-vsx-badge]: https://badgen.net/open-vsx/version/biomejs/biome?label=open-vsx&color=green
[open-vsx-url]: https://open-vsx.org/extension/biomejs/biome
</div>
<!-- Insert new entries lexicographically by language code.
For example given below is the same order as these files appear on page:
https://github.com/biomejs/biome/tree/main/packages/%40biomejs/biome -->
<div align="center">
[हिन्दी](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.hi.md) | [English](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.md) | [繁體中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-TW.md) | [简体中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-CN.md) | [日本語](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.ja.md) | [Português do Brasil](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.pt-br.md) | 한글
</div>
**Biome** 은 웹 프로젝트를 위한 고성능 툴체인으로, 프로젝트를 건전하게 유지하기 위한 개발자 툴을 제공하는 것을 목표로 하고 있습니다.
**Biome** 은 _JavaScript_, _TypeScript_, _JSX_ 그리고 _JSON_ 을 위한 **[고속 Formatter](./benchmark#formatting)** 로, **[_Prettier_ 와의 호환성 97%](https://console.algora.io/challenges/prettier)** 을 달성하고 있습니다.
**Biome** 은 _JavaScript_, _TypeScript_, _JSX_을 위한 **[고성능 Linter](https://github.com/biomejs/biome/tree/main/benchmark#linting)** 로、ESLint, typescript-eslint, [등의 리소스](https://github.com/biomejs/biome/discussions/3)에서 **[270개 이상의 룰](https://biomejs.dev/linter/rules/)** 을 제공하고 있습니다. Biome 은 **상세하며 문맥에 맞는 결과를 출력**하기 위해, 코드를 개선하고, 더 좋은 프로그래머가 되기 위한 도움을 드립니다!
**Biome** 은 처음부터 [**에디터 내에서 인터렉티브하게**](https://biomejs.dev/ja/guides/integrate-in-editor/) 사용하도록 설계되어 있습니다.
여러분이 코드를 작성할 때, 형식이 잘못된 코드에 format, lint 를 적용합니다.
### 설치
```shell
npm install --save-dev --save-exact @biomejs/biome
```
### 사용법
```shell
# 파일의 format을 체크
npx @biomejs/biome format --write ./src
# 파일의 lint를 체크
npx @biomejs/biome lint ./src
# format、lint 등을 실행하고, Biome으로부터의 제안을 적용
npx @biomejs/biome check --write ./src
# CI 환경에서는 모든 파일을 대상으로 format과 lint를 체크
npx @biomejs/biome ci ./src
```
Biome 을 설치하지 않고 사용해보고 싶다면, WebAssembly 에 컴파일된 [온라인 플레이그라운드](https://biomejs.dev/playground/)을 이용해주세요.
## 문서
Biome 에 대해 알아보기 위해 [홈페이지][biomejs]를 체크하거나, Biome 을 사용하기 위해 [시작하기][getting-started]을 확인하세요!
## Biome 를 자세히 알아보기
**Biome** 은 이성적인 디폴트 세팅을 가지고 있어, 설정을 필요로 하지 않습니다.
**Biome** 은 모던한 웹개발에 대한 [모든 주요 언어][language-support]를 지원하는 것을 목표로 합니다.
**Biome** 이 동작하는 데에 Node.js 는 필요하지 않습니다.
**Biome** 은 소스 코드의 완전한 표현력과 에러 회피 능력을 가진, 세련된 Parser 에 의해 우수한 LSP 지원을 제공합니다.
**Biome** 은 지금까지 서로 다른 툴로 제공하던 기능들을 통합합니다. 공통된 기반을 구축하는 것으로 코드 처리, 에러 표시, 병렬 처리, 캐시, 설정에 대해 일관된 경험을 제공합니다.
관심이 있는 분은 [프로젝트 철학][biome-philosophy] 을 확인해주세요.
**Biome** 은 [MIT 라이센스](https://github.com/biomejs/biome/tree/main/LICENSE-MIT) 혹은 [Apache 2.0 라이센스](https://github.com/biomejs/biome/tree/main/LICENSE-APACHE)로, [기여자 행동 규범](https://github.com/biomejs/biome/tree/main/CODE_OF_CONDUCT.md)에 따라 관리되고 있습니다.
## 펀딩
다양한 방법으로 프로젝트를 지원할 수 있습니다.
### 프로젝트 스폰서와 펀딩
[Open collective](https://opencollective.com/biome) 혹은 [GitHub sponsors](https://github.com/sponsors/biomejs)를 통해 스폰서과 되거나 프로젝트에 지원을 할 수 있습니다.
Biome 은 간단하게 다양한 개발자들 사이에서의 인지도를 얻을 수 있는 스폰서쉽 프로그램을 제공합니다.
### 이슈 펀딩
우리는 투표와 여러분들이 원하는 신기능 추진을 위해 [Polar.sh](https://polar.sh/biomejs)을 사용하고 있습니다. 백로그를 체크하고 지원해주세요!
<a href="https://polar.sh/biomejs"><img src="https://polar.sh/embed/fund-our-backlog.svg?org=biomejs" /></a>
## 후원
### 골드 스폰서
### 실버 스폰서
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://l2beat.com/" target="_blank"><img src="https://images.opencollective.com/l2beat/c2b2a27/logo/256.png" height="100"></a>
</td>
<td align="center" valign="middle">
<a href="https://www.phoenixlabs.dev/" target="_blank"><img src="https://images.opencollective.com/phoenix-labs/2824ed4/logo/100.png?height=100" height="100"></a>
</td>
</tr>
</tbody>
</table>
### 브론즈 스폰서
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.kanamekey.com" target="_blank"><img src="https://images.opencollective.com/kaname/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://nanabit.dev/" target="_blank"><img src="https://images.opencollective.com/nanabit/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://vital.io/" target="_blank"><img src="https://avatars.githubusercontent.com/u/25357309?s=200" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/" target="_blank"><img src="https://avatars.githubusercontent.com/u/132028505?s=200&v=4" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://forge42.dev/" target="_blank"><img src="https://avatars.githubusercontent.com/u/161314831?s=200&v=4" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://transloadit.com/" target="_blank"><img src="https://avatars.githubusercontent.com/u/125754?s=200&v=4" width="80"></a>
</td>
</tr>
</tbody>
</table>
[biomejs]: https://biomejs.dev/ja/
[biome-philosophy]: https://biomejs.dev/ja/internals/philosophy/
[language-support]: https://biomejs.dev/ja/internals/language-support/
[getting-started]: https://biomejs.dev/ja/guides/getting-started/

View file

@ -0,0 +1,159 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-dark-transparent.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg">
<img alt="Shows the banner of Biome, with its logo and the phrase 'Biome - Toolchain of the web'." src="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg" width="700">
</picture>
</p>
<div align="center">
[![CI on main][ci-badge]][ci-url]
[![Discord chat][discord-badge]][discord-url]
[![npm version][npm-badge]][npm-url]
[![VSCode version][vscode-badge]][vscode-url]
[![Open VSX version][open-vsx-badge]][open-vsx-url]
[![Polar bounties][polar-badge]][polar-url]
[ci-badge]: https://github.com/biomejs/biome/actions/workflows/main.yml/badge.svg
[ci-url]: https://github.com/biomejs/biome/actions/workflows/main.yml
[discord-badge]: https://badgen.net/discord/online-members/BypW39g6Yc?icon=discord&label=discord&color=60a5fa
[discord-url]: https://biomejs.dev/chat
[npm-badge]: https://badgen.net/npm/v/@biomejs/biome?icon=npm&color=60a5fa&label=%40biomejs%2Fbiome
[npm-url]: https://www.npmjs.com/package/@biomejs/biome/v/latest
[vscode-badge]: https://badgen.net/vs-marketplace/v/biomejs.biome?label=vscode&icon=visualstudio&color=60a5fa
[vscode-url]: https://marketplace.visualstudio.com/items?itemName=biomejs.biome
[open-vsx-badge]: https://badgen.net/open-vsx/version/biomejs/biome?label=open-vsx&color=60a5fa
[open-vsx-url]: https://open-vsx.org/extension/biomejs/biome
[polar-badge]: https://polar.sh/embed/seeks-funding-shield.svg?org=biomejs
[polar-url]: https://polar.sh/biomejs
</div>
<!-- Insert new entries lexicographically by language code.
For example given below is the same order as these files appear on page:
https://github.com/biomejs/biome/tree/main/packages/%40biomejs/biome -->
<div align="center">
[हिन्दी](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.hi.md) | English | [繁體中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-TW.md) | [简体中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-CN.md) | [日本語](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.ja.md) | [Português do Brasil](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.pt-BR.md) | [한글](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.kr.md)
</div>
**Biome** is a performant toolchain for web projects, it aims to provide developer tools to maintain the health of said projects.
**Biome is a [fast formatter](./benchmark#formatting)** for _JavaScript_, _TypeScript_, _JSX_, _JSON_, _CSS_ and _GraphQL_ that scores **[97% compatibility with _Prettier_](https://console.algora.io/challenges/prettier)**.
**Biome is a [performant linter](https://github.com/biomejs/biome/tree/main/benchmark#linting)** for _JavaScript_, _TypeScript_, _JSX_, _CSS_ and _GraphQL_ that features **[more than 270 rules](https://biomejs.dev/linter/rules/)** from ESLint, typescript-eslint, and [other sources](https://github.com/biomejs/biome/discussions/3).
It **outputs detailed and contextualized diagnostics** that help you to improve your code and become a better programmer!
**Biome** is designed from the start to be used [interactively within an editor](https://biomejs.dev/guides/integrate-in-editor/).
It can format and lint malformed code as you are writing it.
### Installation
```shell
npm install --save-dev --save-exact @biomejs/biome
```
### Usage
```shell
# format files
npx @biomejs/biome format --write ./src
# lint files and apply the safe fixes
npx @biomejs/biome lint --write ./src
# run format, lint, etc. and apply the safe fixes
npx @biomejs/biome check --write ./src
# check all files against format, lint, etc. in CI environments
npx @biomejs/biome ci ./src
```
If you want to give Biome a run without installing it, use the [online playground](https://biomejs.dev/playground/), compiled to WebAssembly.
## Documentation
Check out our [homepage][biomejs] to learn more about Biome,
or directly head to the [Getting Started guide][getting-started] to start using Biome.
## More about Biome
**Biome** has sane defaults and it doesn't require configuration.
**Biome** aims to support [all main languages][language-support] of modern web development.
**Biome** [doesn't require Node.js](https://biomejs.dev/guides/manual-installation/) to function.
**Biome** has first-class LSP support, with a sophisticated parser that represents the source text in full fidelity and top-notch error recovery.
**Biome** unifies functionality that has previously been separate tools. Building upon a shared base allows us to provide a cohesive experience for processing code, displaying errors, parallelize work, caching, and configuration.
Read more about our [project philosophy][biome-philosophy].
**Biome** is [MIT licensed](https://github.com/biomejs/biome/tree/main/LICENSE-MIT) or [Apache 2.0 licensed](https://github.com/biomejs/biome/tree/main/LICENSE-APACHE) and moderated under the [Contributor Covenant Code of Conduct](https://github.com/biomejs/biome/tree/main/CODE_OF_CONDUCT.md).
## Funding
You can fund the project in different ways
### Project sponsorship and funding
You can sponsor or fund the project via [Open collective](https://opencollective.com/biome) or [GitHub sponsors](https://github.com/sponsors/biomejs)
Biome offers a simple sponsorship program that allows companies to get visibility and recognition among various developers.
### Issue funding
We use [Polar.sh](https://polar.sh/biomejs) to up-vote and promote specific features that you would like to see and implement. Check our backlog and help us:
## Sponsors
### Silver Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://l2beat.com/" target="_blank"><img src="https://images.opencollective.com/l2beat/c2b2a27/logo/256.png" height="100"></a>
</td>
<td align="center" valign="middle">
<a href="https://www.phoenixlabs.dev/" target="_blank"><img src="https://images.opencollective.com/phoenix-labs/2824ed4/logo/100.png?height=100" height="100"></a>
</td>
</tr>
</tbody>
</table>
### Bronze Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.kanamekey.com" target="_blank"><img src="https://images.opencollective.com/kaname/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://nanabit.dev/" target="_blank"><img src="https://images.opencollective.com/nanabit/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://vital.io/" target="_blank"><img src="https://avatars.githubusercontent.com/u/25357309?s=200" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/" target="_blank"><img src="https://avatars.githubusercontent.com/u/132028505?s=200&v=4" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://forge42.dev/" target="_blank"><img src="https://avatars.githubusercontent.com/u/161314831?s=200&v=4" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://transloadit.com/" target="_blank"><img src="https://avatars.githubusercontent.com/u/125754?s=200&v=4" width="80"></a>
</td>
</tr>
</tbody>
</table>
[biomejs]: https://biomejs.dev/
[biome-philosophy]: https://biomejs.dev/internals/philosophy/
[language-support]: https://biomejs.dev/internals/language-support/
[getting-started]: https://biomejs.dev/guides/getting-started/

View file

@ -0,0 +1,118 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-dark-transparent.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg">
<img alt="Biome - Conjunto de ferramentas da web" src="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg" width="700">
</picture>
</p>
<div align="center">
[![Chat no Discord][discord-badge]][discord-url]
[![CI na `main`][ci-badge]][ci-url]
[![versão npm][npm-badge]][npm-url]
[![versão VSCode][vscode-badge]][vscode-url]
[![versão Open VSX][open-vsx-badge]][open-vsx-url]
[discord-badge]: https://badgen.net/discord/online-members/BypW39g6Yc?icon=discord&label=discord&color=green
[discord-url]: https://biomejs.dev/chat
[ci-badge]: https://github.com/biomejs/biome/actions/workflows/main.yml/badge.svg
[ci-url]: https://github.com/biomejs/biome/actions/workflows/main.yml
[npm-badge]: https://badgen.net/npm/v/@biomejs/biome?icon=npm&color=green&label=%40biomejs%2Fbiome
[npm-url]: https://www.npmjs.com/package/@biomejs/biome/v/latest
[vscode-badge]: https://badgen.net/vs-marketplace/v/biomejs.biome?label=vscode&icon=visualstudio&color=green
[vscode-url]: https://marketplace.visualstudio.com/items?itemName=biomejs.biome
[open-vsx-badge]: https://badgen.net/open-vsx/version/biomejs/biome?label=open-vsx&color=green
[open-vsx-url]: https://open-vsx.org/extension/biomejs/biome
</div>
<!-- Insert new entries lexicographically by language code.
For example given below is the same order as these files appear on page:
https://github.com/biomejs/biome/tree/main/packages/%40biomejs/biome -->
<div align="center">
[हिन्दी](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.hi.md) | [English](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.md) | [繁體中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-TW.md) | [简体中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-CN.md) | [日本語](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.ja.md) | Português do Brasil | [한글](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.kr.md)
</div>
**Biome** é um conjunto de ferramentas de alto desempenho para projetos web, visando fornecer recursos de desenvolvimento para manter a saúde desses projetos.
**Biome é um [formatador rápido](./benchmark#formatting)** para _JavaScript_, _TypeScript_, _JSX_, e _JSON_ que atinge **[97% de compatibilidade com o _Prettier_](https://console.algora.io/challenges/prettier)**.
**Biome é um [linter eficiente](https://github.com/biomejs/biome/tree/main/benchmark#linting)** para _JavaScript_, _TypeScript_, e _JSX_ que possui **[mais de 270 regras](https://biomejs.dev/linter/rules/)** do ESLint, typescript-eslint, e de [outras fontes](https://github.com/biomejs/biome/discussions/3).
Ele **fornece diagnósticos detalhados e contextualizados** que ajudam você a melhorar seu código e se tornar um programador melhor!
**Biome** é projetado desde o início para ser usado [interativamente dentro de um editor](https://biomejs.dev/guides/integrate-in-editor/).
Isso permite formatar e lintar códigos malformados enquanto você programa.
### Instalação
```shell
npm install --save-dev --save-exact @biomejs/biome
```
### Uso
```shell
# formatar arquivos
npx @biomejs/biome format --write ./src
# lintar arquivos
npx @biomejs/biome lint ./src
# executar formatação, lint, etc. e aplicar as sugestões seguras
npx @biomejs/biome check --write ./src
# verificar todos os arquivos contra formatação, lint, etc. em ambientes CI
npx @biomejs/biome ci ./src
```
Se você quiser experimentar o Biome sem instalá-lo, use o [playground online](https://biomejs.dev/playground/), compilado para WebAssembly.
## Documentação
Confira nossa [página inicial][biomejs] para saber mais sobre o Biome,
ou vá ao [Guia de Introdução][getting-started] para começar a usar o Biome.
## Mais sobre o Biome
**Biome** tem padrões robustos e não requer configuração.
**Biome** visa suportar [todas as principais linguagens][language-support] do desenvolvimento web moderno.
**Biome** [não requer Node.js](https://biomejs.dev/guides/manual-installation/) para funcionar.
**Biome** tem suporte de primeira linha para LSP, com um
parser sofisticado que representa o texto-fonte em sua total fidelidade e recuperação de erro de ponta.
**Biome** unifica funcionalidades que anteriormente eram ferramentas separadas. Construindo sobre uma base compartilhada, podemos fornecer uma experiência coesa para processar código, exibir erros, paralelizar trabalho, cache e configuração.
Leia mais sobre nossa [filosofia de projeto][biome-philosophy].
**Biome** é licenciado sob [MIT](https://github.com/biomejs/biome/tree/main/LICENSE-MIT) ou [Apache 2.0](https://github.com/biomejs/biome/tree/main/LICENSE-APACHE) e moderado sob o [Código de Conduta do Contribuidor](https://github.com/biomejs/biome/tree/main/CODE_OF_CONDUCT.md).
## Patrocinadores
### Patrocinadores Ouro
### Patrocinadores Bronze
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.kanamekey.com" target="_blank"><img src="https://images.opencollective.com/kaname/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://nanabit.dev/" target="_blank"><img src="https://images.opencollective.com/nanabit/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
</tr>
</tbody>
</table>
[biomejs]: https://biomejs.dev/pt-br/
[biome-philosophy]: https://biomejs.dev/pt-br/internals/philosophy/
[language-support]: https://biomejs.dev/pt-br/internals/language-support/
[getting-started]: https://biomejs.dev/pt-br/guides/getting-started/

View file

@ -0,0 +1,110 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-dark-transparent.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg">
<img alt="Biome - Toolchain of the web" src="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg" width="700">
</picture>
</p>
<div align="center">
[![Discord chat][discord-badge]][discord-url]
[![CI on main][ci-badge]][ci-url]
[![npm version][npm-badge]][npm-url]
[![VSCode version][vscode-badge]][vscode-url]
[![Open VSX version][open-vsx-badge]][open-vsx-url]
[discord-badge]: https://badgen.net/discord/online-members/BypW39g6Yc?icon=discord&label=discord&color=green
[discord-url]: https://biomejs.dev/chat
[ci-badge]: https://github.com/biomejs/biome/actions/workflows/main.yml/badge.svg
[ci-url]: https://github.com/biomejs/biome/actions/workflows/main.yml
[npm-badge]: https://badgen.net/npm/v/@biomejs/biome?icon=npm&color=green&label=%40biomejs%2Fbiome
[npm-url]: https://www.npmjs.com/package/@biomejs/biome/v/latest
[vscode-badge]: https://badgen.net/vs-marketplace/v/biomejs.biome?label=vscode&icon=visualstudio&color=green
[vscode-url]: https://marketplace.visualstudio.com/items?itemName=biomejs.biome
[open-vsx-badge]: https://badgen.net/open-vsx/version/biomejs/biome?label=open-vsx&color=green
[open-vsx-url]: https://open-vsx.org/extension/biomejs/biome
</div>
<!-- Insert new entries lexicographically by language code.
For example given below is the same order as these files appear on page:
https://github.com/biomejs/biome/tree/main/packages/%40biomejs/biome -->
<div align="center">
[हिन्दी](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.hi.md) | [English](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.md) | [繁體中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-TW.md) | 简体中文 | [日本語](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.ja.md) | [Português do Brasil](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.pt-br.md) | [한글](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.kr.md)
</div>
**Biome** 是一个用于 Web 项目的高性能工具链,旨在为开发者提供维护项目的工具。
**Biome 是一个[快速的格式化工具](./benchmark#formatting)**,适用于 _JavaScript_、_TypeScript_、_JSX_、_JSON_ 等,与 _Prettier_ 的兼容性达到了 **[97%](https://console.algora.io/challenges/prettier)**。
**Biome 是一个[高性能的 Linter](https://github.com/biomejs/biome/tree/main/benchmark#linting)**,适用于 _JavaScript_、_TypeScript_、_JSX_ 等,包含了来自 ESLint、typescript-eslint 和[其他来源](https://github.com/biomejs/biome/discussions/3)的 **[270 余项规则](https://biomejs.dev/zh-cn/linter/rules/)**。它**输出详细且有上下文诊断信息**,能帮助你优化代码,成为一名更好的程序员!
**Biome** 从一开始就设计为[在编辑器中交互式使用](https://biomejs.dev/zh-cn/guides/integrate-in-editor/)。它可以在你编写代码时格式化并检查出不规范的代码。
### 安装
```shell
npm install --save-dev --save-exact @biomejs/biome
```
### 使用
```shell
# 格式化文件
npx @biomejs/biome format --write ./src
# Lint 文件
npx @biomejs/biome lint ./src
# 运行格式化Lint 等,并应用安全的建议
npx @biomejs/biome check --write ./src
# 在 CI 环境中检查所有文件是否符合格式Lint 等
npx @biomejs/biome ci ./src
```
如果你想在不安装的情况下试用 Biome可以使用[在线 playground](https://biomejs.dev/playground/),它被编译为 WebAssembly。
## 文档
查看我们的[主页][biomejs]以了解更多关于 Biome 的信息,或者直接前往[入门指南][getting-started]开始使用 Biome。
## 更多信息
**Biome** 有合理的默认设置,不需要配置。
**Biome** 旨在支持[所有主要的现代网络开发语言][language-support]。
**Biome** [不需要 Node.js](https://biomejs.dev/zh-cn/guides/manual-installation/) 就可以运行。
**Biome** 有一流的 LSP 支持,具有精密的解析器,可以完全保真地表示源文本,并具有顶级的错误恢复能力。
**Biome** 统一了以前分散的功能。基于共享的基础,我们可以提供一个处理代码、显示错误、并行工作、缓存和配置的一致体验。
阅读更多关于我们的[项目理念][biome-philosophy]。
**Biome** 采用 [MIT 许可](https://github.com/biomejs/biome/tree/main/LICENSE-MIT) 或 [Apache 2.0 许可](https://github.com/biomejs/biome/tree/main/LICENSE-APACHE),并在 [贡献者公约行为准则](https://github.com/biomejs/biome/tree/main/CODE_OF_CONDUCT.md) 下进行管理。
## 赞助商
### 金牌赞助商
### 铜牌赞助商
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.kanamekey.com" target="_blank"><img src="https://images.opencollective.com/kaname/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
</tr>
</tbody>
</table>
[biomejs]: https://biomejs.dev/zh-cn/
[biome-philosophy]: https://biomejs.dev/zh-cn/internals/philosophy/
[language-support]: https://biomejs.dev/zh-cn/internals/language-support/
[getting-started]: https://biomejs.dev/zh-cn/guides/getting-started/

View file

@ -0,0 +1,169 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-dark-transparent.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg">
<img alt="Shows the banner of Biome, with its logo and the phrase 'Biome - Toolchain of the web'." src="https://raw.githubusercontent.com/biomejs/resources/main/svg/slogan-light-transparent.svg" width="700">
</picture>
</p>
<div align="center">
[![Discord chat][discord-badge]][discord-url]
[![CI on main][ci-badge]][ci-url]
[![npm version][npm-badge]][npm-url]
[![VSCode version][vscode-badge]][vscode-url]
[![Open VSX version][open-vsx-badge]][open-vsx-url]
[discord-badge]: https://badgen.net/discord/online-members/BypW39g6Yc?icon=discord&label=discord&color=green
[discord-url]: https://biomejs.dev/chat
[ci-badge]: https://github.com/biomejs/biome/actions/workflows/main.yml/badge.svg
[ci-url]: https://github.com/biomejs/biome/actions/workflows/main.yml
[npm-badge]: https://badgen.net/npm/v/@biomejs/biome?icon=npm&color=green&label=%40biomejs%2Fbiome
[npm-url]: https://www.npmjs.com/package/@biomejs/biome/v/latest
[vscode-badge]: https://badgen.net/vs-marketplace/v/biomejs.biome?label=vscode&icon=visualstudio&color=green
[vscode-url]: https://marketplace.visualstudio.com/items?itemName=biomejs.biome
[open-vsx-badge]: https://badgen.net/open-vsx/version/biomejs/biome?label=open-vsx&color=green
[open-vsx-url]: https://open-vsx.org/extension/biomejs/biome
</div>
<!-- Insert new entries lexicographically by language code.
For example given below is the same order as these files appear on page:
https://github.com/biomejs/biome/tree/main/packages/%40biomejs/biome -->
<div align="center">
[हिन्दी](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.hi.md) | [English](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.md) | 繁體中文 | [简体中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-CN.md) | [日本語](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.ja.md) | [Português do Brasil](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.pt-BR.md) | [한글](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.kr.md)
</div>
**Biome** 是一個高效能的 Web 專案工具鏈,旨在提供開發工具以維持這些專案的健康。
**Biome 是一個 [快速格式化工具](./benchmark#formatting)**,支持 _JavaScript_、_TypeScript_、_JSX_、_JSON_、_CSS_ 和 _GraphQL_,其 **與 _Prettier_ 的相容性達到 [97%](https://console.algora.io/challenges/prettier)**
**Biome 是一個 [高效能的語法檢查工具](https://github.com/biomejs/biome/tree/main/benchmark#linting)**,支持 _JavaScript_、_TypeScript_、_JSX_、_CSS_ 和 _GraphQL_,擁有來自 ESLint、typescript-eslint 和 [其他來源](https://github.com/biomejs/biome/discussions/3)的 **超過 270 條規則**
**輸出詳細且具上下文的診斷資訊**,幫助你改進程式碼並成為更好的程式設計師!
**Biome** 從一開始就設計為可在 [編輯器中互動使用](https://biomejs.dev/guides/integrate-in-editor/)。
它可以在你編寫程式碼時格式化和檢查錯誤的程式碼。
### 安裝
```shell
npm install --save-dev --save-exact @biomejs/biome
```
### 使用
```shell
# 格式化文件
npx @biomejs/biome format --write ./src
# 檢查文件並應用安全的修正
npx @biomejs/biome lint --write ./src
# 執行格式化、檢查等並應用安全的修正
npx @biomejs/biome check --write ./src
# 在 CI 環境中檢查所有文件的格式、檢查等
npx @biomejs/biome ci ./src
```
如果你想在不安裝 Biome 的情況下運行它,請使用 [線上 Playground](https://biomejs.dev/playground/),他被編譯為 WebAssembly。
## 文件
訪問我們的[首頁][biomejs]以了解更多關於 Biome 的資訊,
或直接前往[入門指南][getting-started]開始使用 Biome。
## 關於 Biome 的更多資訊
**Biome** 擁有合理的預設設定,無需配置。
**Biome** 旨在支持現代 Web 開發的 [所有主要開發語言][language-support]。
**Biome** [不需要 Node.js](https://biomejs.dev/guides/manual-installation/) 即可運行。
**Biome** 擁有一流的 LSP 支持,配備了能完整保留原文的先進解析器和頂級的錯誤修復能力。
**Biome** 整合了以前分離的工具功能。基於共享基礎構建,讓我們能夠為程式碼處理、錯誤顯示、並行工作、快取記憶體和配置提供一致的體驗。
閱讀更多關於我們的[專案理念][biome-philosophy]。
**Biome** 採用 [MIT 授權](https://github.com/biomejs/biome/tree/main/LICENSE-MIT) 或 [Apache 2.0 授權](https://github.com/biomejs/biome/tree/main/LICENSE-APACHE),並根據 [貢獻者公約行為準則](https://github.com/biomejs/biome/tree/main/CODE_OF_CONDUCT.md) 進行管理。
## 資金支持
你可以通過不同的方式支持這個專案
### 專案贊助和資金支持
你可以通過 [Open Collective](https://opencollective.com/biome) 或 [GitHub Sponsors](https://github.com/sponsors/biomejs) 贊助或資助這個專案。
Biome 提供了一個簡單的贊助計劃,允許公司在各種開發者中獲得可見性和認可。
### 問題資金支持
我們使用 [Polar.sh](https://polar.sh/biomejs) 來提升和推廣你希望看到和實現的特定功能。查看我們的待辦事項並幫助我們:
<a href="https://polar.sh/biomejs"><img src="https://polar.sh/embed/fund-our-backlog.svg?org=biomejs" /></a>
## 贊助商
### 金牌贊助商
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://shiguredo.jp/" target="_blank"><img src="https://shiguredo.jp/official_shiguredo_logo.svg" height="120"></a>
</td>
</tr>
</tbody>
</table>
### 銀牌贊助商
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://l2beat.com/" target="_blank"><img src="https://images.opencollective.com/l2beat/c2b2a27/logo/256.png" height="100"></a>
</td>
<td align="center" valign="middle">
<a href="https://www.phoenixlabs.dev/" target="_blank"><img src="https://images.opencollective.com/phoenix-labs/2824ed4/logo/100.png?height=100" height="100"></a>
</td>
</tr>
</tbody>
</table>
### 銅牌贊助商
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.kanamekey.com" target="_blank"><img src="https://images.opencollective.com/kaname/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://nanabit.dev/" target="_blank"><img src="https://images.opencollective.com/nanabit/d15fd98/logo/256.png?height=80" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://vital.io/" target="_blank"><img src="https://avatars.githubusercontent.com/u/25357309?s=200" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/" target="_blank"><img src="https://avatars.githubusercontent.com/u/132028505?s=200&v=4" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://forge42.dev/" target="_blank"><img src="https://avatars.githubusercontent.com/u/161314831?s=200&v=4" width="80"></a>
</td>
<td align="center" valign="middle">
<a href="https://transloadit.com/" target="_blank"><img src="https://avatars.githubusercontent.com/u/125754?s=200&v=4" width="80"></a>
</td>
</tr>
</tbody>
</table>
[biomejs]: https://biomejs.dev/
[biome-philosophy]: https://biomejs.dev/internals/philosophy/
[language-support]: https://biomejs.dev/internals/language-support/
[getting-started]: https://biomejs.dev/guides/getting-started/

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2023 Rome Tools is Rome Tools, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

98
modules/desktop/astal/node_modules/@biomejs/biome/bin/biome generated vendored Executable file
View file

@ -0,0 +1,98 @@
#!/usr/bin/env node
const { platform, arch, env, version, release } = process;
const { execSync } = require("child_process");
function isMusl() {
let stderr;
try {
stderr = execSync("ldd --version", {
stdio: ['pipe', 'pipe', 'pipe']
});
} catch (err) {
stderr = err.stderr;
}
if (stderr.indexOf("musl") > -1) {
return true;
}
return false;
}
const PLATFORMS = {
win32: {
x64: "@biomejs/cli-win32-x64/biome.exe",
arm64: "@biomejs/cli-win32-arm64/biome.exe",
},
darwin: {
x64: "@biomejs/cli-darwin-x64/biome",
arm64: "@biomejs/cli-darwin-arm64/biome",
},
linux: {
x64: "@biomejs/cli-linux-x64/biome",
arm64: "@biomejs/cli-linux-arm64/biome",
},
"linux-musl": {
x64: "@biomejs/cli-linux-x64-musl/biome",
arm64: "@biomejs/cli-linux-arm64-musl/biome",
},
};
if (env.ROME_BINARY) {
console.warn(`[WARN] The environment variable "ROME_BINARY" is deprecated. Use "BIOME_BINARY" instead.`)
}
const binPath = env.BIOME_BINARY || env.ROME_BINARY ||
(platform === "linux" && isMusl()
? PLATFORMS?.["linux-musl"]?.[arch]
: PLATFORMS?.[platform]?.[arch]
);
if (binPath) {
const packageManager = detectPackageManager();
const result = require("child_process").spawnSync(
require.resolve(binPath),
process.argv.slice(2),
{
shell: false,
stdio: "inherit",
env: {
...env,
JS_RUNTIME_VERSION: version,
JS_RUNTIME_NAME: release.name,
...(packageManager != null
? { NODE_PACKAGE_MANAGER: packageManager }
: {}),
},
},
);
if (result.error) {
throw result.error;
}
process.exitCode = result.status;
} else {
console.error(
"The Biome CLI package doesn't ship with prebuilt binaries for your platform yet. " +
"You can still use the CLI by cloning the biome/tools repo from GitHub, " +
"and follow the instructions there to build the CLI for your platform.",
);
process.exitCode = 1;
}
/**
* NPM, Yarn, and other package manager set the `npm_config_user_agent`. It has the following format:
*
* ```
* "npm/8.3.0 node/v16.13.2 win32 x64 workspaces/false
* ```
*
* @returns The package manager string (`npm/8.3.0`) or null if the user agent string isn't set.
*/
function detectPackageManager() {
const userAgent = env.npm_config_user_agent;
if (userAgent == null) {
return null;
}
return userAgent.split(" ")[0];
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
{
"name": "@biomejs/biome",
"version": "1.9.4",
"bin": {
"biome": "bin/biome"
},
"scripts": {
"postinstall": "node scripts/postinstall.js"
},
"homepage": "https://biomejs.dev",
"repository": {
"type": "git",
"url": "git+https://github.com/biomejs/biome.git",
"directory": "packages/@biomejs/biome"
},
"author": "Emanuele Stoppa",
"license": "MIT OR Apache-2.0",
"bugs": "https://github.com/biomejs/biome/issues",
"description": "Biome is a toolchain for the web: formatter, linter and more",
"files": [
"bin/biome",
"scripts/postinstall.js",
"configuration_schema.json",
"README.md",
"LICENSE-APACHE",
"LICENSE-MIT",
"ROME-LICENSE-MIT"
],
"keywords": [
"format",
"lint",
"toolchain",
"JavaScript",
"TypeScript",
"JSON",
"JSONC",
"JSX",
"TSX",
"CSS",
"GraphQL"
],
"engines": {
"node": ">=14.21.3"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/biome"
},
"publishConfig": {
"provenance": true
},
"optionalDependencies": {
"@biomejs/cli-win32-x64": "1.9.4",
"@biomejs/cli-win32-arm64": "1.9.4",
"@biomejs/cli-darwin-x64": "1.9.4",
"@biomejs/cli-darwin-arm64": "1.9.4",
"@biomejs/cli-linux-x64": "1.9.4",
"@biomejs/cli-linux-arm64": "1.9.4",
"@biomejs/cli-linux-x64-musl": "1.9.4",
"@biomejs/cli-linux-arm64-musl": "1.9.4"
}
}

Binary file not shown.

View file

@ -0,0 +1,23 @@
{
"name": "@biomejs/cli-linux-x64",
"version": "1.9.4",
"license": "MIT OR Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/biomejs/biome.git",
"directory": "packages/@biomejs/biome"
},
"engines": {
"node": ">=14.21.3"
},
"homepage": "https://biomejs.dev",
"os": [
"linux"
],
"cpu": [
"x64"
],
"libc": [
"glibc"
]
}

1
modules/desktop/astal/node_modules/astal generated vendored Symbolic link
View file

@ -0,0 +1 @@
/usr/share/astal/gjs

188
modules/desktop/astal/package-lock.json generated Normal file
View file

@ -0,0 +1,188 @@
{
"name": "astal-shell",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "astal-shell",
"dependencies": {
"astal": "/usr/share/astal/gjs"
},
"devDependencies": {
"@biomejs/biome": "1.9.4"
}
},
"../../../../usr/share/astal/gjs": {
"name": "astal",
"license": "LGPL-2.1"
},
"node_modules/@biomejs/biome": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
"dev": true,
"hasInstallScript": true,
"license": "MIT OR Apache-2.0",
"bin": {
"biome": "bin/biome"
},
"engines": {
"node": ">=14.21.3"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/biome"
},
"optionalDependencies": {
"@biomejs/cli-darwin-arm64": "1.9.4",
"@biomejs/cli-darwin-x64": "1.9.4",
"@biomejs/cli-linux-arm64": "1.9.4",
"@biomejs/cli-linux-arm64-musl": "1.9.4",
"@biomejs/cli-linux-x64": "1.9.4",
"@biomejs/cli-linux-x64-musl": "1.9.4",
"@biomejs/cli-win32-arm64": "1.9.4",
"@biomejs/cli-win32-x64": "1.9.4"
}
},
"node_modules/@biomejs/cli-darwin-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-darwin-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64-musl": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64-musl": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/astal": {
"resolved": "../../../../usr/share/astal/gjs",
"link": true
}
}
}

View file

@ -0,0 +1,9 @@
{
"name": "astal-shell",
"dependencies": {
"astal": "/usr/share/astal/gjs"
},
"devDependencies": {
"@biomejs/biome": "1.9.4"
}
}

View file

@ -0,0 +1,91 @@
@use "../variables.scss" as *;
.Bar {
color: $fg;
background-color: $bg;
border-bottom: 2px solid $accent;
font-size: $font-large;
}
.SecondaryBar {
color: $fg;
background-color: $bg;
font-size: $font-large;
transition:
opacity $transition,
border-color $transition;
&.left {
border-left: 2px solid $accent;
border-bottom: 2px solid $accent;
border-bottom-left-radius: $radius;
}
&.right {
border-right: 2px solid $accent;
border-bottom: 2px solid $accent;
border-bottom-right-radius: $radius;
}
&.top {
border-right: 2px solid $accent;
border-top: 2px solid $accent;
border-top-right-radius: $radius;
}
&.bottom {
border-right: 2px solid $accent;
border-bottom: 2px solid $accent;
border-bottom-right-radius: $radius;
}
&.inactive {
opacity: 0.7;
border-color: transparent;
}
}
.Time {
font-weight: bold;
}
.SysTray {
button {
all: unset;
&:hover {
all: unset;
}
}
.menu,
modelbutton {
background-color: $bg;
transition: background-color $transition;
}
modelbutton:hover {
background-color: $bg-alt;
}
}
.FocusedClient {
color: $accent;
padding: 0 5px;
}
.Slider {
highlight {
background-color: $accent;
border-radius: $radius;
}
trough {
background-color: $bg-alt;
border-radius: $radius;
min-height: 5px;
min-width: 80px;
}
}
.Right > * {
border-left: 2px solid $accent;
padding: 4px 15px;
}
.Left > * {
border-right: 2px solid $accent;
padding: 4px 15px;
}

View file

@ -0,0 +1,122 @@
import Hyprland from "gi://AstalHyprland";
import Tray from "gi://AstalTray";
import Pango from "gi://Pango?version=1.0";
import { GLib, Variable, bind } from "astal";
import { App, Astal, type Gdk, Gtk } from "astal/gtk4";
import config from "../config";
import { getHyprlandMonitor } from "../utils/monitors";
import { Calendar } from "../widgets";
import { connectDropdown } from "./sections/Dropdown";
import Media from "./sections/Media";
import { Playback } from "./sections/Playback";
import { Workspaces } from "./sections/Workspace";
function SysTray() {
const tray = Tray.get_default();
const item = bind(tray, "items").as((items) =>
items.filter((item) => config.tray.ignore.every((test) => !test(item.id))),
);
return (
<box
cssClasses={["SysTray"]}
visible={bind(item).as((items) => items.length > 0)}
>
{bind(item).as((items) =>
items.map((item) => (
<menubutton
tooltipMarkup={bind(item, "tooltipMarkup")}
// popover={false}
// actionGroup={bind(item, "actionGroup").as((ag) => ["dbusmenu", ag])}
menuModel={bind(item, "menuModel")}
>
<image gicon={bind(item, "gicon")} />
</menubutton>
)),
)}
</box>
);
}
function FocusedClient() {
const hypr = Hyprland.get_default();
const focused = bind(hypr, "focusedClient");
return (
<box cssClasses={["Focused"]} visible={focused.as(Boolean)}>
{focused.as((client) => {
if (!client) {
return;
}
return (
<label
label={bind(client, "title").as(String)}
ellipsize={Pango.EllipsizeMode.MIDDLE}
maxWidthChars={120}
/>
);
})}
</box>
);
}
function Time(props: { monitor: Gdk.Monitor }) {
const datetime = Variable<GLib.DateTime>(GLib.DateTime.new_now_local()).poll(
1000,
() => {
return GLib.DateTime.new_now_local();
},
);
const date = bind(datetime).as((dt) => dt.format("%Y-%m-%d") ?? "");
const time = bind(datetime).as((dt) => dt.format("%H:%M") ?? "");
return (
<box
cssClasses={["DateTime"]}
spacing={10}
setup={(self) => {
connectDropdown(self, <Calendar showWeekNumbers />, props.monitor);
}}
>
<label label={date} cssClasses={["Date"]} />
<label label={time} cssClasses={["Time"]} />
</box>
);
}
export default function Bar(monitor: Gdk.Monitor) {
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor;
const hyprlandMonitor = getHyprlandMonitor(monitor);
return (
<window
visible
name={"Bar"}
// window lags hard if css classes with padding border ect are used so we apply them to a child instead
// cssClasses={["Bar"]}
gdkmonitor={monitor}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
layer={Astal.Layer.OVERLAY}
anchor={TOP | LEFT | RIGHT}
application={App}
>
<centerbox cssClasses={["Bar"]}>
<box halign={Gtk.Align.START} cssClasses={["Left"]}>
<Time monitor={monitor} />
<Workspaces
monitor={hyprlandMonitor}
selectedWorkspaces={[1, 2, 3, 4, 5]}
/>
</box>
<box halign={Gtk.Align.CENTER}>
<FocusedClient />
</box>
<box halign={Gtk.Align.END} cssClasses={["Right"]}>
<Media monitor={monitor} />
<Playback monitor={monitor} />
<SysTray />
</box>
</centerbox>
</window>
);
}

View file

@ -0,0 +1,106 @@
import Hyprland from "gi://AstalHyprland";
import { App } from "astal/gtk4";
import { Astal, type Gdk, Gtk } from "astal/gtk4";
import { getHyprlandMonitor } from "../utils/monitors";
import { Workspaces } from "./sections/Workspace";
import { bind, type Binding, Variable } from "astal";
const hypr = Hyprland.get_default();
interface AddWorkspaceButtonProps {
show: Binding<boolean>;
cssClasses: string[];
}
const AddWorkspaceButton = ({ show, cssClasses }: AddWorkspaceButtonProps) => {
return (
<revealer
revealChild={show}
transitionType={Gtk.RevealerTransitionType.SLIDE_RIGHT}
transitionDuration={500}
>
<button
cssClasses={["workspace", ...cssClasses]}
visible={show}
onClicked={() => {
hypr.dispatch("workspace", "emptynm");
}}
valign={Gtk.Align.CENTER}
>
<image iconName="plus-symbolic" pixelSize={18} />
</button>
</revealer>
);
};
export default function SecondaryBar(
monitor: Gdk.Monitor,
relation: "top" | "bottom" | "left" | "right",
) {
const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor;
const hyprlandMonitor = getHyprlandMonitor(monitor);
const anchor = {
top: BOTTOM | LEFT,
left: TOP | RIGHT,
right: TOP | LEFT,
bottom: TOP | LEFT,
}[relation];
const cssClasses = {
top: ["SecondaryBar", "top"],
left: ["SecondaryBar", "left"],
right: ["SecondaryBar", "right"],
bottom: ["SecondaryBar", "bottom"],
}[relation];
const alignment = {
top: Gtk.Align.START,
left: Gtk.Align.END,
right: Gtk.Align.START,
bottom: Gtk.Align.START,
}[relation];
const showAddWorkspaceButton = Variable(false);
const monitorFocused = bind(hypr, "focusedMonitor").as(
(fm) => fm === hyprlandMonitor,
);
return (
<window
visible
gdkmonitor={monitor}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
layer={Astal.Layer.OVERLAY}
anchor={anchor}
application={App}
halign={alignment}
defaultWidth={1} // Ensure the window shinks when content is removed
defaultHeight={26}
onHoverEnter={() => showAddWorkspaceButton.set(true)}
onHoverLeave={() => showAddWorkspaceButton.set(false)}
>
<box
halign={alignment}
spacing={10}
cssClasses={monitorFocused.as((x) =>
x ? cssClasses : [...cssClasses, "inactive"],
)}
>
{relation === "left" ? (
<AddWorkspaceButton
show={bind(showAddWorkspaceButton)}
cssClasses={["add-left"]}
/>
) : null}
<Workspaces monitor={hyprlandMonitor} reverse={relation === "left"} />
{relation !== "left" ? (
<AddWorkspaceButton
show={bind(showAddWorkspaceButton)}
cssClasses={["add-right"]}
/>
) : null}
</box>
</window>
);
}

View file

@ -0,0 +1,10 @@
@use "../../variables.scss" as *;
.Dropdown {
padding: 15px;
background-color: $bg;
border: 2px solid $accent;
border-top: 0;
border-bottom-left-radius: $radius;
border-bottom-right-radius: $radius;
}

View file

@ -0,0 +1,133 @@
import { type Binding, Variable, bind } from "astal";
import { App, Astal, type Gdk, Gtk, hook } from "astal/gtk4";
import { cancelTimeout, cancelableTimeout } from "../../utils/timeout";
const ANIMATION_DURATION = 500;
/**
* Calculate the offset and width of the parent widget
*
* @returns [offset, width]
*/
const calculateParentSize = (widget: Gtk.Widget): [number, number] => {
const [_, x, __] = widget.translate_coordinates(widget.root, 0, 0);
// These properties are apparently deprecated, but I can't find a better way to get them
const padding = widget.get_style_context().get_padding().left;
const margin = widget.get_style_context().get_margin().left;
const borderWidth = widget.get_style_context().get_border().left;
const offset = x - padding - margin - borderWidth;
// Get allocated width doesn't include border width, so we have to add it back
const width = widget.get_allocated_width() + borderWidth;
return [offset, width];
};
interface ConnectDropdownProps {
fullWidth?: boolean;
}
export function connectDropdown(
widget: Gtk.Widget,
child: JSX.Element | Binding<JSX.Element | null> | null,
gdkmonitor: Gdk.Monitor,
options: ConnectDropdownProps = {},
) {
const hoverTrigger = Variable(false);
const hoverOverlay = Variable(false);
const offsetX = Variable(0);
const width = Variable(-1);
const isHovering = Variable.derive(
[hoverTrigger, hoverOverlay],
// (trigger, overlay) => trigger || overlay,
(trigger, overlay) => trigger || overlay,
);
const box = (
<box
widthRequest={bind(width)}
marginStart={bind(offsetX)}
cssClasses={["Dropdown"]}
onHoverEnter={() => hoverOverlay.set(true)}
onHoverLeave={() => hoverOverlay.set(false)}
>
{child}
</box>
);
const dropdown = (
<window
cssClasses={["DropdownWindow"]}
gdkmonitor={gdkmonitor}
layer={Astal.Layer.OVERLAY}
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT}
application={App}
>
<revealer
transitionDuration={ANIMATION_DURATION}
transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
valign={Gtk.Align.START}
setup={(self) => {
bind(self, "child_revealed").subscribe((is_revealed) => {
if (!is_revealed) {
dropdown.hide();
}
});
}}
>
{box}
</revealer>
</window>
) as Gtk.Window;
isHovering.subscribe((hovering) => {
if (hovering) {
dropdown.show();
(dropdown.get_first_child() as Gtk.Revealer).set_reveal_child(true);
} else {
(dropdown.get_first_child() as Gtk.Revealer).set_reveal_child(false);
}
});
const hoverController = new Gtk.EventControllerMotion();
widget.add_controller(hoverController);
hoverController.connect("enter", () => {
cancelableTimeout(
() => {
const [offset, parentWidth] = calculateParentSize(widget);
if (options.fullWidth) {
width.set(parentWidth);
}
const dropdownWidth =
(box.get_preferred_size()[1]?.width ?? 0) - offsetX.get();
const centerOffset = dropdownWidth / 2 - parentWidth / 2;
const totalOffset = offset - centerOffset;
// Ensure the dropdown doesn't go off the screen
const maxOffset = gdkmonitor.get_geometry().width - dropdownWidth;
offsetX.set(Math.max(Math.min(totalOffset, maxOffset), 0));
hoverTrigger.set(true);
},
"showDropdown",
100,
);
});
hoverController.connect("leave", () => {
cancelTimeout("showDropdown");
hoverTrigger.set(false);
});
widget.connect("destroy", () => {
isHovering.drop();
hoverOverlay.drop();
hoverTrigger.drop();
offsetX.drop();
widget.remove_controller(hoverController);
});
}

View file

@ -0,0 +1,37 @@
@use "../../variables.scss" as *;
.MediaDropdown {
.MediaCover {
border-radius: $radius;
margin-bottom: 5px;
}
.MediaArtist {
color: $fg-alt;
}
.MediaAlbum {
color: $fg-alt;
font-size: 12px;
}
.Slider {
margin-top: 10px;
margin-bottom: 10px;
}
separator {
margin-top: 10px;
margin-bottom: 10px;
}
.MediaOther {
margin-top: 10px;
button.active {
background-color: $accent;
border-color: $accent;
color: $bg;
}
}
}

View file

@ -0,0 +1,387 @@
import Mpris from "gi://AstalMpris";
import Pango from "gi://Pango?version=1.0";
import { type Binding, Variable, bind } from "astal";
import type { Subscribable } from "astal/binding";
import { type Gdk, Gtk } from "astal/gtk4";
import { hasIcon } from "../../utils/gtk";
import { Expander, Separator } from "../../widgets";
import { connectDropdown } from "./Dropdown";
const mpris = Mpris.get_default();
const MARQUEE_LENGTH = 30;
interface MprisStatus {
status: Mpris.PlaybackStatus;
lastPlayed: number;
canControl: boolean;
}
class ActiveMediaDetector implements Subscribable {
#userOverride: string | undefined;
#players: { [busName: string]: MprisStatus } = {};
#listenerSignal = new Map<Mpris.Player, number>();
#active = Variable<Mpris.Player | undefined>(undefined);
#updateActive() {
const busName = Object.entries<MprisStatus>(this.#players)
.filter(([, status]) => {
// Don't consider players that are stopped or can't be controlled
if (status.status === Mpris.PlaybackStatus.STOPPED) {
return false;
}
return status.canControl;
})
.sort(([aName, a], [bName, b]) => {
if (aName === this.#userOverride) {
return -1;
}
if (bName === this.#userOverride) {
return 1;
}
if (
a.status === Mpris.PlaybackStatus.PLAYING &&
b.status !== Mpris.PlaybackStatus.PLAYING
) {
return -1;
}
if (
b.status === Mpris.PlaybackStatus.PLAYING &&
a.status !== Mpris.PlaybackStatus.PLAYING
) {
return 1;
}
return b.lastPlayed - a.lastPlayed;
})[0]?.[0];
const player = busName
? mpris.get_players().find((player) => player.bus_name === busName)
: undefined;
this.#active.set(player);
}
#handleUpdate(player: Mpris.Player) {
const lastStatus = this.#players[player.bus_name]?.status;
let lastPlayed = this.#players[player.bus_name]?.lastPlayed ?? -1;
// If the player is playing (or was just playing), update the last played time
if (
player.playback_status === Mpris.PlaybackStatus.PLAYING ||
lastStatus === Mpris.PlaybackStatus.PLAYING
) {
lastPlayed = Date.now();
}
this.#players[player.bus_name] = {
status: player.playback_status,
lastPlayed: lastPlayed,
canControl: player.can_control,
};
this.#updateActive();
}
#connect(player: Mpris.Player) {
const signal = player.connect("notify::playback-status", () => {
this.#handleUpdate(player);
});
this.#listenerSignal.set(player, signal);
}
#disconnect(player: Mpris.Player) {
const signal = this.#listenerSignal.get(player);
if (signal) {
player.disconnect(signal);
this.#listenerSignal.delete(player);
}
}
constructor() {
for (const player of mpris.players) {
this.#handleUpdate(player);
this.#connect(player);
}
mpris.connect("player-added", (_, player) => {
this.#handleUpdate(player);
this.#connect(player);
});
mpris.connect("player-closed", (_, player) => {
delete this.#players[player.bus_name];
this.#disconnect(player);
});
}
get override() {
return this.#userOverride;
}
set override(busName: string | undefined) {
this.#userOverride = busName;
this.#updateActive();
}
get(): Mpris.Player | undefined {
return this.#active.get();
}
subscribe(callback: (value: Mpris.Player | undefined) => void): () => void {
return this.#active.subscribe(callback);
}
}
const formatTime = (time: number) => {
const hours = Math.floor(time / 3600);
const minutes = Math.floor((time % 3600) / 60)
.toString()
.padStart(2, "0");
const seconds = Math.floor(time % 60)
.toString()
.padStart(2, "0");
return `${hours > 0 ? `${hours}:` : ""}${minutes}:${seconds}`;
};
interface MediaDropdownProps {
activePlayer: Binding<Mpris.Player | undefined>;
onOverride: (busName: string) => void;
}
function MediaDropdown({ activePlayer, onOverride }: MediaDropdownProps) {
const allPlayers = bind(mpris, "players");
return (
<box cssClasses={["MediaDropdown"]} vertical spacing={5}>
{activePlayer.as((player) => {
if (!player) {
return null;
}
return (
<>
<image
file={bind(player, "coverArt")}
visible={Boolean(bind(player, "coverArt"))}
cssClasses={["MediaCover"]}
pixelSize={220}
/>
<label
label={bind(player, "title")}
ellipsize={Pango.EllipsizeMode.END}
maxWidthChars={30}
justify={Gtk.Justification.CENTER}
lines={2}
visible={Boolean(bind(player, "title"))}
cssClasses={["MediaTitle"]}
/>
<label
label={bind(player, "artist")}
ellipsize={Pango.EllipsizeMode.END}
justify={Gtk.Justification.CENTER}
maxWidthChars={30}
visible={Boolean(bind(player, "artist"))}
cssClasses={["MediaArtist"]}
/>
<label
label={bind(player, "album")}
ellipsize={Pango.EllipsizeMode.END}
justify={Gtk.Justification.CENTER}
maxWidthChars={30}
wrap={true}
visible={Boolean(bind(player, "album"))}
cssClasses={["MediaAlbum"]}
/>
<slider
cssClasses={["Slider"]}
hexpand
min={0}
max={bind(player, "length")}
onChangeValue={({ value }) => {
player.position = value;
}}
value={bind(player, "position")}
/>
<centerbox hexpand>
<label
halign={Gtk.Align.START}
label={bind(player, "position").as(formatTime)}
/>
<box
cssClasses={["MediaControls"]}
spacing={10}
halign={Gtk.Align.CENTER}
>
<button onClicked={() => player.previous()}>
<image iconName="media-skip-backward-symbolic" />
</button>
<button onClicked={() => player.play_pause()}>
<image
iconName={bind(player, "playbackStatus").as((s) =>
s === Mpris.PlaybackStatus.PLAYING
? "media-playback-pause-symbolic"
: "media-playback-start-symbolic",
)}
/>
</button>
<button onClicked={() => player.next()}>
<image iconName="media-skip-forward-symbolic" />
</button>
</box>
<label
halign={Gtk.Align.END}
label={bind(player, "length").as(formatTime)}
/>
</centerbox>
</>
);
})}
<Separator visible={allPlayers.as((players) => players.length > 1)} />
<Expander
label={"Other media players"}
visible={allPlayers.as((players) => players.length > 1)}
>
<box vertical spacing={10} cssClasses={["MediaOther"]}>
{allPlayers.as((players) => {
return players.map((p) => (
<button
onClicked={() => onOverride(p.bus_name)}
cssClasses={activePlayer.as((player) => {
return p.bus_name === player?.bus_name ? ["active"] : [];
})}
>
<label label={p.identity} />
</button>
));
})}
</box>
</Expander>
</box>
);
}
interface MediaProps {
monitor: Gdk.Monitor;
}
export default function Media({ monitor }: MediaProps) {
const activeMedia = new ActiveMediaDetector();
const activePlayer = bind(activeMedia);
return (
<box
cssClasses={["Media"]}
spacing={10}
setup={(self) =>
connectDropdown(
self,
<MediaDropdown
activePlayer={activePlayer}
onOverride={(busName) => {
if (activeMedia.override === busName) {
activeMedia.override = undefined;
} else {
activeMedia.override = busName;
}
}}
/>,
monitor,
{ fullWidth: true },
)
}
visible={activePlayer.as(Boolean)}
>
{activePlayer.as((player) => {
if (!player) {
return;
}
const icon = bind(player, "entry").as((e) =>
hasIcon(e) ? e : "audio-x-generic-symbolic",
);
const marqueeOffset = Variable(0).poll(100, (offset) => {
return offset + 1;
});
// show marquee for the first and last 10 seconds of a song
const showMarquee = Variable.derive(
[
bind(player, "length"),
bind(player, "position"),
bind(player, "playbackStatus"),
],
(length, position, status) => {
if (status !== Mpris.PlaybackStatus.PLAYING) {
return false;
}
return position < 10 || length - position < 10;
},
);
showMarquee.subscribe((show) => {
if (show) {
marqueeOffset.poll(100, (offset) => {
return offset + 1;
});
} else {
marqueeOffset.stopPoll();
}
});
bind(player, "title").subscribe(() => marqueeOffset.set(0));
const marquee = Variable.derive(
[bind(player, "title"), bind(player, "artist"), bind(marqueeOffset)],
(title, artist, mo) => {
const line = `${title} - ${artist} `;
if (line.length <= MARQUEE_LENGTH) {
// center the text
return line
.padStart(20 + line.length / 2, " ")
.padEnd(MARQUEE_LENGTH, " ");
}
const offset = mo % line.length;
return (line + line).slice(offset, offset + MARQUEE_LENGTH);
},
);
return (
<>
<image iconName={icon} />
<stack
visibleChildName={bind(showMarquee).as((show) =>
show ? "marquee" : "progress",
)}
transitionType={Gtk.StackTransitionType.CROSSFADE}
transitionDuration={200}
>
<box name={"progress"} spacing={10}>
<label label={bind(player, "position").as(formatTime)} />
<slider
cssClasses={["Slider"]}
hexpand
min={0}
max={bind(player, "length")}
onChangeValue={({ value }) => {
const player = activePlayer.get();
if (player) {
player.position = value;
}
}}
value={bind(player, "position")}
/>
<label label={bind(player, "length").as(formatTime)} />
</box>
<label
name={"marquee"}
label={bind(marquee)}
ellipsize={Pango.EllipsizeMode.END}
widthChars={MARQUEE_LENGTH}
maxWidthChars={MARQUEE_LENGTH}
/>
</stack>
</>
);
})}
</box>
);
}

View file

@ -0,0 +1,28 @@
@use "../../variables.scss" as *;
@keyframes pulse {
0% {
background-color: $accent;
}
50% {
background-color: $bg-alt;
}
100% {
background-color: $accent;
}
}
.PlaybackDropdown {
.no-streams {
color: $muted;
font-size: 14px;
}
}
.Playback {
.recording {
background-color: $accent;
border-radius: $radius;
animation: pulse 1s 10;
}
}

View file

@ -0,0 +1,250 @@
import Wp from "gi://AstalWp";
import { Variable, bind } from "astal";
import type { Binding, Subscribable } from "astal/binding";
import { Gtk, type Gdk } from "astal/gtk4";
import { hasIcon } from "../../utils/gtk";
import { Expander, FlowBox, Separator } from "../../widgets";
import { connectDropdown } from "./Dropdown";
import Pango from "gi://Pango?version=1.0";
import { Box } from "astal/gtk4/widget";
interface PlaybackEndpointProps {
endpoint: Wp.Endpoint;
visible?: Binding<boolean>;
}
function PlaybackEndpoint({ endpoint, visible }: PlaybackEndpointProps) {
const name = Variable.derive(
[bind(endpoint, "description"), bind(endpoint, "name")],
(description, name) => name || description || "Unknown",
);
const defaultable = Variable.derive(
[bind(endpoint, "is_default"), bind(endpoint, "media_class")],
(isDefault, mediaClass) =>
!isDefault &&
[Wp.MediaClass.AUDIO_MICROPHONE, Wp.MediaClass.AUDIO_SPEAKER].includes(
mediaClass,
),
);
return (
<box
vertical
spacing={5}
hexpand
onDestroy={() => {
name.drop();
defaultable.drop();
}}
visible={visible}
>
<box spacing={10} hexpand>
<button
onButtonPressed={() => {
endpoint.set_mute(!endpoint.mute);
}}
>
<image iconName={bind(endpoint, "volumeIcon")} pixelSize={16} />
</button>
<label
label={bind(name)}
maxWidthChars={bind(defaultable).as((x) => (x ? 23 : 28))}
ellipsize={Pango.EllipsizeMode.END}
halign={Gtk.Align.START}
hexpand
/>
<button
visible={bind(defaultable)}
onButtonPressed={() => {
endpoint.set_is_default(true);
}}
>
<image iconName="star-filled" pixelSize={20} />
</button>
</box>
<box spacing={5} hexpand>
<slider
cssClasses={["Slider"]}
hexpand
value={bind(endpoint, "volume")}
onChangeValue={({ value }) => {
endpoint.set_volume(value);
}}
/>
<label
label={bind(endpoint, "volume").as((v) =>
`${Math.floor(v * 100)}%`.padStart(4, " "),
)}
/>
</box>
</box>
);
}
function PlaybackDropdown({ audioDevices }: { audioDevices: Wp.Audio }) {
return (
<box
spacing={10}
vertical
widthRequest={300}
cssClasses={["PlaybackDropdown"]}
>
<label label="Default Speaker" halign={Gtk.Align.START} />
{bind(audioDevices, "default_speaker").as((speaker) => {
return <PlaybackEndpoint endpoint={speaker} />;
})}
<Expander
label={"All Speakers"}
visible={bind(audioDevices, "speakers").as(
(speakers) => speakers.length > 1,
)}
>
<Box spacing={5} vertical marginTop={10}>
{bind(audioDevices, "speakers").as((speakers) => {
return speakers.map((speaker) => (
<PlaybackEndpoint
endpoint={speaker}
visible={bind(speaker, "is_default").as((x) => !x)}
/>
));
})}
</Box>
</Expander>
<Separator />
<label label="Playback streams" halign={Gtk.Align.START} />
{bind(audioDevices, "streams").as((streams) => {
if (streams.length === 0) {
return (
<label
label="No playback streams"
halign={Gtk.Align.START}
cssClasses={["no-streams"]}
/>
);
}
return streams.map((stream) => <PlaybackEndpoint endpoint={stream} />);
})}
<Separator />
<label label="Default Microphone" halign={Gtk.Align.START} />
{bind(audioDevices, "default_microphone").as((microphone) => {
return <PlaybackEndpoint endpoint={microphone} />;
})}
<Expander
label={"All Microphones"}
visible={bind(audioDevices, "microphones").as(
(microphones) => microphones.length > 1,
)}
>
<Box spacing={5} vertical marginTop={10}>
{bind(audioDevices, "microphones").as((microphones) => {
return microphones.map((microphone) => (
<PlaybackEndpoint
endpoint={microphone}
visible={bind(microphone, "is_default").as((x) => !x)}
/>
));
})}
</Box>
</Expander>
<Separator />
<label label="Recording streams" halign={Gtk.Align.START} />
{bind(audioDevices, "recorders").as((streams) => {
if (streams.length === 0) {
return (
<label
label="No recording streams"
halign={Gtk.Align.START}
cssClasses={["no-streams"]}
/>
);
}
return streams.map((stream) => <PlaybackEndpoint endpoint={stream} />);
})}
</box>
);
}
export function Playback({ monitor }: { monitor: Gdk.Monitor }) {
const audioDevices = Wp.get_default()?.get_audio?.();
if (!audioDevices) {
return <label label="No WirePlumber" visible={false} />;
}
// const endpoints = new PlaybackEndpoints(WirePlumber);
// biome-ignore lint/style/noNonNullAssertion: <explanation>
const speaker = Wp.get_default()?.audio.defaultSpeaker!;
const volume = Variable.derive(
[bind(speaker, "volume"), bind(speaker, "mute")],
(v, m) => {
return m ? 0 : v;
},
);
const recording = bind(audioDevices, "recorders").as(
(recorders) => recorders.length > 0,
);
return (
<box
cssClasses={["Playback"]}
spacing={15}
onDestroy={() => {
volume.drop();
}}
setup={(self) => {
connectDropdown(
self,
<PlaybackDropdown audioDevices={audioDevices} />,
monitor,
);
}}
>
{bind(audioDevices, "default_speaker").as((speaker) => {
const volume = Variable.derive(
[bind(speaker, "volume"), bind(speaker, "mute")],
(v, m) => {
return m ? 0 : v;
},
);
return (
<>
<slider
cssClasses={["Slider"]}
inverted
hexpand
onChangeValue={({ value }) => {
speaker.volume = value;
speaker.mute = false;
}}
value={bind(volume)}
/>
<box
spacing={10}
onButtonPressed={() => {
speaker.mute = !speaker.mute;
}}
>
<image iconName={bind(speaker, "volumeIcon")} pixelSize={12} />
<label
label={bind(volume).as((v) =>
`${Math.floor(v * 100)}%`.padStart(4, " "),
)}
/>
<image
iconName={"microphone-custom"}
cssClasses={["recording"]}
pixelSize={16}
widthRequest={18}
hexpand
visible={bind(recording)}
/>
</box>
</>
);
})}
</box>
);
}

View file

@ -0,0 +1,62 @@
@use "../../variables.scss" as *;
.workspace {
all: unset; // unset default button styles
&:hover {
all: unset;
}
transition: opacity $transition;
opacity: 0.2;
&.focused {
opacity: 1;
}
&.add-left {
padding-left: 10px;
}
&.add-right {
padding-right: 10px;
}
}
calendar {
header {
padding-bottom: 10px;
border-bottom: 1px solid $bg-alt;
}
button {
margin-left: 2px;
margin-right: 2px;
padding: 3px;
}
grid {
padding-top: 10px;
label {
padding: 3px;
}
.today {
background-color: $accent;
color: $bg;
border-radius: 3px;
padding: 0;
margin: 3px;
}
.other-month {
color: $muted;
}
.day-name {
border-bottom: 1px solid $bg-alt;
}
.week-number {
border-right: 1px solid $bg-alt;
}
}
}

View file

@ -0,0 +1,102 @@
import Hyprland from "gi://AstalHyprland";
import { bind } from "astal";
import { Gdk, Gtk } from "astal/gtk4";
type WorkspacesProps = {
monitor: Hyprland.Monitor;
reverse?: boolean;
selectedWorkspaces?: number[];
};
const hypr = Hyprland.get_default();
const ICON_MAP = {
terminal: ["kitty", "com.mitchellh.ghostty"],
"firefox-custom": ["firefox", "firefox-developer-edition"],
"chrome-custom": ["google-chrome", "chromium"],
python: ["jetbrains-pycharm"],
"vscode-custom": ["Code", "code-oss"],
"git-symbolic": ["smerge", "sublime_merge"],
};
function Workspace(workspace: Hyprland.Workspace) {
const focused = bind(hypr, "focusedWorkspace").as((fw) => fw === workspace);
const icon = bind(workspace, "clients").as((clients) => {
if (clients.length === 0) {
return "circle";
}
const icons = clients
.map((client) => {
for (const [name, classes] of Object.entries(ICON_MAP)) {
if (classes.includes(client.get_class())) {
return name;
}
}
})
.filter(Boolean) as string[];
const count = icons.reduce(
(acc, cur) => {
acc[cur] = (acc[cur] || 0) + 1;
return acc;
},
{} as Record<string, number>,
);
// Don't return on a tie
if (
Object.values(count).filter((x) => x === count[Object.keys(count)[0]])
.length > 1
) {
return "circle-filled";
}
return Object.keys(count)[0] ?? "circle-filled";
});
return (
<button
cssClasses={focused.as((focused) =>
focused ? ["workspace", "focused"] : ["workspace"],
)}
cursor={Gdk.Cursor.new_from_name("pointer", null)}
onClicked={() => workspace.focus()}
valign={Gtk.Align.CENTER}
>
<image
iconName={bind(icon).as((icon) => `${icon}-symbolic`)}
pixelSize={18}
/>
</button>
);
}
export function Workspaces({
reverse,
monitor,
selectedWorkspaces,
}: WorkspacesProps) {
const workspaces = bind(hypr, "workspaces")
.as((workspaces) => workspaces.filter((ws) => ws.monitor === monitor))
.as((workspaces) => {
if (!selectedWorkspaces) {
return workspaces;
}
return selectedWorkspaces.map((id) => {
return (
workspaces.find((ws) => ws.get_id() === id) ??
Hyprland.Workspace.dummy(id, monitor)
);
});
})
.as((x) => (reverse ? x.reverse() : x));
return (
<box cssClasses={["Workspaces"]} valign={Gtk.Align.CENTER} spacing={10}>
{workspaces.as((ws) => ws.map(Workspace))}
</box>
);
}

View file

@ -0,0 +1,42 @@
import GLib from "gi://GLib";
type ignoreFn = (test: string) => boolean;
interface Config {
monitor: {
main: string;
};
notification: {
ignore: ignoreFn[];
};
tray: {
ignore: ignoreFn[];
};
}
const envArray = (name: string): string[] => {
const value = GLib.getenv(name);
if (!value) return [];
return value.split(",");
};
const envIgnoreArray = (name: string): ignoreFn[] => {
return envArray(name).map((r: string) => {
if (r.startsWith("/")) {
return new RegExp(r.slice(1, -1)).test;
}
return (test: string) => test === r;
});
};
export default {
monitor: {
main: GLib.getenv("ASTRAL_PRIMARY_MONITOR") || "",
},
notification: {
ignore: envIgnoreArray("ASTRAL_NOTIFICATION_IGNORE"),
},
tray: {
ignore: envIgnoreArray("ASTRAL_TRAY_IGNORE"),
},
} as Config;

View file

@ -0,0 +1,51 @@
@use "./variables" as *;
@use "./bar/Bar.scss" as *;
@use "./notification/Notification.scss" as *;
@use "./bar/sections/Workspace.scss" as *;
@use "./bar/sections/Dropdown.scss" as *;
@use "./bar/sections/Media.scss" as *;
@use "./bar/sections/Playback.scss" as *;
// Global styles
* {
all: unset; //Unsets everything so you can style everything from scratch
font-family: Hack;
}
separator {
background-color: $bg-alt;
min-height: 1px;
}
button {
transition: border-color $transition;
background-color: $bg-alt;
border-radius: $radius;
border: 2px solid $bg-alt;
padding: 5px;
&.small {
padding: 3px;
}
&.large {
padding: 7px 15px;
}
&:hover {
border: 2px solid $accent;
}
}
expander-widget {
title {
color: $fg;
}
expander {
-gtk-icon-source: -gtk-icontheme("pan-end-symbolic");
&:checked {
-gtk-icon-source: -gtk-icontheme("pan-down-symbolic");
}
}
}

View file

@ -0,0 +1,32 @@
import Bar from "./bar/Bar";
import SecondaryBar from "./bar/SecondaryBar";
import NotificationPopups from "./notification/NotificationPopups";
import { getMonitors } from "./utils/monitors";
export default function main() {
const { main, secondary } = getMonitors();
// Notify
NotificationPopups(main);
// Set bars
Bar(main);
for (const monitor of secondary) {
SecondaryBar(monitor, monitor.relation);
}
// const bars = new Map<Gdk.Monitor, Gtk.Widget>()
// bars.set(main, Bar(main))
// for (const monitor of secondary) {
// bars.set(monitor, SecondaryBar(monitor, monitor.relation))
// }
// App.connect("monitor-added", (_, gdkmonitor) => {
// bars.set(gdkmonitor, Bar(gdkmonitor))
// })
// App.connect("monitor-removed", (_, gdkmonitor) => {
// bars.get(gdkmonitor)?.destroy()
// bars.delete(gdkmonitor)
// })
}

View file

@ -0,0 +1,47 @@
@use "../variables.scss" as *;
.Notification {
border-radius: $radius;
background-color: $bg;
margin: 10px;
padding: 15px;
border: 2px solid $accent;
&.low {
border: 2px solid $muted;
.app-name {
color: $muted;
}
}
&.critical {
border: 2px solid $error;
.app-name {
color: $error;
}
}
.app-name {
font-weight: bold;
}
.content {
.body {
color: $fg-alt;
}
}
}
.NotificationMenu {
border-radius: $radius;
background-color: $bg;
margin: 10px;
padding: 15px;
border: 2px solid $accent;
button.disabled {
background-color: $muted;
border-color: $muted;
color: $fg;
}
}

View file

@ -0,0 +1,117 @@
import Notifd from "gi://AstalNotifd";
import { GLib, timeout } from "astal";
import { type Astal, Gtk } from "astal/gtk4";
import { hook } from "astal/gtk4";
import { Separator } from "../widgets";
const ANIMATION_DURATION = 500;
const fileExists = (path: string) => GLib.file_test(path, GLib.FileTest.EXISTS);
const time = (time: number, format = "%H:%M") =>
// biome-ignore lint/style/noNonNullAssertion: <explanation>
GLib.DateTime.new_from_unix_local(time).format(format)!;
const urgency = (urgency: Notifd.Urgency) => {
if (urgency === Notifd.Urgency.LOW) return "low";
if (urgency === Notifd.Urgency.CRITICAL) return "critical";
return "normal";
};
type Props = {
notification: Notifd.Notification;
};
const resolveImageProps = (image?: string) => {
if (!image) {
return { visible: false };
}
if (fileExists(image)) {
return { file: image };
}
return { iconName: image };
};
export default function Notification({ notification: n }: Props) {
const icon = n.appIcon || n.desktopEntry;
return (
<box
cssClasses={["Notification", urgency(n.urgency)]}
onButtonReleased={(_, asdf) => {
n.dismiss();
}}
vertical
widthRequest={300}
spacing={10}
>
<box cssClasses={["header"]} spacing={10}>
<image cssClasses={["app-icon"]} {...resolveImageProps(icon)} />
<label
cssClasses={["app-name"]}
maxWidthChars={40}
wrap
label={n.appName || "Unknown"}
hexpand
halign={Gtk.Align.START}
/>
<label
cssClasses={["time"]}
halign={Gtk.Align.END}
label={time(n.time)}
/>
</box>
<Separator />
<box cssClasses={["content"]} spacing={10}>
<image
{...resolveImageProps(
n.summary === "message"
? "/home/nickolaj/Downloads/billigvvs.dk.png"
: n.image,
)}
pixelSize={160}
halign={Gtk.Align.START}
valign={Gtk.Align.START}
/>
<box vertical spacing={10}>
<label
hexpand
cssClasses={["summary"]}
label={n.summary}
wrap
halign={Gtk.Align.START}
maxWidthChars={n.image ? 40 : 60}
/>
{n.body && (
<label
hexpand
cssClasses={["body"]}
useMarkup
label={n.body}
halign={Gtk.Align.START}
maxWidthChars={n.summary === "message" ? 40 : 60}
wrap
/>
)}
{n.get_actions().length > 0 && (
<box
cssClasses={["actions"]}
halign={Gtk.Align.CENTER}
spacing={10}
vertical={n.get_actions().length > 2}
>
{n.get_actions().map(({ label, id }) => (
<button
onClicked={(eve) => {
n.invoke(id);
}}
cssClasses={["large"]}
>
<label label={label} maxWidthChars={20} wrap />
</button>
))}
</box>
)}
</box>
</box>
</box>
);
}

View file

@ -0,0 +1,109 @@
import Notifd from "gi://AstalNotifd";
import { Variable, bind, interval, timeout } from "astal";
import { App, hook } from "astal/gtk4";
import { Astal, type Gdk, Gtk } from "astal/gtk4";
import config from "../config";
import { VarMap } from "../utils/var-map";
import Notification from "./Notification";
class NotificationMap extends VarMap<number, Gtk.Widget> {
#notifd = Notifd.get_default();
get() {
return [...this.map.entries()].sort(([a], [b]) => b - a).map(([_, v]) => v);
}
constructor() {
super();
this.#notifd.connect("notified", (_, id) => {
const notification = this.#notifd.get_notification(id);
if (notification === null) {
return;
}
// Ignore notifications based on the app name
for (const test of config.notification.ignore) {
if (test(notification.app_name)) {
notification.dismiss();
return;
}
}
this.set(id, Notification({ notification }));
});
// notifications can be closed by the outside before
// any user input, which have to be handled too
this.#notifd.connect("resolved", (_, id) => {
this.delete(id);
});
}
}
export default function NotificationPopups(gdkmonitor: Gdk.Monitor) {
const { TOP, RIGHT } = Astal.WindowAnchor;
const notificationsMap = new NotificationMap();
const offset = new Variable(0);
const count = bind(notificationsMap).as((map) => map.length);
const offsetNotifications = Variable.derive(
[notificationsMap, offset],
(map, offset) => map.slice(offset),
);
const offsetLength = bind(offsetNotifications).as((map) => map.length);
return (
<window
name={"notifications"}
application={App}
cssClasses={["NotificationPopups"]}
gdkmonitor={gdkmonitor}
layer={Astal.Layer.OVERLAY}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
anchor={TOP | RIGHT}
visible={count.as((n) => n > 0)}
vexpand={true}
valign={Gtk.Align.START}
>
<box vertical={true} halign={Gtk.Align.END}>
{bind(offsetNotifications).as((map) => map.slice(0, 5))}
<box
cssClasses={["NotificationMenu"]}
visible={count.as((n) => n > 2)}
halign={Gtk.Align.END}
spacing={10}
>
<box
visible={count.as((n) => n > 5)}
vertical
spacing={10}
widthRequest={50}
>
{bind(offset).as((n) => (
<button
hexpand
onClicked={() => offset.set(Math.max(offset.get() - 5, 0))}
cssClasses={n > 0 ? [] : ["disabled"]}
label={n > 0 ? `${n}` : "▲ 0"}
/>
))}
{offsetLength.as((n) => (
<button
hexpand
onClicked={() =>
offset.set(Math.min(offset.get() + 5, count.get() - 5))
}
cssClasses={n > 5 ? [] : ["disabled"]}
label={n > 5 ? `${n - 5}` : "▼ 0"}
/>
))}
</box>
<button
cssClasses={["large"]}
onClicked={() => notificationsMap.clear()}
label={count.as((n) => `Dismiss all (${n})`)}
/>
</box>
</box>
</window>
);
}

View file

@ -0,0 +1,13 @@
import { Gdk, Gtk } from "astal/gtk4";
export const hasIcon = (name: string): boolean => {
if (!name) {
return false;
}
const display = Gdk.Display.get_default();
if (!display) {
return false;
}
return Gtk.IconTheme.get_for_display(display).has_icon(name);
};

View file

@ -0,0 +1,59 @@
import { GLib, Gio } from "astal";
interface WalkDirOptions {
pattern: string;
}
export const findFiles = async (
path: string | Gio.File,
options?: WalkDirOptions,
): Promise<string[]> => {
const patternSpec = options?.pattern
? GLib.PatternSpec.new(options.pattern)
: null;
// const src = Gio.File.new_for_path(path);
const src = typeof path === "string" ? Gio.File.new_for_path(path) : path;
const root = Gio.File.new_for_path(".");
const iter = src.enumerate_children(
"standard::*",
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
null,
);
const result = [];
while (true) {
const [open, info, file] = iter.iterate(null);
if (!open || !info) {
break; // end of iterator
}
if (!file) {
throw new Error("Failed to get file");
}
const file_type = info.get_file_type();
if (file_type === Gio.FileType.DIRECTORY) {
result.push(...(await findFiles(file, options)));
}
if (info.get_file_type() !== Gio.FileType.REGULAR) {
continue;
}
const name = info.get_name();
const path = root.get_relative_path(file);
if (!path) {
throw new Error("Failed to get relative path");
}
if (patternSpec && !patternSpec.match_string(name)) {
continue;
}
result.push(`./${path}`);
}
return result;
};

View file

@ -0,0 +1,53 @@
import Hyprland from "gi://AstalHyprland";
import { App, type Gdk } from "astal/gtk4";
import config from "../config";
export type SecondaryMonitor = Gdk.Monitor & {
relation: "left" | "right" | "top" | "bottom";
};
export const getMonitors = (): {
main: Gdk.Monitor;
secondary: SecondaryMonitor[];
} => {
const scanFn = [
// Monitor in config
(monitor: Gdk.Monitor) => config.monitor.main === monitor.get_connector(),
// First monitor
() => true,
];
const monitors = App.get_monitors();
console.log("config.monitor.main", config.monitor.main);
const main =
scanFn.map((fn) => monitors.find(fn)).find((m) => m) || monitors[0];
const secondary = monitors
.filter((m) => m !== main)
.map((m) => {
const monitor = m as SecondaryMonitor;
const { x: mx, y: my } = main.get_geometry();
const { x, y } = m.get_geometry();
const verticalDiff = Math.abs(y - my);
const horizontalDiff = Math.abs(x - mx);
if (verticalDiff > horizontalDiff) {
monitor.relation = y < my ? "top" : "bottom";
} else {
monitor.relation = x < mx ? "left" : "right";
}
return monitor;
});
return { main, secondary };
};
export function getHyprlandMonitor(monitor: Gdk.Monitor): Hyprland.Monitor {
const hyprland = Hyprland.get_default();
const monitors = hyprland.get_monitors();
for (const hyprmonitor of monitors) {
if (hyprmonitor.get_name() === monitor.get_connector()) return hyprmonitor;
}
throw new Error("GDK monitor does not map to a Hyprland monitor");
}

View file

@ -0,0 +1,24 @@
import { GLib, type Time, timeout } from "astal";
const runningTimeouts: Map<string, Time> = new Map();
export const cancelTimeout = (id: string) => {
const runningTimeout = runningTimeouts.get(id);
if (runningTimeout) {
runningTimeout.cancel();
runningTimeouts.delete(id);
}
};
export const cancelableTimeout = (
callback: () => void,
id: string,
delay: number,
) => {
cancelTimeout(id);
runningTimeouts.set(
id,
timeout(delay, () => {
callback();
runningTimeouts.delete(id);
}),
);
};

View file

@ -0,0 +1,55 @@
import type { Subscribable } from "astal/binding";
import { Gtk } from "astal/gtk4";
export class VarMap<K, T = Gtk.Widget> implements Subscribable {
#subs = new Set<(v: T[]) => void>();
protected map: Map<K, T>;
#notifiy() {
const value = this.get();
for (const sub of this.#subs) {
sub(value);
}
}
#delete(key: K) {
const v = this.map.get(key);
if (v instanceof Gtk.Widget) {
v.unparent();
}
this.map.delete(key);
}
constructor(initial?: Iterable<[K, T]>) {
this.map = new Map(initial);
}
clear() {
for (const key of this.map.keys()) {
this.#delete(key);
}
this.#notifiy();
}
set(key: K, value: T) {
this.#delete(key);
this.map.set(key, value);
this.#notifiy();
}
delete(key: K) {
this.#delete(key);
this.#notifiy();
}
get() {
return [...this.map.values()];
}
subscribe(callback: (v: T[]) => void) {
this.#subs.add(callback);
return () => this.#subs.delete(callback);
}
}

View file

@ -0,0 +1,13 @@
$bg: #1c1b1a;
$bg-alt: #282726;
$fg: #dad8ce;
$fg-alt: #b7b5ac;
$muted: #878580;
$accent: #cf6a4c;
$error: #d14d41;
$radius: 8px;
$transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
$font-large: 14px;
$icon-size: 12px;

View file

@ -0,0 +1,90 @@
import { register } from "astal";
import { Gtk, astalify } from "astal/gtk4";
export const Separator = astalify<
Gtk.Separator,
Gtk.Separator.ConstructorProps
>(Gtk.Separator, {});
export const Expander = astalify<Gtk.Expander, Gtk.Expander.ConstructorProps>(
Gtk.Expander,
{
getChildren(self) {
const child = self.get_child();
if (child) return [child];
return [];
},
setChildren(self, children) {
if (children.length === 0) self.set_child(null);
if (children.length > 1) {
console.error("Expander can only have one child.");
return;
}
self.set_child(children[0]);
},
},
);
export const Calendar = astalify<Gtk.Calendar, Gtk.Calendar.ConstructorProps>(
Gtk.Calendar,
{
getChildren(self) {
return [];
},
setChildren(self, children) {},
},
);
export const ScrolledWindow = astalify<
Gtk.ScrolledWindow,
Gtk.ScrolledWindow.ConstructorProps
>(Gtk.ScrolledWindow, {
getChildren(self) {
const child = self.get_child();
if (child) return [child];
return [];
},
setChildren(self, children) {
if (children.length === 0) self.set_child(null);
if (children.length > 1) {
console.error("ScrolledWindow can only have one child.");
return;
}
self.set_child(children[0]);
},
});
export const Viewport = astalify<Gtk.Viewport, Gtk.Viewport.ConstructorProps>(
Gtk.Viewport,
{
getChildren(self) {
const child = self.get_child();
if (child) return [child];
return [];
},
setChildren(self, children) {
if (children.length === 0) self.set_child(null);
if (children.length > 1) {
console.error("Viewport can only have one child.");
return;
}
self.set_child(children[0]);
},
},
);
export const FlowBox = astalify<Gtk.FlowBox, Gtk.FlowBox.ConstructorProps>(
Gtk.FlowBox,
{
setChildren(self, children) {
self.remove_all();
for (const child of children) {
self.append(child);
}
},
},
);

View file

@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"experimentalDecorators": true,
"strict": true,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"jsxImportSource": "astal/gtk4"
}
}

View file

@ -0,0 +1,6 @@
{pkgs, ...}: {
fonts.enableDefaultPackages = true;
fonts.packages = with pkgs; [
(nerdfonts.override {fonts = ["Hack"];})
];
}

View file

@ -0,0 +1,10 @@
{pkgs, ...}: {
services.greetd = {
enable = true;
settings = {
default_session = {
command = "${pkgs.greetd.tuigreet}/bin/tuigreet --user-menu";
};
};
};
}

View file

@ -0,0 +1,38 @@
{pkgs, ...}: {
environment.systemPackages = with pkgs; [
nautilus
gnome-photos
evince
nautilus-open-any-terminal
sushi
];
services.gvfs.enable = true;
programs.dconf.enable = true;
fireproof.home-manager = {
home.pointerCursor = {
gtk.enable = true;
name = "Adwaita";
package = pkgs.adwaita-icon-theme;
size = 24;
};
gtk = {
enable = true;
theme = {
name = "adw-gtk3-dark";
package = pkgs.adw-gtk3;
};
gtk3.extraConfig = {gtk-application-prefer-dark-theme = true;};
gtk3.extraCss = builtins.readFile ./theme.css;
gtk4.extraConfig = {gtk-application-prefer-dark-theme = true;};
gtk4.extraCss = builtins.readFile ./theme.css;
};
dconf = {
enable = true;
settings."org/gnome/desktop/interface".color-scheme = "prefer-dark";
};
};
}

View file

View file

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="900">
<g>
<path fill="#1C1B1A" d="M0 0h1600v900H0z"/>
<path fill="#cf6a4c" d="M400,350 L200,550 L1200,550 L1400,350"/>
<path fill="#b14d2f" d="M350,400 L200,550 L1200,550 L1350,400"/>
<path fill="#a1462b" d="M300,450 L200,550 L1200,550 L1300,450"/>
<path fill="#71311e" d="M250,500 L200,550 L1200,550 L1250,500"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 410 B

View file

@ -0,0 +1,214 @@
{
lib,
config,
pkgs,
...
}:
with lib; let
cfg = config.fireproof;
primaryMonitorName =
if builtins.length config.monitors > 0
then (builtins.elemAt config.monitors 0).name
else "";
mkKeyboard = name: {
inherit name;
kb_layout = "eu";
};
in {
imports = [
./hyprpolkitagent.nix
./hyprpaper.nix
];
config = {
programs.uwsm.enable = true;
programs.hyprland = {
enable = true;
withUWSM = true;
xwayland.enable = true;
};
security.polkit.enable = true;
xdg.portal.enable = true;
services.dbus.enable = true;
hardware = {
graphics = {
enable = true;
};
};
environment.sessionVariables.NIXOS_OZONE_WL = "1";
environment.systemPackages = with pkgs; [
hyprcursor
];
fireproof.home-manager = {
wayland.windowManager.hyprland = {
enable = true;
xwayland.enable = true;
systemd.enable = false; # Conficts with UWSM
settings = {
env = [
"HYPRCURSOR_THEME,Adwaita"
"HYPRCURSOR_SIZE,24"
];
monitor =
map (
m: let
name =
if m.name != null
then m.name
else "";
resolution =
if m.resolution != null
then m.resolution
else "preferred";
refreshRate =
if m.refreshRate != null
then "@${builtins.toString m.refreshRate}"
else "";
position =
if m.position != null
then m.position
else "auto";
in "${name}, ${resolution}${refreshRate}, ${position}, 1"
)
config.monitors;
exec = ["systemctl --user start hyprpaper"];
input = {
# Most unknown keyboards will be of the DK layout, we set known keyboards to eu in `devices`
kb_layout = "dk";
kb_options = "caps:backspace";
# Cursor focus will be detached from keyboard focus. Clicking on a window will move keyboard focus to that window.
follow_mouse = 2;
touchpad = {
natural_scroll = false;
};
sensitivity = 0;
accel_profile = "flat";
};
workspace =
if primaryMonitorName != ""
then [
"1, monitor:${primaryMonitorName}, persistent:true, default:true"
"2, monitor:${primaryMonitorName}, persistent:true"
"3, monitor:${primaryMonitorName}, persistent:true"
"4, monitor:${primaryMonitorName}, persistent:true"
"5, monitor:${primaryMonitorName}, persistent:true"
]
else [
"1, persistent:true, default:true"
"2, persistent:true"
"3, persistent:true"
"4, persistent:true"
"5, persistent:true"
];
# Names can be found with:
# $ hyprctl devices -j | jq '.["keyboards"].[].name' -r | grep -vE "(system|consumer)-control"
device = map mkKeyboard [
"splitkb-kyria-rev1"
"zsa-technology-labs-inc-ergodox-ez-shine"
"zsa-technology-labs-inc-ergodox-ez-shine-keyboard"
];
general = {
gaps_in = 5;
gaps_out = 10;
border_size = 2;
"col.active_border" = "rgb(cf6a4c)";
"col.inactive_border" = "rgb(343331)";
layout = "dwindle";
};
cursor = {
no_warps = true;
};
misc = {
focus_on_activate = true;
disable_hyprland_logo = true;
force_default_wallpaper = 0;
middle_click_paste = false;
font_family = "Hack Nerd Font";
};
decoration = {
rounding = 4;
shadow = {
enabled = true;
range = 4;
render_power = 3;
color = "rgba(1a1a1aee)";
};
};
animations = {
enabled = true;
animation = [
"windows, 1, 4, default"
"windowsOut, 1, 4, default, popin 80%"
"border, 1, 10, default"
"borderangle, 1, 8, default"
"fade, 1, 7, default"
"workspaces, 1, 3, default"
];
};
dwindle = {
pseudotile = true;
preserve_split = true;
force_split = 2;
use_active_for_splits = true;
};
bind = [
"SUPER, RETURN, exec, ${getExe config.programs.uwsm.package} app -- ${cfg.default-apps.terminal}"
"SUPER, BACKSPACE, killactive"
"SUPER, SPACE, exec, ${getExe config.programs.uwsm.package} app -- walker"
"SUPER, p, exec, ${getExe config.programs.uwsm.package} app -- loginctl lock-session"
"SUPER, S, togglefloating"
"SUPER, A, pseudo"
"SUPER, D, fullscreen"
"SUPER, BACKSLASH, togglesplit"
"SUPER, M, togglegroup"
"SUPER, left, movefocus, l"
"SUPER, right, movefocus, r"
"SUPER, up, movefocus, u"
"SUPER, down, movefocus, d"
"SUPER, h, movefocus, l"
"SUPER, l, movefocus, r"
"SUPER, k, movefocus, u"
"SUPER, j, movefocus, d"
"SUPER, p, submap, preselect"
"SUPER, q, workspace, 1"
"SUPER, w, workspace, 2"
"SUPER, e, workspace, 3"
"SUPER, r, workspace, 4"
"SUPER, t, workspace, 5"
"SUPER SHIFT, q, movetoworkspace, 1"
"SUPER SHIFT, w, movetoworkspace, 2"
"SUPER SHIFT, e, movetoworkspace, 3"
"SUPER SHIFT, r, movetoworkspace, 4"
"SUPER SHIFT, t, movetoworkspace, 5"
"SUPER SHIFT, h, workspace, r-1"
"SUPER SHIFT, l, workspace, r+1"
"SUPER, tab, changegroupactive, f"
"SUPER SHIFT, tab, changegroupactive, b"
];
bindm = [
"SUPER, mouse:272, movewindow"
"SUPER, mouse:273, resizewindow"
];
layerrule = [
"noanim, gtk4-layer-shell"
];
};
};
};
};
}

View file

@ -0,0 +1,39 @@
{
pkgs,
lib,
...
}: let
background = pkgs.stdenvNoCC.mkDerivation {
pname = "desktop-background";
version = "0.1";
src = lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions [
./background.svg
];
};
nativeBuildInputs = [pkgs.inkscape];
buildPhase = ''
inkscape -w 3840 -h 2160 background.svg -o background.png
'';
installPhase = ''
mkdir -p $out/share/backgrounds
cp *.svg *.png $out/share/backgrounds
'';
};
png = background + "/share/backgrounds/background.png";
in {
fireproof.home-manager = {
services.hyprpaper = {
enable = true;
settings = {
preload = [png];
wallpaper = [",${png}"];
};
};
};
}

View file

@ -0,0 +1,26 @@
{
pkgs,
config,
...
}: {
config = {
fireproof.home-manager.systemd.user.services.hyprpolkitagent = {
Unit = {
Description = "Hyprland Polkit Authentication Agent";
};
Service = {
Type = "simple";
ExecStart = "${pkgs.hyprpolkitagent}/libexec/hyprpolkitagent";
Restart = "on-failure";
RestartSec = 1;
TimeoutStopSec = 10;
Slice = "session.slice";
};
Install = {
WantedBy = ["graphical-session.target"];
};
};
};
}

View file

@ -0,0 +1,23 @@
{
inputs,
pkgs,
lib,
config,
...
}: {
environment.systemPackages = [
inputs.walker.packages.${pkgs.system}.walker
];
fireproof.home-manager = {
imports = [inputs.walker.homeManagerModules.default];
programs.walker = {
enable = true;
runAsService = true;
theme = import ./theme.nix;
config = {
app_launch_prefix = "${lib.getExe config.programs.uwsm.package} app -- ";
};
};
};
}

View file

@ -0,0 +1,433 @@
{
style = ''
@define-color fg #DAD8CE;
@define-color fg-alt #B7B5AC;
@define-color bg #1C1B1A;
@define-color bg-alt #282726;
@define-color accent #CF6A4C;
@define-color muted #878580;
@define-color error #D14D41;
#window,
#box,
#aiScroll,
#aiList,
#search,
#password,
#input,
#prompt,
#clear,
#typeahead,
#list,
child,
scrollbar,
slider,
#item,
#text,
#label,
#bar,
#sub,
#activationlabel {
all: unset;
font-family: Hack;
}
#cfgerr {
background: rgba(255, 0, 0, 0.4);
margin-top: 20px;
padding: 8px;
font-size: 14px;
font-family: "Hack Nerd Font";
}
#window {
color: @fg;
}
#box {
border-radius: 8px;
background: @bg;
padding: 8px;
border: 2px solid @accent;
}
#search {
background: @bg-alt;
padding: 8px;
}
#prompt {
margin-left: 4px;
margin-right: 12px;
color: @fg;
opacity: 0.2;
}
#clear {
color: @fg;
@define-color fg #DAD8CE;
@define-color fg-alt #B7B5AC;
@define-color bg #1C1B1A;
@define-color bg-alt #282726;
@define-color accent #CF6A4C;
@define-color muted #878580;
@define-color error #D14D41;
#window,
#box,
#aiScroll,
#aiList,
#search,
#password,
#input,
#prompt,
#clear,
#typeahead,
#list,
child,
scrollbar,
slider,
#item,
#text,
#label,
#bar,
#sub,
#activationlabel {
all: unset;
font-family: Hack;
}
#cfgerr {
background: rgba(255, 0, 0, 0.4);
margin-top: 20px;
padding: 8px;
font-size: 14px;
font-family: "Hack Nerd Font";
}
#window {
color: @fg;
}
#box {
border-radius: 8px;
background: @bg;
padding: 8px;
border: 2px solid @accent;
}
#search {
background: @bg-alt;
padding: 8px;
}
#prompt {
margin-left: 4px;
margin-right: 12px;
color: @fg;
opacity: 0.2;
}
#clear {
color: @fg;
opacity: 0.8;
}
#password,
#input,
#typeahead {
border-radius: 2px;
}
#input {
background: none;
}
#password {
}
#spinner {
padding: 8px;
}
#typeahead {
color: @fg;
opacity: 0.8;
}
#input placeholder {
opacity: 0.5;
}
#list {
}
child {
padding: 8px;
border-radius: 2px;
}
child:selected,
child:hover {
background: alpha(@accent, 0.4);
}
#item {
}
#icon {
margin-right: 8px;
}
#text {
}
#label {
font-weight: 500;
}
#sub {
opacity: 0.5;
font-size: 0.8em;
}
#activationlabel {
}
#bar {
}
.barentry {
}
.activation #activationlabel {
}
.activation #text,
.activation #icon,
.activation #search {
opacity: 0.5;
}
.aiItem {
padding: 10px;
border-radius: 2px;
color: @fg;
background: @bg;
}
.aiItem.user {
padding-left: 0;
padding-right: 0;
}
.aiItem.assistant {
background: @bg-alt;
}
opacity: 0.8;
}
#password,
#input,
#typeahead {
border-radius: 2px;
}
#input {
background: none;
}
#password {
}
#spinner {
padding: 8px;
}
#typeahead {
color: @fg;
opacity: 0.8;
}
#input placeholder {
opacity: 0.5;
}
#list {
}
child {
padding: 8px;
border-radius: 2px;
}
child:selected,
child:hover {
background: alpha(@accent, 0.4);
}
#item {
}
#icon {
margin-right: 8px;
}
#text {
}
#label {
font-weight: 500;
}
#sub {
opacity: 0.5;
font-size: 0.8em;
}
#activationlabel {
}
#bar {
}
.barentry {
}
.activation #activationlabel {
}
.activation #text,
.activation #icon,
.activation #search {
opacity: 0.5;
}
.aiItem {
padding: 10px;
border-radius: 2px;
color: @fg;
background: @bg;
}
.aiItem.user {
padding-left: 0;
padding-right: 0;
}
.aiItem.assistant {
background: @bg-alt;
}
'';
layout = {
ui = {
anchors = {
bottom = true;
left = true;
right = true;
top = true;
};
window = {
h_align = "fill";
v_align = "fill";
box = {
h_align = "center";
v_align = "center";
width = 800;
height = 600;
bar = {
orientation = "horizontal";
position = "end";
entry = {
h_align = "fill";
h_expand = true;
icon = {
h_align = "center";
h_expand = true;
pixel_size = 24;
theme = "";
};
};
};
ai_scroll = {
name = "aiScroll";
h_align = "fill";
v_align = "fill";
margins = {
top = 8;
};
list = {
name = "aiList";
orientation = "vertical";
width = 400;
spacing = 10;
item = {
name = "aiItem";
h_align = "fill";
v_align = "fill";
x_align = 0;
y_align = 0;
wrap = true;
};
};
};
scroll = {
v_expand = true;
v_align = "fill";
list = {
v_expand = true;
h_expand = true;
h_align = "fill";
v_align = "fill";
item = {
activation_label = {
h_align = "fill";
v_align = "fill";
width = 20;
x_align = 0;
y_align = 0;
};
icon = {
pixel_size = 26;
theme = "";
};
};
margins = {
top = 8;
};
};
};
search = {
h_expand = false;
v_expand = false;
prompt = {
name = "prompt";
icon = "edit-find";
theme = "";
pixel_size = 18;
h_align = "center";
v_align = "center";
};
clear = {
name = "clear";
icon = "edit-clear";
theme = "";
pixel_size = 18;
h_align = "center";
v_align = "center";
};
input = {
h_align = "fill";
h_expand = true;
icons = true;
};
spinner = {
hide = true;
};
};
};
};
};
};
}

14
modules/dev/docker.nix Normal file
View file

@ -0,0 +1,14 @@
{
username,
pkgs,
...
}: {
environment.systemPackages = [
pkgs.docker
pkgs.docker-compose
];
virtualisation.docker.enable = true;
virtualisation.docker.storageDriver = "btrfs";
users.extraGroups.docker.members = [username];
}

5
modules/dev/just.nix Normal file
View file

@ -0,0 +1,5 @@
{pkgsUnstable, ...}: {
environment.systemPackages = [
pkgsUnstable.just
];
}

30
modules/dev/k8s.nix Normal file
View file

@ -0,0 +1,30 @@
{
pkgs,
username,
config,
...
}: {
environment.systemPackages = [
pkgs.kubectl
];
age.secrets.k8s-ao-dev = {
rekeyFile = ../../secrets/k8s/ao-dev.age;
path = "/home/${username}/.kube/config.ao-dev";
mode = "0600";
owner = username;
};
age.secrets.k8s-ao-prod = {
rekeyFile = ../../secrets/k8s/ao-prod.age;
path = "/home/${username}/.kube/config.ao-prod";
mode = "0600";
owner = username;
};
fireproof.home-manager = {
home.sessionVariables = {
KUBECONFIG = "${config.age.secrets.k8s-ao-dev.path}:${config.age.secrets.k8s-ao-prod.path}:$HOME/.kube/config";
};
};
}

18
modules/dev/python.nix Normal file
View file

@ -0,0 +1,18 @@
{
pkgs,
pkgsUnstable,
...
}: {
environment.systemPackages = [
pkgsUnstable.uv
pkgsUnstable.rye
pkgs.python3
];
# uv tool adds executable to $HOME/.local/bin, so add it to PATH
fireproof.home-manager = {
home.sessionPath = [
"$HOME/.local/bin"
];
};
}

5
modules/dev/tilt.nix Normal file
View file

@ -0,0 +1,5 @@
{pkgsUnstable, ...}: {
environment.systemPackages = [
pkgsUnstable.tilt
];
}

12
modules/devenv.nix Normal file
View file

@ -0,0 +1,12 @@
{
imports = [
./apps/pycharm.nix
./apps/vscode.nix
./apps/sublime-merge.nix
./apps/virtualbox.nix
./dev/python.nix
./dev/k8s.nix
./dev/docker.nix
./dev/tilt.nix
];
}

15
modules/graphical.nix Normal file
View file

@ -0,0 +1,15 @@
{
imports = [
./hardware/monitors.nix
./hardware/audio.nix
./desktop/fonts.nix
./desktop/greetd.nix
./desktop/hyprland/default.nix
./desktop/astal/default.nix
./desktop/walker/default.nix
./desktop/gtk/default.nix
./apps/spotify.nix
./apps/firefox.nix
./apps/ghostty.nix
];
}

View file

@ -0,0 +1,11 @@
_: {
config = {
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
};
}

View file

@ -0,0 +1,42 @@
# https://github.com/ChangeCaps/nixos-config/tree/0cec356abc0e46ca6ba27b3cf01cd51273bd4a69
{lib, ...}: {
options.monitors = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "DP-1";
};
resolution = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "1920x1080";
};
refreshRate = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
example = 60;
};
position = lib.mkOption {
type = lib.types.str;
default = "0x0";
};
scale = lib.mkOption {
type = lib.types.float;
default = 1.0;
};
enable = lib.mkOption {
type = lib.types.bool;
default = true;
};
};
});
default = [];
};
}

4
modules/hardware/usb.nix Normal file
View file

@ -0,0 +1,4 @@
_: {
services.devmon.enable = true;
services.udisks2.enable = true;
}

View file

@ -0,0 +1,3 @@
_: {
services.pcscd.enable = true;
}

Some files were not shown because too many files have changed in this diff Show more