Compare commits

...

20 Commits

Author SHA1 Message Date
d9a4f392e6 Disable auto expand 2025-12-04 11:28:07 +01:00
54e9e1b567 Update stuff 2025-12-01 11:12:45 +01:00
df08da25f3 Remove events and deprecated endpoints from kga 2025-11-28 18:01:48 +01:00
75ab0bfd12 Switch to noctalia 2025-11-23 14:46:20 +01:00
cfa2a6e37e Init quickshell 2025-11-21 20:38:02 +01:00
6a43765a38 Enable telepresence 2025-11-20 18:29:18 +01:00
4dfec90cbc Update stuff 2025-11-18 11:54:26 +01:00
78542a3c83 Add ssh tunnel stuff 2025-11-14 23:14:05 +01:00
34faa25d3a Fix snack picker breaking change 2025-11-14 23:14:05 +01:00
9aec0768ae Use osc52 for every copy/paste in the terminal 2025-11-07 13:50:51 +01:00
0e83fa95e0 Add tmux get-clipboard patch 2025-11-07 12:20:32 +01:00
da06a331c4 Add slack 2025-11-07 09:50:41 +01:00
a90656fb9e Kitty remap 2025-11-07 09:20:49 +01:00
b0e4919f1a Mac improvmenets 2025-11-06 10:57:13 +01:00
a389ad81a5 Update stuff 2025-11-05 11:08:11 +01:00
d75ac8bb2e Update stuff 2025-10-30 17:07:13 +01:00
bbf687eae8 Cleanups 2025-10-29 15:58:09 +01:00
747374133e Fix taskfiel 2025-10-28 16:59:30 +01:00
c3f1877067 Disable on type formatting of biome 2025-10-26 11:41:24 +01:00
e637e48a66 Fix prompt 2025-10-26 11:41:24 +01:00
59 changed files with 670 additions and 3162 deletions

View File

@@ -7,8 +7,11 @@ trim_trailing_whitespace = true
insert_final_newline = true
indent_style = tab
indent_size = tab
max_line_length = 120
[{*.yaml,*.yml}]
[{*.yaml,*.yml,*.nix}]
indent_style = space
indent_size = 2
[*.qml]
indent_style = tab
indent_size = 2

View File

@@ -30,5 +30,5 @@ Format disk with:
nix-shell -p git go-task
git clone https://github.com/zoriya/flake
cd flake
sudo task install
sudo task install:host
```

View File

@@ -17,7 +17,7 @@
user = "greeter";
};
initial_session = {
command = ./niri-session.sh; # "${pkgs.niri}/bin/niri-session";
command = ./niri-session.sh;
user = user;
};
};

View File

@@ -178,7 +178,7 @@ binds {
Mod+Escape allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; }
}
workspace "emtpy" {
workspace "music" {
open-on-output "Dell Inc. DELL S2722QC 2HHZH24"
}
workspace "chat" {
@@ -197,10 +197,15 @@ window-rule {
open-focused false
}
window-rule {
match app-id="com.github.th_ch.youtube_music"
open-on-workspace "music"
}
window-rule {
match app-id="discord"
match app-id="vesktop"
match app-id="com.github.th_ch.youtube_music"
match app-id="slack"
open-on-workspace "chat"
}

230
flake.lock generated
View File

@@ -3,43 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"lastModified": 1761588595,
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_3": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
@@ -56,11 +24,11 @@
]
},
"locked": {
"lastModified": 1760948891,
"narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=",
"lastModified": 1763759067,
"narHash": "sha256-LlLt2Jo/gMNYAwOgdRQBrsRoOz7BPRkzvNaI/fzXi2Q=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04",
"rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0",
"type": "github"
},
"original": {
@@ -69,76 +37,6 @@
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat_2",
"gitignore": "gitignore",
"nixpkgs": [
"neovim-nightly",
"nixpkgs"
]
},
"locked": {
"lastModified": 1760663237,
"narHash": "sha256-BflA6U4AM1bzuRMR8QqzPXqh8sWVCNDzOdsxXEguJIc=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"neovim-nightly",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"hercules-ci-effects": {
"inputs": {
"flake-parts": [
"neovim-nightly",
"flake-parts"
],
"nixpkgs": [
"neovim-nightly",
"nixpkgs"
]
},
"locked": {
"lastModified": 1758022363,
"narHash": "sha256-ENUhCRWgSX4ni751HieNuQoq06dJvApV/Nm89kh+/A0=",
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"rev": "1a3667d33e247ad35ca250698d63f49a5453d824",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
@@ -146,11 +44,11 @@
]
},
"locked": {
"lastModified": 1761005073,
"narHash": "sha256-r6qbieh8iC1q1eCaWv15f4UIp8SeGffwswhNSA1Qk3s=",
"lastModified": 1764544324,
"narHash": "sha256-GVBGjO7UsmzLrlOJV8NlKSxukHaHencrJqWkCA6FkqI=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "84e1adb0cdd13f5f29886091c7234365e12b1e7f",
"rev": "e4e25a8c310fa45f2a8339c7972dc43d2845a612",
"type": "github"
},
"original": {
@@ -176,22 +74,18 @@
},
"neovim-nightly": {
"inputs": {
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"git-hooks": "git-hooks",
"hercules-ci-effects": "hercules-ci-effects",
"neovim-src": "neovim-src",
"nixpkgs": [
"nixpkgs"
],
"treefmt-nix": "treefmt-nix"
]
},
"locked": {
"lastModified": 1761005134,
"narHash": "sha256-9bSlfRleXFl50M6AnurWr1oKDTk3uF5DaTVHxeds0CY=",
"lastModified": 1764547590,
"narHash": "sha256-PDVKI5QCPfzVCPY/ZYAQTHGwC9ksT8ISNVaVZDVzb54=",
"owner": "nix-community",
"repo": "neovim-nightly-overlay",
"rev": "3a6201e41d13f1a73b2e2c734dbd36b4c42584b0",
"rev": "71ba04d8a26327031fd58284af4a4891d7b3c842",
"type": "github"
},
"original": {
@@ -203,11 +97,11 @@
"neovim-src": {
"flake": false,
"locked": {
"lastModified": 1761000337,
"narHash": "sha256-fBz9U/k/YWoS4QgcoQ54NKDEopTdL2zI0gzLlWv/xR8=",
"lastModified": 1764519732,
"narHash": "sha256-R0UspjBwPi5St0Dxq9Ej9ejJ34K8ivF5WeCS9dMvvVQ=",
"owner": "neovim",
"repo": "neovim",
"rev": "b67eff38fe19876ab228007897224ec04b58aa40",
"rev": "d62bbe24cbe5311ce595d73a0c40dc87af989666",
"type": "github"
},
"original": {
@@ -223,11 +117,11 @@
]
},
"locked": {
"lastModified": 1760721282,
"narHash": "sha256-aAHphQbU9t/b2RRy2Eb8oMv+I08isXv2KUGFAFn7nCo=",
"lastModified": 1764161084,
"narHash": "sha256-HN84sByg9FhJnojkGGDSrcjcbeioFWoNXfuyYfJ1kBE=",
"owner": "LnL7",
"repo": "nix-darwin",
"rev": "c3211fcd0c56c11ff110d346d4487b18f7365168",
"rev": "e95de00a471d07435e0527ff4db092c84998698e",
"type": "github"
},
"original": {
@@ -243,11 +137,11 @@
]
},
"locked": {
"lastModified": 1760846226,
"narHash": "sha256-xmU8kAsRprJiTGBTaGrwmjBP3AMA9ltlrxHKFuy5JWc=",
"lastModified": 1764475780,
"narHash": "sha256-77jL5H5x51ksLiOUDjY0ZK8e2T4ZXLhj3ap8ETvknWI=",
"owner": "nix-community",
"repo": "nix-index-database",
"rev": "5024e1901239a76b7bf94a4cd27f3507e639d49e",
"rev": "5a3ff8c1a09003f399f43d5742d893c0b1ab8af0",
"type": "github"
},
"original": {
@@ -261,11 +155,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1758048506,
"narHash": "sha256-I7cLckLwnppaqoUFvTrgGKDevNnIn3qV/3ELxetm6jk=",
"lastModified": 1763322257,
"narHash": "sha256-eiKNbZXvkB2p/YhM5ltK1CO1znm5Nn2aHLW3Awcqq9g=",
"owner": "nix-community",
"repo": "nixos-avf",
"rev": "1b7bf91cef5e3aeada4bc81977eb12b71585b45c",
"rev": "3fae0a3692b993bc0c40c61138a76fc1455d0b6e",
"type": "github"
},
"original": {
@@ -276,11 +170,11 @@
},
"nixos-hardware": {
"locked": {
"lastModified": 1760958188,
"narHash": "sha256-2m1S4jl+GEDtlt2QqeHil8Ny456dcGSKJAM7q3j/BFU=",
"lastModified": 1764440730,
"narHash": "sha256-ZlJTNLUKQRANlLDomuRWLBCH5792x+6XUJ4YdFRjtO4=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "d6645c340ef7d821602fd2cd199e8d1eed10afbc",
"rev": "9154f4569b6cdfd3c595851a6ba51bfaa472d9f3",
"type": "github"
},
"original": {
@@ -292,17 +186,17 @@
},
"nixos-wsl": {
"inputs": {
"flake-compat": "flake-compat_3",
"flake-compat": "flake-compat",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1760536587,
"narHash": "sha256-wfWqt+igns/VazjPLkyb4Z/wpn4v+XIjUeI3xY/1ENg=",
"lastModified": 1764072830,
"narHash": "sha256-ezkjlUCohD9o9c47Ey0/I4CamSS0QEORTqGvyGqMud0=",
"owner": "nix-community",
"repo": "NixOS-WSL",
"rev": "f98ee1de1fa36eca63c67b600f5d617e184e82ea",
"rev": "c7832dd786175e20f2697179e0e03efadffe4201",
"type": "github"
},
"original": {
@@ -329,11 +223,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1760878510,
"narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=",
"lastModified": 1764517877,
"narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67",
"rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c",
"type": "github"
},
"original": {
@@ -342,6 +236,26 @@
"type": "indirect"
}
},
"noctalia": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1764582162,
"narHash": "sha256-bqnw//qOLtDh6apLcIpgrcUK0dNRiDThLc2+kHeF98w=",
"owner": "zoriya",
"repo": "noctalia-shell",
"rev": "93dd3ee2a362834ed4f3bdb5525d4b8304350af7",
"type": "github"
},
"original": {
"owner": "zoriya",
"repo": "noctalia-shell",
"type": "github"
}
},
"root": {
"inputs": {
"home-manager": "home-manager",
@@ -353,6 +267,7 @@
"nixos-hardware": "nixos-hardware",
"nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs_2",
"noctalia": "noctalia",
"tmux": "tmux",
"zen-browser": "zen-browser"
}
@@ -360,11 +275,11 @@
"tmux": {
"flake": false,
"locked": {
"lastModified": 1760950867,
"narHash": "sha256-81CIlZt+eG4m4HqVQgSbEjCHexF+1+QvaK86HOms1LQ=",
"lastModified": 1764577332,
"narHash": "sha256-v7GmXzUZpXh88+GptfUZ8JBm6SH2MYvcmURjJduAvyw=",
"owner": "tmux",
"repo": "tmux",
"rev": "35ad72e56ffb2e8e09a2e2ac59bee0912fe45c6c",
"rev": "b2d6ebaa1067ddc7aa7b760318faa24822c77f8c",
"type": "github"
},
"original": {
@@ -373,27 +288,6 @@
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"neovim-nightly",
"nixpkgs"
]
},
"locked": {
"lastModified": 1760945191,
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
},
"zen-browser": {
"inputs": {
"nixpkgs": [
@@ -401,11 +295,11 @@
]
},
"locked": {
"lastModified": 1759982773,
"narHash": "sha256-HlTQoXRytul3jjek7vRV0Qk7voDB3Fy8RSIzDSvHIAQ=",
"lastModified": 1764476124,
"narHash": "sha256-QHvDAJdc7Sx8rIUpEjZYtn9n334F0Rg/8C/NcUWv7M4=",
"owner": "youwen5",
"repo": "zen-browser-flake",
"rev": "f2f8aff94529e763665b807bad23396aed9d1fe8",
"rev": "0f07eb42107d2b82d8a2e94d3c01aa3d7f500733",
"type": "github"
},
"original": {

View File

@@ -28,6 +28,10 @@
url = "github:youwen5/zen-browser-flake";
inputs.nixpkgs.follows = "nixpkgs";
};
noctalia = {
url = "github:zoriya/noctalia-shell";
inputs.nixpkgs.follows = "nixpkgs";
};
# use tmux's master for mode 2031
tmux = {
url = "github:tmux/tmux";
@@ -62,6 +66,14 @@
env = "niri";
custom = [
nixos-hardware.nixosModules.tuxedo-infinitybook-pro14-gen7
{
services.sshd.enable = true;
}
];
customHome = [
({pkgs, ...}: {
home.packages = with pkgs; [slack];
})
];
};
@@ -69,20 +81,6 @@
env = "server";
};
nixosConfigurations.kadan = mkSystem "kadan" {
env = "server";
custom = [
({pkgs, ...}: {
environment.systemPackages = with pkgs; [
python3Packages.guessit
mediainfo
yt-dlp
mkvtoolnix-cli
];
})
];
};
nixosConfigurations.virtual = mkSystem "virtual" {
env = "niri";
};
@@ -160,8 +158,8 @@
};
in rec {
default = pkgs.mkShell {
inputsFrom = [nvim-lua];
packages = with pkgs; [go-task];
inputsFrom = [nvim-lua];
packages = with pkgs; [go-task];
};
nvim-lua = pkgs.mkShell {
name = "nvim-lua";

View File

@@ -1,149 +0,0 @@
{
config,
lib,
pkgs,
modulesPath,
...
}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod"];
boot.kernelModules = ["kvm-intel" "coretemp" "nct6775"];
boot.extraModulePackages = [config.boot.kernelPackages.nvidia_x11];
boot.blacklistedKernelModules = ["nouveau"];
fileSystems."/" = {
device = "none";
fsType = "tmpfs";
options = ["size=2G" "mode=755"];
};
fileSystems."/tmp" = {
device = "none";
fsType = "tmpfs";
options = ["size=8G" "mode=755"];
};
fileSystems."/nix" = {
device = "/dev/disk/by-label/kadan";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-label/boot";
fsType = "vfat";
};
fileSystems."/mnt/a" = {
device = "/dev/disk/by-label/a";
fsType = "ext4";
};
fileSystems."/mnt/b" = {
device = "/dev/disk/by-label/b";
fsType = "ext4";
};
fileSystems."/mnt/c" = {
device = "/dev/disk/by-label/c";
fsType = "ext4";
};
fileSystems."/mnt/parity" = {
device = "/dev/disk/by-label/parity";
fsType = "ext4";
};
swapDevices = [
{
device = "/nix/persist/var/cache/swapfile";
size = 4 * 1024;
}
];
environment.systemPackages = with pkgs; [mergerfs];
fileSystems."/mnt/kyoo" = {
device = "/mnt/a:/mnt/b:/mnt/c";
depends = ["/mnt/a" "/mnt/b" "/mnt/c"];
fsType = "fuse.mergerfs";
options = [
"func.getattr=newest" # For kyoo's scanner
"cache.files=partial" # To enable mmap (used by rtorrent)
"dropcacheonclose=true"
"category.create=mfs"
];
};
services.snapraid = {
enable = true;
exclude = [
"*.unrecoverable"
"/tmp/"
"/lost+found/"
];
dataDisks = {
a = "/mnt/a/";
b = "/mnt/b/";
c = "/mnt/c/";
};
contentFiles = [
"/var/snapraid.content"
"/mnt/a/snapraid.content"
"/mnt/b/snapraid.content"
"/mnt/c/snapraid.content"
];
parityFiles = [
"/mnt/parity/snapraid.parity"
];
};
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
#networking.interfaces.eno1.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
hardware.graphics = {
enable = true;
enable32Bit = true;
extraPackages = with pkgs; [vaapiVdpau];
};
# Load nvidia driver for Xorg and Wayland
services.xserver.videoDrivers = ["nvidia"];
hardware.nvidia-container-toolkit.enable = true;
hardware.nvidia = {
# Modesetting is required.
modesetting.enable = true;
# Nvidia power management. Experimental, and can cause sleep/suspend to fail.
powerManagement.enable = true;
# Fine-grained power management. Turns off GPU when not in use.
# Experimental and only works on modern Nvidia GPUs (Turing or newer).
powerManagement.finegrained = false;
# Use the NVidia open source kernel module (not to be confused with the
# independent third-party "nouveau" open source driver).
# Support is limited to the Turing and later architectures. Full list of
# supported GPUs is at:
# https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus
# Only available from driver 515.43.04+
# Do not disable this unless your GPU is unsupported or if you have a good reason to.
open = false;
# Enable the Nvidia settings menu,
# accessible via `nvidia-settings`.
nvidiaSettings = true;
# Optionally, you may need to select the appropriate driver version for your specific GPU.
package = config.boot.kernelPackages.nvidiaPackages.stable;
};
system.stateVersion = "23.05";
}

View File

@@ -1,7 +1,4 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{ lib, pkgs, modulesPath, ... }:
{
imports =
@@ -26,7 +23,7 @@
};
fileSystems."/nix" =
{ device = "/dev/disk/by-label/virtual";
{ device = "/dev/disk/by-label/nix";
fsType = "ext4";
};

View File

@@ -48,10 +48,10 @@ in
openssh.authorizedKeys.keys = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGcLP/ZEjnSgkzQMBeLLOWn5uejSr9Gg1h9PJZECVTLm+VDQ7KyI3ORZt+qbfEnsnGL73iwcAqB5Upy9Cdj0182mnrTk2ZViNMeFT7kLBF0yXpiajQTtMjENYj0nbNWpQ5+sJrtJKKYK/tBghW8PyTrJPpVQcrLcf4D66U5DkkJNRDeu4v9SjHKaASUeyia4gRSVV59Ugtrl0lz8sl4yBSL4957zwzdkNR0pVmftaKmUP4KfBvpNcFOOpHcdvzDtEPQs8j0g2l65YOQNNFSMsYQfxt1X4zmEi4unRIlECglaPz12CyoTiM2xmCWa/mS5nm0dR1VbEHFMRtGbbgm9MwedXoxYAfycbu08fqi1AAvg7MQxDNLfWWBIHe7+imGLKrVkqk8B89I409iI4YiOytnUkxKZkxynqVYtEE0bx5J15mniq2vJTw9JD89qSVkvGjZNGuJgh4leIlxPGj4iP8KY3N3Ifaf72PsmmwW4rB5JPDW93RL1DZV8lk3NgyF8M= zoriya@fuhen" # laptop
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCpQ8Td98YIS0EtVQ7xabYVe9A9/+ZECrHBpKi01NKQ0Mleg9Z4fnTsdGFX1uhbG6Pu7niBVzYReVTC1CbyVWKmm/4DbbRpaqY94eOzQEe0p4wMSURQ9weuB5737k+5MuLDLUbhc1ytDa84Ubj/A/rQUueKdq2K1o+YSN7b7HKe7kXoXACEpbrSCC43mteBgCtvgsLY0New9xXnvGFJPSe7PcjYkOhSJB1xA0Gu4DoDdOyErvV62QQH4sSQMu5cFICJGfdXQzBdshA8MgWKXFv3Hq7K5/GGDNyCsMxeoPQET3vbmgUsE+KGtcdqizdFM3bAfCBGXOBx6h7BoNuQzkp8hgmrq62CmMwF0krX05Sb3qR/wVjRKDo9pYuSk6/awnnBp5kY6sNgEruI93ZXNQWMkxXQNbmpCi+uEvzMveP16O/uP3NxklD4wtmfpSZsxBi+jRGFqcjdy3Qlc13Tiz98EBaXkir9YwUAh8SNs3gRaJGI0Fn2HzUCPH0zNh42EY8= zoriya@kadan" # server
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJ/c2rQ9xUI6XpDR/+dmCK5IcxkOIezvNtbC2EVTrfh73H5juotME6JrQSxgQjtgsaUAzZzrac9kI/7Do8/lisbofdKRcneXi2UEeERKrKEwC/EGcQgqnoPLL1+mnqwvQ923d3105DV4hFksoDbblCinFuUr5s55EMm991IL/T70cy820AOgAf+hgleM1Its47EBkZBzpa4KwxYepJG0+kBa7K1Loi9QgBvTGpxs7rWMDxllfL6ivrWJxAKRZdWlJ/MKBVQIYhv0W+vaQ7OZA1qUY4bq/9wY/i88nixbVSPJmikj0+QNeLksU78bOIxLpTTeLdH4HQ6+qKOBT3JhEpBtUHdBxOT5tYJTr4qwjevlFqceLw3x1V9URxPS2XBDjlxnzYzdnD40LK5BehXdmElGio9dy98/qJINbDW/7AH+BpP1GWNKVhiYXPj7A/2fkFD2K7DgIgGlsrZthS+LxDTEcQ8Yx0iD/+nI8LcnvU42S0muSvmP7LE4xBl8AoaI0= zoriya@nixos" # lucca windows
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB/TujCSbUueF4p3wbzImPkEvgJjshDfh2sb/bwGdaRN" # bitwarden
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBzfKjYeQ80s/M+qEKCxBhseJjLa2OwBk9ZrHeku90Vg zoriya@kujima" # android
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCq7uaYigZWAhk/EIn9SHd6mhWJHtYPRFCUlpYwGSG6SDW2Ef0epsYJL2hBuMhhmhOvmpzzKiP83hZjNXchq8u7xcAhLsCPdPXhrQ4kOScJaeLuoxyY/7wiOqTSvSwz/N2s9tdNNcOLLUAset1Kvyp1OPBkEuXbIFfRGGqwAwcS2YYimlHf9mpcg/tZujBYQHIetHQkMaO+P0h+vMjVHBBcFLcsao+1QsVjoQnZOE96QTf2Oc66SxDBXnyS+1y1OnlWAEslSDL9AXVl6wF4O1JBWcsKNk+X4ShaaJMFRiPJEDQKSqRMKXWflkdFe1vNQ0bSiA6TLeH1lPeT8PXye2sUhu/DhpEswB7sV9YPpiP216QGsVM/2MepjCRq2sdr4EZ/17tubYdNcO8PkhVlQWOhHWXvIwIrvAzOVG1l9laAL1mxAY2iQLLN/gHGFPuiLcZCBem2LrDLQ7Ny+rDYQe4jZMEX4NYzNgNbHPk1ecwj/fqeLM4Qn+TkQgUL/kSM3ZU= zroux@zroux-mac" # lucca mac
];
};
})

View File

@@ -1,4 +1,4 @@
{pkgs, ...}: {
{pkgs, lib, ...}: {
imports = [
./nix/nix.nix
];
@@ -40,4 +40,24 @@
StandardErrorPath = "/tmp/caffeinate.err";
};
};
launchd.user.agents.ssh-tunnel = let
ssh-tunnel = pkgs.writeShellScriptBin "ssh-tunnel" ''
while true; do
dns-sd -m -Q fuhen.local
echo "Host found, starting tunnel" | tee /dev/stderr
ssh -NR "2222:localhost:22" zoriya@fuhen.local
echo "Connetion closed" | tee /dev/stderr
sleep 5
done
'';
in {
command = lib.getExe ssh-tunnel;
serviceConfig = {
KeepAlive = true;
RunAtLoad = true;
StandardOutPath = "/tmp/ssh-tunnel.log";
StandardErrorPath = "/tmp/ssh-tunnel.err";
};
};
}

View File

@@ -45,7 +45,13 @@
tmux
jq
mosh
# https://github.com/NixOS/nixpkgs/issues/463295 (telepresence needs a system wide iptables)
iptables
telepresence2
];
# also needed for telepresence
programs.fuse.userAllowOther = true;
programs.zsh.enable = true;
environment.shells = with pkgs; [zsh];

View File

@@ -69,6 +69,7 @@
".zen"
".config/google-chrome"
".config/discord"
".config/Slack"
".config/vesktop"
".config/YouTube\ Music"
".config/gh"

View File

@@ -21,8 +21,14 @@ fi
current_session=$(tmux display-message -p "#S")
if ! tmux has-session "-t=$selected_name" 2> /dev/null; then
tmux new-session -ds "$selected_name" -c "$selected" -e "CMD=$EDITOR ."
tmux new-window -dt "$selected_name:1" -c "$selected" -e "CMD="
if [[ "$selected" == "$HOME/work/new" ]]; then
selected_name="work"
ssh_tunnel="ssh zroux@localhost -p 2222 -D 6666"
tmux new-session -ds "$selected_name" -c "$selected" -e "CMD=$ssh_tunnel"
else
tmux new-session -ds "$selected_name" -c "$selected" -e "CMD=$EDITOR ."
tmux new-window -dt "$selected_name:1" -c "$selected" -e "CMD="
fi
fi
tmux switch-client -t "$selected_name"

View File

@@ -26,6 +26,8 @@
''
set -g status off
set -s set-clipboard on
# request clipboard from parent terminal instead of using tmux's internal buffer
set -s get-clipboard request
set -g extended-keys on
# from tmux-sensible
@@ -54,8 +56,9 @@
# suspend inner tmux (to allow nested sessions)
bind @ { set prefix None; set key-table off }
# NOTE: C-@ doesn't work since v3.5a
bind -T off C-@ { set -u prefix; set -u key-table }
# NOTE: C-@ doesn't work since v3.5a (since tmux doesn't support kitty keyboard protocol)
# Instead, we bind f11 and use kitty to remap C-@ to f11.
bind -T off f11 { set -u prefix; set -u key-table }
'';
};
}

View File

@@ -52,7 +52,7 @@
kns = "kubens";
knsc = "kubectl config set-context --current --namespace ''";
kg = "k get";
kga = "kg $(k api-resources --verbs=list --namespaced -o name | paste -sd ,)";
kga = "kg $(k api-resources --verbs=list --namespaced -o name | grep -Ev 'event|^endpoints$' | paste -sd ,)";
# use custom function to decode secrets data instead of a simple alias for kgy
# kgy = "k get -o yaml";
kgw = "k get -w";
@@ -89,9 +89,10 @@
dr = "direnv reload";
nixos-option = "nixos-option --flake ~/projects/flake";
# i will never remember those flags
ss = "ss -tlpun";
# habits
copyfile = "clipcopy";
ss =
if pkgs.stdenv.isLinux
then "ss -tlpun"
else "netstat -anvp tcp | awk 'NR<3 || /LISTEN/'";
# viu doesn't work with tmux, icat does. using that while waiting
viu = "kitty +kitten icat";
@@ -125,16 +126,6 @@
src = pkgs.oh-my-zsh;
file = "share/oh-my-zsh/plugins/git/git.plugin.zsh";
}
{
name = "clipcopy"; # dependency of copypath & copyfile
src = pkgs.oh-my-zsh;
file = "share/oh-my-zsh/lib/clipboard.zsh";
}
{
name = "copypath";
src = pkgs.oh-my-zsh;
file = "share/oh-my-zsh/plugins/copypath/copypath.plugin.zsh";
}
];
completionInit =
#bash
@@ -359,6 +350,7 @@
usql
rsync
moreutils
osc
# bitwarden-cli
]
++ lib.optionals pkgs.stdenv.isLinux [

View File

@@ -108,3 +108,10 @@ _kgy() {
_kubectl
}
compdef _kgy kgy
alias copyfile="osc copy"
copypath() {
local file="${1:-.}"
[[ $file = /* ]] || file="$PWD/$file"
print -n "${file:a}" | osc copy || return 1
}

View File

@@ -6,7 +6,7 @@ zmodload zsh/stat
timer_preexec() {
timer=$EPOCHREALTIME
}
add-zsh-hook preexec timer_precmd
add-zsh-hook preexec timer_preexec
timer_precmd() {
if [ -z $timer ]; then
EXEC_TIME=""
@@ -39,7 +39,7 @@ kube_precmd() {
add-zsh-hook precmd kube_precmd
FILL="%F{#808080}${(l.$COLUMNS..·.)}%f"
FILL='%F{#808080}${(l.$COLUMNS..·.)}%f'
NEWLINE=$'\n'
WORKDIR='%B%F{blue}%~%b%f'
@@ -50,8 +50,8 @@ KUBE='%F{cyan}$KCTX${KNS:+/$KNS}%f'
PROMPT_SHLVL='%(?.%F{green}.%F{red})$(printf "%.0s" {1..$SHLVL})%f'
EXEC_TIME=""
EXIT_CODE=' %(?..%F{red}x${(j[|])pipestatus}%f)'
JOBS=' %F{cyan}%(1j.&%j.)%f'
EXIT_CODE='%(?.. %F{red}x${(j[|])pipestatus}%f)'
JOBS='%F{cyan}%(1j. &%j.)%f'
export PROMPT="${FILL}${NEWLINE}${WORKDIR}$RO$GIT $KUBE $PROMPT_SHLVL "
export RPROMPT="\${EXEC_TIME}${EXIT_CODE}${JOBS}"

View File

@@ -14,10 +14,7 @@
};
};
qt = {
enable = true;
platformTheme.name = "gtk";
};
qt.enable = true;
home.pointerCursor = {
gtk.enable = true;

View File

@@ -20,7 +20,7 @@ in {
home.packages = with pkgs;
[
firefox
zen-browser.packages.${pkgs.system}.default
zen-browser.packages.${pkgs.stdenv.hostPlatform.system}.default
vesktop
freecad
kicad

View File

@@ -28,6 +28,9 @@
map ctrl+minus change_font_size current -1.0
map ctrl+0 change_font_size current 0
map ctrl+backspace change_font_size current 0
# this is used to map
map ctrl+@ send_key f11
'';
};

View File

@@ -1,4 +0,0 @@
node_modules
package-lock.json
weather_key
settings.json

View File

@@ -1,48 +0,0 @@
import Gtk from "gi://Gtk?version=3.0";
import Gdk from "gi://Gdk";
import { Bar } from "./layouts/bar.js";
import { Notifications } from "./layouts/notifications.js";
import { OSD } from "./layouts/osd.js";
import { Quicksettings } from "./layouts/quicksettings.js";
/**
* @param {Array<(monitor: number) => Gtk.Window>} widgets
*/
export function forMonitors(widgets) {
const display = Gdk.Display.get_default();
display?.connect("monitor-added", (disp, gdkmonitor) => {
let monitor = 0;
for (let i = 0; i < display.get_n_monitors(); i++) {
if (gdkmonitor === display.get_monitor(i)) {
monitor = i;
break;
}
}
widgets.forEach((win) => App.addWindow(win(monitor)));
});
display?.connect("monitor-removed", (disp, monitor) => {
App.windows.forEach((win) => {
// @ts-ignore
if (win.gdkmonitor === monitor) App.removeWindow(win);
});
});
const n = display?.get_n_monitors() || 1;
return Array.from({ length: n }, (_, i) => i).flatMap((mon) =>
widgets.map((x) => x(mon)),
);
}
App.config({
closeWindowDelay: {
quicksettings: 300,
notifications: 200,
osd: 300,
},
style: `${App.configDir}/style.css`,
windows: [...forMonitors([Bar]), Quicksettings(), Notifications(), OSD()],
});

View File

@@ -1,49 +0,0 @@
#! /usr/bin/env python3
from material_color_utilities_python import *
from PIL import Image
import json
import os
import sys
import hashlib
CACHE_DIR = os.path.expanduser("~/.cache/ags/tempcolors")
def cache_and_get_colors(file_path):
file_hash = hashlib.sha256(file_path.encode('utf-8')).hexdigest()
cache_path = os.path.join(CACHE_DIR, file_hash[:2], file_hash[2:])
os.makedirs(cache_path, exist_ok=True)
colors_path = os.path.join(cache_path, "colors.json")
if os.path.exists(colors_path):
with open(colors_path, "r") as f:
parsed_colors = json.load(f)
elif not os.path.exists(file_path):
return None
img = Image.open(file_path)
basewidth = 8
wpercent = (basewidth/float(img.size[0]))
hsize = int((float(img.size[1])*float(wpercent)))
img = img.resize((basewidth, hsize), Image.Resampling.LANCZOS)
theme = themeFromImage(img)
scheme = theme.get("schemes")
parsed_colors = {
"primary": hexFromArgb(scheme.get("dark").primary),
"onPrimary": hexFromArgb(scheme.get("dark").onPrimary),
"background": hexFromArgb(scheme.get("dark").background),
"onBackground": hexFromArgb(scheme.get("dark").onBackground),
}
with open(colors_path, "w") as f:
json.dump(parsed_colors, f)
return parsed_colors
fn = sys.argv[1]
parsed_colors = cache_and_get_colors(fn)
print(json.dumps(parsed_colors, indent=4))

View File

@@ -1,48 +0,0 @@
{pkgs, ...}: let
covercolors = pkgs.stdenv.mkDerivation {
name = "covercolors";
dontUnpack = true;
propagatedBuildInputs = [
(pkgs.python3.withPackages (pyPkgs:
with pyPkgs; [
material-color-utilities
pillow
]))
];
installPhase = "install -Dm755 ${./covercolors.py} $out/bin/covercolors";
};
# systemdTarget = "graphical-session.target";
ags = pkgs.ags_1.overrideAttrs (_: prev: {
buildInputs =
prev.buildInputs
++ [
pkgs.libdbusmenu-gtk3
];
});
in {
home.packages = with pkgs; [
ags
# TODO: Find a way to add this for ags only
covercolors
brightnessctl
blueberry
];
# systemd.user.services.ags = {
# Unit = {
# Description = " A customizable and extensible shell ";
# PartOf = systemdTarget;
# Requires = systemdTarget;
# After = systemdTarget;
# };
#
# Service = {
# Type = "simple";
# ExecStart = "${ags}/bin/ags";
# Restart = "always";
# };
#
# Install = {WantedBy = [systemdTarget];};
# };
xdg.configFile."ags".source = ./.;
}

View File

@@ -1,88 +0,0 @@
import { Clock } from "../modules/clock.js";
import * as wm from "../modules/wm.js";
import * as audio from "../modules/audio.js";
import * as network from "../modules/network.js";
import * as bluetooth from "../modules/bluetooth.js";
import * as battery from "../modules/battery.js";
import * as notifications from "../modules/notifications.js";
import * as mpris from "../modules/mpris.js";
/**
*@param {number} monitor
*/
export const Bar = (monitor) =>
Widget.Window({
monitor,
name: `bar${monitor}`,
className: "transparent",
exclusivity: "exclusive",
anchor: ["top", "left", "right"],
layer: "bottom",
child: Widget.CenterBox({
// startWidget: Widget.Box({
// children: [
// wm.Tags({
// monitor,
// labels: ["一", "二", "三", "四", "五", "六", "七", "八", "九"],
// }),
// wm.Layout({ monitor }),
// wm.ClientLabel({ monitor }),
// ],
// }),
centerWidget: Widget.Box({
hpack: "center",
children: [
Widget.Button({
css: "min-width: 200px;",
onClicked: () => App.toggleWindow("notifications"),
child: notifications.Indicator({
hexpand: true,
hpack: "center",
}),
}),
],
}),
endWidget: Widget.Box({
hpack: "end",
children: [
Widget.Box({
className: "module",
css: "margin-right: 48px",
visible: mpris.activePlayer.bind().as((x) => !!x),
children: mpris.activePlayer
.bind()
.as((player) => (player ? [mpris.LinePlayer({ player })] : [])),
}),
Widget.Button({
onClicked: () => App.toggleWindow("quicksettings"),
className: "module quicksettings",
child: Widget.Box({
children: [
audio.MicrophoneIndicator({
className: "qs-item",
}),
notifications.DNDIndicator({
className: "qs-item",
}),
network.Indicator({ className: "qs-item" }),
audio.VolumeIndicator({ className: "qs-item" }),
bluetooth.Indicator({
hideIfDisabled: true,
className: "qs-item",
}),
battery.Indicator({ className: "qs-item" }),
],
}),
}).hook(App, (self, win, visible) => {
self.toggleClassName("active", win === "quicksettings" && visible);
}),
Clock({ format: "%a %d %b", className: "module bold" }),
Clock({
format: "%H:%M",
className: "module accent bold",
css: "margin-right: 0px",
}),
],
}),
}),
});

View File

@@ -1,55 +0,0 @@
import PopupWindow from "../misc/popup.js";
import * as notifications from "../modules/notifications.js";
const notificationsService = await Service.import("notifications");
const Header = () =>
Widget.Box({
hexpand: true,
css: "margin-bottom: 40px",
children: [
notifications.DNDToggle({
className: "surface p10 round",
css: `
min-width: 24px;
min-height: 24px;
`,
hpack: "start",
hexpand: true,
}),
notifications.ClearButton({
hpack: "end",
hexpand: true,
}),
],
});
export const Notifications = () =>
PopupWindow({
name: "notifications",
exclusivity: "exclusive",
transition: "slide_down",
layout: "top-center",
duration: 300,
child: Widget.Box({
vertical: true,
className: "bgcont",
css: `
min-width: 600px;
margin: 10px;
padding: 12px;
border-radius: 20px;
`,
children:
/** @type {any} */
(
notificationsService
.bind("notifications")
.as((x) =>
x.length > 0
? [Header(), notifications.List({})]
: [Header(), notifications.Placeholder({})],
)
),
}),
});

View File

@@ -1,85 +0,0 @@
import { getIcon } from "../modules/audio.js";
import brightness from "../services/brightness.js";
const audio = await Service.import("audio");
const DELAY = 1000;
function OnScreenProgress() {
const indicator = Widget.Icon({
vpack: "start",
hpack: "center",
size: 30,
css: "padding-right: 12px;",
});
const progress = Widget.Slider({
drawValue: false,
hexpand: true,
});
const revealer = Widget.Revealer({
transition: "crossfade",
css: "opacity: 0",
revealChild: true,
vpack: "center",
hpack: "center",
child: Widget.Box({
vpack: "center",
hpack: "center",
className: "osd bgcount",
css: "padding: 20px;",
children: [indicator, progress],
}),
});
// Prevent OSD to be shown when starting ags.
Utils.timeout(DELAY * 2, () => {
revealer.css = "opacity: 1";
});
let count = 0;
/**
* @param {number} value
* @param {string} icon
*/
function show(value, icon) {
revealer.reveal_child = true;
indicator.icon = icon;
progress.value = value;
count++;
Utils.timeout(DELAY, () => {
count--;
if (count === 0) revealer.reveal_child = false;
});
}
return revealer
.hook(
brightness,
() => show(brightness.screen, "display-brightness-symbolic"),
"notify::screen",
)
.hook(
audio.speaker,
() => show(audio.speaker.volume, getIcon(audio.speaker.volume * 100)),
"notify::volume",
)
.hook(
audio.speaker,
() =>
show(
audio.speaker.is_muted ? 0 : audio.speaker.volume,
audio.speaker.is_muted
? "audio-volume-muted-symbolic"
: getIcon(audio.speaker.volume * 100),
),
"notify::is-muted",
);
}
export const OSD = () =>
Widget.Window({
name: "osd",
className: "indicator",
layer: "overlay",
clickThrough: true,
anchor: ["bottom"],
child: OnScreenProgress(),
});

View File

@@ -1,160 +0,0 @@
import Gtk from "gi://Gtk?version=3.0";
import * as audio from "../modules/audio.js";
import * as brightness from "../modules/brightness.js";
import * as network from "../modules/network.js";
import * as bluetooth from "../modules/bluetooth.js";
import * as darkmode from "../modules/darkmode.js";
import * as powerprofile from "../modules/powerprofile.js";
// import * as nightmode from "../modules/nightmode.js";
import * as mpris from "../modules/mpris.js";
import * as systray from "../modules/systray.js";
import PopupWindow from "../misc/popup.js";
import { opened, Menu } from "../misc/menu.js";
const mprisService = await Service.import("mpris");
/**
* @param {Array<Gtk.Widget>} toggles
* @param {Array<Gtk.Widget>} menus
*/
const Row = (toggles = [], menus = []) =>
Widget.Box({
vertical: true,
children: [
Widget.Box({
homogeneous: true,
children: toggles,
}),
...menus,
],
});
const Header = () =>
Widget.Box({
vertical: true,
children: [
Widget.Box({
css: "margin-bottom: 12px",
children: [
Widget.Box({
className: "avatar",
css: `background-image: url("/home/${Utils.USER}/.face");`,
}),
Widget.Box({ hexpand: true }),
Widget.Button({
child: Widget.Icon("emblem-system-symbolic"),
onClicked: () => {
Utils.execAsync("gnome-control-center");
App.closeWindow("quicksettings");
},
vpack: "center",
className: "surface sys-button",
}),
Widget.Button({
child: Widget.Icon("system-log-out-symbolic"),
onClicked: () => {
opened.value = opened.value === "sleep" ? "" : "sleep";
},
vpack: "center",
css: "margin: 12px",
className: "surface sys-button",
}),
Widget.Button({
child: Widget.Icon("system-shutdown-symbolic"),
onClicked: () => {
opened.value = opened.value === "shutdown" ? "" : "shutdown";
},
vpack: "center",
className: "surface sys-button",
}),
],
}),
VerificationMenu({
name: "sleep",
icon: "system-log-out-symbolic",
title: "Hybernate?",
command: "systemctl suspend --now",
}),
VerificationMenu({
name: "shutdown",
icon: "system-shutdown-symbolic",
title: "Shutdown?",
command: "shutdown now",
}),
],
});
/** @param {{
* name: string,
* icon: string,
* title: string,
* command: string,
* }} props */
const VerificationMenu = ({ name, icon, title, command }) =>
Menu({
name,
icon: Widget.Icon(icon),
title: title,
content: [
Widget.Button({
onClicked: () => {
opened.value = "";
App.closeWindow("quicksettings");
Utils.execAsync(command);
},
child: Widget.Label({
label: "Yes",
hpack: "start",
css: "margin-left: 12px",
}),
}),
Widget.Button({
onClicked: () => {
opened.value = "";
},
child: Widget.Label({
label: "No",
hpack: "start",
css: "margin-left: 12px",
}),
}),
],
});
export const Quicksettings = () =>
PopupWindow({
name: "quicksettings",
exclusivity: "exclusive",
transition: "slide_down",
layout: "top-right",
duration: 300,
child: Widget.Box({
vertical: true,
className: "bgcont qs-container",
children: [
Header(),
Row(
[audio.Volume({ type: "speaker" })],
[audio.SinkSelector({}), audio.AppMixer({})],
),
brightness.Brightness({}),
Row(
[network.Toggle({}), bluetooth.Toggle({})],
[network.Selection({}), bluetooth.Selection({})],
),
Widget.Box({
homogeneous: true,
children: [darkmode.Toggle(), audio.MuteToggle({})],
}),
Row(
[systray.Toggle({}), powerprofile.Toggle({})],
[systray.Selection({}), powerprofile.Selection({})],
),
Widget.Box({
children: mpris.activePlayer
.bind()
.as((player) => player ? [mpris.MprisPlayer({ player })] : []),
}),
],
}),
});

View File

@@ -1,166 +0,0 @@
const { Gtk } = imports.gi;
const Lang = imports.lang;
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
// -- Styling --
// min-height for diameter
// min-width for trough stroke
// padding for space between trough and progress
// margin for space between widget and parent
// background-color for trough color
// color for progress color
// -- Usage --
// font size for progress value (0-100px) (hacky i know, but i want animations)
export const AnimatedCircProg = ({
initFrom = 0,
initTo = 0,
initAnimTime = 2900,
initAnimPoints = 1,
extraSetup = () => {},
...rest
}) =>
Widget.DrawingArea({
...rest,
css: `${
initFrom !== initTo
? `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`
: ""
}`,
setup: (area) => {
const styleContext = area.get_style_context();
const width = styleContext.get_property(
"min-height",
Gtk.StateFlags.NORMAL,
);
const height = styleContext.get_property(
"min-height",
Gtk.StateFlags.NORMAL,
);
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
const marginBottom = styleContext.get_margin(
Gtk.StateFlags.NORMAL,
).bottom;
area.set_size_request(
width + marginLeft + marginRight,
height + marginTop + marginBottom,
);
area.connect(
"draw",
Lang.bind(area, (area, cr) => {
const styleContext = area.get_style_context();
const width = styleContext.get_property(
"min-height",
Gtk.StateFlags.NORMAL,
);
const height = styleContext.get_property(
"min-height",
Gtk.StateFlags.NORMAL,
);
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
const marginLeft = styleContext.get_margin(
Gtk.StateFlags.NORMAL,
).left;
const marginRight = styleContext.get_margin(
Gtk.StateFlags.NORMAL,
).right;
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
const marginBottom = styleContext.get_margin(
Gtk.StateFlags.NORMAL,
).bottom;
area.set_size_request(
width + marginLeft + marginRight,
height + marginTop + marginBottom,
);
const progressValue =
styleContext.get_property("font-size", Gtk.StateFlags.NORMAL) /
100.0;
const bg_stroke = styleContext.get_property(
"min-width",
Gtk.StateFlags.NORMAL,
);
const fg_stroke = bg_stroke - padding;
const radius =
Math.min(width, height) / 2.0 -
Math.max(bg_stroke, fg_stroke) / 2.0;
const center_x = width / 2.0 + marginLeft;
const center_y = height / 2.0 + marginTop;
const start_angle = -Math.PI / 2.0;
const end_angle = start_angle + 2 * Math.PI * progressValue;
const start_x = center_x + Math.cos(start_angle) * radius;
const start_y = center_y + Math.sin(start_angle) * radius;
const end_x = center_x + Math.cos(end_angle) * radius;
const end_y = center_y + Math.sin(end_angle) * radius;
// Draw background
const background_color = styleContext.get_property(
"background-color",
Gtk.StateFlags.NORMAL,
);
cr.setSourceRGBA(
background_color.red,
background_color.green,
background_color.blue,
background_color.alpha,
);
cr.arc(center_x, center_y, radius, 0, 2 * Math.PI);
cr.setLineWidth(bg_stroke);
cr.stroke();
if (progressValue == 0) return;
// Draw progress
const color = styleContext.get_property(
"color",
Gtk.StateFlags.NORMAL,
);
cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
cr.arc(center_x, center_y, radius, start_angle, end_angle);
cr.setLineWidth(fg_stroke);
cr.stroke();
// Draw rounded ends for progress arcs
cr.setLineWidth(0);
cr.arc(start_x, start_y, fg_stroke / 2, 0, 0 - 0.01);
cr.fill();
cr.arc(end_x, end_y, fg_stroke / 2, 0, 0 - 0.01);
cr.fill();
}),
);
// Init animation
if (initFrom != initTo) {
area.css = `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`;
Utils.timeout(
20,
() => {
area.css = `font-size: ${initTo}px;`;
},
area,
);
const transitionDistance = initTo - initFrom;
const oneStep = initAnimTime / initAnimPoints;
area.css = `
font-size: ${initFrom}px;
transition: ${oneStep}ms linear;
`;
for (let i = 0; i < initAnimPoints; i++) {
Utils.timeout(Math.max(10, i * oneStep), () => {
if (!area) return;
area.css = `${
initFrom != initTo
? "font-size: " +
(initFrom + (transitionDistance / initAnimPoints) * (i + 1)) +
"px;"
: ""
}`;
});
}
} else area.css = "font-size: 0px;";
extraSetup(area);
},
});

View File

@@ -1,186 +0,0 @@
import GObject from "gi://GObject?version=2.0";
import Gtk from "gi://Gtk?version=3.0";
export const opened = Variable("");
App.connect("window-toggled", (_, name, visible) => {
if (name === "quicksettings" && !visible)
Utils.timeout(500, () => {
opened.value = "";
});
});
/**
* @param {{
* name: string,
* activate?: false | (() => void),
* } & import("../types/widgets/button").ButtonProps} props
*/
export const Arrow = ({ name, activate, ...props }) => {
let deg = 0;
let iconOpened = false;
const icon = Widget.Icon("pan-end-symbolic").hook(opened, () => {
if (
(opened.value === name && !iconOpened) ||
(opened.value !== name && iconOpened)
) {
const step = opened.value === name ? 10 : -10;
iconOpened = !iconOpened;
for (let i = 0; i < 9; ++i) {
Utils.timeout(15 * i, () => {
deg += step;
icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`);
});
}
}
});
return Widget.Button({
child: icon,
className: "qs-icon",
onClicked: () => {
opened.value = opened.value === name ? "" : name;
if (typeof activate === "function") activate();
},
...props,
});
};
/**
* @typedef {{
* name: string,
* icon: Gtk.Widget,
* label: Gtk.Widget,
* activate: () => void
* deactivate: () => void
* activateOnArrow?: boolean
* connection: [GObject.Object, () => boolean]
* } & import("../types/widgets/box").BoxProps} ArrowToggleButtonProps
* @param {ArrowToggleButtonProps} props
*/
export const ArrowToggleButton = ({
name,
icon,
label,
activate,
deactivate,
activateOnArrow = true,
connection: [service, condition],
}) =>
Widget.Box({
className: "qs-button surface",
setup: (self) =>
self.hook(service, () => {
self.toggleClassName("accent", condition());
}),
children: [
Widget.Button({
child: Widget.Box({
hexpand: true,
children: [icon, label],
}),
onClicked: () => {
if (condition()) {
deactivate();
if (opened.value === name) opened.value = "";
} else {
activate();
}
},
}),
Arrow({ name, activate: activateOnArrow && activate }),
],
});
/**
* @typedef {{
* icon: Gtk.Widget,
* label: Gtk.Widget,
* activate: () => void
* deactivate: () => void
* connection: [GObject.Object, () => boolean]
* } & import("../types/widgets/box").BoxProps} SimpleToggleButtonProps
* @param {SimpleToggleButtonProps} props
*/
export const SimpleToggleButton = ({
icon,
label,
activate,
deactivate,
connection: [service, condition],
}) =>
Widget.Box({
className: "qs-button surface",
setup: (self) =>
self.hook(service, () => {
self.toggleClassName("accent", condition());
}),
children: [
Widget.Button({
child: Widget.Box({
hexpand: true,
children: [icon, label],
}),
onClicked: () => {
if (condition()) {
deactivate();
} else {
activate();
}
},
}),
],
});
/**
* @typedef {{
* name: string,
* icon: Gtk.Widget,
* title: string,
* content: Gtk.Widget[],
* } & import("../types/widgets/revealer").RevealerProps} MenuProps
* @param {MenuProps} props
*/
export const Menu = ({ name, icon, title, content, ...props }) =>
Widget.Revealer({
transition: "slide_down",
reveal_child: opened.bind().as((v) => v === name),
child: Widget.Box({
className: "qs-submenu surface",
vertical: true,
children: [
Widget.Box({
className: "qs-sub-title accent",
children: [
icon,
Widget.Label({
className: "bold f16",
truncate: "end",
label: title,
}),
],
}),
Widget.Box({
vertical: true,
className: "qs-sub-content",
children: content,
}),
],
}),
...props,
});
/** @param {{type?: string, command?: string} & import("../types/widgets/button").ButtonProps} props */
export const SettingsButton = ({ type, command, ...props }) =>
Widget.Button({
onClicked: () => {
Utils.execAsync(command ?? `gnome-control-center ${type}`);
App.closeWindow("quicksettings");
},
hexpand: true,
child: Widget.Box({
children: [
Widget.Icon("emblem-system-symbolic"),
Widget.Label("Settings"),
],
}),
...props,
});

View File

@@ -1,198 +0,0 @@
// Stollen from https://github.com/Aylur/dotfiles/blob/main/ags/widget/PopupWindow.ts#
/** @typedef {import('../types/widgets/window').WindowProps} WindowProps */
/** @typedef {import('../types/widgets/revealer').RevealerProps} RevealerProps */
/** @typedef {import('../types/widgets/eventbox').EventBoxProps} EventBoxProps */
/** @typedef {import('gi://Gtk?version=3.0')} Gtk */
/**
* @param {string} name
* @param {EventBoxProps}
* @returns {any}
*/
export const Padding = (
name,
{ css = "", hexpand = true, vexpand = true } = {},
) =>
// Widget.Box({});
Widget.EventBox({
hexpand,
vexpand,
can_focus: false,
child: Widget.Box({ css }),
setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)),
});
/**
* @param {string} name
* @param {Child} child
* @param {Transition} [transition="slide_down"]
* @param {number} duration
*/
const PopupRevealer = (
name,
child,
transition = "slide_down",
duration = 500,
) =>
Widget.Box(
{ css: "padding: 1px;" },
Widget.Revealer({
transition,
child: Widget.Box({
class_name: "window-content",
child,
}),
transitionDuration: duration,
setup: (self) =>
self.hook(App, (_, wname, visible) => {
if (wname === name) self.reveal_child = visible;
}),
}),
);
/**
* @param {string} name
* @param {Child} child
* @param {Transition} [transition]
* @param {number} [duration]
* @returns {{ center: () => any; top: () => any; "top-right": () => any; "top-center": () => any; "top-left": () => any; "bottom-left": () => any; "bottom-center": () => any; "bottom-right": () => any; }}
*/
const Layout = (name, child, transition, duration) => ({
center: () =>
Widget.CenterBox(
{},
Padding(name),
Widget.CenterBox(
{ vertical: true },
Padding(name),
PopupRevealer(name, child, transition, duration),
Padding(name),
),
Padding(name),
),
top: () =>
Widget.CenterBox(
{},
Padding(name),
Widget.Box(
{ vertical: true },
PopupRevealer(name, child, transition, duration),
Padding(name),
),
Padding(name),
),
"top-right": () =>
Widget.Box(
{},
Padding(name),
Widget.Box(
{
hexpand: false,
vertical: true,
},
PopupRevealer(name, child, transition, duration),
Padding(name),
),
),
"top-center": () =>
Widget.Box(
{},
Padding(name),
Widget.Box(
{
hexpand: false,
vertical: true,
},
PopupRevealer(name, child, transition, duration),
Padding(name),
),
Padding(name),
),
"top-left": () =>
Widget.Box(
{},
Widget.Box(
{
hexpand: false,
vertical: true,
},
PopupRevealer(name, child, transition, duration),
Padding(name),
),
Padding(name),
),
"bottom-left": () =>
Widget.Box(
{},
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name),
PopupRevealer(name, child, transition, duration),
),
Padding(name),
),
"bottom-center": () =>
Widget.Box(
{},
Padding(name),
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name),
PopupRevealer(name, child, transition, duration),
),
Padding(name),
),
"bottom-right": () =>
Widget.Box(
{},
Padding(name),
Widget.Box(
{
hexpand: false,
vertical: true,
},
Padding(name),
PopupRevealer(name, child, transition, duration),
),
),
});
/** @typedef {RevealerProps["transition"]} Transition */
/** @typedef {WindowProps["child"]} Child */
/**
* @typedef {Omit<WindowProps, "name"> & {
* name: string
* layout?: keyof ReturnType<typeof Layout>
* transition?: Transition,
* duration?: number
* }} PopupWindowProps
* @param {PopupWindowProps} props
*/
export default ({
name,
child,
layout = "center",
transition,
exclusivity = "ignore",
duration,
...props
}) =>
Widget.Window({
name,
class_names: [name, "popup-window"],
setup: (w) => w.keybind("Escape", () => App.closeWindow(name)),
visible: false,
keymode: "on-demand",
exclusivity,
layer: "top",
// anchor: ["top", "right"],
anchor: ["top", "bottom", "right", "left"],
child: Layout(name, child, transition, duration)[layout](),
...props,
});

View File

@@ -1,32 +0,0 @@
import GLib from "gi://GLib?version=2.0";
/**
* @param {string | null | undefined} name
* @param {string | null | undefined} [fallback]
*/
export function icon(name, fallback) {
if (!name) return fallback || "";
if (typeof name !== "string") return name;
const sub = substitutes[name];
if (sub && Utils.lookUpIcon(sub)) return sub;
if (Utils.lookUpIcon(name)) return name;
if (GLib.file_test(name, GLib.FileTest.EXISTS)) return name;
return fallback || "";
}
export const substitutes = {
"transmission-gtk": "transmission",
"blueberry.py": "blueberry",
Caprine: "facebook-messenger",
"com.raggesilver.BlackBox-symbolic": "terminal-symbolic",
"org.wezfurlong.wezterm-symbolic": "terminal-symbolic",
"audio-headset-bluetooth": "audio-headphones-symbolic",
"audio-card-analog-usb": "audio-speakers-symbolic",
"audio-card-analog-pci": "audio-card-symbolic",
"preferences-system": "emblem-system-symbolic",
"com.github.Aylur.ags-symbolic": "controls-symbolic",
"com.github.Aylur.ags": "controls-symbolic",
};

View File

@@ -1,228 +0,0 @@
import { icon } from "../misc/utils.js";
import {
Arrow,
Menu,
SettingsButton,
SimpleToggleButton,
} from "../misc/menu.js";
const audio = await Service.import("audio");
const volumeIcons = /** @type {const} */ ([
[101, "overamplified"],
[67, "high"],
[34, "medium"],
[1, "low"],
[0, "muted"],
]);
/** @param {number} volume */
export const getIcon = (volume) => {
const icon = volumeIcons.find(([threshold]) => threshold <= volume)?.[1];
return `audio-volume-${icon}-symbolic`;
};
/** @param {{type?: "speaker" | "microphone"} & import("../types/widgets/icon.js").IconProps} props */
export const VolumeIndicator = ({ type = "speaker", ...props }) =>
Widget.Icon(props).hook(audio, (self) => {
if (audio[type].is_muted) {
self.icon = "audio-volume-muted-symbolic";
self.tooltip_text = "Muted";
return;
}
const vol = audio[type].volume * 100;
self.icon = getIcon(vol);
self.tooltip_text = `Volume: ${Math.floor(vol)}%`;
});
/** @param {import("../types/widgets/icon.js").IconProps} props */
export const MicrophoneIndicator = (props) =>
Widget.Icon(props).hook(audio, (self) => {
self.visible = audio.microphone.is_muted || audio.recorders.length > 0;
if (audio.microphone.is_muted) self.icon = "microphone-disabled-symbolic";
else if (audio.recorders.length > 0)
self.icon = "microphone-sensitivity-high-symbolic";
});
/** @param {{type?: "speaker" | "microphone"} & import("../types/widgets/slider.js").SliderProps} props */
const VolumeSlider = ({ type = "speaker", ...props }) =>
Widget.Slider({
hexpand: true,
drawValue: false,
onChange: ({ value, dragging }) => {
if (dragging) {
audio[type].volume = value;
audio[type].is_muted = false;
}
},
value: audio[type].bind("volume"),
css: audio[type].bind("is_muted").as((x) => `opacity: ${x ? 0.7 : 1}`),
...props,
});
/** @param {{type?: "speaker" | "microphone"} & import("../types/widgets/box.js").BoxProps} props */
export const Volume = ({ type = "speaker", ...props }) =>
Widget.Box({
className: "qs-slider",
children: [
Widget.Button({
onClicked: () => {
audio[type].is_muted = !audio[type].is_muted;
},
child: VolumeIndicator({ type }),
}),
VolumeSlider({ type }),
Widget.Label({
label: audio[type].bind("volume").as(
(vol) =>
`${Math.floor(vol * 100)
.toString()
.padStart(3)}%`,
),
}),
Widget.Box({
vpack: "center",
child: Arrow({ name: "sink-selector" }),
}),
Widget.Box({
vpack: "center",
child: Arrow({ name: "app-mixer" }),
visible: audio.bind("apps").as((a) => a.length > 0),
}),
],
...props,
});
/** @param {Partial<import("../misc/menu.js").SimpleToggleButtonProps>} props */
export const MuteToggle = ({ ...props } = {}) =>
SimpleToggleButton({
icon: Widget.Icon({
className: "qs-icon",
icon: audio.microphone
.bind("is_muted")
.as((x) =>
x
? "microphone-disabled-symbolic"
: "microphone-sensitivity-high-symbolic",
),
}),
label: Widget.Label({
label: audio.microphone
.bind("is_muted")
.as((x) => (x ? "Unmute" : "Mute")),
}),
activate: () => {
audio.microphone.is_muted = true;
},
deactivate: () => {
audio.microphone.is_muted = false;
},
connection: [audio.microphone, () => audio.microphone.is_muted || false],
...props,
});
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
export const SinkSelector = (props) =>
Menu({
name: "sink-selector",
icon: Widget.Icon("audio-headphones-symbolic"),
title: "Sink Selector",
content: [
Widget.Box({
className: "qs-sub-sub-content",
vertical: true,
children: audio.bind("speakers").as((a) => a.map(SinkItem)),
}),
Widget.Separator({ className: "accent" }),
SettingsButton({ type: "sound" }),
],
...props,
});
/** @param {import("../types/service/audio.js").Stream} stream */
const SinkItem = (stream) =>
Widget.Button({
hexpand: true,
onClicked: () => {
audio.speaker = stream;
},
child: Widget.Box({
children: [
Widget.Icon({
icon: icon(stream.icon_name, "audio-x-generic-symbolic"),
tooltip_text: stream.icon_name || "",
}),
Widget.Label({
label: (stream.description || "").split(" ").slice(0, 4).join(" "),
}),
Widget.Icon({
icon: "object-select-symbolic",
hexpand: true,
hpack: "end",
visible: audio.speaker.bind("stream").as((s) => s === stream.stream),
}),
],
}),
});
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
export const AppMixer = (props) =>
Menu({
name: "app-mixer",
icon: Widget.Icon("audio-volume-high-symbolic"),
title: "App Mixer",
content: [
Widget.Box({
vertical: true,
className: "qs-sub-sub-content",
children: audio.bind("apps").as((a) => a.map(MixerItem)),
}),
Widget.Separator({ className: "accent" }),
SettingsButton({ type: "sound" }),
],
...props,
});
/** @param {import("../types/service/audio.js").Stream} stream */
const MixerItem = (stream) =>
Widget.Box({
hexpand: true,
children: [
Widget.Icon({
tooltipText: stream.bind("name").as((n) => n || ""),
icon: stream
.bind("name")
.as((n) =>
n && Utils.lookUpIcon(n) ? n : "audio-x-generic-symbolic",
),
}),
Widget.Box({
vertical: true,
children: [
Widget.Label({
xalign: 0,
truncate: "end",
max_width_chars: 28,
label: stream.bind("description").as((d) => d || ""),
}),
Widget.Slider({
hexpand: true,
draw_value: false,
value: stream.bind("volume"),
onChange: ({ value }) => {
stream.volume = value;
},
}),
],
}),
Widget.Label({
css: "padding: 12px",
label: stream.bind("volume").as(
(x) =>
`${Math.floor(x * 100)
.toString()
.padStart(3)}%`,
),
}),
],
});

View File

@@ -1,28 +0,0 @@
const battery = await Service.import("battery");
/** @param {import("../types/widgets/box").BoxProps} props */
export const Indicator = ({ ...props }) =>
Widget.Box({
children: [
Widget.Icon({
icon: battery.bind("icon_name"),
className: Utils.merge(
[
battery.bind("charging"),
battery.bind("charged"),
battery.bind("percent"),
],
(charging, charged, percent) => {
if (charging || charged) return "green";
if (percent < 30) return "red";
return "";
},
),
}),
Widget.Label({
label: battery.bind("percent").as((x) => `${x}%`),
}),
],
visible: battery.bind("available"),
...props,
});

View File

@@ -1,94 +0,0 @@
import { ArrowToggleButton, Menu, SettingsButton } from "../misc/menu.js";
const bluetooth = await Service.import("bluetooth");
const connected = Utils.merge(
[bluetooth.bind("enabled"), bluetooth.bind("connected_devices")],
(enabled, devices) => enabled && devices.length > 0,
);
/** @param {{hideIfDisabled?: boolean} & import("../types/widgets/icon.js").IconProps} props */
export const Indicator = ({ hideIfDisabled = false, ...props } = {}) =>
Widget.Icon({
icon: connected.as(
(x) => `bluetooth-${x ? "active" : "disabled"}-symbolic`,
),
visible: connected.as((x) => x || !hideIfDisabled),
...props,
});
/** @param {import("../types/widgets/label.js").LabelProps} props */
export const ConnectedLabel = (props) =>
Widget.Label(props).hook(bluetooth, (self) => {
if (!bluetooth.enabled) self.label = "Disabled";
if (bluetooth.connected_devices.length === 0) self.label = "Disconnected";
else if (bluetooth.connected_devices.length === 1)
self.label = bluetooth.connected_devices[0].alias;
else self.label = `${bluetooth.connected_devices.length} Connected`;
});
/** @param {Partial<import("../misc/menu.js").ArrowToggleButtonProps>} props */
export const Toggle = (props) =>
ArrowToggleButton({
name: "bluetooth",
icon: Indicator({ className: "qs-icon" }),
label: ConnectedLabel({
max_width_chars: 20,
}),
activate: () => {
bluetooth.enabled = true;
},
deactivate: () => {
bluetooth.enabled = false;
},
connection: [bluetooth, () => bluetooth.enabled],
...props,
});
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
export const Selection = (props) =>
Menu({
name: "bluetooth",
icon: Indicator({}),
title: "Bluetooth",
content: [
Widget.Box({
vertical: true,
className: "qs-sub-sub-content",
children: bluetooth.bind("devices").as((x) => x.map(DeviceItem)),
}),
Widget.Separator({ className: "accent" }),
SettingsButton({ command: "blueberry" }),
],
...props,
});
/** @param {import("../types/service/bluetooth.js").BluetoothDevice} device */
const DeviceItem = (device) =>
Widget.Box({
children: [
Widget.Icon(`${device.icon_name}-symbolic`),
Widget.Label(device.name),
Widget.Box({ hexpand: true }),
Widget.Label({
label: `${device.battery_percentage}%`,
css: "padding-right: 24px;",
visible: device.bind("battery_percentage").as((x) => x > 0),
}),
Widget.Spinner({
active: device.bind("connecting"),
visible: device.bind("connecting"),
}),
Widget.Switch({
active: device.bind("connected"),
visible: device.bind("connecting").as((p) => !p),
setup: (self) =>
// TODO: If connecting to the device failed, reset back the switch to `active: false`.
self.connect("state_set", () => {
device.setConnection(self.active);
return true;
}),
}),
],
});

View File

@@ -1,38 +0,0 @@
import brightness from "../services/brightness.js";
/** @param {import("../types/widgets/slider.js").SliderProps} props */
const BrightnessSlider = (props) =>
Widget.Slider({
drawValue: false,
hexpand: true,
value: brightness.bind("screen"),
onChange: ({ value }) => {
brightness.screen = value;
},
...props,
});
/** @param {import("../types/widgets/box.js").BoxProps} props */
export const Brightness = (props) =>
Widget.Box({
className: "qs-slider",
children: [
Widget.Icon({
vpack: "center",
icon: "display-brightness-symbolic",
tooltipText: brightness
.bind("screen")
.as((x) => `Screen Brightness: ${Math.floor(x * 100)}%`),
}),
BrightnessSlider({}),
Widget.Label({
label: brightness.bind("screen").as(
(x) =>
`${Math.floor(x * 100)
.toString()
.padStart(3)}%`,
),
}),
],
...props,
});

View File

@@ -1,14 +0,0 @@
import GLib from "gi://GLib";
export const clock = Variable(GLib.DateTime.new_now_local(), {
poll: [1000, () => GLib.DateTime.new_now_local()],
});
/**
* @param {{format?: string} & import("../types/widgets/label").LabelProps} props
*/
export const Clock = ({ format = "%a %d %b %H:%M ", ...props } = {}) =>
Widget.Label({
...props,
label: Utils.derive([clock], (c) => c.format(format) || "").bind(),
});

View File

@@ -1,57 +0,0 @@
import Gio from "gi://Gio";
import { SimpleToggleButton } from "../misc/menu.js";
const interfaceXml = `
<node name="/nl/whynothugo/darkman">
<interface name="nl.whynothugo.darkman">
<signal name="ModeChanged">
<arg name="NewMode" type="s" />
</signal>
<property name="Mode" type="s" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true" />
</property>
</interface>
</node>
`;
const Darkman = Gio.DBusProxy.makeProxyWrapper(interfaceXml);
const theme = Variable(/** @type {"light" | "dark"} */ ("light"));
/** @param {Partial<import("../misc/menu.js").SimpleToggleButtonProps>} props */
export const Toggle = ({ ...props } = {}) =>
SimpleToggleButton({
icon: Widget.Icon({
className: "qs-icon",
icon: theme
.bind()
.as((x) =>
x === "light"
? "weather-clear-symbolic"
: "weather-clear-night-symbolic",
),
}),
label: Widget.Label({
label: theme.bind().as((x) => (x === "light" ? "Light" : "Dark")),
}),
activate: () => theme.setValue("dark"),
deactivate: () => theme.setValue("light"),
connection: [theme, () => theme.value === "dark"],
...props,
});
function init() {
const darkman = Darkman(
Gio.DBus.session,
"nl.whynothugo.darkman",
"/nl/whynothugo/darkman",
);
theme.value = darkman.Mode;
theme.connect("changed", () => {
darkman.Mode = theme.value;
});
darkman.connectSignal("ModeChanged", (_proxy, _senderName, nTheme) => {
theme.value = nTheme[0];
});
}
init();

View File

@@ -1,290 +0,0 @@
import { AnimatedCircProg } from "../misc/circular-progress.js";
const mpris = await Service.import("mpris");
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/icon").IconProps} props */
const PlayerIcon = ({ player, ...props }) =>
Widget.Icon({
size: 24,
hpack: "start",
vpack: "start",
tooltipText: player.identity || "",
icon: player.bind("entry").transform((entry) => {
const name = `${entry}-symbolic`;
return Utils.lookUpIcon(name)
? name
: Utils.lookUpIcon(entry)
? entry
: "audio-x-generic-symbolic";
}),
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/label").LabelProps} props */
const TitleLabel = ({ player, ...props }) =>
Widget.Label({
wrap: true,
truncate: "end",
hpack: "start",
label: player.bind("track_title"),
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/label").LabelProps} props */
const ArtistLabel = ({ player, ...props }) =>
Widget.Label({
wrap: true,
truncate: "end",
hpack: "start",
label: player.bind("track_artists").transform((x) => x.map(y => y.replace(/- Topic$/, "")).join(", ")),
...props,
});
/** @param {{
* player: import("types/service/mpris").MprisPlayer,
* iconProps?: import("types/widgets/icon").IconProps,
* } & import("types/widgets/button").ButtonProps} props */
export const PlayPause = ({ player, iconProps = {}, ...props }) =>
Widget.Button({
child: Widget.Icon({
icon: player.bind("play_back_status").as(
(x) =>
({
Playing: "media-playback-pause-symbolic",
Paused: "media-playback-start-symbolic",
Stopped: "media-playback-start-symbolic",
})[x],
),
...iconProps,
}),
onClicked: () => player.playPause(),
visible: player.bind("can_play"),
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */
const PreviousButton = ({ player, ...props }) =>
Widget.Button({
child: Widget.Icon({ icon: "media-skip-backward-symbolic" }),
onClicked: () => player.previous(),
visible: player.bind("can_go_prev"),
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */
const NextButton = ({ player, ...props }) =>
Widget.Button({
child: Widget.Icon({ icon: "media-skip-forward-symbolic" }),
onClicked: () => player.next(),
visible: player.bind("can_go_next"),
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/slider").SliderProps} props */
const PositionSlider = ({ player, ...props }) =>
Widget.Slider({
className: "mpris-position-slider",
drawValue: false,
onChange: ({ value }) => {
player.position = value * player.length;
},
visible: player.bind("length").as((l) => l > 0),
setup: (self) => {
function update() {
const value = player.position / player.length;
self.value = value > 0 ? value : 0;
}
self.hook(player, update);
self.hook(player, update, "position");
self.poll(1000, update);
},
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/box").BoxProps} props */
const PositionCircle = ({ player, child, ...props }) =>
Widget.Box({
child: Widget.Overlay({
child: AnimatedCircProg({
css: `
min-width: 0.136rem;
min-height: 1.636rem;
padding: 0rem;
`,
vpack: "center",
hpack: "center",
className: "accent-rev",
extraSetup: (self) => {
function update() {
const value = player.position / player.length;
self.css = `
font-size: ${Math.max(value * 100, 0)}px;
min-width: 0.136rem;
min-height: 1.636rem;
padding: 0rem;
`;
}
self.hook(player, update);
self.hook(player, update, "position");
self.poll(1000, update);
},
}),
overlays: [child],
}),
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/box").BoxProps} props */
export const LinePlayer = ({ player, ...props }) =>
Widget.Box({
visible: player.bind("play_back_status").as((x) => x !== "Stopped"),
children: [
PositionCircle({
player,
child: PlayPause({ player, iconProps: { size: 10 } }),
css: "margin-right: 12px;",
}),
Widget.Label({
label: Utils.merge(
[player.bind("track_title"), player.bind("track_artists")],
(title, artists) => `${title} - ${artists.map(x => x.replace(/- Topic$/, "")).join(", ")}`,
),
maxWidthChars: 30,
wrap: true,
truncate: "end",
hpack: "start",
}),
],
...props,
});
export const activePlayer = Variable(mpris.players[0]);
mpris.connect("player-added", (_, bus) => {
mpris.getPlayer(bus)?.connect("changed", (player) => {
if (player?.play_back_status !== "Stopped") {
activePlayer.value = player || mpris.players[0];
} else {
activePlayer.value = mpris.players[0];
}
});
});
mpris.connect("player-closed", (_, bus) => {
if (activePlayer.value.bus_name === bus)
activePlayer.value = mpris.players[0];
});
/** @param {{player?: import("types/service/mpris").MprisPlayer | null} & import("../types/widgets/box").BoxProps} props */
export const MprisPlayer = ({ player, ...props }) => {
if (!player) return Widget.Box({ visible: false });
const colors = getMaterialColors(player);
return Widget.Box({
visible: player.bind("play_back_status").as((x) => x !== "Stopped"),
className: `mpris-cover-art ${props.className}`,
css: Utils.merge(
[colors.bind(), player.bind("cover_path")],
(colors, cover) => `
background-image: radial-gradient(circle, rgba(0, 0, 0, 0.4) 30%, ${colors.primary}), url("${cover}"); \
color: ${colors.onBackground};
`,
),
vexpand: false,
children: [
Widget.CenterBox({
vertical: true,
hexpand: true,
startWidget: Widget.Box({
vertical: true,
vexpand: true,
css: colors.bind().as((x) => `color: ${x.onBackground}`),
child: PlayerIcon({ player }),
}),
centerWidget: Widget.Box({
hexpand: true,
children: [
Widget.Box({
css: colors.bind().as(
(x) => `
color: ${x.onBackground};
margin-right: 12px;
`,
),
vertical: true,
vpack: "center",
hexpand: true,
children: [
TitleLabel({
player,
css: "font-weight: 600; font-size: 19px;",
maxWidthChars: 33,
}),
ArtistLabel({
player,
css: "font-weight: 400; font-size: 17px;",
maxWidthChars: 40,
}),
],
}),
Widget.Box({
children: [
PlayPause({
player,
hpack: "end",
className: "mpris-play",
css: colors.bind().as(
(x) => `
background-color: ${x.primary};
color: ${x.onPrimary};
`,
),
}),
],
}),
],
}),
endWidget: Widget.Box({
css: colors.bind().as((x) => `color: ${x.onBackground}`),
vpack: "end",
children: [
Widget.Box({
hexpand: true,
children: [
PreviousButton({ player, css: "margin-left: 16px;" }),
PositionSlider({ player, hexpand: true }),
NextButton({ player, css: "margin-right: 16px;" }),
],
}),
],
}),
}),
],
...props,
});
};
// TODO: Move ret inside getMaterialColors to support multiple players.
const ret = Variable({
primary: "#222222",
onPrimary: "#ffffff",
background: "#222222",
onBackground: "#ffffff",
});
/** @param {import("types/service/mpris").MprisPlayer} player */
export const getMaterialColors = (player) => {
// TODO: Move that to a hook to allow graceful disconnections
player.connect("changed", (player) => {
const cover = player.cover_path;
// TODO: Wait for the cover to be downloaded, currently we hope that it's ready in <100ms
Utils.timeout(100, () => {
Utils.execAsync(["covercolors", cover])
.then((colors) => {
const col = JSON.parse(colors);
if (!col) return;
ret.setValue(col);
})
.catch(print);
});
});
return ret;
};

View File

@@ -1,114 +0,0 @@
import { ArrowToggleButton, Menu, SettingsButton } from "../misc/menu.js";
const network = await Service.import("network");
/** @param {import("../types/widgets/icon.js").IconProps} props*/
export const Indicator = (props) =>
Widget.Icon(props).hook(network, (self) => {
self.icon =
network[network.primary || "wifi"].icon_name ??
"network-wireless-offline-symbolic";
});
/** @param {import("../types/widgets/label.js").LabelProps} props */
export const SSIDLabel = (props) =>
Widget.Label({
truncate: "end",
...props,
}).hook(network, (self) => {
if (network.primary === "wifi")
self.label = network.wifi.ssid || "Not Connected";
else
self.label =
network.wired.internet !== "disconnected" ? "Wired" : "Not Connected";
});
/** @param {Partial<import("../misc/menu.js").ArrowToggleButtonProps>} props */
export const Toggle = (props) =>
ArrowToggleButton({
name: "network",
icon: Indicator({ className: "qs-icon" }),
label: SSIDLabel({
max_width_chars: 20,
}),
activate: () => {
network.wifi.enabled = true;
network.wifi.scan();
},
deactivate: () => {
network.wifi.enabled = false;
},
connection: [network.wifi, () => network.wifi.enabled],
...props,
});
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
export const Selection = (props) =>
Menu({
name: "network",
icon: Indicator({}),
title: "Network Selection",
content: [
Wired(),
Widget.Box({
vertical: true,
className: "qs-sub-sub-content",
children: network.wifi.bind("access_points").as((x) =>
x
.sort((a, b) => b.strength - a.strength)
.reduce((acc, x) => {
if (!acc.find((y) => y.ssid === x.ssid)) acc.push(x);
return acc;
}, [])
.slice(0, 10)
.map(WifiItem),
),
}),
Widget.Separator({ className: "accent" }),
SettingsButton({ type: "wifi" }),
],
...props,
});
const Wired = () =>
Widget.Button({
// onClicked:
// visible: network.wired.bind("state").as(x => (console.log(x), true)),
child: Widget.Box({
children: [
Widget.Icon({ icon: network.wired.bind("icon_name") }),
Widget.Label("Wired"),
Widget.Icon({
icon: "object-select-symbolic",
hexpand: true,
hpack: "end",
visible: network.bind("primary").as((x) => x === "wired"),
}),
],
}),
});
/** @param {import("../types/service/network.js").Wifi["access_points"][0]} wifi */
const WifiItem = (wifi) =>
Widget.Button({
onClicked: () => Utils.execAsync(`nmcli device wifi connect ${wifi.bssid}`),
child: Widget.Box({
children: [
Widget.Icon(wifi.iconName),
Widget.Label({
truncate: "end",
max_width_chars: 28,
label: wifi.ssid || "",
}),
Widget.Icon({
icon: "object-select-symbolic",
hexpand: true,
hpack: "end",
setup: (self) =>
Utils.idle(() => {
if (!self.is_destroyed) self.visible = wifi.active;
}),
}),
],
}),
});

View File

@@ -1,270 +0,0 @@
const notifications = await Service.import("notifications");
notifications.popupTimeout = 2000; //in seconds
notifications.forceTimeout = true; //force all notifications to timeout
/** @param {import("../types/widgets/icon").IconProps} props */
export const DNDIndicator = (props) =>
Widget.Icon({
visible: notifications.bind("dnd"),
icon: "notifications-disabled-symbolic",
...props,
});
/** @param {import("../types/widgets/button").ButtonProps} props */
export const DNDToggle = (props) =>
Widget.Button({
onClicked: () => {
notifications.dnd = !notifications.dnd;
},
child: Widget.Icon({
icon: notifications
.bind("dnd")
.as((x) =>
x
? "preferences-system-notifications-symbolic"
: "notifications-disabled-symbolic",
),
}),
className: notifications.bind("dnd").as((x) => (x ? "on" : "")),
...props,
});
/** @param {import("../types/widgets/box").BoxProps} props */
export const Indicator = ({ ...props }) =>
Widget.Box({
visible: Utils.merge(
[
notifications.bind("notifications").as((x) => x.length > 0),
notifications.bind("dnd"),
],
(hasNotif, dnd) => hasNotif && !dnd,
),
children: [
Widget.Icon({ icon: "preferences-system-notifications-symbolic" }),
Widget.Revealer({
transition: "slide_right",
revealChild: notifications.bind("popups").as((x) => x.length > 0),
child: Widget.Label({
use_markup: true,
truncate: "end",
wrap: false,
label: notifications.bind("popups").as((x) => {
const notif = x[x.length - 1];
// Keep the text of the old notif for the fade out animation.
if (!notif) return old_notif;
const summary = notif.summary.substring(0, 18).trim();
const body = notif.body.substring(0, 45).trim();
old_notif = `${summary}: ${body}`.replaceAll("\n", " ");
return old_notif;
}),
}),
}),
],
...props,
});
let old_notif = "";
/** @param {import("../types/service/notifications").Notification} param */
const NotificationIcon = ({ app_entry, app_icon, image }) => {
if (image) {
return Widget.Box({
vpack: "start",
hexpand: false,
className: "r20",
css: `
background-image: url("${image}");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
min-width: 78px;
min-height: 78px;
margin-right: 12px;
`,
});
}
let icon = "dialog-information-symbolic";
if (Utils.lookUpIcon(app_icon)) icon = app_icon;
if (Utils.lookUpIcon(app_entry || "")) icon = app_entry || "";
return Widget.Box({
vpack: "start",
hexpand: false,
className: "r20",
css: `
min-width: 78px;
min-height: 78px;
margin-right: 12px;
`,
child: Widget.Icon({
icon,
size: 58,
hpack: "center",
hexpand: true,
vpack: "center",
vexpand: true,
}),
});
};
import GLib from "gi://GLib";
/** @param {number} time */
const time = (time, format = "%H:%M") =>
GLib.DateTime.new_from_unix_local(time).format(format);
/** @param {import("../types/service/notifications").Notification} notification */
export const Notification = (notification) => {
const content = Widget.Box({
children: [
NotificationIcon(notification),
Widget.Box({
hexpand: true,
vertical: true,
children: [
Widget.Box({
children: [
Widget.Label({
css: `
font-size: 1.1em;
margin-right: 12pt;
`,
xalign: 0,
justification: "left",
hexpand: true,
max_width_chars: 24,
truncate: "end",
wrap: true,
label: notification.summary.trim(),
use_markup: true,
}),
Widget.Label({
vpack: "start",
label: time(notification.time),
}),
Widget.Button({
css: `
margin-left: 6pt;
min-width: 1.2em;
min-height: 1.2em;
`,
vpack: "start",
child: Widget.Icon("window-close-symbolic"),
on_clicked: notification.close,
}),
],
}),
Widget.Label({
css: "font-size: .9em;",
hexpand: true,
use_markup: true,
xalign: 0,
justification: "left",
label: notification.body.trim(),
max_width_chars: 24,
wrap: true,
}),
],
}),
],
});
const actionsbox =
notification.actions.length > 0
? Widget.Revealer({
transition: "slide_down",
child: Widget.EventBox({
child: Widget.Box({
class_name: "actions horizontal",
children: notification.actions.map((action) =>
Widget.Button({
className: "button",
on_clicked: () => notification.invoke(action.id),
hexpand: true,
child: Widget.Label(action.label),
}),
),
}),
}),
})
: null;
const eventbox = Widget.EventBox({
vexpand: false,
on_primary_click: notification.dismiss,
on_hover() {
if (actionsbox) actionsbox.reveal_child = true;
},
on_hover_lost() {
if (actionsbox) actionsbox.reveal_child = true;
notification.dismiss();
},
child: Widget.Box({
vertical: true,
children: actionsbox ? [content, actionsbox] : [content],
}),
});
return Widget.Box({
className: "surface r20 p10",
css: "margin: 8px 0;",
child: eventbox,
});
};
/** @param {import("../types/widgets/scrollable").ScrollableProps} props */
export const List = (props) =>
Widget.Scrollable({
vscroll: "automatic",
hscroll: "never",
css: "min-height: 500px;",
child: Widget.Box({
vertical: true,
children: notifications
.bind("notifications")
.as((x) => x.map(Notification)),
}),
...props,
});
/** @param {import("../types/widgets/box").BoxProps} props */
export const Placeholder = (props) =>
Widget.Box({
vertical: true,
vpack: "center",
hpack: "center",
children: [
Widget.Icon({
icon: "notifications-disabled-symbolic",
size: 75,
css: "margin: 50px 0",
}),
Widget.Label({
label: "Your inbox is empty",
css: "margin-bottom: 150px;",
}),
],
...props,
});
/** @param {import("../types/widgets/button").ButtonProps} props */
export const ClearButton = (props) =>
Widget.Button({
className: "surface r20 p10",
onClicked: () => notifications.clear(),
sensitive: notifications.bind("notifications").as((x) => x.length > 0),
child: Widget.Box({
children: [
Widget.Label({ label: "Clear " }),
Widget.Icon({
icon: notifications
.bind("notifications")
.as((x) =>
x.length > 0 ? "user-trash-full-symbolic" : "user-trash-symbolic",
),
}),
],
}),
...props,
});

View File

@@ -1,62 +0,0 @@
import { ArrowToggleButton, Menu } from "../misc/menu.js";
const powerProfiles = await Service.import("powerprofiles");
/** @param {string} x */
const capitalize = (x) => x.charAt(0).toUpperCase() + x.slice(1);
/** @param {Partial<import("../misc/menu.js").ArrowToggleButtonProps>} props */
export const Toggle = (props) =>
ArrowToggleButton({
name: "powerprofile",
icon: Widget.Icon({
icon: powerProfiles.bind("icon_name"),
className: "qs-icon",
}),
label: Widget.Label({
label: powerProfiles.bind("active_profile").as(capitalize),
truncate: "end",
max_width_chars: 20,
}),
activate: () => {},
deactivate: () => {},
connection: [powerProfiles, () => false],
...props,
});
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
export const Selection = (props) =>
Menu({
name: "powerprofile",
icon: Widget.Icon({ icon: powerProfiles.bind("icon_name") }),
title: "Power profile",
// Hard coding the list of profiles since ags does not give them.
content: ["power-saver", "balanced", "performance"].map(ProfileItem),
...props,
});
/** @param {string} profile */
const ProfileItem = (profile) =>
Widget.Button({
onClicked: () => (powerProfiles.active_profile = profile),
child: Widget.Box({
children: [
Widget.Icon({
icon: `power-profile-${profile}-symbolic`,
}),
Widget.Label({
label: capitalize(profile),
truncate: "end",
maxWidthChars: 28,
}),
Widget.Icon({
icon: "object-select-symbolic",
hexpand: true,
hpack: "end",
visible: powerProfiles
.bind("active_profile")
.as((x) => x === profile),
}),
],
}),
});

View File

@@ -1,52 +0,0 @@
import { icon } from "../misc/utils.js";
import { ArrowToggleButton, Menu } from "../misc/menu.js";
const systemtray = await Service.import("systemtray");
/** @param {Partial<import("../misc/menu.js").ArrowToggleButtonProps>} props */
export const Toggle = (props) =>
ArrowToggleButton({
name: "systray",
icon: Widget.Icon({ icon: "open-menu-symbolic", className: "qs-icon" }),
label: Widget.Label("Systray"),
activate: () => {},
deactivate: () => {},
connection: [systemtray, () => systemtray.items.length > 0],
...props,
});
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
export const Selection = (props) =>
Menu({
name: "systray",
icon: Widget.Icon("open-menu-symbolic"),
title: "Systray",
content: [
Widget.Box({
vertical: true,
className: "qs-sub-sub-content",
children: systemtray.bind("items").as((i) => i.map(SysTrayItem)),
}),
],
...props,
});
/** @param {import('../types/service/systemtray.js').TrayItem} item */
const SysTrayItem = (item) =>
Widget.Button({
css: "margin: 12px;",
child: Widget.Box({
children: [
Widget.Icon({ icon: item.bind("icon").as(icon) }),
Widget.Label({
truncate: "end",
maxWidthChars: 28,
}).hook(item, (self) => {
self.label = item.title || item.tooltip_markup;
}),
],
}),
tooltipMarkup: item.bind("tooltip_markup"),
onPrimaryClick: (_, event) => item.activate(event),
onSecondaryClick: (_, event) => item.openMenu(event),
});

View File

@@ -1,96 +0,0 @@
import Gdk from "gi://Gdk?version=3.0";
// const hyprland = await Service.import("hyprland");
//
// const display = Gdk.Display.get_default();
// /** @param {number} monitor */
// const getMonitorName = (monitor) => {
// return display.get_default_screen().get_monitor_plug_name(monitor);
// };
//
// /** @type {Record<number, boolean>} */
// const urgents = {}
//
// /** @param {{monitor: number, labels: string[]} & import("types/widgets/box").BoxProps} props */
// export const Tags = ({ monitor, labels, ...props }) => {
// const monName = getMonitorName(monitor);
// // @ts-ignore
// return Widget.Box({
// ...props,
// children: Array.from({ length: 9 }, (_, i) => i).map((i) =>
// Widget.EventBox({
// child: Widget.Label({ label: labels[i], className: "tags" }),
// attribute: i + 1,
// onPrimaryClickRelease: () => {
// hyprland.message(`dispatch workspace ${i + 1}`);
// },
// }),
// ),
// setup: (self) => {
// self.hook(hyprland, (_, address) => {
// const client = hyprland.getClient(address);
// if (!client) return;
// const ws = client.workspace.id;
// urgents[ws] = true;
// }, "urgent-window");
//
// self.hook(hyprland, () =>
// self.children.forEach((btn) => {
// const mon = hyprland.monitors.find((x) => x.name === monName);
// const ws = hyprland.workspaces.find((x) => x.id === btn.attribute);
//
// const occupied = (ws?.windows ?? 0) > 0;
// const selected = mon?.activeWorkspace?.id === btn.attribute;
// if (selected) urgents[btn.attribute] = false;
// const urgent = urgents[btn.attribute];
//
// btn.visible = occupied || selected;
// btn.class_names = [
// selected ? "accent" : "",
// urgent ? "secondary" : "",
// ];
// }),
// );
// },
// });
// };
//
// /** @param {{monitor: number } & import("types/widgets/label").LabelProps} props */
// export const Layout = ({ monitor, ...props }) => {
// const monName = getMonitorName(monitor);
// return Widget.Label({
// className: "module",
// ...props,
// }).hook(
// hyprland,
// (self) => {
// const mon = hyprland.monitors.find((x) => x.name === monName);
// const ws = hyprland.workspaces.find(
// (x) => x.id === mon?.activeWorkspace?.id,
// );
// self.label = ws?.windows ? `[${ws?.windows}]` : "";
// },
// "changed",
// );
// }
//
// /** @param {{monitor: number, fallback?: string} & import("types/widgets/label").LabelProps} props */
// export const ClientLabel = ({ monitor, fallback = "", ...props }) => {
// const monName = getMonitorName(monitor);
// return Widget.Label({
// truncate: "end",
// maxWidthChars: 25,
// className: "module",
// ...props,
// }).hook(
// hyprland,
// (self) => {
// const mon = hyprland.monitors.find((x) => x.name === monName);
// const ws = hyprland.workspaces.find(
// (x) => x.id === mon?.activeWorkspace?.id,
// );
// self.label = ws?.lastwindowtitle || fallback;
// },
// "changed",
// );
// };

View File

@@ -1,44 +0,0 @@
const screen = await Utils.execAsync(
"/bin/sh -c 'ls -w1 /sys/class/backlight | head -n 1'",
);
const max = Number(await Utils.execAsync("brightnessctl m"));
class Brightness extends Service {
static {
Service.register(this, {}, { screen: ["float", "rw"] });
}
#screen = 0;
get screen() {
return this.#screen;
}
set screen(percent) {
if (percent < 0) percent = 0;
if (percent > 1) percent = 1;
Utils.execAsync(`brightnessctl s ${percent * 100}% -q`)
.then(() => {
this.#screen = percent;
this.emit("changed");
this.notify("screen");
})
.catch(print);
}
constructor() {
super();
this.#screen = Number(Utils.exec("brightnessctl g")) / max;
const screenPath = `/sys/class/backlight/${screen}/brightness`;
Utils.monitorFile(screenPath, async (f) => {
const v = await Utils.readFileAsync(f);
this.#screen = Number(v) / max;
this.changed("screen");
});
}
}
export default new Brightness();

View File

@@ -1,232 +0,0 @@
* {
font-family: monospace;
padding: 0;
margin: 0;
}
button {
all: unset;
}
/* Colors */
.transparent {
background-color: rgba(0, 0, 0, 0.6);
}
.bgcont {
background-color: #1E1E2E;
border: 1px solid #11111B;
}
.surface {
background-color: #11111B;
color: #CDD6F4;
}
.accent {
background-color: #94e2d5;
color: #1e1e2e;
}
.accent-rev {
color: #94e2d5;
background-color: #1e1e2e;
}
.secondary {
background-color: #cba6f7;
color: #1e1e2e;
}
.red {
color: #F38BA8;
}
.green {
color: #A6E3A1;
}
/* Misc */
.bold {
font-weight: bold;
}
.f16 {
font-size: 16px;
}
.round {
border-radius: 50%;
}
.r20 {
border-radius: 20px;
}
.r100 {
border-radius: 100px;
}
.p10 {
padding: 10px;
}
separator {
border-radius: 10px;
min-height: 1px;
min-width: 1px;
}
/* Slider */
slider {
box-shadow: none;
background-color: transparent;
border: 1px solid transparent;
transition: 200ms;
min-height: 1rem;
border-radius: 100px;
}
trough {
transition: 200ms;
border: 1px;
background-color: #000;
border-radius: 100px;
}
highlight, fill {
background-color: #94e2d5;
border-radius: 100px;
transition: 200ms;
}
mark {
background-color: #F38BA8;
min-width: 1px;
min-height: 3px;
}
switch {
background-color: #1E1E2E;
border-radius: 100px;
}
switch slider {
all: unset;
transition: 200ms;
background-color: #94e2d5;
border-radius: 100px;
min-width: 1rem;
min-height: 1rem;
}
switch image {
all: unset;
color: transparent;
}
/* Bar */
.module {
padding: 1px 10px;
margin: 0 4px;
}
.tags {
min-width: 2rem;
}
.qs-item {
padding: 3px;
}
/* On Screen Display */
.osd {
border-radius: 20px;
margin-bottom: 150px;
min-width: 175px;
min-height: 30px;
}
/* Quick settings */
.qs-container {
min-width: 400px;
padding: 25px;
margin: 10px;
border-radius: 20px;
}
.qs-button {
padding: 0px;
margin: 5px;
border-radius: 20px;
min-width: 225px;
min-height: 75px;
transition: background-color 0.3s ease-in-out;
transition: color 0.3s ease-in-out;
}
.qs-icon {
padding: 12px;
}
.qs-slider > * {
margin: 5px;
}
.qs-submenu {
margin-top: 8px;
margin-bottom: 8px;
padding: 12px;
border-radius: 20px;
}
.qs-sub-title {
padding: 14px 20px;
border-radius: 100px;
margin-bottom: 8px;
}
.qs-sub-title > *:first-child {
margin-right: 14px;
}
.qs-sub-content image {
margin-right: 8px;
}
.qs-sub-content > * {
margin: 6px;
}
.qs-sub-sub-content > * {
margin: 6px 0px;
}
.avatar {
border-radius: 50%;
min-width: 70px;
min-height: 70px;
background-size: cover;
}
.sys-button {
min-width: 40px;
min-height: 40px;
border-radius: 15px;
}
.mpris-cover-art {
background-size: cover;
background-position: center;
margin: 10px 0;
min-height: 205px;
border-radius: 20px;
transition: all 0.2s;
}
.mpris-cover-art > * {
padding: 20px;
}
.mpris-play {
min-width: 60px;
min-height: 60px;
border-radius: 20px;
padding: 0;
transition: all 0.3s;
}
.mpris-position-slider {
margin: 0 10px;
}
.mpris-position-slider trough {
border-radius: 100px;
background-color: rgba(255, 255, 255, 0.6);
min-height: 4px;
}
.mpris-position-slider highlight, .mpris-position-slider progress {
background-color: #fff;
border-radius: 100px;
}
.mpris-position-slider slider {
border-radius: 100px;
min-height: 4px;
min-width: 4px;
background-color: #fff;
}

View File

@@ -1,18 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": [
"ES2022"
],
"allowJs": true,
"checkJs": true,
"strict": true,
"noImplicitAny": false,
"baseUrl": ".",
"typeRoots": [
"./types"
],
"skipLibCheck": true
}
}

View File

@@ -1 +0,0 @@
/nix/store/g5s50dy9za9inrw5zps05s7kh4ypnbpa-ags-1.8.2/share/com.github.Aylur.ags/types

View File

@@ -1,6 +1,7 @@
{
pkgs,
lib,
noctalia,
...
}: let
wallpaper = pkgs.writeShellScriptBin "wallpaper" ''
@@ -39,9 +40,9 @@
in {
imports = [
./rofi
./ags
./fcitx5.nix
./hyprlock.nix
noctalia.homeModules.default
];
home.packages = [
wallpaper
@@ -89,4 +90,124 @@ in {
button-layout = "";
};
};
programs.noctalia-shell = {
enable = true;
systemd.enable = true;
settings = {
bar = {
capsuleOpacity = 0.5;
showCapsule = false;
outerCorners = false;
widgets = {
left = [
{
id = "TaskbarGrouped";
labelMode = "none";
}
];
center = [
{
id = "NotificationHistory";
}
];
right = [
{
id = "MediaMini";
maxWidth = 250;
showArtistFirst = false;
}
{
id = "Spacer";
}
{
id = "Tray";
}
{
id = "Battery";
displayMode = "alwaysShow";
warningThreshold = 30;
}
{
id = "Volume";
displayMode = "alwaysHide";
}
{
id = "Bluetooth";
displayMode = "alwaysHide";
}
{
id = "WiFi";
displayMode = "alwaysHide";
}
{
id = "Spacer";
}
{
id = "Clock";
formatHorizontal = "HH:mm\\nyyyy-MM-dd";
}
];
};
};
controlCenter = {
position = "top_center";
};
audio.visualizerType = "none";
notifications = {
enabled = true;
location = "bar";
lowUrgencyDuration = 3;
normalUrgencyDuration = 3;
criticalUrgencyDuration = 3;
};
osd = {
enabled = true;
location = "bottom";
};
sessionMenu = {
enableCountdown = false;
powerOptions = [
{
action = "lock";
enabled = true;
}
{
action = "suspend";
enabled = true;
}
{
action = "hibernate";
enabled = false;
}
{
action = "reboot";
enabled = true;
}
{
action = "logout";
enabled = false;
}
{
action = "shutdown";
enabled = true;
}
];
showHeader = true;
};
screenRecorder.directory = "~/stuff";
settingsVersion = 23;
setupCompleted = true;
general = {
lockOnSuspend = false;
showScreenCorners = false;
};
colorSchemes = {
predefinedScheme = "Catppuccin";
};
wallpaper.enabled = false;
dock.enabled = false;
nightLight.enabled = false;
};
};
}

View File

@@ -1,13 +1,16 @@
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local client_id = args.data.client_id
local client = assert(vim.lsp.get_client_by_id(client_id))
if client.name == 'biome' then
vim.lsp.on_type_formatting.enable(false, { client_id = client_id })
end
end,
})
return {
-- Disable lunching from node_modules (no nix binary)
cmd = { "biome", "lsp-proxy" },
-- Inline package.json (remove check for biome installed)
root_dir = function(_, on_dir)
-- To support monorepos, biome recommends starting the search for the root from cwd
-- https://biomejs.dev/guides/big-projects/#use-multiple-configuration-files
local cwd = vim.fn.getcwd()
local root_files = { 'biome.json', 'biome.jsonc', 'package.json', 'package.json5' }
local root_dir = vim.fs.dirname(vim.fs.find(root_files, { path = cwd, upward = true })[1])
on_dir(root_dir)
end,
-- for json files
workspace_required = false,
}

View File

@@ -140,6 +140,7 @@ in
bash-language-server
sqls
biome
kdePackages.qtdeclarative # qmlls
# gopls also needs go /shame
gopls

View File

@@ -18,8 +18,9 @@ return {
sql = { "pg_format" },
cs = { "csharpier" },
nix = { "alejandra" },
["*"] = { "injected" },
["_"] = { "injected", lsp_format = "last" },
-- ["*"] = { "injected" },
-- ["_"] = { "injected", lsp_format = "last" },
["_"] = { lsp_format = "last" },
},
formatters = {
csharpier = function()

View File

@@ -25,7 +25,6 @@ return {
strategies = {
chat = {
adapter = "copilot",
model = "claude-3-7-sonnet",
start_in_insert_mode = true,
keymaps = {
send = {
@@ -35,10 +34,16 @@ return {
modes = { n = "q", i = "<C-d>" },
},
},
tools = {
opts = {
auto_submit_success = true,
auto_submit_errors = true,
},
}
},
inline = {
adapter = "copilot",
model = "claude-3-7-sonnet",
},
},
},

View File

@@ -18,6 +18,7 @@ vim.lsp.enable({
"jsonls",
"biome",
"sqls",
"qmlls"
-- "roslyn_ls", we use roslyn.nvim plugin instead.
})

View File

@@ -1,9 +1,9 @@
local function git_show(ref)
local git_root = Snacks.git.get_root()
local function finder(opts, ctx)
return require("snacks.picker.source.proc").proc({
opts,
{
---@type snacks.picker.finder
local function finder(_opts, ctx)
return require("snacks.picker.source.proc").proc(
ctx:opts({
cmd = "git",
args = { "show", "--name-status", "--pretty=tformat:", ref },
cwd = Snacks.git.get_root(),
@@ -12,8 +12,9 @@ local function git_show(ref)
item.file = string.sub(item.text, 3)
item.commit = ref
end,
},
}, ctx)
}),
ctx
)
end
Snacks.picker.pick({
@@ -263,19 +264,19 @@ return {
end, { desc = "Grep" })
vim.keymap.set("n", "<leader>gl", function()
Snacks.picker.git_log({cwd = Snacks.git.get_root() })
Snacks.picker.git_log({ cwd = Snacks.git.get_root() })
end, { desc = "Git log" })
vim.keymap.set("n", "<leader>gh", function()
Snacks.picker.git_log_file({cwd = Snacks.git.get_root() })
Snacks.picker.git_log_file({ cwd = Snacks.git.get_root() })
end, { desc = "Git logs buffer" })
vim.keymap.set("n", "<leader>gB", function()
Snacks.picker.git_branches({cwd = Snacks.git.get_root() })
Snacks.picker.git_branches({ cwd = Snacks.git.get_root() })
end, { desc = "Git branches" })
vim.keymap.set("n", "<leader>gs", function()
Snacks.picker.git_status({cwd = Snacks.git.get_root() })
Snacks.picker.git_status({ cwd = Snacks.git.get_root() })
end, { desc = "Git status" })
end,
},

View File

@@ -73,6 +73,7 @@ vim.keymap.set("v", "<", "<gv")
vim.keymap.set("v", ">", ">gv")
-- Copy to/from system clipboard
vim.g.clipboard = 'osc52'
vim.keymap.set({ "n", "x" }, "<leader>y", '"+y', { desc = "Yank to system clipboard" })
vim.keymap.set({ "n", "x" }, "<leader>Y", '"+y$', { desc = "Yank line to system clipboard" })
vim.keymap.set({ "n", "x" }, "<leader>p", '"+p', { desc = "Past from system clipboard" })

View File

@@ -18,18 +18,29 @@
in {
tmux = super.tmux.overrideAttrs {
src = tmux;
patches = [
./tmux-get_clipboard.diff
];
};
# it doesn't start without this, no clue why.
freecad = wrapProgram super.freecad ["freecad" "FreeCAD" "freecadcmd" "FreeCADCmd"] ''
--set QT_QPA_PLATFORM 'wayland;xcb' \
--set QT_QPA_PLATFORMTHEME qt5ct
# they try to use passthrough if they detect tmux. we don't want that.
osc = wrapProgram super.osc ["osc"] ''
--set TMUX ""
'';
# Gnome-control-center can only be launched if XDG_CURRENT_DESKTOP is GNOME.
gnome-control-center = wrapProgram super.gnome-control-center ["gnome-control-center"] "--set XDG_CURRENT_DESKTOP GNOME";
slack = enableWayland super.slack ["slack"];
# i can't get this to work /shrug
# slack = super.symlinkJoin {
# name = super.slack.name;
# paths = [super.slack];
# buildInputs = [super.makeWrapper];
# postBuild = ''
# wrapProgram $out/bin/slack --add-flags "--disable-smooth-scrolling"
# substituteInPlace ${super.slack}/share/applications/slack.desktop --replace ${super.slack} $out
# '';
# };
discord = enableWayland super.discord ["discord" "Discord"];
vesktop = enableWayland super.vesktop ["vesktop"];
youtube-music = enableWayland super.youtube-music ["youtube-music"];

View File

@@ -0,0 +1,332 @@
# source: https://github.com/tmux/tmux/issues/4275
diff --git a/input.c b/input.c
index 3a61ec26..58a1b091 100644
--- a/input.c
+++ b/input.c
@@ -3069,18 +3069,41 @@ input_osc_133(struct input_ctx *ictx, const char *p)
}
}
+/* Handle OSC 52 reply. */
+static void
+input_osc_52_reply(struct input_ctx *ictx)
+{
+ struct paste_buffer *pb;
+ int state;
+ const char *buf;
+ size_t len;
+
+ state = options_get_number(global_options, "get-clipboard");
+ if (state == 0)
+ return;
+ if (state == 1) {
+ if ((pb = paste_get_top(NULL)) == NULL)
+ return;
+ buf = paste_buffer_data(pb, &len);
+ if (ictx->input_end == INPUT_END_BEL)
+ input_reply_clipboard(ictx->event, buf, len, "\007");
+ else
+ input_reply_clipboard(ictx->event, buf, len, "\033\\");
+ return;
+ }
+ input_add_request(ictx, INPUT_REQUEST_CLIPBOARD, ictx->input_end);
+}
+
/* Handle the OSC 52 sequence for setting the clipboard. */
static void
input_osc_52(struct input_ctx *ictx, const char *p)
{
struct window_pane *wp = ictx->wp;
+ size_t len;
char *end;
- const char *buf = NULL;
- size_t len = 0;
u_char *out;
int outlen, state;
struct screen_write_ctx ctx;
- struct paste_buffer *pb;
const char* allow = "cpqs01234567";
char flags[sizeof "cpqs01234567"] = "";
u_int i, j = 0;
@@ -3105,12 +3128,7 @@ input_osc_52(struct input_ctx *ictx, const char *p)
log_debug("%s: %.*s %s", __func__, (int)(end - p - 1), p, flags);
if (strcmp(end, "?") == 0) {
- if ((pb = paste_get_top(NULL)) != NULL)
- buf = paste_buffer_data(pb, &len);
- if (ictx->input_end == INPUT_END_BEL)
- input_reply_clipboard(ictx->event, buf, len, "\007");
- else
- input_reply_clipboard(ictx->event, buf, len, "\033\\");
+ input_osc_52_reply(ictx);
return;
}
@@ -3169,6 +3187,7 @@ input_osc_104(struct input_ctx *ictx, const char *p)
free(copy);
}
+/* Send a clipboard reply. */
void
input_reply_clipboard(struct bufferevent *bev, const char *buf, size_t len,
const char *end)
@@ -3305,6 +3324,9 @@ input_add_request(struct input_ctx *ictx, enum input_request_type type, int idx)
xsnprintf(s, sizeof s, "\033]4;%d;?\033\\", idx);
tty_puts(&c->tty, s);
break;
+ case INPUT_REQUEST_CLIPBOARD:
+ tty_putcode_ss(&c->tty, TTYC_MS, "", "?");
+ break;
case INPUT_REQUEST_QUEUE:
break;
}
@@ -3312,6 +3334,39 @@ input_add_request(struct input_ctx *ictx, enum input_request_type type, int idx)
return (0);
}
+/* Handle a palette reply. */
+static void
+input_request_palette_reply(struct input_request *ir, void *data)
+{
+ struct input_request_palette_data *pd = data;
+
+ input_osc_colour_reply(ir->ictx, 0, 4, pd->idx, pd->c, ir->end);
+}
+
+/* Handle a clipboard reply. */
+static void
+input_request_clipboard_reply(struct input_request *ir, void *data)
+{
+ struct input_ctx *ictx = ir->ictx;
+ struct input_request_clipboard_data *cd = data;
+ int state;
+ char *copy;
+
+ state = options_get_number(global_options, "get-clipboard");
+ if (state == 0 || state == 1)
+ return;
+ if (state == 3) {
+ copy = xmalloc(cd->len);
+ memcpy(copy, cd->buf, cd->len);
+ paste_add(NULL, copy, cd->len);
+ }
+
+ if (ir->idx == INPUT_END_BEL)
+ input_reply_clipboard(ictx->event, cd->buf, cd->len, "\007");
+ else
+ input_reply_clipboard(ictx->event, cd->buf, cd->len, "\033\\");
+}
+
/* Handle a reply to a request. */
void
input_request_reply(struct client *c, enum input_request_type type, void *data)
@@ -3321,11 +3376,18 @@ input_request_reply(struct client *c, enum input_request_type type, void *data)
int complete = 0;
TAILQ_FOREACH_SAFE(ir, &c->input_requests, centry, ir1) {
- if (ir->type == type && pd->idx == ir->idx) {
+ if (ir->type != type) {
+ input_free_request(ir);
+ continue;
+ }
+ if (type == INPUT_REQUEST_PALETTE && pd->idx == ir->idx) {
+ found = ir;
+ break;
+ }
+ if (type == INPUT_REQUEST_CLIPBOARD) {
found = ir;
break;
}
- input_free_request(ir);
}
if (found == NULL)
return;
@@ -3335,8 +3397,11 @@ input_request_reply(struct client *c, enum input_request_type type, void *data)
break;
if (ir->type == INPUT_REQUEST_QUEUE)
input_send_reply(ir->ictx, ir->data);
- else if (ir == found && ir->type == INPUT_REQUEST_PALETTE) {
- input_osc_colour_reply(ir->ictx, 0, 4, pd->idx, pd->c, ir->end);
+ else if (ir == found) {
+ if (ir->type == INPUT_REQUEST_PALETTE)
+ input_request_palette_reply(ir, data);
+ else if (ir->type == INPUT_REQUEST_CLIPBOARD)
+ input_request_clipboard_reply(ir, data);
complete = 1;
}
input_free_request(ir);
diff --git a/options-table.c b/options-table.c
index 6946a085..b3ba39cd 100644
--- a/options-table.c
+++ b/options-table.c
@@ -84,6 +84,9 @@ static const char *options_table_popup_border_lines_list[] = {
static const char *options_table_set_clipboard_list[] = {
"off", "external", "on", NULL
};
+static const char *options_table_get_clipboard_list[] = {
+ "off", "buffer", "request", "both", NULL
+};
static const char *options_table_window_size_list[] = {
"largest", "smallest", "manual", "latest", NULL
};
@@ -405,6 +408,18 @@ const struct options_table_entry options_table[] = {
.text = "Whether to send focus events to applications."
},
+ { .name = "get-clipboard",
+ .type = OPTIONS_TABLE_CHOICE,
+ .scope = OPTIONS_TABLE_SERVER,
+ .choices = options_table_get_clipboard_list,
+ .default_num = 1,
+ .text = "When an application requests the clipboard, whether to "
+ "ignore the request ('off'); respond with the newest buffer "
+ "('buffer'); request the clipboard from the most recently "
+ "used terminal ('request'); or to request the clipboard, "
+ "create a buffer, and send it to the application ('both')."
+ },
+
{ .name = "history-file",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_SERVER,
diff --git a/tmux.1 b/tmux.1
index a25da1fb..2ae1b724 100644
--- a/tmux.1
+++ b/tmux.1
@@ -4241,6 +4241,32 @@ passed through to applications running in
.Nm .
Attached clients should be detached and attached again after changing this
option.
+.It Xo Ic get-clipboard
+.Op Ic both | request | buffer | off
+.Xc
+Controls the behaviour when an application requests the clipboard from
+.Nm .
+.Pp
+If
+.Ic off ,
+the request is ignored;
+if
+.Ic buffer ,
+.Nm
+responds with the newest paste buffer;
+.Ic request
+causes
+.Nm
+to request the clipboard from the most recently used client (if possible) and
+send the reply (if any) back to the application;
+.Ic buffer
+is the same as
+.Ic request
+but also creates a paste buffer.
+.Pp
+See also the
+.Ic set-clipboard
+option.
.It Ic history-file Ar path
If not empty, a file to which
.Nm
diff --git a/tmux.h b/tmux.h
index c3959d2a..3dece217 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1129,6 +1129,7 @@ struct window_mode_entry {
/* Type of request to client. */
enum input_request_type {
INPUT_REQUEST_PALETTE,
+ INPUT_REQUEST_CLIPBOARD,
INPUT_REQUEST_QUEUE
};
@@ -1138,6 +1139,12 @@ struct input_request_palette_data {
int c;
};
+/* Clipboard request reply data. */
+struct input_request_clipboard_data {
+ char *buf;
+ size_t len;
+};
+
/* Request sent to client on behalf of pane. */
TAILQ_HEAD(input_requests, input_request);
diff --git a/tty-keys.c b/tty-keys.c
index 77254591..7acee80f 100644
--- a/tty-keys.c
+++ b/tty-keys.c
@@ -1301,12 +1301,13 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size,
static int
tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size)
{
- struct client *c = tty->client;
- struct window_pane *wp;
- size_t end, terminator = 0, needed;
- char *copy, *out;
- int outlen;
- u_int i;
+ struct client *c = tty->client;
+ struct window_pane *wp;
+ size_t end, terminator = 0, needed;
+ char *copy, *out;
+ int outlen;
+ u_int i;
+ struct input_request_clipboard_data cd;
*size = 0;
@@ -1364,12 +1365,6 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size)
buf++;
end--;
- /* If we did not request this, ignore it. */
- if (~tty->flags & TTY_OSC52QUERY)
- return (0);
- tty->flags &= ~TTY_OSC52QUERY;
- evtimer_del(&tty->clipboard_timer);
-
/* It has to be a string so copy it. */
copy = xmalloc(end + 1);
memcpy(copy, buf, end);
@@ -1384,18 +1379,37 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size)
return (0);
}
free(copy);
-
- /* Create a new paste buffer and forward to panes. */
log_debug("%s: %.*s", __func__, outlen, out);
- if (c->flags & CLIENT_CLIPBOARDBUFFER) {
- paste_add(NULL, out, outlen);
- c->flags &= ~CLIENT_CLIPBOARDBUFFER;
+
+ /* Set reply if any. */
+ if (c->clipboard_npanes == 0) {
+ cd.buf = out;
+ cd.len = outlen;
+ input_request_reply(c, INPUT_REQUEST_CLIPBOARD, &cd);
+ free(out);
+ return (0);
}
+
+ /* If we did not request this, ignore it. */
+ if (~tty->flags & TTY_OSC52QUERY) {
+ free(out);
+ return (0);
+ }
+ tty->flags &= ~TTY_OSC52QUERY;
+ evtimer_del(&tty->clipboard_timer);
+
+ /* Create a new paste buffer and forward to panes. */
for (i = 0; i < c->clipboard_npanes; i++) {
wp = window_pane_find_by_id(c->clipboard_panes[i]);
if (wp != NULL)
input_reply_clipboard(wp->event, out, outlen, "\033\\");
}
+ if (~c->flags & CLIENT_CLIPBOARDBUFFER)
+ free (out);
+ else {
+ paste_add(NULL, out, outlen);
+ c->flags &= ~CLIENT_CLIPBOARDBUFFER;
+ }
free(c->clipboard_panes);
c->clipboard_panes = NULL;
c->clipboard_npanes = 0;

View File

@@ -19,15 +19,20 @@ tasks:
# Install stuff
install:
install:*:
desc: install to disks labeled `nix` and `boot`
deps: [password:root, password:zoriya]
vars:
HOST: "{{index .MATCH 0}}"
cmds:
- task: password:root
- task: password:zoriya
- mkdir -p /mnt/{boot,nix}
- mount /dev/disk/by-label/boot /mnt/boot
- mount /dev/disk/by-label/nix /mnt/nix
- task: swap
- defer: { task: swap-off }
- nixos-install --no-channel-copy --no-root-password
- mkdir -p /mnt/nix/persist/tmp
- TMPDIR=/mnt/nix/persist/tmp nixos-install --no-channel-copy --no-root-password --flake path:.#{{.HOST}}
password:*:
desc: setup passwords