Compare commits

..

1 Commits

Author SHA1 Message Date
237a62ea8a feat: init quickshell 2025-07-13 22:17:13 +08:00
192 changed files with 16912 additions and 1395 deletions

View File

@@ -1,11 +1,13 @@
keys:
- &imxyy-nix ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEFLUkyeaK8ZPPZdVNEmtx8zvoxi7xqS2Z6oxRBuUPO
- &imxyy-nix-server ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB28jpN+h5euh3NtdN+A+EtqgIatC22e4i1TPTioKire
- &imxyy-nix-x16 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMb5G/ieEYBOng66YeyttBQLThyM6W//z2POsNyq4Rw/
- &imxyy-nix age1jf5pg2x6ta8amj40xdy0stvcvrdlkwc2nrwtmkpymu0qclk0eg5qmm9kns
- &imxyy-nix-server age1hpgg6psejh4y6jcdd34wxuml75fnweqpe0kh8376yqsctsfn9qxs037kk6
- &imxyy-nix-x16 age1r0fv0tagxupfacv0aaxk5ss7sqvswv6kq8tk3x46ndqrj6f5afvqegahxq
- &imxyy-cloudwin age1tp7th3rrv3x0l6jl76n0hjqjp223w2y586pkgr0hcjwdm254jd5shkj6a8
creation_rules:
- path_regex: secrets/.*\..*
- path_regex: secrets/.*\.(yaml|toml|json|env|dae|txt|conf)$
key_groups:
- age:
- *imxyy-nix
- *imxyy-nix-server
- *imxyy-nix-x16
- *imxyy-cloudwin

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,8 +1,4 @@
{
config,
hostname,
...
}:
{ config, hostname, ... }:
{
# I prefer this to the default issue text
# ported from ArchLinux IIRC

View File

@@ -4,7 +4,7 @@
enable = true;
host = "127.0.0.1";
port = 8089;
package = pkgs.open-webui;
package = pkgs.stable.open-webui;
};
services.caddy.virtualHosts."ai.imxyy.top" = {
extraConfig = ''

View File

@@ -1,12 +1,13 @@
{
config,
lib,
pkgs,
secrets,
sopsRoot,
...
}:
{
sops.secrets.et-imxyy-nix-server-nixremote = {
sopsFile = secrets.et-imxyy-nix-server-nixremote;
sopsFile = sopsRoot + /et-imxyy-nix-server-nixremote.toml;
format = "binary";
};
environment.systemPackages = [ pkgs.easytier ];
@@ -14,8 +15,10 @@
enable = true;
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix-server-nixremote.path}";
serviceConfig = {
Restart = "always";
RestartSec = 30;
Restart = lib.mkOverride 500 "always";
RestartMaxDelaySec = lib.mkOverride 500 "1m";
RestartSec = lib.mkOverride 500 "100ms";
RestartSteps = lib.mkOverride 500 9;
User = "root";
};
wantedBy = [ "multi-user.target" ];
@@ -34,7 +37,6 @@
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWOy0QmAyxENg/O5m3cus8U3c9jCLioivwcWsh5/a82 imxyy-hisense-pad"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8pivvE8PMtsOxmccfNhH/4KehDKhBfUfJbQZxo/SZT imxyy-ace5"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKALTBn/QSGcSPgMg0ViSazFcaA0+nEF05EJpjbsI6dE imxyy_soope_@imxyy-cloudwin"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMb5G/ieEYBOng66YeyttBQLThyM6W//z2POsNyq4Rw/ imxyy@imxyy-nix-x16"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIENauvvhVMLsUwH9cPYsvnOg7VCL3a4yEiKm8I524TE efl@efl-nix"
];

View File

@@ -7,22 +7,26 @@
}
];
services.postgresql.ensureDatabases = [ "coder" ];
virtualisation.oci-containers.containers.coder = {
image = "ghcr.io/coder/coder:latest";
environment = {
CODER_ACCESS_URL = "https://coder.imxyy.top";
CODER_HTTP_ADDRESS = "0.0.0.0:8086";
CODER_PG_CONNECTION_URL = "postgresql://coder:coderdatabase@127.0.0.1/coder?sslmode=disable";
virtualisation.oci-containers = {
containers = {
coder = {
image = "ghcr.io/coder/coder:latest";
environment = {
CODER_ACCESS_URL = "https://coder.imxyy.top";
CODER_HTTP_ADDRESS = "0.0.0.0:8086";
CODER_PG_CONNECTION_URL = "postgresql://coder:coderdatabase@127.0.0.1/coder?sslmode=disable";
};
extraOptions = [
"--network=host"
"--group-add=${toString config.users.groups.podman.gid}"
];
volumes = [
"/var/lib/coder:/home/coder/.config"
"/var/run/docker.sock:/var/run/docker.sock"
];
ports = [ "8086:8086" ];
};
};
extraOptions = [
"--network=host"
"--group-add=${toString config.users.groups.podman.gid}"
];
volumes = [
"/var/lib/coder:/home/coder/.config"
"/var/run/docker.sock:/var/run/docker.sock"
];
ports = [ "8086:8086" ];
};
services.caddy.virtualHosts."coder.imxyy.top" = {
extraConfig = ''

View File

@@ -15,6 +15,7 @@
./vault.nix
./homepage.nix
./code.nix
./yesplaymusic.nix
./ai.nix
./grafana.nix
./note.nix
@@ -22,8 +23,5 @@
./minio.nix
./build.nix
./immich.nix
./efl.nix
./plant.nix
];
}

View File

@@ -5,10 +5,5 @@
enable = true;
dockerCompat = true;
dockerSocket.enable = true;
defaultNetwork.settings.dns_enabled = true;
};
# avoid collision with dnsmasq
virtualisation.containers = {
containersConf.settings.network.dns_bind_port = 5353;
};
}

View File

@@ -1,54 +0,0 @@
{ config, secrets, ... }:
{
sops.secrets.efl-tuwunel-env = {
sopsFile = secrets.efl-tuwunel;
format = "dotenv";
};
virtualisation.oci-containers.containers = {
tuwunel = {
image = "jevolk/tuwunel:latest";
volumes = [
"tuwunel_db:/var/lib/tuwunel"
];
ports = [ "6167:6167" ];
networks = [ "podman" ];
environment = {
TUWUNEL_SERVER_NAME = "mtx.eflx.top";
TUWUNEL_PORT = "6167";
TUWUNEL_ADDRESS = "0.0.0.0";
TUWUNEL_WELL_KNOWN__SERVER = "mtx.eflx.top:443";
TUWUNEL_WELL_KNOWN__CLIENT = "https://mtx.eflx.top";
};
environmentFiles = [
config.sops.secrets.efl-tuwunel-env.path
];
};
mautrix-telegram = {
image = "dock.mau.dev/mautrix/telegram:latest";
ports = [ "8099:8099" ];
networks = [ "podman" ];
extraOptions = [ "--ip=10.88.0.254" ];
volumes = [ "/var/lib/efl-mautrix-telegram:/data" ];
};
send = {
image = "lanol/filecodebox:latest";
ports = [ "12345:12345" ];
volumes = [ "/var/lib/send:/app/data:rw" ];
};
};
services.caddy.virtualHosts."mtx.eflx.top" = {
extraConfig = ''
reverse_proxy :6167 {
header_up X-Real-IP {remote_host}
}
'';
};
services.caddy.virtualHosts."send.eflx.top" = {
extraConfig = ''
reverse_proxy :12345 {
header_up X-Real-IP {remote_host}
}
'';
};
}

View File

@@ -1,10 +1,14 @@
{
virtualisation.oci-containers.containers.sun-panel = {
image = "hslr/sun-panel:latest";
volumes = [
"/var/lib/sun-panel:/app/conf"
];
ports = [ "8085:3002" ];
virtualisation.oci-containers = {
containers = {
sun-panel = {
image = "hslr/sun-panel:latest";
volumes = [
"/var/lib/sun-panel:/app/conf"
];
ports = [ "8085:3002" ];
};
};
};
services.caddy.virtualHosts."home.imxyy.top" = {
extraConfig = ''

View File

@@ -1,9 +1,5 @@
{ ... }:
{
users.users.immich = {
home = "/mnt/nas/immich";
createHome = true;
};
services.immich = {
enable = true;
host = "127.0.0.1";

View File

@@ -1,104 +1,46 @@
{
config,
secrets,
...
}:
{
nixpkgs.config.permittedInsecurePackages = [
"olm-3.2.16"
];
sops.secrets.tuwunel-reg-token = {
sopsFile = secrets.tuwunel-reg-token;
format = "binary";
owner = config.services.matrix-tuwunel.user;
group = config.services.matrix-tuwunel.group;
};
sops.secrets.tuwunel-turn-secret = {
sopsFile = secrets.tuwunel-turn-secret;
format = "binary";
owner = config.services.matrix-tuwunel.user;
group = config.services.matrix-tuwunel.group;
};
services.matrix-tuwunel = {
services.matrix-synapse = {
enable = true;
settings.global = {
address = [ "127.0.0.1" ];
port = [ 8094 ];
server_name = "imxyy.top";
well_known = {
server = "matrix.imxyy.top:443";
client = "https://matrix.imxyy.top";
};
allow_registration = true;
registration_token_file = config.sops.secrets.tuwunel-reg-token.path;
suppress_push_when_active = true;
turn_uris = [
"turn:hk.vkvm.imxyy.top?transport=udp"
"turn:hk.vkvm.imxyy.top?transport=tcp"
settings = {
server_name = "matrix.imxyy.top";
public_baseurl = "https://matrix.imxyy.top";
listeners = [
{
port = 8094;
bind_addresses = [ "127.0.0.1" ];
type = "http";
tls = false;
x_forwarded = true;
resources = [
{
names = [
"client"
"federation"
];
compress = true;
}
];
}
];
turn_secret_file = config.sops.secrets.tuwunel-turn-secret.path;
new_user_displayname_suffix = "";
turn_uris = [ "turns:vkvm.imxyy.top:5349" ];
turn_shared_secret = "ac779a48c03bb451839569d295a29aa6ab8c264277bec2df9c9c7f5e22936288";
turn_user_lifetime = "1h";
database_type = "psycopg2";
database_args.database = "matrix-synapse";
};
};
services.caddy.virtualHosts."imxyy.top" = {
extraConfig = ''
handle /.well-known/matrix/server {
header Content-Type application/json
header "Access-Control-Allow-Origin" "*"
respond `{"m.server": "matrix.imxyy.top:443"}` 200
}
handle /.well-known/matrix/client {
header Content-Type application/json
header "Access-Control-Allow-Origin" "*"
respond `{"m.homeserver": {"base_url": "https://matrix.imxyy.top/"}}` 200
}
'';
extraConfigFiles = [
"/var/lib/matrix-synapse/secret"
];
};
services.caddy.virtualHosts."matrix.imxyy.top" = {
extraConfig = ''
reverse_proxy :8094
handle_path /_matrix {
reverse_proxy :8094
}
handle_path /_synapse/client {
reverse_proxy :8094
}
'';
};
sops.secrets.mautrix-telegram = {
sopsFile = secrets.mautrix-telegram;
format = "dotenv";
owner = "mautrix-telegram";
group = "mautrix-telegram";
};
services.mautrix-telegram = {
enable = true;
environmentFile = config.sops.secrets.mautrix-telegram.path;
settings = {
homeserver = {
address = "http://127.0.0.1:8094";
domain = "imxyy.top";
};
appservice = {
address = "http://127.0.0.1:8098";
hostname = "127.0.0.1";
port = "8098";
bot_username = "telegrambot";
};
bridge = {
username_template = "telegram_{userid}";
alias_template = "telegram_{groupname}";
displayname_template = "{displayname} (Telegram)";
permissions = {
"@imxyy_soope_:imxyy.top" = "admin";
};
};
telegram = {
# borrowed from https://github.com/telegramdesktop/tdesktop/blob/9bdc19e2fd4d497c8f403891848383a88faadc25/snap/snapcraft.yaml#L134-L135
api_id = "611335";
api_hash = "d524b414d21f4d37f08684c1df41ac9c";
};
};
};
}

View File

@@ -8,10 +8,9 @@
WorkingDirectory = "/opt/minecraft/fabric1.20.6";
ExecStart = "${lib.getExe' pkgs.openjdk21 "java"} -Xms1G -Xmx5G -jar fabric-server-mc.1.20.6-loader.0.15.11-launcher.1.0.1.jar";
Restart = "always";
RestartSec = 120;
RestartSec = "10s";
};
};
my.persist = {
nixosDirs = [
"/opt/minecraft"

View File

@@ -1,7 +1,7 @@
{ config, secrets, ... }:
{ config, sopsRoot, ... }:
{
sops.secrets.minio-env = {
sopsFile = secrets.minio;
sopsFile = sopsRoot + /minio.env;
format = "dotenv";
};
services.minio = {
@@ -16,4 +16,11 @@
];
rootCredentialsFile = config.sops.secrets.minio-env.path;
};
services.caddy.virtualHosts."minio.imxyy.top" = {
extraConfig = ''
handle_path /* {
reverse_proxy :9000
}
'';
};
}

View File

@@ -3,7 +3,7 @@
lib,
pkgs,
username,
secrets,
sopsRoot,
...
}:
{
@@ -131,29 +131,26 @@
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEFLUkyeaK8ZPPZdVNEmtx8zvoxi7xqS2Z6oxRBuUPO imxyy@imxyy-nix"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWOy0QmAyxENg/O5m3cus8U3c9jCLioivwcWsh5/a82 imxyy-hisense-pad"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8pivvE8PMtsOxmccfNhH/4KehDKhBfUfJbQZxo/SZT imxyy-ace5"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMb5G/ieEYBOng66YeyttBQLThyM6W//z2POsNyq4Rw/ imxyy@imxyy-nix-x16"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKALTBn/QSGcSPgMg0ViSazFcaA0+nEF05EJpjbsI6dE imxyy_soope_@imxyy-cloudwin"
];
users.users.${username}.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEFLUkyeaK8ZPPZdVNEmtx8zvoxi7xqS2Z6oxRBuUPO imxyy@imxyy-nix"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWOy0QmAyxENg/O5m3cus8U3c9jCLioivwcWsh5/a82 imxyy-hisense-pad"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8pivvE8PMtsOxmccfNhH/4KehDKhBfUfJbQZxo/SZT imxyy-ace5"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMb5G/ieEYBOng66YeyttBQLThyM6W//z2POsNyq4Rw/ imxyy@imxyy-nix-x16"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKALTBn/QSGcSPgMg0ViSazFcaA0+nEF05EJpjbsI6dE imxyy_soope_@imxyy-cloudwin"
];
sops.secrets.dae-imxyy-nix-server = {
sopsFile = secrets.dae-imxyy-nix-server;
sopsFile = sopsRoot + /dae-imxyy-nix-server.dae;
format = "binary";
};
services.dae = {
enable = true;
configFile = config.sops.secrets.dae-imxyy-nix-server.path;
};
systemd.services.dae = {
after = [ "sops-nix.service" ];
serviceConfig.MemoryMax = "1G";
};
systemd.services.dae.after = [ "sops-nix.service" ];
sops.secrets.mihomo = {
sopsFile = secrets.mihomo;
sopsFile = sopsRoot + /mihomo.yaml;
format = "yaml";
key = "";
};
@@ -165,7 +162,7 @@
};
sops.secrets.frp-env = {
sopsFile = secrets.frp;
sopsFile = sopsRoot + /frp.env;
format = "dotenv";
};
systemd.services.frp.serviceConfig.EnvironmentFile = [
@@ -208,6 +205,20 @@
localPort = 443;
customDomains = [ "oidc.imxyy.top" ];
}
{
name = "headscale-http";
type = "http";
localIP = "127.0.0.1";
localPort = 80;
customDomains = [ "headscale.imxyy.top" ];
}
{
name = "headscale-https";
type = "https";
localIP = "127.0.0.1";
localPort = 443;
customDomains = [ "headscale.imxyy.top" ];
}
{
name = "mail-http";
@@ -291,6 +302,21 @@
customDomains = [ "coder.imxyy.top" ];
}
{
name = "music-http";
type = "http";
localIP = "127.0.0.1";
localPort = 80;
customDomains = [ "music.imxyy.top" ];
}
{
name = "music-https";
type = "https";
localIP = "127.0.0.1";
localPort = 443;
customDomains = [ "music.imxyy.top" ];
}
{
name = "ai-http";
type = "http";
@@ -321,6 +347,21 @@
customDomains = [ "grafana.imxyy.top" ];
}
{
name = "note-http";
type = "http";
localIP = "127.0.0.1";
localPort = 80;
customDomains = [ "note.imxyy.top" ];
}
{
name = "note-https";
type = "https";
localIP = "127.0.0.1";
localPort = 443;
customDomains = [ "note.imxyy.top" ];
}
{
name = "siyuan-http";
type = "http";
@@ -336,20 +377,6 @@
customDomains = [ "sy.imxyy.top" ];
}
{
name = "matrix-root-http";
type = "http";
localIP = "127.0.0.1";
localPort = 80;
customDomains = [ "imxyy.top" ];
}
{
name = "matrix-root-https";
type = "https";
localIP = "127.0.0.1";
localPort = 443;
customDomains = [ "imxyy.top" ];
}
{
name = "matrix-http";
type = "http";
@@ -381,54 +408,63 @@
}
{
name = "memo-http";
type = "http";
name = "minecraft";
type = "tcp";
localIP = "127.0.0.1";
localPort = 80;
customDomains = [ "memo.imxyy.top" ];
}
{
name = "memo-https";
type = "https";
localIP = "127.0.0.1";
localPort = 443;
customDomains = [ "memo.imxyy.top" ];
}
{
name = "efl-matrix-http";
type = "http";
localIP = "127.0.0.1";
localPort = 80;
customDomains = [ "mtx.eflx.top" ];
}
{
name = "efl-matrix-https";
type = "https";
localIP = "127.0.0.1";
localPort = 443;
customDomains = [ "mtx.eflx.top" ];
}
{
name = "efl-send-http";
type = "http";
localIP = "127.0.0.1";
localPort = 80;
customDomains = [ "send.eflx.top" ];
}
{
name = "efl-send-https";
type = "https";
localIP = "127.0.0.1";
localPort = 443;
customDomains = [ "send.eflx.top" ];
localPort = 25565;
remotePort = 25565;
}
];
};
};
services.tailscale = {
enable = true;
useRoutingFeatures = "both";
extraSetFlags = [ "--accept-dns=false" ];
};
services.headscale = {
enable = true;
address = "0.0.0.0";
port = 8080;
settings = {
logtail.enabled = false;
server_url = "https://headscale.imxyy.top";
dns.magic_dns = false;
dns.override_local_dns = false;
ip_prefixes = "100.64.0.0/10";
oidc = {
only_start_if_oidc_is_available = true;
issuer = "https://oidc.imxyy.top";
client_id = "https://headscale.imxyy.top";
allowed_domains = [
"imxyy.top"
"*.imxyy.top"
];
client_secret = "";
expiry = 0;
extra_params.domain_hint = "imxyy.top";
};
};
};
systemd.services."headscale" = {
serviceConfig = {
Restart = lib.mkOverride 500 "always";
RestartMaxDelaySec = lib.mkOverride 500 "1m";
RestartSec = lib.mkOverride 500 "100ms";
RestartSteps = lib.mkOverride 500 9;
};
after = [
"podman-obligator.service"
];
requires = [
"podman-obligator.service"
];
};
sops.secrets.et-imxyy-nix-server = {
sopsFile = secrets.et-imxyy-nix-server;
sopsFile = sopsRoot + /et-imxyy-nix-server.toml;
format = "binary";
};
environment.systemPackages = [ pkgs.easytier ];
@@ -436,8 +472,10 @@
enable = true;
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix-server.path}";
serviceConfig = {
Restart = "always";
RestartSec = 30;
Restart = lib.mkOverride 500 "always";
RestartMaxDelaySec = lib.mkOverride 500 "1m";
RestartSec = lib.mkOverride 500 "100ms";
RestartSteps = lib.mkOverride 500 9;
User = "root";
};
wantedBy = [ "multi-user.target" ];
@@ -447,23 +485,34 @@
];
};
virtualisation.oci-containers.containers.obligator = {
image = "anderspitman/obligator:latest";
volumes = [
"/var/lib/obligator:/data"
"/var/lib/obligator:/api"
];
ports = [ "8081:1616" ];
cmd = [
"-storage-dir"
"/data"
"-api-socket-dir"
"/api"
"-root-uri"
"https://oidc.imxyy.top"
"-port"
"1616"
];
virtualisation.oci-containers = {
containers = {
obligator = {
image = "anderspitman/obligator:latest";
volumes = [
"/var/lib/obligator:/data"
"/var/lib/obligator:/api"
];
ports = [ "8081:1616" ];
cmd = [
"-storage-dir"
"/data"
"-api-socket-dir"
"/api"
"-root-uri"
"https://oidc.imxyy.top"
"-port"
"1616"
];
};
};
};
services.caddy.virtualHosts."headscale.imxyy.top" = {
extraConfig = ''
reverse_proxy :8080 {
header_up X-Real-IP {remote_host}
}
'';
};
services.caddy.virtualHosts."oidc.imxyy.top" = {
extraConfig = ''
@@ -475,10 +524,9 @@
systemd.services.ddns-go =
let
version = "6.6.7";
ddns-go = pkgs.buildGoModule {
inherit version;
ddns-go = pkgs.buildGoModule rec {
pname = "ddns-go";
version = "6.6.7";
src = pkgs.fetchFromGitHub {
owner = "jeessy2";
repo = "ddns-go";
@@ -513,6 +561,7 @@
"git"
"vault"
"coder"
"headscale"
"grafana"
"matrix"
"note"
@@ -521,22 +570,23 @@
"music"
"ai"
"sy"
"immich"
"minio"
];
in
{
enable = true;
resolveLocalQueries = false;
settings = {
no-resolv = true;
server = [ "192.168.3.1" ];
server = [
"120.53.53.53"
"223.5.5.5"
];
address = map (sub: "/${sub}.imxyy.top/192.168.3.2") subDomains ++ [
"/imxyy-nix-server/192.168.3.2"
"/imxyy-cloudwin/192.168.3.4"
"/printer.home/192.168.3.53"
];
cache-size = 0;
log-queries = "extra";
};
};
}

View File

@@ -2,7 +2,7 @@
lib,
config,
username,
secrets,
sopsRoot,
...
}:
{
@@ -13,16 +13,17 @@
timeout = 0;
};
hardware.bluetooth.enable = true;
hardware.bluetooth.powerOnBoot = true;
systemd.services.nix-daemon = {
environment.TMPDIR = "/var/cache/nix";
serviceConfig.CacheDirectory = "nix";
};
environment.variables.NIX_REMOTE = "daemon";
my.audio.enable = false;
sops.secrets.imxyy-nix-server-hashed-password = {
sopsFile = secrets.imxyy-nix-server-hashed-password;
sopsFile = sopsRoot + /imxyy-nix-server-hashed-password.txt;
format = "binary";
neededForUsers = true;
};

View File

@@ -1,52 +1,61 @@
{
config,
secrets,
sopsRoot,
...
}:
{
sops.secrets = {
flatnote-env = {
sopsFile = sopsRoot + /flatnote.env;
format = "dotenv";
};
siyuan-env = {
sopsFile = secrets.siyuan;
sopsFile = sopsRoot + /siyuan.env;
format = "dotenv";
};
};
virtualisation.oci-containers.containers = {
siyuan = {
image = "apkdv/siyuan-unlock:v3.1.30";
volumes = [
"/mnt/nas/siyuan/workspace:/workspace"
"/mnt/nas/siyuan:/home/siyuan"
];
cmd = [
"--workspace=/workspace"
];
environment = {
PUID = "0";
PGID = "0";
virtualisation.oci-containers = {
containers = {
flatnotes = {
image = "dullage/flatnotes:latest";
volumes = [
"/mnt/nas/flatnotes/data:/data"
];
environmentFiles = [
"${config.sops.secrets.flatnote-env.path}"
];
ports = [ "8093:8080" ];
};
siyuan = {
image = "apkdv/siyuan-unlock:v3.1.30";
volumes = [
"/mnt/nas/siyuan/workspace:/workspace"
"/mnt/nas/siyuan:/home/siyuan"
];
cmd = [
"--workspace=/workspace"
];
environment = {
PUID = "0";
PGID = "0";
};
environmentFiles = [
"${config.sops.secrets.siyuan-env.path}"
];
ports = [ "8095:6806" ];
};
environmentFiles = [
"${config.sops.secrets.siyuan-env.path}"
];
ports = [ "8095:6806" ];
};
memos = {
image = "neosmemo/memos:stable";
volumes = [
"/mnt/nas/memos:/var/opt/memos"
];
ports = [ "8097:5230" ];
};
};
services.caddy.virtualHosts = {
"note.imxyy.top" = {
extraConfig = ''
reverse_proxy :8093
'';
};
"sy.imxyy.top" = {
extraConfig = ''
reverse_proxy :8095
'';
};
"memo.imxyy.top" = {
extraConfig = ''
reverse_proxy :8097
'';
};
};
}

View File

@@ -1,74 +0,0 @@
{
inputs,
pkgs,
lib,
...
}:
let
app = pkgs.buildNpmPackage (finalAttrs: {
pname = "HF-plant";
version = "unstable-2025-09-21";
src = inputs.plant;
buildPhase = ''
runHook preBuild
npm run build
npm run build:proxy
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir $out
mv dist $out
cp .env proxy-server-bundled.js $out
runHook postInstall
'';
npmDepsHash = "sha256-ret4BtjrEt8L1nlvJmFiejAKmbz89Z7NSiKs+qlB51w=";
});
in
{
systemd.services.HF-plant-proxy = {
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${lib.getExe pkgs.bash} -c 'source ${app}/.env; export FEISHU_APP_ID FEISHU_APP_SECRET AMAP_JSCODE; ${lib.getExe pkgs.nodejs} ${app}/proxy-server-bundled.js'";
Restart = "always";
RestartSec = 120;
};
};
services.caddy.virtualHosts."plant.imxyy.top" = {
extraConfig = ''
handle /api/* {
reverse_proxy localhost:3001
}
handle /* {
root * ${app}/dist
try_files {path} /index.html
file_server
}
'';
};
services.frp.settings.proxies = [
{
name = "plant-http";
type = "http";
localIP = "127.0.0.1";
localPort = 80;
customDomains = [ "plant.imxyy.top" ];
}
{
name = "plant-https";
type = "https";
localIP = "127.0.0.1";
localPort = 443;
customDomains = [ "plant.imxyy.top" ];
}
];
}

View File

@@ -1,7 +1,7 @@
{ config, secrets, ... }:
{ config, sopsRoot, ... }:
{
sops.secrets.vaultwarden-env = {
sopsFile = secrets.vaultwarden;
sopsFile = sopsRoot + /vaultwarden.env;
format = "dotenv";
};
services.postgresql.ensureUsers = [

View File

@@ -13,7 +13,7 @@ let
in
{
boot = {
initrd.kernelModules = lib.mkBefore [
initrd.kernelModules = [
"vfio_pci"
"vfio"
"vfio_iommu_type1"

View File

@@ -0,0 +1,23 @@
{
virtualisation.oci-containers.containers."YesPlayMusic" = {
image = "git.imxyy.top/imxyy1soope1/yesplaymusic:latest";
environment = {
"NODE_TLS_REJECT_UNAUTHORIZED" = "0";
};
volumes = [
"/etc/localtime:/etc/localtime:ro"
];
ports = [
"8088:80/tcp"
];
log-driver = "journald";
};
services.caddy.virtualHosts."music.imxyy.top" = {
extraConfig = ''
reverse_proxy :8088 {
header_up X-Real-IP {remote_host}
}
'';
};
}

View File

@@ -4,7 +4,7 @@
localproxy_on = "export http_proxy=http://192.168.128.1:7890 https_proxy=http://192.168.128.1:7890 all_proxy=socks://192.168.128.1:7890";
};
my = {
sops.sshKeyFile = "/home/${username}/.ssh/id_ed25519";
sops.sshKeyPath = "/home/${username}/.ssh/id_ed25519";
coding.all.enable = true;
coding.editor.vscode.enable = lib.mkForce false;
cli.misc.enable = true;

View File

@@ -1,4 +1,6 @@
{
config,
lib,
pkgs,
username,
...
@@ -8,42 +10,34 @@
home.packages = with pkgs; [
localsend
rclone
wpsoffice-cn
wps-office-fonts
ttf-wps-fonts
papers
evince
anki
ayugram-desktop
telegram-desktop
signal-desktop
element-desktop
fractal
discord
qq
wechat
gnome-clocks
wineWowPackages.waylandFull
pavucontrol
pamixer
];
programs.zsh = {
sessionVariables = {
PATH = "/home/${username}/bin:$PATH";
};
};
programs.niri.settings = {
environment.STEAM_FORCE_DESKTOPUI_SCALING = "1.25";
outputs = {
eDP-1 = {
enable = true;
mode = {
width = 1920;
height = 1200;
refresh = 60.002;
};
scale = 1.25;
};
};
};
};
my = {
@@ -51,7 +45,8 @@
cli.all.enable = true;
coding.all.enable = true;
desktop.all.enable = true;
virt.moonlight.enable = true;
desktop.browser.librewolf.enable = lib.mkForce false;
i18n.fcitx5.enable = true;
@@ -64,9 +59,9 @@
imageviewer = [ "org.gnome.Shotwell-Viewer.desktop" ];
in
{
"inode/directory" = [ "org.gnome.Nautilus.desktop" ];
"inode/directory" = [ "nemo.desktop" ];
"application/pdf" = [ "org.gnome.Papers.desktop" ];
"application/pdf" = [ "org.gnome.Evince.desktop" ];
"text/*" = editor;
"application/json" = editor;
@@ -97,6 +92,15 @@
"image/png" = imageviewer;
"image/webp" = imageviewer;
};
extraBookmarks =
let
homedir = config.my.home.home.homeDirectory;
in
[
"file://${homedir}/NAS NAS"
"file://${homedir}/NAS/imxyy_soope_ NAS imxyy_soope_"
"file://${homedir}/NAS/imxyy_soope_/OS NAS OS"
];
};
persist = {
enable = true;
@@ -114,26 +118,23 @@
".local/state"
".local/share/Anki2"
".local/share/shotwell"
".local/share/cheat.sh"
".local/share/Kingsoft"
".local/share/AyuGramDesktop"
".local/share/fractal"
".local/share/TelegramDesktop"
".config/Signal"
".config/Element"
".config/discord"
".config/QQ"
".xwechat"
".config/Kingsoft"
".config/dconf"
".config/gh"
".config/pulse"
".config/pip"
".config/libreoffice"
".config/sunshine"
".gemini"
".claude"
".claude-code-router"
];
homeFiles = [
".claude.json"
];
};
};

View File

@@ -1,7 +1,8 @@
{
config,
lib,
pkgs,
secrets,
sopsRoot,
...
}:
{
@@ -9,51 +10,10 @@
"biosdevname=0"
"net.ifnames=0"
];
networking = {
networkmanager.enable = true;
firewall.enable = false;
nftables = {
enable = true;
flushRuleset = true;
ruleset = ''
table inet firewall {
set LANv4 {
type ipv4_addr
flags interval
elements = { 10.0.0.0/8, 100.64.0.0/10, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 }
}
set LANv6 {
type ipv6_addr
flags interval
elements = { fd00::/8, fe80::/10 }
}
chain output {
type filter hook output priority 100; policy accept;
}
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
ct state invalid drop
ct state established,related accept
ip saddr @LANv4 accept
ip6 saddr @LANv6 accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
}
'';
};
};
networking.networkmanager.enable = true;
sops.secrets.dae-imxyy-nix-x16 = {
sopsFile = secrets.dae-imxyy-nix-x16;
sopsFile = sopsRoot + /dae-imxyy-nix-x16.dae;
format = "binary";
};
services.dae = {
@@ -62,7 +22,7 @@
};
systemd.services.dae.after = [ "sops-nix.service" ];
sops.secrets.mihomo = {
sopsFile = secrets.mihomo;
sopsFile = sopsRoot + /mihomo.yaml;
format = "yaml";
key = "";
};
@@ -74,7 +34,7 @@
};
sops.secrets.et-imxyy-nix-x16 = {
sopsFile = secrets.et-imxyy-nix-x16;
sopsFile = sopsRoot + /et-imxyy-nix-x16.toml;
format = "binary";
};
environment.systemPackages = with pkgs; [
@@ -84,8 +44,10 @@
enable = true;
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix-x16.path}";
serviceConfig = {
Restart = "always";
RestartSec = 30;
Restart = lib.mkOverride 500 "always";
RestartMaxDelaySec = lib.mkOverride 500 "1m";
RestartSec = lib.mkOverride 500 "100ms";
RestartSteps = lib.mkOverride 500 9;
User = "root";
};
wantedBy = [ "multi-user.target" ];

View File

@@ -1,8 +1,9 @@
{
lib,
pkgs,
config,
username,
secrets,
sopsRoot,
...
}:
{
@@ -35,6 +36,36 @@
};
environment.variables.NIX_REMOTE = "daemon";
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = false;
alsa.support32Bit = false;
pulse.enable = false;
audio.enable = false;
};
services.pulseaudio = {
enable = true;
support32Bit = true;
package = pkgs.pulseaudioFull;
extraConfig = ''
load-module module-switch-on-connect
unload-module module-suspend-on-idle
'';
};
hardware.bluetooth = {
enable = true;
powerOnBoot = true;
settings = {
General = {
Enable = "Source,Sink,Media,Socket";
Disable = "HeadSet";
MultiProfile = "multiple";
};
};
};
users.extraUsers.${username}.extraGroups = [ "audio" ];
fonts = {
enableDefaultPackages = false;
fontDir.enable = true;
@@ -71,9 +102,6 @@
services.printing.enable = true;
services.upower.enable = true;
services.power-profiles-daemon.enable = true;
services.keyd = {
enable = true;
keyboards.default.settings = {
@@ -106,7 +134,7 @@
];
sops.secrets.imxyy-nix-rclone = {
sopsFile = secrets.imxyy-nix-rclone;
sopsFile = sopsRoot + /imxyy-nix-rclone.conf;
format = "binary";
};
fileSystems = {
@@ -126,6 +154,21 @@
"vfs-cache-mode=full"
];
};
"/home/${username}/NAS" = {
device = "//10.0.0.1/share";
fsType = "cifs";
options = [
"username=nas"
"password=nasshare"
"x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s"
"nodev"
"nofail"
"uid=1000"
"gid=100"
"vers=3"
"rw"
];
};
};
my.persist.nixosDirs = [ "/etc/NetworkManager/system-connections" ];

View File

@@ -1,4 +1,6 @@
{
config,
lib,
pkgs,
username,
...
@@ -8,21 +10,28 @@
home.packages = with pkgs; [
localsend
rclone
wpsoffice-cn
wps-office-fonts
ttf-wps-fonts
papers
evince
anki
ayugram-desktop
telegram-desktop
signal-desktop
element-desktop
fractal
discord
qq
wechat
gnome-clocks
wineWowPackages.waylandFull
pavucontrol
pamixer
];
programs.zsh = {
shellAliases = {
@@ -82,6 +91,8 @@
coding.all.enable = true;
desktop.all.enable = true;
desktop.browser.librewolf.enable = lib.mkForce false;
i18n.fcitx5.enable = true;
xdg = {
@@ -93,9 +104,9 @@
imageviewer = [ "org.gnome.Shotwell-Viewer.desktop" ];
in
{
"inode/directory" = [ "org.gnome.Nautilus.desktop" ];
"inode/directory" = [ "nemo.desktop" ];
"application/pdf" = [ "org.gnome.Papers.desktop" ];
"application/pdf" = [ "org.gnome.Evince.desktop" ];
"text/*" = editor;
"application/json" = editor;
@@ -126,6 +137,16 @@
"image/png" = imageviewer;
"image/webp" = imageviewer;
};
extraBookmarks =
let
homedir = config.my.home.home.homeDirectory;
in
[
"file://${homedir}/Documents/%E7%8F%AD%E7%BA%A7%E4%BA%8B%E5%8A%A1 "
"file://${homedir}/NAS NAS"
"file://${homedir}/NAS/imxyy_soope_ NAS imxyy_soope_"
"file://${homedir}/NAS/imxyy_soope_/OS NAS OS"
];
};
persist = {
enable = true;
@@ -141,23 +162,23 @@
".local/state"
".local/share/Anki2"
".local/share/shotwell"
".local/share/cheat.sh"
".local/share/Kingsoft"
".local/share/AyuGramDesktop"
".local/share/fractal"
".local/share/TelegramDesktop"
".config/Signal"
".config/Element"
".config/discord"
".config/QQ"
".xwechat"
".config/Kingsoft"
".config/dconf"
".config/gh"
".config/pulse"
".config/pip"
".config/libreoffice"
".config/sunshine"
".gemini"
".claude"
".claude-code-router"
];
};
};

View File

@@ -2,7 +2,8 @@
config,
lib,
pkgs,
secrets,
sopsRoot,
username,
...
}:
{
@@ -69,7 +70,6 @@
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
iifname waydroid0 accept
ct state invalid drop
ct state established,related accept
@@ -79,9 +79,6 @@
chain forward {
type filter hook forward priority 0; policy drop;
iifname waydroid0 accept
oifname waydroid0 accept
}
}
'';
@@ -89,7 +86,7 @@
};
sops.secrets.dae-imxyy-nix = {
sopsFile = secrets.dae-imxyy-nix;
sopsFile = sopsRoot + /dae-imxyy-nix.dae;
format = "binary";
};
services.dae = {
@@ -98,7 +95,7 @@
};
systemd.services.dae.after = [ "sops-nix.service" ];
sops.secrets.mihomo = {
sopsFile = secrets.mihomo;
sopsFile = sopsRoot + /mihomo.yaml;
format = "yaml";
key = "";
};
@@ -110,7 +107,7 @@
};
sops.secrets.et-imxyy-nix = {
sopsFile = secrets.et-imxyy-nix;
sopsFile = sopsRoot + /et-imxyy-nix.toml;
format = "binary";
};
environment.systemPackages = [ pkgs.easytier ];
@@ -118,8 +115,10 @@
enable = true;
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix.path}";
serviceConfig = {
Restart = "always";
RestartSec = 30;
Restart = lib.mkOverride 500 "always";
RestartMaxDelaySec = lib.mkOverride 500 "1m";
RestartSec = lib.mkOverride 500 "100ms";
RestartSteps = lib.mkOverride 500 9;
User = "root";
};
wantedBy = [ "multi-user.target" ];

View File

@@ -3,7 +3,7 @@
pkgs,
config,
username,
secrets,
sopsRoot,
...
}:
let
@@ -22,7 +22,7 @@ let
temp = line.split(" ")
bus = temp[1]
device = temp[3][:-1]
subprocess.run(["${lib.getExe' pkgs.usbutils "usbreset"}", f"{bus}/{device}"])
subprocess.run(["${lib.getExe usbreset}", f"/dev/bus/usb/{bus}/{device}"])
return True
if __name__ == "__main__":
@@ -36,6 +36,44 @@ let
with open("/tmp/.btreseted", "w"):
...
'';
usbreset = pkgs.writeCBin "usbreset" ''
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/usbdevice_fs.h>
int main(int argc, char **argv)
{
const char *filename;
int fd;
int rc;
if (argc != 2) {
fprintf(stderr, "Usage: usbreset device-filename\n");
return 1;
}
filename = argv[1];
fd = open(filename, O_WRONLY);
if (fd < 0) {
perror("Error opening output file");
return 1;
}
printf("Resetting USB device %s\n", filename);
rc = ioctl(fd, USBDEVFS_RESET, 0);
if (rc < 0) {
perror("Error in ioctl");
return 1;
}
printf("Reset successful\n");
close(fd);
return 0;
}
'';
in
{
security.pam.loginLimits = [
@@ -73,6 +111,36 @@ in
};
environment.variables.NIX_REMOTE = "daemon";
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = false;
alsa.support32Bit = false;
pulse.enable = false;
audio.enable = false;
};
services.pulseaudio = {
enable = true;
support32Bit = true;
package = pkgs.pulseaudioFull;
extraConfig = ''
load-module module-switch-on-connect
unload-module module-suspend-on-idle
'';
};
hardware.bluetooth = {
enable = true;
powerOnBoot = true;
settings = {
General = {
Enable = "Source,Sink,Media,Socket";
Disable = "HeadSet";
MultiProfile = "multiple";
};
};
};
users.extraUsers.${username}.extraGroups = [ "audio" ];
fonts = {
enableDefaultPackages = false;
fontDir.enable = true;
@@ -111,22 +179,16 @@ in
services.keyd = {
enable = true;
keyboards = {
default.settings = {
main = {
capslock = "overload(control, esc)";
home = "end";
};
shift = {
home = "home";
};
control = {
delete = "print";
};
keyboards.default.settings = {
main = {
capslock = "overload(control, esc)";
home = "end";
};
kone-pro-owl-eye = {
ids = [ "1e7d:2dcd" ];
settings.main.mouse2 = "rightmouse";
shift = {
home = "home";
};
control = {
delete = "print";
};
};
};
@@ -137,9 +199,6 @@ in
programs.wireshark.package = pkgs.wireshark;
users.users.${username}.extraGroups = [ "wireshark" ];
virtualisation.waydroid.enable = true;
my.persist.homeDirs = [ ".local/share/waydroid" ];
services.sunshine = {
enable = true;
autoStart = true;
@@ -166,7 +225,7 @@ in
];
sops.secrets.imxyy-nix-rclone = {
sopsFile = secrets.imxyy-nix-rclone;
sopsFile = sopsRoot + /imxyy-nix-rclone.conf;
format = "binary";
};
fileSystems = {

320
flake.lock generated
View File

@@ -5,11 +5,11 @@
"fromYaml": "fromYaml"
},
"locked": {
"lastModified": 1755819240,
"narHash": "sha256-qcMhnL7aGAuFuutH4rq9fvAhCpJWVHLcHVZLtPctPlo=",
"lastModified": 1746562888,
"narHash": "sha256-YgNJQyB5dQiwavdDFBMNKk1wyS77AtdgDk/VtU6wEaI=",
"owner": "SenchoPens",
"repo": "base16.nix",
"rev": "75ed5e5e3fce37df22e49125181fa37899c3ccd6",
"rev": "806a1777a5db2a1ef9d5d6f493ef2381047f2b89",
"type": "github"
},
"original": {
@@ -37,11 +37,11 @@
"base16-helix": {
"flake": false,
"locked": {
"lastModified": 1752979451,
"narHash": "sha256-0CQM+FkYy0fOO/sMGhOoNL80ftsAzYCg9VhIrodqusM=",
"lastModified": 1748408240,
"narHash": "sha256-9M2b1rMyMzJK0eusea0x3lyh3mu5nMeEDSc4RZkGm+g=",
"owner": "tinted-theming",
"repo": "base16-helix",
"rev": "27cf1e66e50abc622fb76a3019012dc07c678fac",
"rev": "6c711ab1a9db6f51e2f6887cc3345530b33e152e",
"type": "github"
},
"original": {
@@ -98,26 +98,6 @@
"type": "github"
}
},
"catppuccin": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1759273174,
"narHash": "sha256-aHN6dAD72IsNvNlzU3nbV4DJRb1qPvURgWIzHeYsBbc=",
"owner": "catppuccin",
"repo": "nix",
"rev": "5f7dc8bab8af6ba612ef8dc7cd44e38ba6cfd51a",
"type": "github"
},
"original": {
"owner": "catppuccin",
"repo": "nix",
"type": "github"
}
},
"chaotic": {
"inputs": {
"flake-schemas": "flake-schemas",
@@ -127,11 +107,11 @@
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1759235653,
"narHash": "sha256-sKFehUxXCzM6E1LcmnRa/O6HKsRI/TGtciG5ulAJt08=",
"lastModified": 1752141190,
"narHash": "sha256-RHNq77Z84BtLTwyRtrBffm5V9006Dqw4vh3vrvULlxM=",
"owner": "chaotic-cx",
"repo": "nyx",
"rev": "2bf7f138e42fa8b2133761edab64263505cb83bf",
"rev": "ef0794b8e94eea166407141f7e92da75f6df925a",
"type": "github"
},
"original": {
@@ -148,11 +128,11 @@
]
},
"locked": {
"lastModified": 1757962227,
"narHash": "sha256-BENROvRjnKLjCc1qwGgScfk6WIdHGwz4vZhsmfa///Q=",
"lastModified": 1750940343,
"narHash": "sha256-qmc/jreM09MOwQ8dOa/+yyh99rU7TowSqo8L33VHfto=",
"owner": "Bali10050",
"repo": "Darkly",
"rev": "c1446a049c4e2f78afbd90d8b642dc73a3027f1a",
"rev": "77770c8d3c35f7ad39da2c57122c360096df0aac",
"type": "github"
},
"original": {
@@ -191,11 +171,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1759251341,
"narHash": "sha256-0vt4IQnTLyAhaeom3h9GOCpO2+av+wK4zP7O8BSKr6I=",
"lastModified": 1752129689,
"narHash": "sha256-0Xq5tZbvgZvxbbxv6kRHFuZE4Tq2za016NXh32nX0+Q=",
"owner": "nix-community",
"repo": "fenix",
"rev": "af8a7505a1c62fab493157ea380a05c094bb63af",
"rev": "70bb04a7de606a75ba0a2ee9d47b99802780b35d",
"type": "github"
},
"original": {
@@ -207,11 +187,11 @@
"firefox-gnome-theme": {
"flake": false,
"locked": {
"lastModified": 1758112371,
"narHash": "sha256-lizRM2pj6PHrR25yimjyFn04OS4wcdbc38DCdBVa2rk=",
"lastModified": 1748383148,
"narHash": "sha256-pGvD/RGuuPf/4oogsfeRaeMm6ipUIznI2QSILKjKzeA=",
"owner": "rafaelmardojai",
"repo": "firefox-gnome-theme",
"rev": "0909cfe4a2af8d358ad13b20246a350e14c2473d",
"rev": "4eb2714fbed2b80e234312611a947d6cb7d70caf",
"type": "github"
},
"original": {
@@ -293,6 +273,27 @@
}
},
"flake-parts_3": {
"inputs": {
"nixpkgs-lib": [
"nur",
"nixpkgs"
]
},
"locked": {
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_4": {
"inputs": {
"nixpkgs-lib": [
"stylix",
@@ -300,11 +301,11 @@
]
},
"locked": {
"lastModified": 1756770412,
"narHash": "sha256-+uWLQZccFHwqpGqr2Yt5VsW/PbeJVTn9Dk6SHWhNRPw=",
"lastModified": 1751413152,
"narHash": "sha256-Tyw1RjYEsp5scoigs1384gIg6e0GoBVjms4aXFfRssQ=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "4524271976b625a4a605beefd893f270620fd751",
"rev": "77826244401ea9de6e3bac47c2db46005e1f30b5",
"type": "github"
},
"original": {
@@ -456,11 +457,11 @@
]
},
"locked": {
"lastModified": 1755219990,
"narHash": "sha256-/znXwik9nC9TY6dwq0SR60MAi9IEZi2InRSRjfhKu1s=",
"lastModified": 1748528448,
"narHash": "sha256-Tyn+PgBm78Ibq28/WbEz8+pYJZMdbJKsyXMCpT6TjrM=",
"owner": "imxyy1soope1",
"repo": "go-musicfox",
"rev": "938c4d6bb2a318bc6251f3b11fa8cf54d29b4419",
"rev": "1870fd3501631577ad4daef1489b03885abcb037",
"type": "github"
},
"original": {
@@ -499,11 +500,11 @@
]
},
"locked": {
"lastModified": 1759172751,
"narHash": "sha256-E8W8sRXfrvkFW26GuuiWq6QfReU7m5+cngwHuRo/3jc=",
"lastModified": 1751824240,
"narHash": "sha256-aDDC0CHTlL7QDKWWhdbEgVPK6KwWt+ca0QkmHYZxMzI=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "12fa8548feefa9a10266ba65152fd1a787cdde8f",
"rev": "fd9e55f5fac45a26f6169310afca64d56b681935",
"type": "github"
},
"original": {
@@ -519,11 +520,11 @@
]
},
"locked": {
"lastModified": 1759261733,
"narHash": "sha256-G104PUPKBgJmcu4NWs0LUaPpSOTD4jiq4mamLWu3Oc0=",
"lastModified": 1752202894,
"narHash": "sha256-knafgng4gCjZIUMyAEWjxxdols6n/swkYnbWr+oF+1w=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "5a21f4819ee1be645f46d6b255d49f4271ef6723",
"rev": "fab659b346c0d4252208434c3c4b3983a4b38fec",
"type": "github"
},
"original": {
@@ -541,11 +542,11 @@
]
},
"locked": {
"lastModified": 1752603129,
"narHash": "sha256-S+wmHhwNQ5Ru689L2Gu8n1OD6s9eU9n9mD827JNR+kw=",
"lastModified": 1743604125,
"narHash": "sha256-ZD61DNbsBt1mQbinAaaEqKaJk2RFo9R/j+eYWeGMx7A=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "e8c19a3cec2814c754f031ab3ae7316b64da085b",
"rev": "180fd43eea296e62ae68e079fcf56aba268b9a1a",
"type": "github"
},
"original": {
@@ -594,11 +595,11 @@
]
},
"locked": {
"lastModified": 1757230583,
"narHash": "sha256-4uqu7sFPOaVTCogsxaGMgbzZ2vK40GVGMfUmrvK3/LY=",
"lastModified": 1751529406,
"narHash": "sha256-jwKDHyUycp678zDYa5Hyfq3msO73YMXdZPxp96dU7po=",
"owner": "Jovian-Experiments",
"repo": "Jovian-NixOS",
"rev": "fc3960e6c32c9d4f95fff2ef84444284d24d3bea",
"rev": "b2e5ce654e4f5bf8905c2e07a96dcf4966e6277d",
"type": "github"
},
"original": {
@@ -657,17 +658,21 @@
"inputs": {
"niri-stable": "niri-stable",
"niri-unstable": "niri-unstable",
"nixpkgs": "nixpkgs_5",
"nixpkgs-stable": "nixpkgs-stable",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": [
"nixpkgs-stable"
],
"xwayland-satellite-stable": "xwayland-satellite-stable",
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
},
"locked": {
"lastModified": 1759207481,
"narHash": "sha256-xhUr1oMQwL/8h8xnPi5QxUHRFDHoCofhw8Jy7qTD4BY=",
"lastModified": 1752078530,
"narHash": "sha256-TrRmlYdhWcadWvBpDjB9Xlry4uT4ZUIO46d+o5tjtCQ=",
"owner": "sodiboo",
"repo": "niri-flake",
"rev": "d425163158a96a26924597574316a627d2e982aa",
"rev": "d231d92313192d4d0c78d6ef04167fed9dee87cf",
"type": "github"
},
"original": {
@@ -679,16 +684,16 @@
"niri-stable": {
"flake": false,
"locked": {
"lastModified": 1756556321,
"narHash": "sha256-RLD89dfjN0RVO86C/Mot0T7aduCygPGaYbog566F0Qo=",
"lastModified": 1748151941,
"narHash": "sha256-z4viQZLgC2bIJ3VrzQnR+q2F3gAOEQpU1H5xHtX/2fs=",
"owner": "YaLTeR",
"repo": "niri",
"rev": "01be0e65f4eb91a9cd624ac0b76aaeab765c7294",
"rev": "8ba57fcf25d2fc9565131684a839d58703f1dae7",
"type": "github"
},
"original": {
"owner": "YaLTeR",
"ref": "v25.08",
"ref": "v25.05.1",
"repo": "niri",
"type": "github"
}
@@ -696,11 +701,11 @@
"niri-unstable": {
"flake": false,
"locked": {
"lastModified": 1758691861,
"narHash": "sha256-CYgoGrY/Fx+hjzp8graTxJw1M7mn1f2jBkK26M04T0s=",
"lastModified": 1750791124,
"narHash": "sha256-F5iVU/hjoSHSSe0gllxm0PcAaseEtGNanYK5Ha3k2Tg=",
"owner": "YaLTeR",
"repo": "niri",
"rev": "e837e39623457dc5ad29c34a5ce4d4616e5fbf1e",
"rev": "37458d94b288945f6cfbd3c5c233f634d59f246c",
"type": "github"
},
"original": {
@@ -799,11 +804,11 @@
]
},
"locked": {
"lastModified": 1758785683,
"narHash": "sha256-mRn51IeEBXeNh5a6xNLylk4PKBX0s/QQxgkEbYoPq/w=",
"lastModified": 1752199438,
"narHash": "sha256-xSBMmGtq8K4Qv80TMqREmESCAsRLJRHAbFH2T/2Bf1Y=",
"owner": "nix-community",
"repo": "NixOS-WSL",
"rev": "1bfb978f2f6261b6086e04af17f9418e1fe36d70",
"rev": "d34d9412556d3a896e294534ccd25f53b6822e80",
"type": "github"
},
"original": {
@@ -814,11 +819,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1759147044,
"narHash": "sha256-3ZPFytJOcLjTChljeaGgoaNj+tOqzgEpqZAvRe3bU90=",
"owner": "PedroHLC",
"lastModified": 1751984180,
"narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "18e83bbe13aa50992777832b52bd0e0d8585fb3b",
"rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0",
"type": "github"
},
"original": {
@@ -842,11 +847,11 @@
},
"nixpkgs-master": {
"locked": {
"lastModified": 1759299389,
"narHash": "sha256-2W+bpX2yD4uAV9XfjaUO4uRv63e+0CIPnafub3VPw6o=",
"lastModified": 1752206449,
"narHash": "sha256-NVAbC/s4CupABWGXF8M9mDiVw/n0YCftxwc1KatVjDk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "53389b4777be03b7d223abef9bb0d8ee030ee1a5",
"rev": "1bd4d0d4a678d48b63eb18f457d74df2fcee6c69",
"type": "github"
},
"original": {
@@ -858,27 +863,11 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1759143472,
"narHash": "sha256-TvODmeR2W7yX/JmOCmP+lAFNkTT7hAxYcF3Kz8SZV3w=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5ed4e25ab58fd4c028b59d5611e14ea64de51d23",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable_2": {
"locked": {
"lastModified": 1759281824,
"narHash": "sha256-FIBE1qXv9TKvSNwst6FumyHwCRH3BlWDpfsnqRDCll0=",
"lastModified": 1752203688,
"narHash": "sha256-uJ054F5PVGPu5SvLPMevhdY/EfK0X5DUyRtXhQYNUyo=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "5b5be50345d4113d04ba58c444348849f5585b4a",
"rev": "a70a12c75e13aa546c20ce0fe515de634d52c39e",
"type": "github"
},
"original": {
@@ -890,16 +879,16 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1759036355,
"narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=",
"lastModified": 1752124863,
"narHash": "sha256-5rWuf6RAlMDp/CAEuyYEz7ryxzgjxOCgUDhWEef864c=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127",
"rev": "40de82b434526744da778ed53c742c1282d9e75e",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"ref": "nixos-unstable-small",
"repo": "nixpkgs",
"type": "github"
}
@@ -954,21 +943,40 @@
},
"nixpkgs_5": {
"locked": {
"lastModified": 1759036355,
"narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=",
"owner": "NixOS",
"lastModified": 1751984180,
"narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127",
"rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0",
"type": "github"
},
"original": {
"owner": "NixOS",
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nur": {
"inputs": {
"flake-parts": "flake-parts_3",
"nixpkgs": "nixpkgs_5"
},
"locked": {
"lastModified": 1752207112,
"narHash": "sha256-dnVoQSGQqEGJQzS6iHAG95c0oFrezzBinwu1bDLj9J4=",
"owner": "nix-community",
"repo": "NUR",
"rev": "f166dc14862dfec043f9545e8291cc4402f8b866",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "NUR",
"type": "github"
}
},
"nur_2": {
"inputs": {
"flake-parts": [
"stylix",
@@ -980,11 +988,11 @@
]
},
"locked": {
"lastModified": 1758998580,
"narHash": "sha256-VLx0z396gDCGSiowLMFz5XRO/XuNV+4EnDYjdJhHvUk=",
"lastModified": 1751906969,
"narHash": "sha256-BSQAOdPnzdpOuCdAGSJmefSDlqmStFNScEnrWzSqKPw=",
"owner": "nix-community",
"repo": "NUR",
"rev": "ba8d9c98f5f4630bcb0e815ab456afd90c930728",
"rev": "ddb679f4131e819efe3bbc6457ba19d7ad116f25",
"type": "github"
},
"original": {
@@ -993,26 +1001,28 @@
"type": "github"
}
},
"plant": {
"flake": false,
"quickshell": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1758423341,
"narHash": "sha256-dHcKh7T8I70bKPwgqO5MxZ9bWHINwzRPhFp5waQy2r8=",
"lastModified": 1752146885,
"narHash": "sha256-ZJK989GL+bTCQSxbG8v8/7tHMCEl/FPovkeDBNyClQE=",
"ref": "refs/heads/master",
"rev": "08dc0b3889797eb3618c7475c3c367ec0e5fdf40",
"revCount": 6,
"rev": "d7079b75241c6e2b67f2429996fa7679ffc052e2",
"revCount": 616,
"type": "git",
"url": "ssh://git@git.imxyy.top:2222/imxyy1soope1/HF-plant.git"
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
},
"original": {
"rev": "08dc0b3889797eb3618c7475c3c367ec0e5fdf40",
"type": "git",
"url": "ssh://git@git.imxyy.top:2222/imxyy1soope1/HF-plant.git"
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
}
},
"root": {
"inputs": {
"catppuccin": "catppuccin",
"chaotic": "chaotic",
"darkly": "darkly",
"fenix": "fenix",
@@ -1027,9 +1037,10 @@
"nixpkgs-unstable"
],
"nixpkgs-master": "nixpkgs-master",
"nixpkgs-stable": "nixpkgs-stable_2",
"nixpkgs-stable": "nixpkgs-stable",
"nixpkgs-unstable": "nixpkgs-unstable",
"plant": "plant",
"nur": "nur",
"quickshell": "quickshell",
"sops-nix": "sops-nix",
"stylix": "stylix",
"zen": "zen"
@@ -1038,11 +1049,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1759134797,
"narHash": "sha256-YPi+jL3tx/yC5J5l7/OB7Lnlr9BMTzYnZtm7tRJzUNg=",
"lastModified": 1752086493,
"narHash": "sha256-USpVUdiWXDfPoh+agbvoBQaBhg3ZdKZgHXo/HikMfVo=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "062ac7a5451e8e92a32e22a60d86882d6a034f3f",
"rev": "6e3abe164b9036048dce1a3aa65a7e7e5200c0d3",
"type": "github"
},
"original": {
@@ -1060,11 +1071,11 @@
]
},
"locked": {
"lastModified": 1759113356,
"narHash": "sha256-xm4kEUcV2jk6u15aHazFP4YsMwhq+PczA+Ul/4FDKWI=",
"lastModified": 1751856221,
"narHash": "sha256-/QE1eV0ckFvgRMcKjZqgdJDoXFNwSMepwRoBjaw2MCk=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "be3b8843a2be2411500f6c052876119485e957a2",
"rev": "34cae4b56929c5b340e1c5b10d9a98a425b2a51e",
"type": "github"
},
"original": {
@@ -1080,16 +1091,15 @@
]
},
"locked": {
"lastModified": 1759191583,
"narHash": "sha256-gO4oCYU9TSoqtQzUqI8aiotKEhcObTEBXE5M2RRAsT8=",
"lastModified": 1751606940,
"narHash": "sha256-KrDPXobG7DFKTOteqdSVeL1bMVitDcy7otpVZWDE6MA=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "9f2e820c1643e55e88e83481e234dcf08143f3cf",
"rev": "3633fc4acf03f43b260244d94c71e9e14a2f6e0d",
"type": "github"
},
"original": {
"owner": "Mic92",
"ref": "pull/779/merge",
"repo": "sops-nix",
"type": "github"
}
@@ -1101,12 +1111,12 @@
"base16-helix": "base16-helix",
"base16-vim": "base16-vim",
"firefox-gnome-theme": "firefox-gnome-theme",
"flake-parts": "flake-parts_3",
"flake-parts": "flake-parts_4",
"gnome-shell": "gnome-shell",
"nixpkgs": [
"nixpkgs"
],
"nur": "nur",
"nur": "nur_2",
"systems": "systems_2",
"tinted-foot": "tinted-foot",
"tinted-kitty": "tinted-kitty",
@@ -1115,11 +1125,11 @@
"tinted-zed": "tinted-zed"
},
"locked": {
"lastModified": 1759131326,
"narHash": "sha256-fFhUx2C0Wtz0YkndtnlpSesrqj4lP3d5BUnMprpXtTk=",
"lastModified": 1752201883,
"narHash": "sha256-SZVbQ4YThvYU50cJ4W4GNMy7/rVOJI8qmXqbEcRNsug=",
"owner": "danth",
"repo": "stylix",
"rev": "fe74ba4ade9f3bb1496fbff27cc7a0ca873e40c4",
"rev": "d395780b9c5c36f191b990b2021c71af180a1982",
"type": "github"
},
"original": {
@@ -1194,11 +1204,11 @@
"tinted-schemes": {
"flake": false,
"locked": {
"lastModified": 1757716333,
"narHash": "sha256-d4km8W7w2zCUEmPAPUoLk1NlYrGODuVa3P7St+UrqkM=",
"lastModified": 1750770351,
"narHash": "sha256-LI+BnRoFNRa2ffbe3dcuIRYAUcGklBx0+EcFxlHj0SY=",
"owner": "tinted-theming",
"repo": "schemes",
"rev": "317a5e10c35825a6c905d912e480dfe8e71c7559",
"rev": "5a775c6ffd6e6125947b393872cde95867d85a2a",
"type": "github"
},
"original": {
@@ -1210,11 +1220,11 @@
"tinted-tmux": {
"flake": false,
"locked": {
"lastModified": 1757811970,
"narHash": "sha256-n5ZJgmzGZXOD9pZdAl1OnBu3PIqD+X3vEBUGbTi4JiI=",
"lastModified": 1751159871,
"narHash": "sha256-UOHBN1fgHIEzvPmdNMHaDvdRMgLmEJh2hNmDrp3d3LE=",
"owner": "tinted-theming",
"repo": "tinted-tmux",
"rev": "d217ba31c846006e9e0ae70775b0ee0f00aa6b1e",
"rev": "bded5e24407cec9d01bd47a317d15b9223a1546c",
"type": "github"
},
"original": {
@@ -1226,11 +1236,11 @@
"tinted-zed": {
"flake": false,
"locked": {
"lastModified": 1757811247,
"narHash": "sha256-4EFOUyLj85NRL3OacHoLGEo0wjiRJzfsXtR4CZWAn6w=",
"lastModified": 1751158968,
"narHash": "sha256-ksOyv7D3SRRtebpXxgpG4TK8gZSKFc4TIZpR+C98jX8=",
"owner": "tinted-theming",
"repo": "base16-zed",
"rev": "824fe0aacf82b3c26690d14e8d2cedd56e18404e",
"rev": "86a470d94204f7652b906ab0d378e4231a5b3384",
"type": "github"
},
"original": {
@@ -1242,16 +1252,16 @@
"xwayland-satellite-stable": {
"flake": false,
"locked": {
"lastModified": 1755491097,
"narHash": "sha256-m+9tUfsmBeF2Gn4HWa6vSITZ4Gz1eA1F5Kh62B0N4oE=",
"lastModified": 1748488455,
"narHash": "sha256-IiLr1alzKFIy5tGGpDlabQbe6LV1c9ABvkH6T5WmyRI=",
"owner": "Supreeeme",
"repo": "xwayland-satellite",
"rev": "388d291e82ffbc73be18169d39470f340707edaa",
"rev": "3ba30b149f9eb2bbf42cf4758d2158ca8cceef73",
"type": "github"
},
"original": {
"owner": "Supreeeme",
"ref": "v0.7",
"ref": "v0.6",
"repo": "xwayland-satellite",
"type": "github"
}
@@ -1259,11 +1269,11 @@
"xwayland-satellite-unstable": {
"flake": false,
"locked": {
"lastModified": 1758577423,
"narHash": "sha256-sB2GAOjhjoWnjU6A/uHNJiY6O3UeztV5pJAN2g1FkXU=",
"lastModified": 1751228685,
"narHash": "sha256-MENtauGBhJ+kDeFaawvWGXaFG3Il6qQzjaP0RmtfM0k=",
"owner": "Supreeeme",
"repo": "xwayland-satellite",
"rev": "03368548ba745e17a85bd631613a59cb2d8469a4",
"rev": "557ebeb616e03d5e4a8049862bbbd1f02c6f020b",
"type": "github"
},
"original": {
@@ -1280,11 +1290,11 @@
]
},
"locked": {
"lastModified": 1759292536,
"narHash": "sha256-fWTojLEpXgqwtKZb+qJ5gn9y8N6MAKM35yu0k+4yWmo=",
"lastModified": 1752164817,
"narHash": "sha256-LJFIx27IOUowLsJn5wci9mHZ4CesJsiAivQWDjnZPCc=",
"owner": "0xc000022070",
"repo": "zen-browser-flake",
"rev": "d11cff279fb1d879cd72d6fb3bbd1ae7b584674b",
"rev": "9193992c4c2c4349b4280ec2b49648cae208fe63",
"type": "github"
},
"original": {

View File

@@ -3,7 +3,7 @@
inputs = {
# Nixpkgs
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable-small";
nixpkgs-stable.url = "github:nixos/nixpkgs/release-25.05";
nixpkgs-master.url = "github:nixos/nixpkgs/master";
# nixpkgs.follows = "nixpkgs-stable";
@@ -13,9 +13,8 @@
# Nyxpkgs
chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable";
# TODO: sops-nix: remove pr patch once merged
# https://github.com/Mic92/sops-nix/pull/779
sops-nix.url = "github:Mic92/sops-nix/pull/779/merge";
# SOPS
sops-nix.url = "github:Mic92/sops-nix";
sops-nix.inputs.nixpkgs.follows = "nixpkgs";
# Home manager
@@ -25,8 +24,16 @@
# Impermanence
impermanence.url = "github:nix-community/impermanence";
# NUR
nur.url = "github:nix-community/NUR";
# Niri
niri.url = "github:sodiboo/niri-flake";
niri.inputs.nixpkgs.follows = "nixpkgs";
niri.inputs.nixpkgs-stable.follows = "nixpkgs-stable";
quickshell.url = "git+https://git.outfoxxed.me/outfoxxed/quickshell";
quickshell.inputs.nixpkgs.follows = "nixpkgs";
darkly.url = "github:Bali10050/Darkly";
darkly.inputs.nixpkgs.follows = "nixpkgs";
@@ -48,12 +55,6 @@
zen.url = "github:0xc000022070/zen-browser-flake";
zen.inputs.nixpkgs.follows = "nixpkgs";
plant.url = "git+ssh://git@git.imxyy.top:2222/imxyy1soope1/HF-plant.git?rev=08dc0b3889797eb3618c7475c3c367ec0e5fdf40";
plant.flake = false;
catppuccin.url = "github:catppuccin/nix";
catppuccin.inputs.nixpkgs.follows = "nixpkgs";
infuse.url = "git+https://codeberg.org/amjoseph/infuse.nix";
infuse.flake = false;
@@ -70,10 +71,10 @@
let
inherit (self) outputs;
vars = import ./vars.nix;
forAllSystems = lib.genAttrs lib.systems.flakeExposed;
forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
forAllHosts =
mkSystem:
lib.mergeAttrsList (
nixpkgs.lib.attrsets.mergeAttrsList (
builtins.map (hostname: {
${hostname} = mkSystem hostname;
}) (builtins.attrNames (builtins.readDir ./config/hosts))
@@ -138,6 +139,17 @@
darkly-qt5 = inputs.darkly.packages.${final.system}.darkly-qt5;
darkly-qt6 = inputs.darkly.packages.${final.system}.darkly-qt6;
})
(final: prev: {
quickshell = inputs.quickshell.packages.${final.system}.default.override {
withJemalloc = true;
withQtSvg = true;
withWayland = true;
withPipewire = true;
withPam = false;
withX11 = false;
withHyprland = false;
};
})
(final: prev: {
inherit lib;
})
@@ -173,16 +185,9 @@
outputs
hostname
;
secrets =
with lib.haumea;
load {
src = ./secrets;
loader = [
(matchers.always loaders.path)
];
};
}
// vars;
sopsRoot = ./secrets;
flake = ./.;
} // vars;
modules =
(lib.umport {
paths = [ ./modules ];
@@ -201,7 +206,6 @@
inputs.impermanence.nixosModules.impermanence
inputs.home-manager.nixosModules.default
inputs.niri.nixosModules.niri
inputs.catppuccin.nixosModules.catppuccin
home
pkgsConf
];

View File

@@ -1,26 +0,0 @@
{
config,
lib,
pkgs,
username,
...
}:
lib.my.makeSwitch {
inherit config;
default = true;
optionName = "default audio settings";
optionPath = [ "audio" ];
config' = {
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
audio.enable = true;
};
users.extraUsers.${username}.extraGroups = [ "audio" ];
my.persist.homeDirs = [ ".local/state/wireplumber" ];
my.home.home.packages = [ pkgs.pwvucontrol ];
};
}

View File

@@ -1,20 +0,0 @@
{ config, lib, ... }:
lib.my.makeSwitch {
inherit config;
default = true;
optionName = "default bluetooth settings";
optionPath = [ "bluetooth" ];
config' = {
hardware.bluetooth = {
enable = true;
powerOnBoot = true;
settings = {
General = {
Enable = "Source,Sink,Media,Socket";
Disable = "HeadSet";
MultiProfile = "multiple";
};
};
};
};
}

View File

@@ -9,6 +9,7 @@ lib.my.makeSwitch {
];
config' = {
my.cli.media = {
cava.enable = true;
go-musicfox.enable = true;
mpd.enable = true;
ffmpeg.enable = true;

View File

@@ -0,0 +1,167 @@
## Configuration file for CAVA. Default values are commented out. Use either ';' or '#' for commenting.
[general]
# Smoothing mode. Can be 'normal', 'scientific' or 'waves'.
mode = normal
# Accepts only non-negative values.
framerate = 60
# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off
# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens.
; autosens = 1
; overshoot = 20
# Manual sensitivity in %. Autosens must be turned off for this to take effect.
# 200 means double height. Accepts only non-negative values.
; sensitivity = 100
# The number of bars (0-200). 0 sets it to auto (fill up console).
# Bars' width and space between bars in number of characters.
bars = 0
bar_width = 5
bar_spacing = 1
# Lower and higher cutoff frequencies for lowest and highest bars
# the bandwidth of the visualizer.
# Note: there is a minimum total bandwidth of 43Mhz x number of bars.
# Cava will automatically increase the higher cutoff if a too low band is specified.
; lower_cutoff_freq = 50
; higher_cutoff_freq = 10000
[input]
# Audio capturing method. Possible methods are: 'pulse', 'alsa' or 'fifo'.
# Defaults to 'pulse', 'alsa' or 'fifo', in that order, dependent on what support cava was built with.
#
# All input methods uses the same config variable 'source'
# to define where it should get the audio.
#
# For pulseaudio 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink
# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).
#
# For alsa 'source' will be the capture device.
# For fifo 'source' will be the path to fifo-file.
method = pulse
source = auto
; method = alsa
; source = hw:Loopback,1
; method = fifo
; source = /tmp/mpd.fifo
[output]
# Ouput method. Can be 'ncurses', 'noncurses' or 'raw'.
# 'noncurses' is for systems that does not suport ncurses.
# 'raw' is a 16 bit data stream of the bar heights that can be used to send to other applications.
# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above.
method = ncurses
# Visual styles. Can be 'stereo' or 'mono'.
# 'stereo' mirrors both channels with low frequencies in center.
# 'mono' averages both channels and outputs left to right lowest to highest frequencies.
style = mono
# Raw output target. A fifo will be created if target does not exist.
; raw_target = /dev/stdout
# Raw data format. Can be 'binary' or 'ascii'.
; data_format = binary
# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).
; bit_format = 16bit
# Ascii max value. In 'ascii' mode range will run from 0 to value specified here
; ascii_max_range = 1000
# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.
# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)).
; bar_delimiter = 59
; frame_delimiter = 10
# [color]
# # Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.
# # Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires a
# # terminal that can change color definitions such as Gnome-terminal or rxvt.
# ; background = black
# ; foreground = cyan
# # Gradient mode, only hex defined colors are supported, background must also be defined in hex
# # or remain commented out. 1 = on, 0 = off. Warning: for certain terminal emulators cava will
# # not able to restore color definitions on exit, simply restart your terminal to restore colors.
# gradient = 1
# gradient_color_1 = '#0099ff'
# gradient_color_2 = '#ff3399'
[smoothing]
# Multiplier for the integral smoothing calculations. Takes values from 0-0.99.
# Higher values means smoother, but less precise. Set to 0 to disable.
; integral = 0.7
# Disables or enables the so-called "Monstercat smoothing". Set to 0 to disable.
; monstercat = 1
; waves = 1
# Set gravity multiplier for "drop off". Higher values means bars will drop faster.
# Accepts only non-negative values. 0.5 means half gravity, 2 means double. Set to 0 to disable "drop off".
; gravity = 2
# In bar height, bars that would have been lower that this will not be drawn.
; ignore = 0
[eq]
# This one is tricky. You can have as much keys as you want.
# Remember to uncomment more then one key! More keys = more precision.
# Look at readme.md on github for further explanations and examples.
#; 1 = 1 # bass
#; 2 = 1
#; 3 = 1 # midtone
#; 4 = 1
#; 5 = 1 # treble
1=1
2=1
3=2
4=1
5=1
[color]
#background = '#191724'
gradient = 1
gradient_count = 6
gradient_color_1 = '#31748f'
gradient_color_2 = '#9ccfd8'
gradient_color_3 = '#c4a7e7'
gradient_color_4 = '#ebbcba'
gradient_color_5 = '#f6c177'
gradient_color_6 = '#eb6f92'
[color]
gradient = 1
gradient_color_1 = '#94e2d5'
gradient_color_2 = '#89dceb'
gradient_color_3 = '#74c7ec'
gradient_color_4 = '#89b4fa'
gradient_color_5 = '#cba6f7'
gradient_color_6 = '#f5c2e7'
gradient_color_7 = '#eba0ac'
gradient_color_8 = '#f38ba8'

View File

@@ -0,0 +1,167 @@
## Configuration file for CAVA. Default values are commented out. Use either ';' or '#' for commenting.
[general]
# Smoothing mode. Can be 'normal', 'scientific' or 'waves'.
mode = normal
# Accepts only non-negative values.
framerate = 60
# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off
# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens.
; autosens = 1
; overshoot = 20
# Manual sensitivity in %. Autosens must be turned off for this to take effect.
# 200 means double height. Accepts only non-negative values.
; sensitivity = 100
# The number of bars (0-200). 0 sets it to auto (fill up console).
# Bars' width and space between bars in number of characters.
bars = 0
bar_width = 2
bar_spacing = 1
# Lower and higher cutoff frequencies for lowest and highest bars
# the bandwidth of the visualizer.
# Note: there is a minimum total bandwidth of 43Mhz x number of bars.
# Cava will automatically increase the higher cutoff if a too low band is specified.
; lower_cutoff_freq = 50
; higher_cutoff_freq = 10000
[input]
# Audio capturing method. Possible methods are: 'pulse', 'alsa' or 'fifo'.
# Defaults to 'pulse', 'alsa' or 'fifo', in that order, dependent on what support cava was built with.
#
# All input methods uses the same config variable 'source'
# to define where it should get the audio.
#
# For pulseaudio 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink
# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).
#
# For alsa 'source' will be the capture device.
# For fifo 'source' will be the path to fifo-file.
method = pulse
source = auto
; method = alsa
; source = hw:Loopback,1
; method = fifo
; source = /tmp/mpd.fifo
[output]
# Ouput method. Can be 'ncurses', 'noncurses' or 'raw'.
# 'noncurses' is for systems that does not suport ncurses.
# 'raw' is a 16 bit data stream of the bar heights that can be used to send to other applications.
# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above.
method = ncurses
# Visual styles. Can be 'stereo' or 'mono'.
# 'stereo' mirrors both channels with low frequencies in center.
# 'mono' averages both channels and outputs left to right lowest to highest frequencies.
style = mono
# Raw output target. A fifo will be created if target does not exist.
; raw_target = /dev/stdout
# Raw data format. Can be 'binary' or 'ascii'.
; data_format = binary
# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).
; bit_format = 16bit
# Ascii max value. In 'ascii' mode range will run from 0 to value specified here
; ascii_max_range = 1000
# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.
# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)).
; bar_delimiter = 59
; frame_delimiter = 10
# [color]
# # Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.
# # Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires a
# # terminal that can change color definitions such as Gnome-terminal or rxvt.
# ; background = black
# ; foreground = cyan
# # Gradient mode, only hex defined colors are supported, background must also be defined in hex
# # or remain commented out. 1 = on, 0 = off. Warning: for certain terminal emulators cava will
# # not able to restore color definitions on exit, simply restart your terminal to restore colors.
# gradient = 1
# gradient_color_1 = '#0099ff'
# gradient_color_2 = '#ff3399'
[smoothing]
# Multiplier for the integral smoothing calculations. Takes values from 0-0.99.
# Higher values means smoother, but less precise. Set to 0 to disable.
; integral = 0.7
# Disables or enables the so-called "Monstercat smoothing". Set to 0 to disable.
; monstercat = 1
; waves = 1
# Set gravity multiplier for "drop off". Higher values means bars will drop faster.
# Accepts only non-negative values. 0.5 means half gravity, 2 means double. Set to 0 to disable "drop off".
; gravity = 1
# In bar height, bars that would have been lower that this will not be drawn.
; ignore = 0
[eq]
# This one is tricky. You can have as much keys as you want.
# Remember to uncomment more then one key! More keys = more precision.
# Look at readme.md on github for further explanations and examples.
#; 1 = 1 # bass
#; 2 = 1
#; 3 = 1 # midtone
#; 4 = 1
#; 5 = 1 # treble
1=1
2=1
3=2
4=1
5=1
[color]
#background = '#191724'
gradient = 1
gradient_count = 6
gradient_color_1 = '#31748f'
gradient_color_2 = '#9ccfd8'
gradient_color_3 = '#c4a7e7'
gradient_color_4 = '#ebbcba'
gradient_color_5 = '#f6c177'
gradient_color_6 = '#eb6f92'
[color]
gradient = 1
gradient_color_1 = '#94e2d5'
gradient_color_2 = '#89dceb'
gradient_color_3 = '#74c7ec'
gradient_color_4 = '#89b4fa'
gradient_color_5 = '#cba6f7'
gradient_color_6 = '#f5c2e7'
gradient_color_7 = '#eba0ac'
gradient_color_8 = '#f38ba8'

View File

@@ -0,0 +1,79 @@
#version 330
in vec2 fragCoord;
out vec4 fragColor;
// bar values. defaults to left channels first (low to high), then right (high to low).
uniform float bars[512];
uniform int bars_count; // number of bars (left + right) (configurable)
uniform int bar_width; // bar width (configurable), not used here
uniform int bar_spacing; // space bewteen bars (configurable)
uniform vec3 u_resolution; // window resolution
//colors, configurable in cava config file (r,g,b) (0.0 - 1.0)
uniform vec3 bg_color; // background color
uniform vec3 fg_color; // foreground color
uniform int gradient_count;
uniform vec3 gradient_colors[8]; // gradient colors
vec3 normalize_C(float y,vec3 col_1, vec3 col_2, float y_min, float y_max)
{
//create color based on fraction of this color and next color
float yr = (y - y_min) / (y_max - y_min);
return col_1 * (1.0 - yr) + col_2 * yr;
}
void main()
{
// find which bar to use based on where we are on the x axis
float x = u_resolution.x * fragCoord.x;
int bar = int(bars_count * fragCoord.x);
//calculate a bar size
float bar_size = u_resolution.x / bars_count;
//the y coordinate and bar values are the same
float y = bars[bar];
// make sure there is a thin line at bottom
if (y * u_resolution.y < 1.0)
{
y = 1.0 / u_resolution.y;
}
//draw the bar up to current height
if (y > fragCoord.y)
{
//make some space between bars basen on settings
if (x > (bar + 1) * (bar_size) - bar_spacing)
{
fragColor = vec4(bg_color,1.0);
}
else
{
if (gradient_count == 0)
{
fragColor = vec4(fg_color,1.0);
}
else
{
//find which color in the configured gradient we are at
int color = int((gradient_count - 1) * fragCoord.y);
//find where on y this and next color is supposed to be
float y_min = color / (gradient_count - 1.0);
float y_max = (color + 1.0) / (gradient_count - 1.0);
//make color
fragColor = vec4(normalize_C(fragCoord.y, gradient_colors[color], gradient_colors[color + 1], y_min, y_max), 1.0);
}
}
}
else
{
fragColor = vec4(bg_color,1.0);
}
}

View File

@@ -0,0 +1,38 @@
#version 330
in vec2 fragCoord;
out vec4 fragColor;
// bar values. defaults to left channels first (low to high), then right (high to low).
uniform float bars[512];
uniform int bars_count; // number of bars (left + right) (configurable)
uniform vec3 u_resolution; // window resolution, not used here
//colors, configurable in cava config file
uniform vec3 bg_color; // background color(r,g,b) (0.0 - 1.0), not used here
uniform vec3 fg_color; // foreground color, not used here
float normalize_C(float x, float x_min, float x_max, float r_min, float r_max )
{
float xr;
xr = (r_max-r_min) * (x - x_min) / (x_max - x_min) + r_min;
return xr;
}
void main()
{
// find which bar to use based on where we are on the x axis
int bar = int(bars_count * fragCoord.x);
// create a normal along the y axis based on the bar height
float x = normalize_C(fragCoord.y, 1.0, 0.0, 0.0, bars[bar]);
// set color
fragColor.r=fg_color.x*x;
fragColor.g=fg_color.y*x;
fragColor.b=fg_color.z*x;
fragColor.a=1.0;
}

View File

@@ -0,0 +1,34 @@
#version 330
in vec2 fragCoord;
out vec4 fragColor;
// bar values. defaults to left channels first (low to high), then right (high to low).
uniform float bars[512];
uniform int bars_count; // number of bars (left + right) (configurable)
uniform vec3 u_resolution; // window resolution, not used here
//colors, configurable in cava config file
uniform vec3 bg_color; // background color(r,g,b) (0.0 - 1.0), not used here
uniform vec3 fg_color; // foreground color, not used here
void main()
{
// find which bar to use based on where we are on the x axis
int bar = int(bars_count * fragCoord.x);
float bar_y = 1.0 - abs((fragCoord.y - 0.5)) * 2.0;
float y = (bars[bar]) * bar_y;
float bar_x = (fragCoord.x - float(bar) / float(bars_count)) * bars_count;
float bar_r = 1.0 - abs((bar_x - 0.5)) * 2;
bar_r = bar_r * bar_r * 2;
// set color
fragColor.r = fg_color.x * y * bar_r;
fragColor.g = fg_color.y * y * bar_r;
fragColor.b = fg_color.z * y * bar_r;
}

View File

@@ -0,0 +1,14 @@
#version 330
// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;
// Output data ; will be interpolated for each fragment.
out vec2 fragCoord;
void main()
{
gl_Position = vec4(vertexPosition_modelspace,1);
fragCoord = (vertexPosition_modelspace.xy+vec2(1,1))/2.0;
}

View File

@@ -0,0 +1,22 @@
{
config,
lib,
pkgs,
...
}:
lib.my.makeHomePackageConfig {
inherit config pkgs;
packageName = "cava";
packagePath = [ "cava" ];
optionPath = [
"cli"
"media"
"cava"
];
extraConfig = {
my.home.xdg.configFile."cava" = {
source = ./config;
recursive = true;
};
};
}

View File

@@ -2,7 +2,6 @@
config,
lib,
pkgs,
secrets,
...
}:
lib.my.makeSwitch {
@@ -20,11 +19,7 @@ lib.my.makeSwitch {
playerctl
go-musicfox
];
sops.secrets.go-musicfox = {
sopsFile = secrets."go-musicfox.ini";
format = "binary";
path = "${config.my.home.xdg.configHome}/go-musicfox/go-musicfox.ini";
};
xdg.configFile."go-musicfox/go-musicfox.ini".source = ./go-musicfox.ini;
};
cli.media.mpd.enable = true;

View File

@@ -0,0 +1,97 @@
# 启动页配置
[startup]
# 是否显示启动页
show=false
# 启动页进度条是否有回弹效果
progressOutBounce=true
# 启动页时长
loadingSeconds=2
# 启动页欢迎语
welcome=musicfox
# 启动时自动签到
signin=false
# 进度条配置
[progress]
# 进度条已加载字符
fullChar=#
# 进度条未加载字符
emptyChar=
# 主页面配置
[main]
# 是否显示标题
showTitle=true
# 加载中提示
loadingText=[加载中...]
# 歌曲音质可选项standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
songLevel=hires
# 主题颜色
# 随机
# primaryColor=random
# 经典网易云音乐红
#primaryColor="#ea403f"
primaryColor="#6186D9"
# 是否显示歌词
showLyric=true
# 歌词偏移 ms
lyricOffset=0
# 显示歌词翻译
showLyricTrans=true
# 是否显示通知信息
showNotify=false
# 开启pprof, --pprof时会开启
pprofPort=9876
# altScreen显示模式
altScreen=true
# 双列显示,开启务必使用等宽字体
doubleColumn=true
# 下载目录,默认为$HOME/.go-musicfox/download
downloadDir=/home/imxyy/Music/go-musicfox
# 缓存目录,默认为${MUSICFOX_ROOT}/cache
cacheDir=/home/imxyy/Music/go-musicfox/.cache
# 缓存大小以MB为单位0为不使用缓存-1为不限制默认为0
cacheLimit=-1
# 是否显示歌单下所有歌曲默认不开启仅获取歌单前1000首开启后可能会占用更多内存大量歌曲数据和带宽会同时发送多个请求获取歌单下歌曲数据
showAllSongsOfPlaylist=false
# 动态显示menu行数
dynamicMenuRows=true
enableMouseEvent = false
[autoplay]
# 是否开启自动播放,默认不开启
autoPlay=true
# 自动播放歌单dailyReco,like,no保持上次退出时的设置无视offset,name:歌单名默认dailyReco
autoPlayList="no"
# 播放偏移0为第一首-1为最后一首默认为0
offset=0
# 播放模式listLoop, order, singleLoop, random, intelligent心动, last上次退出时的模式,default默认为last
playMode=singleLoop
[player]
# 播放引擎 beep / mpd(需要安装配置mpd) / osx(Mac才可用)
# 不填Mac默认使用osx其他系统默认使用beep推荐的配置
engine=mpd
# beep使用的mp3解码器可选go-mp3, minimp3 (minimp3更少的CPU占用但是稳定性不如go-mp3)
#beepMp3Decoder=go-mp3
# mpd配置
mpdBin=mpd
# !!!注意!!! 一定要在配置文件中设置pid_file否则在退出时不会kill掉mpd进程
mpdConfigFile=/home/imxyy/.config/mpd/mpd.conf
mpdNetwork=tcp
mpdAddr=127.0.0.1:6600
[unm]
# UNM开关
switch=true
# UNM源: kuwo,kugou,migu,qq
sources=kuwo,kugou
# UNM搜索其他平台限制 0-3
searchLimit=0
# 解除会员限制
enableLocalVip=true
# 解除音质限制
unlockSoundEffects=true
# QQ音乐cookie文件
qqCookieFile=

View File

@@ -23,3 +23,8 @@ audio_output {
type "pipewire"
name "pipewire"
}
audio_output {
type "pulse"
name "pulseaudio"
}

View File

@@ -42,8 +42,6 @@ lib.my.makeSwitch {
dnsutils
killall
comma
];
programs.dconf.enable = true;
@@ -82,10 +80,6 @@ lib.my.makeSwitch {
enableAutoUpdates = true;
settings.updates.auto_update = true;
};
programs.television = {
enable = true;
enableZshIntegration = true;
};
};
};
}

View File

@@ -44,11 +44,11 @@ lib.my.makeSwitch {
jj = {
ignore_timeout = true;
description = "The current jj status";
when = true;
when = "jj root";
symbol = " ";
command = ''
jj log --revisions @ --no-graph --ignore-working-copy --color always --limit 1 --template '
separate(" ",
" ",
change_id.shortest(4),
bookmarks,
"|",
@@ -64,9 +64,19 @@ lib.my.makeSwitch {
"(no description set)",
) ++ raw_escape_sequence("\x1b[0m"),
)
' || (starship module git_branch && starship module git_status)
'
'';
};
git_branch = {
when = true;
command = "jj root >/dev/null 2>&1 || starship module git_branch";
description = "Only show git_branch if we're not in a jj repo";
};
git_status = {
when = true;
command = "jj root >/dev/null 2>&1 || starship module git_status";
description = "Only show git_status if we're not in a jj repo";
};
};
git_state.disabled = true;
git_commit.disabled = true;
@@ -78,7 +88,7 @@ lib.my.makeSwitch {
};
programs.zsh = {
enable = true;
dotDir = "${config.my.home.xdg.configHome}/zsh";
dotDir = ".config/zsh";
history = {
path = "${stateHome}/zsh_history";
ignorePatterns = [

View File

@@ -26,7 +26,6 @@ lib.my.makeHomeProgramConfig {
ui = {
graph.style = "square";
default-command = "status";
conflict-marker-style = "snapshot";
};
signing = {
backend = "ssh";

View File

@@ -26,9 +26,31 @@ lib.my.makeHomeProgramConfig {
vimAlias = true;
vimdiffAlias = true;
extraPackages = with pkgs; [
gcc # treesitter
gcc
gnumake
ripgrep # telescope
pyright
clang-tools
rust-analyzer
pest-ide-tools
nixd
gotools
gopls
stylua
lua-language-server
nodePackages.vscode-langservers-extracted
nodePackages.typescript-language-server
vue-language-server
typescript
nodejs
ripgrep
];
};
};

View File

@@ -4,3 +4,5 @@ require("core.keymaps")
require("langs.langs-setup")
require("plugins.plugins-setup")
require("core.autostart")

View File

@@ -0,0 +1,7 @@
-- Open tree when in config dir
local configdir = vim.fn.system("echo $HOME/.config")
configdir = string.sub(configdir, 1, string.len(configdir) - 1)
if string.find(vim.fn.system("pwd"), configdir) ~= nil then
vim.cmd("NvimTreeOpen")
vim.cmd("NvimTmuxNavigateRight")
end

View File

@@ -7,21 +7,33 @@ local buf_kill = globals.buf_kill
keymap.set("v", "<S-pageup>", ":m '<-2<CR>gv=gv", opt)
keymap.set("v", "<S-pagedown>", ":m '>+1<CR>gv=gv", opt)
keymap.set({ "i", "n", "v" }, "<S-up>", "<up>", opt)
keymap.set({ "i", "n", "v" }, "<S-down>", "<down>", opt)
keymap.set("n", "<leader>nh", ":nohl<CR>", opt)
keymap.set("n", "<leader>sv", "<C-w>v", opt)
keymap.set("n", "<leader>sh", "<C-w>s", opt)
keymap.set("i", "<S-up>", "<ESC>v<up>", opt)
keymap.set("i", "<S-down>", "<ESC>v<down>", opt)
keymap.set("n", "<S-up>", "v<up>", opt)
keymap.set("n", "<S-down>", "v<down>", opt)
keymap.set("v", "<S-up>", "<up>", opt)
keymap.set("v", "<S-down>", "<down>", opt)
keymap.set("i", "<S-left>", "<ESC>v<left>", opt)
keymap.set("i", "<S-right>", "<ESC>v<right>", opt)
keymap.set("n", "<S-left>", "v<left>", opt)
keymap.set("n", "<S-right>", "v<right>", opt)
keymap.set("v", "<S-left>", "<left>", opt)
keymap.set("v", "<S-right>", "<right>", opt)
keymap.set("v", ".", ">gv", opt)
keymap.set("v", ",", "<gv", opt)
keymap.set({ "n", "v" }, "<pageup>", "9k", opt)
keymap.set({ "n", "v" }, "<pagedown>", "9j", opt)
keymap.set("i", "<pageup>", string.rep("<up>", 9), opt)
keymap.set("i", "<pagedown>", string.rep("<down>", 9), opt)
keymap.set("i", "<pageup>", "<up><up><up><up><up><up><up><up><up>", opt)
keymap.set("i", "<pagedown>", "<down><down><down><down><down><down><down><down><down>", opt)
keymap.set("n", "<leader>ww", ":w<CR>", opt)
keymap.set("n", "<leader>so", ":so<CR>", opt)
@@ -42,6 +54,15 @@ keymap.set("n", "L", ":BufferLineCycleNext<CR>", opt)
keymap.set("n", "<A-h>", ":BufferLineMovePrev<CR>", opt)
keymap.set("n", "<A-l>", ":BufferLineMoveNext<CR>", opt)
-- reload config
keymap.set("n", "<leader>rc", ":so ~/.config/nvim/init.lua<CR>", opt)
keymap.set("n", "<leader>rp", ":so ~/.config/nvim/lua/plugins/plugins-setup.lua<CR>", opt)
-- Workspaces
keymap.set("n", "<leader>wo", ":Telescope workspaces<CR>", opt)
keymap.set("n", "<leader>wa", ":WorkspacesAdd<CR>", opt)
keymap.set("n", "<leader>wr", ":WorkspacesRemove<CR>", opt)
-- Neovide config
if vim.g.neovide then
keymap.set("v", "<C-C>", '"+y', opt)

View File

@@ -9,7 +9,6 @@ local servers = {
"cssls",
"nixd",
"html",
"java_language_server",
}
local extra_config = {
@@ -36,7 +35,7 @@ local extra_config = {
},
rust_analyzer = {
settings = {
["rust-analyzer"] = {
rust_analyzer = {
check = {
command = "clippy",
},
@@ -74,6 +73,7 @@ capabilities.textDocument.foldingRange = {
dynamicRegistration = false,
lineFoldingOnly = true,
}
local lspconfig = require("lspconfig")
for _, server in ipairs(servers) do
local extra = extra_config[server] or {}
local config = {
@@ -82,6 +82,5 @@ for _, server in ipairs(servers) do
for k, v in pairs(extra) do
config[k] = v
end
vim.lsp.config(server, config)
vim.lsp.enable(server)
lspconfig[server].setup(config)
end

View File

@@ -15,7 +15,6 @@ lib.my.makeSwitch {
python.enable = true;
rust.enable = true;
lua.enable = true;
java.enable = true;
qml.enable = true;
};
};

View File

@@ -14,10 +14,6 @@ lib.my.makeHomePackageConfig {
"go"
];
extraConfig = {
my.home.home.packages = with pkgs; [
gotools
gopls
];
my.persist.homeDirs = [
"go"
];

View File

@@ -1,21 +0,0 @@
{
config,
lib,
pkgs,
...
}:
lib.my.makeSwitch {
inherit config;
optionName = "java";
optionPath = [
"coding"
"langs"
"java"
];
config' = {
my.home.home.packages = with pkgs; [
openjdk24
java-language-server
];
};
}

View File

@@ -16,10 +16,9 @@ lib.my.makeSwitch {
my.home = {
home.packages = with pkgs; [
nodejs
typescript
nodePackages.npm
nodePackages.typescript-language-server
vue-language-server
typescript
];
home.file.".npmrc".text = ''
prefix = ''${HOME}/.npm-global

View File

@@ -15,8 +15,6 @@ lib.my.makeSwitch {
config' = {
my.home.home.packages = with pkgs; [
luajit
stylua
lua-language-server
];
};
}

View File

@@ -16,7 +16,6 @@ lib.my.makeHomePackageConfig {
extraConfig = {
my.home.home.packages = with pkgs; [
uv
pyright
];
};
}

View File

@@ -21,11 +21,8 @@ lib.my.makeSwitch {
"rust-src"
"rustc"
"rustfmt"
"rust-analyzer"
])
evcxr # rust repl
pest-ide-tools
];
home.file.".cargo/config.toml".text = ''
[source.crates-io]
@@ -37,9 +34,6 @@ lib.my.makeSwitch {
[net]
git-fetch-with-cli = true
'';
programs.zsh.initContent = lib.mkAfter ''
export PATH=$PATH:$HOME/.cargo/bin
'';
};
my.persist.homeDirs = [
".cargo"

View File

@@ -16,6 +16,7 @@ lib.my.makeSwitch {
terminal.all.enable = true;
wm.all.enable = true;
style.enable = true;
quickshell.enable = true;
wine.enable = true;
};
};

View File

@@ -10,6 +10,7 @@ lib.my.makeSwitch {
config' = {
my.desktop.browser = {
firefox.enable = true;
librewolf.enable = true;
chromium.enable = true;
zen.enable = true;
};

View File

@@ -0,0 +1,15 @@
{ config, lib, ... }:
lib.my.makeHomeProgramConfig {
inherit config;
programName = "librewolf";
optionPath = [
"desktop"
"browser"
"librewolf"
];
extraConfig = {
my.persist.homeDirs = [
".librewolf"
];
};
}

View File

@@ -13,17 +13,18 @@ lib.my.makeSwitch {
"minecraft"
];
config' = {
my.home.home.packages = [
(pkgs.hmcl.overrideAttrs {
postFixup = ''
substituteInPlace $out/share/applications/HMCL.desktop --replace-fail 'Exec=hmcl' 'Exec=sh -c "cd ~/.local/share/hmcl; hmcl"'
'';
})
my.home.home.packages = with pkgs; [
hmcl
openjdk21
];
my.persist.homeDirs = [
".minecraft"
".local/share/hmcl"
];
my.persist.homeFiles = [
".hmcl.json"
];
};
}

View File

@@ -0,0 +1,43 @@
{
config,
lib,
pkgs,
...
}:
let
# FIXME: symlink
homeDir = config.my.home.home.homeDirectory;
quickshellDir = "${homeDir}/workspace/nixos-dotfiles/modules/desktop/quickshell/qml";
quickshellTarget = "${homeDir}/.config/quickshell";
in
lib.my.makeSwitch {
inherit config;
default = false;
optionName = "quickshell";
optionPath = [
"desktop"
"quickshell"
];
config' = {
my.home.home = {
packages = with pkgs; [
quickshell
qt6Packages.qt5compat
libsForQt5.qt5.qtgraphicaleffects
kdePackages.qtbase
kdePackages.qtdeclarative
material-symbols
material-icons
];
sessionVariables.QML2_IMPORT_PATH = lib.concatStringsSep ":" [
"${pkgs.quickshell}/lib/qt-6/qml"
"${pkgs.kdePackages.qtdeclarative}/lib/qt-6/qml"
"${pkgs.kdePackages.kirigami.unwrapped}/lib/qt-6/qml"
];
activation.symlinkQuickshellAndFaceIcon = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
ln -sfn "${quickshellDir}" "${quickshellTarget}"
'';
};
};
}

View File

@@ -0,0 +1,2 @@
/debug.log
/quickshell.log

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,84 @@
import QtQuick
import QtQuick.Shapes
import "root:/Data" as Settings
// Concave corner shape component for rounded panel edges
Shape {
id: root
property string position: "topleft" // Corner position: topleft/topright/bottomleft/bottomright
property real size: 1.0 // Scale multiplier for entire corner
property int concaveWidth: 100 * size
property int concaveHeight: 60 * size
property int offsetX: -20
property int offsetY: -20
property color fillColor: Settings.Colors.bgColor
property int arcRadius: 20 * size
// Position flags derived from position string
property bool _isTop: position.includes("top")
property bool _isLeft: position.includes("left")
property bool _isRight: position.includes("right")
property bool _isBottom: position.includes("bottom")
// Base coordinates for left corner shape
property real _baseStartX: 30 * size
property real _baseStartY: _isTop ? 20 * size : 0
property real _baseLineX: 30 * size
property real _baseLineY: _isTop ? 0 : 20 * size
property real _baseArcX: 50 * size
property real _baseArcY: _isTop ? 20 * size : 0
// Mirror coordinates for right corners
property real _startX: _isRight ? (concaveWidth - _baseStartX) : _baseStartX
property real _startY: _baseStartY
property real _lineX: _isRight ? (concaveWidth - _baseLineX) : _baseLineX
property real _lineY: _baseLineY
property real _arcX: _isRight ? (concaveWidth - _baseArcX) : _baseArcX
property real _arcY: _baseArcY
// Arc direction varies by corner to maintain proper concave shape
property int _arcDirection: {
if (_isTop && _isLeft)
return PathArc.Counterclockwise;
if (_isTop && _isRight)
return PathArc.Clockwise;
if (_isBottom && _isLeft)
return PathArc.Clockwise;
if (_isBottom && _isRight)
return PathArc.Counterclockwise;
return PathArc.Counterclockwise;
}
width: concaveWidth
height: concaveHeight
// Position relative to parent based on corner type
x: _isLeft ? offsetX : (parent ? parent.width - width + offsetX : 0)
y: _isTop ? offsetY : (parent ? parent.height - height + offsetY : 0)
preferredRendererType: Shape.CurveRenderer
layer.enabled: true
layer.samples: 4
ShapePath {
strokeWidth: 0
fillColor: root.fillColor
strokeColor: root.fillColor // Use same color as fill to eliminate artifacts
startX: root._startX
startY: root._startY
PathLine {
x: root._lineX
y: root._lineY
}
PathArc {
x: root._arcX
y: root._arcY
radiusX: root.arcRadius
radiusY: root.arcRadius
useLargeArc: false
direction: root._arcDirection
}
}
}

View File

@@ -0,0 +1,48 @@
import QtQuick
QtObject {
id: root
// Keep track of loaded components
property var activeLoaders: ({})
// Dynamically load a QML component
function load(componentUrl, parent, properties) {
if (!activeLoaders[componentUrl]) {
var loader = Qt.createQmlObject(`
import QtQuick
Loader {
active: false
asynchronous: true
visible: false
}
`, parent);
loader.source = componentUrl;
loader.active = true;
if (properties) {
for (var prop in properties) {
loader[prop] = properties[prop];
}
}
activeLoaders[componentUrl] = loader;
}
return activeLoaders[componentUrl];
}
// Destroy and remove a loaded component
function unload(componentUrl) {
if (activeLoaders[componentUrl]) {
activeLoaders[componentUrl].active = false;
activeLoaders[componentUrl].destroy();
delete activeLoaders[componentUrl];
}
}
// Check if a component is loaded
function isLoaded(componentUrl) {
return !!activeLoaders[componentUrl];
}
}

View File

@@ -0,0 +1,189 @@
pragma Singleton
import QtQuick
import Quickshell.Io
// System process and resource monitoring
QtObject {
id: root
// System resource metrics
property real cpuUsage: 0
property real ramUsage: 0
property real totalRam: 0
property real usedRam: 0
// System control processes
property Process shutdownProcess: Process {
command: ["shutdown", "-h", "now"]
}
property Process rebootProcess: Process {
command: ["reboot"]
}
property Process lockProcess: Process {
command: ["hyprlock"]
}
property Process logoutProcess: Process {
command: ["loginctl", "terminate-user", "$USER"]
}
property Process pavucontrolProcess: Process {
command: ["pavucontrol"]
}
// Resource monitoring processes
property Process cpuProcess: Process {
command: ["sh", "-c", "grep '^cpu ' /proc/stat | awk '{usage=($2+$3+$4)*100/($2+$3+$4+$5)} END {print usage}'"]
stdout: SplitParser {
onRead: data => {
root.cpuUsage = parseFloat(data);
}
}
}
property Process ramProcess: Process {
command: ["sh", "-c", "free -b | awk '/Mem:/ {print $2\" \"$3\" \"$3/$2*100}'"]
stdout: SplitParser {
onRead: data => {
var parts = data.trim().split(/\s+/);
if (parts.length >= 3) {
root.totalRam = parseFloat(parts[0]) / (1024 * 1024 * 1024);
root.usedRam = parseFloat(parts[1]) / (1024 * 1024 * 1024);
root.ramUsage = parseFloat(parts[2]);
}
}
}
}
// Monitoring timers (start manually when needed)
property Timer cpuTimer: Timer {
interval: 30000
running: false
repeat: true
onTriggered: {
cpuProcess.running = false;
cpuProcess.running = true;
}
}
property Timer ramTimer: Timer {
interval: 30000
running: false
repeat: true
onTriggered: {
ramProcess.running = false;
ramProcess.running = true;
}
}
// System control functions
function shutdown() {
console.log("Executing shutdown command");
shutdownProcess.running = true;
}
function reboot() {
console.log("Executing reboot command");
rebootProcess.running = true;
}
function lock() {
console.log("Executing lock command");
lockProcess.running = true;
}
function logout() {
console.log("Executing logout command");
logoutProcess.running = true;
}
function openPavuControl() {
console.log("Opening PavuControl");
pavucontrolProcess.running = true;
}
// Performance monitoring control
function startMonitoring() {
console.log("Starting system monitoring");
cpuTimer.running = true;
ramTimer.running = true;
}
function stopMonitoring() {
console.log("Stopping system monitoring");
cpuTimer.running = false;
ramTimer.running = false;
}
function setMonitoringInterval(intervalMs) {
console.log("Setting monitoring interval to", intervalMs, "ms");
cpuTimer.interval = intervalMs;
ramTimer.interval = intervalMs;
}
function refreshSystemStats() {
console.log("Manually refreshing system stats");
cpuProcess.running = false;
cpuProcess.running = true;
ramProcess.running = false;
ramProcess.running = true;
}
// Process state queries
function isShutdownRunning() {
return shutdownProcess.running;
}
function isRebootRunning() {
return rebootProcess.running;
}
function isLogoutRunning() {
return logoutProcess.running;
}
function isPavuControlRunning() {
return pavucontrolProcess.running;
}
function isMonitoringActive() {
return cpuTimer.running && ramTimer.running;
}
function stopPavuControl() {
pavucontrolProcess.running = false;
}
// Formatted output helpers
function getCpuUsageFormatted() {
return Math.round(cpuUsage) + "%";
}
function getRamUsageFormatted() {
return Math.round(ramUsage) + "% (" + usedRam.toFixed(1) + "GB/" + totalRam.toFixed(1) + "GB)";
}
function getRamUsageSimple() {
return Math.round(ramUsage) + "%";
}
Component.onDestruction: {
// Stop all timers
cpuTimer.running = false;
ramTimer.running = false;
// Stop monitoring processes
cpuProcess.running = false;
ramProcess.running = false;
// Stop control processes if running
if (shutdownProcess.running)
shutdownProcess.running = false;
if (rebootProcess.running)
rebootProcess.running = false;
if (lockProcess.running)
lockProcess.running = false;
if (logoutProcess.running)
logoutProcess.running = false;
if (pavucontrolProcess.running)
pavucontrolProcess.running = false;
}
}

View File

@@ -0,0 +1,38 @@
pragma Singleton
import QtQuick
QtObject {
property var service: null
// Expose current colors from the service
readonly property color primary: service?.colors?.raw?.primary || "#7ed7b8"
readonly property color on_primary: service?.colors?.raw?.on_primary || "#00382a"
readonly property color primary_container: service?.colors?.raw?.primary_container || "#454b03"
readonly property color on_primary_container: service?.colors?.raw?.on_primary_container || "#e2e993"
readonly property color secondary: service?.colors?.raw?.secondary || "#c8c9a6"
readonly property color surface_bright: service?.colors?.raw?.surface_bright || "#373b30"
readonly property bool hasColors: service?.isLoaded || false
// Expose all raw Material 3 colors for complete access
readonly property var rawColors: service?.colors?.raw || ({})
function setService(matugenService) {
service = matugenService;
console.log("MatugenManager: Service registered");
}
function reloadColors() {
if (service && service.reloadColors) {
console.log("MatugenManager: Triggering color reload");
service.reloadColors();
return true;
} else {
console.warn("MatugenManager: No service available for reload");
return false;
}
}
function isAvailable() {
return service !== null;
}
}

View File

@@ -0,0 +1,315 @@
pragma Singleton
import Quickshell
import QtQuick
import Quickshell.Io
Singleton {
id: settings
// Prevent auto-saving during initial load
property bool isLoading: true
// Settings persistence with atomic writes
FileView {
id: settingsFile
path: "settings.json"
blockWrites: true
atomicWrites: true
watchChanges: false
onLoaded: {
settings.isLoading = true; // Disable auto-save during loading
try {
var content = JSON.parse(text());
if (content) {
// Load with fallback defaults
settings.isDarkTheme = content.isDarkTheme ?? true;
settings.currentTheme = content.currentTheme ?? (content.isDarkTheme !== false ? "oxocarbon_dark" : "oxocarbon_light");
settings.useCustomAccent = content.useCustomAccent ?? false;
settings.avatarSource = content.avatarSource ?? "/home/imxyy/Pictures/icon.jpg";
settings.weatherLocation = content.weatherLocation ?? "Dinslaken";
settings.useFahrenheit = content.useFahrenheit ?? false;
settings.displayTime = content.displayTime ?? 6000;
settings.videoPath = content.videoPath ?? "~/Videos/";
settings.customDarkAccent = content.customDarkAccent ?? "#be95ff";
settings.customLightAccent = content.customLightAccent ?? "#8a3ffc";
settings.autoSwitchPlayer = content.autoSwitchPlayer ?? true;
settings.alwaysShowPlayerDropdown = content.alwaysShowPlayerDropdown ?? true;
settings.historyLimit = content.historyLimit ?? 25;
settings.nightLightEnabled = content.nightLightEnabled ?? false;
settings.nightLightWarmth = content.nightLightWarmth ?? 0.4;
settings.nightLightAuto = content.nightLightAuto ?? false;
settings.nightLightStartHour = content.nightLightStartHour ?? 20;
settings.nightLightEndHour = content.nightLightEndHour ?? 6;
settings.nightLightManualOverride = content.nightLightManualOverride ?? false;
settings.nightLightManuallyEnabled = content.nightLightManuallyEnabled ?? false;
settings.ignoredApps = content.ignoredApps ?? [];
settings.workspaceBurstEnabled = content.workspaceBurstEnabled ?? true;
settings.workspaceGlowEnabled = content.workspaceGlowEnabled ?? true;
}
} catch (e) {
console.log("Error parsing user settings:", e);
}
// Re-enable auto-save after loading is complete
settings.isLoading = false;
}
}
// User-configurable settings
property string avatarSource: "/home/imxyy/Pictures/icon.jpg"
property bool isDarkTheme: true // Keep for backwards compatibility
property string currentTheme: "oxocarbon_dark" // New theme system
property bool useCustomAccent: false // Whether to use custom accent colors
property string weatherLocation: "Dinslaken"
property bool useFahrenheit: false // Temperature unit setting
property int displayTime: 6000 // Notification display time in ms
property var ignoredApps: [] // Apps to ignore notifications from (case-insensitive)
property int historyLimit: 25 // Notification history limit
property string videoPath: "~/Videos/"
property string customDarkAccent: "#be95ff"
property string customLightAccent: "#8a3ffc"
// Music Player settings
property bool autoSwitchPlayer: true
property bool alwaysShowPlayerDropdown: true
// Night Light settings
property bool nightLightEnabled: false
property real nightLightWarmth: 0.4
property bool nightLightAuto: false
property int nightLightStartHour: 20 // 8 PM
property int nightLightEndHour: 6 // 6 AM
property bool nightLightManualOverride: false // Track manual user actions
property bool nightLightManuallyEnabled: false // Track if user manually enabled it
// Animation settings
property bool workspaceBurstEnabled: true
property bool workspaceGlowEnabled: true
// UI constants
readonly property real borderWidth: 9
readonly property real cornerRadius: 20
signal settingsChanged
// Helper functions for managing ignored apps
function addIgnoredApp(appName) {
if (appName && appName.trim() !== "") {
var trimmedName = appName.trim();
// Case-insensitive check for existing apps
var exists = false;
for (var i = 0; i < ignoredApps.length; i++) {
if (ignoredApps[i].toLowerCase() === trimmedName.toLowerCase()) {
exists = true;
break;
}
}
if (!exists) {
var newApps = ignoredApps.slice(); // Create a copy
newApps.push(trimmedName);
ignoredApps = newApps;
console.log("Added ignored app:", trimmedName, "Current list:", ignoredApps);
// Force save immediately (only if not loading)
if (!isLoading) {
saveSettings();
}
return true;
}
}
return false;
}
function removeIgnoredApp(appName) {
var index = ignoredApps.indexOf(appName);
if (index > -1) {
var newApps = ignoredApps.slice(); // Create a copy
newApps.splice(index, 1);
ignoredApps = newApps;
console.log("Removed ignored app:", appName, "Current list:", ignoredApps);
// Force save immediately (only if not loading)
if (!isLoading) {
saveSettings();
}
return true;
}
return false;
}
function saveSettings() {
try {
var content = {
isDarkTheme: settings.isDarkTheme,
currentTheme: settings.currentTheme,
useCustomAccent: settings.useCustomAccent,
avatarSource: settings.avatarSource,
weatherLocation: settings.weatherLocation,
useFahrenheit: settings.useFahrenheit,
displayTime: settings.displayTime,
videoPath: settings.videoPath,
customDarkAccent: settings.customDarkAccent,
customLightAccent: settings.customLightAccent,
autoSwitchPlayer: settings.autoSwitchPlayer,
alwaysShowPlayerDropdown: settings.alwaysShowPlayerDropdown,
historyLimit: settings.historyLimit,
nightLightEnabled: settings.nightLightEnabled,
nightLightWarmth: settings.nightLightWarmth,
nightLightAuto: settings.nightLightAuto,
nightLightStartHour: settings.nightLightStartHour,
nightLightEndHour: settings.nightLightEndHour,
nightLightManualOverride: settings.nightLightManualOverride,
nightLightManuallyEnabled: settings.nightLightManuallyEnabled,
ignoredApps: settings.ignoredApps,
workspaceBurstEnabled: settings.workspaceBurstEnabled,
workspaceGlowEnabled: settings.workspaceGlowEnabled
};
var jsonContent = JSON.stringify(content, null, 4);
settingsFile.setText(jsonContent);
} catch (e) {
console.log("Error saving user settings:", e);
}
}
// Auto-save watchers (only save when not loading)
onIsDarkThemeChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onCurrentThemeChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onUseCustomAccentChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onAvatarSourceChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onWeatherLocationChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onUseFahrenheitChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onDisplayTimeChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onHistoryLimitChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onVideoPathChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onCustomDarkAccentChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onCustomLightAccentChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onAutoSwitchPlayerChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onAlwaysShowPlayerDropdownChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onNightLightEnabledChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onNightLightWarmthChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onNightLightAutoChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onNightLightStartHourChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onNightLightEndHourChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onNightLightManualOverrideChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onNightLightManuallyEnabledChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onIgnoredAppsChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onWorkspaceBurstEnabledChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
onWorkspaceGlowEnabledChanged: {
if (!isLoading) {
settingsChanged();
saveSettings();
}
}
Component.onCompleted: {
settingsFile.reload();
}
}

View File

@@ -0,0 +1,240 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import "Themes" as Themes
Singleton {
id: themeManager
// Import all theme definitions
property var oxocarbon: Themes.Oxocarbon
property var dracula: Themes.Dracula
property var gruvbox: Themes.Gruvbox
property var catppuccin: Themes.Catppuccin
property var matugen: Themes.Matugen
// Available theme definitions
readonly property var themes: ({
"oxocarbon_dark": oxocarbon.dark,
"oxocarbon_light": oxocarbon.light,
"dracula_dark": dracula.dark,
"dracula_light": dracula.light,
"gruvbox_dark": gruvbox.dark,
"gruvbox_light": gruvbox.light,
"catppuccin_dark": catppuccin.dark,
"catppuccin_light": catppuccin.light,
"matugen_dark": matugen.dark,
"matugen_light": matugen.light
})
// Current theme selection - defaults to oxocarbon_dark if not set
readonly property string currentThemeId: Settings.currentTheme || "oxocarbon_dark"
readonly property var currentTheme: themes[currentThemeId] || themes["oxocarbon_dark"]
// Auto-update accents when Matugen colors change
Connections {
target: MatugenManager
function onPrimaryChanged() {
if (currentThemeId.startsWith("matugen_")) {
updateMatugenAccents();
}
}
}
// Connect to MatugenService signals for automatic accent updates
Connections {
target: MatugenManager.service
function onMatugenColorsUpdated() {
if (currentThemeId.startsWith("matugen_")) {
console.log("ThemeManager: Received matugen colors update signal");
updateMatugenAccents();
}
}
}
// Initialize currentTheme in settings if not present
Component.onCompleted: {
if (!Settings.currentTheme) {
console.log("Initializing currentTheme in settings");
Settings.currentTheme = "oxocarbon_dark";
Settings.saveSettings();
}
// Matugen theme is now self-contained with service-based colors
console.log("Matugen theme initialized with service-based colors");
// Update accents if already using matugen theme
if (currentThemeId.startsWith("matugen_")) {
updateMatugenAccents();
}
}
// Custom accent colors (can be changed by user)
property string customDarkAccent: Settings.customDarkAccent || "#be95ff"
property string customLightAccent: Settings.customLightAccent || "#8a3ffc"
// Dynamic color properties based on current theme
readonly property color base00: currentTheme.base00
readonly property color base01: currentTheme.base01
readonly property color base02: currentTheme.base02
readonly property color base03: currentTheme.base03
readonly property color base04: currentTheme.base04
readonly property color base05: currentTheme.base05
readonly property color base06: currentTheme.base06
readonly property color base07: currentTheme.base07
readonly property color base08: currentTheme.base08
readonly property color base09: currentTheme.base09
readonly property color base0A: currentTheme.base0A
readonly property color base0B: currentTheme.base0B
readonly property color base0C: currentTheme.base0C
readonly property color base0D: currentTheme.base0D
readonly property color base0E: Settings.useCustomAccent ? (currentTheme.type === "dark" ? customDarkAccent : customLightAccent) : currentTheme.base0E
readonly property color base0F: currentTheme.base0F
// Common UI color mappings
readonly property color bgColor: base00
readonly property color bgLight: base01
readonly property color bgLighter: base02
readonly property color fgColor: base04
readonly property color fgColorBright: base05
readonly property color accentColor: base0E
readonly property color accentColorBright: base0D
readonly property color highlightBg: Qt.rgba(base0E.r, base0E.g, base0E.b, 0.15)
readonly property color errorColor: base08
readonly property color greenColor: base0B
readonly property color redColor: base08
// Alternative semantic aliases for convenience
readonly property color background: base00
readonly property color panelBackground: base01
readonly property color selection: base02
readonly property color border: base03
readonly property color secondaryText: base04
readonly property color primaryText: base05
readonly property color brightText: base06
readonly property color brightestText: base07
readonly property color error: base08
readonly property color warning: base09
readonly property color highlight: base0A
readonly property color success: base0B
readonly property color info: base0C
readonly property color primary: base0D
readonly property color accent: base0E
readonly property color special: base0F
// UI styling constants
readonly property real borderWidth: 9
readonly property real cornerRadius: 20
// Color utility functions
function withOpacity(color, opacity) {
return Qt.rgba(color.r, color.g, color.b, opacity);
}
function withHighlight(color) {
return Qt.rgba(color.r, color.g, color.b, 0.15);
}
// Theme management functions
function setTheme(themeId) {
if (themes[themeId]) {
const previousThemeId = Settings.currentTheme;
Settings.currentTheme = themeId;
// Check if switching between matugen light/dark modes
if (themeId.startsWith("matugen_") && previousThemeId && previousThemeId.startsWith("matugen_")) {
const newMode = themeId.includes("_light") ? "light" : "dark";
const oldMode = previousThemeId.includes("_light") ? "light" : "dark";
if (newMode !== oldMode) {
console.log(`🎨 Switching matugen from ${oldMode} to ${newMode} mode`);
}
}
// Auto-update accents for Matugen themes
if (themeId.startsWith("matugen_")) {
updateMatugenAccents();
}
Settings.saveSettings();
return true;
}
return false;
}
// Auto-update accent colors when using Matugen theme
function updateMatugenAccents() {
if (MatugenManager.isAvailable() && MatugenManager.hasColors) {
// Get colors from the raw matugen palette
const rawColors = MatugenManager.rawColors;
// Use primary for both dark and light themes - it's generated appropriately by matugen
const accent = rawColors.primary;
// Debug log the colors we're using
console.log("Raw colors available:", Object.keys(rawColors));
console.log("Selected accent for both themes:", accent);
// Update custom accents - use the same accent for both
setCustomAccent(accent, accent);
// Enable custom accents for Matugen theme
Settings.useCustomAccent = true;
Settings.saveSettings();
console.log("Auto-updated Matugen accents from service:", accent);
} else {
console.log("MatugenManager service not available or no colors loaded yet");
}
}
function getThemeList() {
return Object.keys(themes).map(function (key) {
return {
id: key,
name: themes[key].name,
type: themes[key].type
};
});
}
function getDarkThemes() {
return getThemeList().filter(function (theme) {
return theme.type === "dark";
});
}
function getLightThemes() {
return getThemeList().filter(function (theme) {
return theme.type === "light";
});
}
function setCustomAccent(darkColor, lightColor) {
customDarkAccent = darkColor;
customLightAccent = lightColor;
Settings.customDarkAccent = darkColor;
Settings.customLightAccent = lightColor;
Settings.saveSettings();
}
function toggleCustomAccent() {
Settings.useCustomAccent = !Settings.useCustomAccent;
Settings.saveSettings();
}
// Legacy function for backwards compatibility
function toggleTheme() {
// Switch between dark and light variants of current theme family
var currentFamily = currentThemeId.replace(/_dark|_light/, "");
var newThemeId = currentTheme.type === "dark" ? currentFamily + "_light" : currentFamily + "_dark";
// If the opposite variant doesn't exist, switch to oxocarbon
if (!themes[newThemeId]) {
newThemeId = currentTheme.type === "dark" ? "oxocarbon_light" : "oxocarbon_dark";
}
setTheme(newThemeId);
}
}

View File

@@ -0,0 +1,76 @@
pragma Singleton
import QtQuick
QtObject {
readonly property var dark: ({
name: "Catppuccin Mocha",
type: "dark",
base00: "#1e1e2e" // Base
,
base01: "#181825" // Mantle
,
base02: "#313244" // Surface0
,
base03: "#45475a" // Surface1
,
base04: "#585b70" // Surface2
,
base05: "#cdd6f4" // Text
,
base06: "#f5e0dc" // Rosewater
,
base07: "#b4befe" // Lavender
,
base08: "#f38ba8" // Red
,
base09: "#fab387" // Peach
,
base0A: "#f9e2af" // Yellow
,
base0B: "#a6e3a1" // Green
,
base0C: "#94e2d5" // Teal
,
base0D: "#89b4fa" // Blue
,
base0E: "#cba6f7" // Mauve
,
base0F: "#f2cdcd" // Flamingo
})
readonly property var light: ({
name: "Catppuccin Latte",
type: "light",
base00: "#eff1f5" // Base
,
base01: "#e6e9ef" // Mantle
,
base02: "#ccd0da" // Surface0
,
base03: "#bcc0cc" // Surface1
,
base04: "#acb0be" // Surface2
,
base05: "#4c4f69" // Text
,
base06: "#dc8a78" // Rosewater
,
base07: "#7287fd" // Lavender
,
base08: "#d20f39" // Red
,
base09: "#fe640b" // Peach
,
base0A: "#df8e1d" // Yellow
,
base0B: "#40a02b" // Green
,
base0C: "#179299" // Teal
,
base0D: "#1e66f5" // Blue
,
base0E: "#8839ef" // Mauve
,
base0F: "#dd7878" // Flamingo
})
}

View File

@@ -0,0 +1,76 @@
pragma Singleton
import QtQuick
QtObject {
readonly property var dark: ({
name: "Dracula",
type: "dark",
base00: "#282a36" // Background
,
base01: "#44475a" // Current line
,
base02: "#565761" // Selection
,
base03: "#6272a4" // Comment
,
base04: "#6272a4" // Dark foreground
,
base05: "#f8f8f2" // Foreground
,
base06: "#f8f8f2" // Light foreground
,
base07: "#ffffff" // Light background
,
base08: "#ff5555" // Red
,
base09: "#ffb86c" // Orange
,
base0A: "#f1fa8c" // Yellow
,
base0B: "#50fa7b" // Green
,
base0C: "#8be9fd" // Cyan
,
base0D: "#bd93f9" // Blue
,
base0E: "#ff79c6" // Magenta
,
base0F: "#ffb86c" // Orange
})
readonly property var light: ({
name: "Dracula Light",
type: "light",
base00: "#f8f8f2" // Light background
,
base01: "#ffffff" // Lighter background
,
base02: "#e5e5e5" // Selection
,
base03: "#bfbfbf" // Comment
,
base04: "#6272a4" // Dark foreground
,
base05: "#282a36" // Dark text
,
base06: "#21222c" // Darker text
,
base07: "#191a21" // Darkest
,
base08: "#e74c3c" // Red (adjusted for light)
,
base09: "#f39c12" // Orange
,
base0A: "#f1c40f" // Yellow
,
base0B: "#27ae60" // Green
,
base0C: "#17a2b8" // Cyan
,
base0D: "#6c7ce0" // Blue
,
base0E: "#e91e63" // Magenta
,
base0F: "#f39c12" // Orange
})
}

View File

@@ -0,0 +1,76 @@
pragma Singleton
import QtQuick
QtObject {
readonly property var dark: ({
name: "Gruvbox Dark",
type: "dark",
base00: "#282828" // Dark background
,
base01: "#3c3836" // Dark1
,
base02: "#504945" // Dark2
,
base03: "#665c54" // Dark3
,
base04: "#bdae93" // Light4
,
base05: "#d5c4a1" // Light3
,
base06: "#ebdbb2" // Light2
,
base07: "#fbf1c7" // Light1
,
base08: "#fb4934" // Red
,
base09: "#fe8019" // Orange
,
base0A: "#fabd2f" // Yellow
,
base0B: "#b8bb26" // Green
,
base0C: "#8ec07c" // Cyan
,
base0D: "#83a598" // Blue
,
base0E: "#d3869b" // Purple
,
base0F: "#d65d0e" // Brown
})
readonly property var light: ({
name: "Gruvbox Light",
type: "light",
base00: "#fbf1c7" // Light background
,
base01: "#ebdbb2" // Light1
,
base02: "#d5c4a1" // Light2
,
base03: "#bdae93" // Light3
,
base04: "#665c54" // Dark3
,
base05: "#504945" // Dark2
,
base06: "#3c3836" // Dark1
,
base07: "#282828" // Dark background
,
base08: "#cc241d" // Red
,
base09: "#d65d0e" // Orange
,
base0A: "#d79921" // Yellow
,
base0B: "#98971a" // Green
,
base0C: "#689d6a" // Cyan
,
base0D: "#458588" // Blue
,
base0E: "#b16286" // Purple
,
base0F: "#d65d0e" // Brown
})
}

View File

@@ -0,0 +1,141 @@
pragma Singleton
import QtQuick
QtObject {
// Reference to the MatugenService
property var matugenService: null
// Debug helper to check service status
function debugServiceStatus() {
console.log("🔍 Debug: matugenService =", matugenService);
console.log("🔍 Debug: matugenService.isLoaded =", matugenService ? matugenService.isLoaded : "N/A");
console.log("🔍 Debug: matugenService.colorVersion =", matugenService ? matugenService.colorVersion : "N/A");
console.log("🔍 Debug: condition result =", (matugenService && matugenService.isLoaded && matugenService.colorVersion >= 0));
if (matugenService && matugenService.colors) {
console.log("🔍 Debug: service.colors.dark =", JSON.stringify(matugenService.colors.dark));
}
}
// Map matugen colors to base16 scheme - using the service when available
// The colorVersion dependency forces re-evaluation when colors update
readonly property var dark: {
debugServiceStatus();
if (matugenService && matugenService.isLoaded && matugenService.colorVersion >= 0) {
// Use service colors if available, or generate fallback if we have light colors
return matugenService.colors.dark || {
name: "Matugen Dark (Generated from Light)",
type: "dark",
// If we only have light colors, create dark fallback
base00: "#141311",
base01: "#1c1c19",
base02: "#20201d",
base03: "#2a2a27",
base04: "#c9c7ba",
base05: "#e5e2de",
base06: "#31302e",
base07: "#e5e2de",
base08: "#ffb4ab",
base09: "#b5ccb9",
base0A: "#e4e5c1",
base0B: "#c8c7b7",
base0C: "#c8c9a6",
base0D: "#c8c9a6",
base0E: "#47483b",
base0F: "#000000"
};
} else {
return {
name: "Matugen Dark",
type: "dark",
// Updated fallback colors to match current quickshell-colors.qml
base00: "#141311",
base01: "#1c1c19",
base02: "#20201d",
base03: "#2a2a27",
base04: "#c9c7ba",
base05: "#e5e2de",
base06: "#31302e",
base07: "#e5e2de",
base08: "#ffb4ab",
base09: "#b5ccb9",
base0A: "#e4e5c1",
base0B: "#c8c7b7",
base0C: "#c8c9a6",
base0D: "#c8c9a6",
base0E: "#47483b",
base0F: "#000000"
};
}
}
readonly property var light: {
if (matugenService && matugenService.isLoaded && matugenService.colorVersion >= 0) {
// Use service colors if available, or generate fallback if we have dark colors
return matugenService.colors.light || {
name: "Matugen Light (Generated from Dark)",
type: "light",
// If we only have dark colors, create light fallback
base00: "#ffffff",
base01: "#f5f5f5",
base02: "#e8e8e8",
base03: "#d0d0d0",
base04: "#666666",
base05: "#1a1a1a",
base06: "#000000",
base07: "#ffffff",
base08: "#d32f2f",
base09: "#7b1fa2",
base0A: "#f57c00",
base0B: "#388e3c",
base0C: "#0097a7",
base0D: "#1976d2",
base0E: "#5e35b1",
base0F: "#000000"
};
} else {
return {
name: "Matugen Light",
type: "light",
// Updated fallback colors based on current colors
base00: "#ffffff",
base01: "#f5f5f5",
base02: "#e8e8e8",
base03: "#d0d0d0",
base04: "#666666",
base05: "#1a1a1a",
base06: "#000000",
base07: "#ffffff",
base08: "#d32f2f",
base09: "#7b1fa2",
base0A: "#f57c00",
base0B: "#388e3c",
base0C: "#0097a7",
base0D: "#1976d2",
base0E: "#5e35b1",
base0F: "#000000"
};
}
}
// Direct access to primary colors for accent updates
readonly property color primary: (matugenService && matugenService.getColor && matugenService.colorVersion >= 0) ? matugenService.getColor("primary") || "#c8c9a6" : "#c8c9a6"
readonly property color on_primary: (matugenService && matugenService.getColor && matugenService.colorVersion >= 0) ? matugenService.getColor("on_primary") || "#303219" : "#303219"
// Function to set the service reference
function setMatugenService(service) {
matugenService = service;
console.log("🔌 MatugenService connected to theme:", service);
// Connect to service signals for automatic updates
if (service) {
service.matugenColorsUpdated.connect(function () {
console.log("🎨 Matugen colors updated in theme (version " + service.colorVersion + ")");
debugServiceStatus();
});
}
}
Component.onCompleted: {
console.log("Matugen theme loaded, waiting for MatugenService connection");
}
}

View File

@@ -0,0 +1,76 @@
pragma Singleton
import QtQuick
QtObject {
readonly property var dark: ({
name: "Oxocarbon Dark",
type: "dark",
base00: "#161616" // OLED-friendly background
,
base01: "#262626" // Surface 1
,
base02: "#393939" // Surface 2
,
base03: "#525252" // Surface 3
,
base04: "#6f6f6f" // Text secondary
,
base05: "#c6c6c6" // Text primary
,
base06: "#e0e0e0" // Text on color
,
base07: "#f4f4f4" // Text inverse
,
base08: "#ff7eb6" // Red (pink)
,
base09: "#ee5396" // Magenta
,
base0A: "#42be65" // Green
,
base0B: "#be95ff" // Purple
,
base0C: "#3ddbd9" // Cyan
,
base0D: "#78a9ff" // Blue
,
base0E: "#be95ff" // Purple (accent)
,
base0F: "#08bdba" // Teal
})
readonly property var light: ({
name: "Oxocarbon Light",
type: "light",
base00: "#f4f4f4" // Light background
,
base01: "#ffffff" // Surface 1
,
base02: "#e0e0e0" // Surface 2
,
base03: "#c6c6c6" // Surface 3
,
base04: "#525252" // Text secondary
,
base05: "#262626" // Text primary
,
base06: "#161616" // Text on color
,
base07: "#000000" // Text inverse
,
base08: "#da1e28" // Red
,
base09: "#d12771" // Magenta
,
base0A: "#198038" // Green
,
base0B: "#8a3ffc" // Purple
,
base0C: "#007d79" // Cyan
,
base0D: "#0f62fe" // Blue
,
base0E: "#8a3ffc" // Purple (accent)
,
base0F: "#005d5d" // Teal
})
}

View File

@@ -0,0 +1,60 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
readonly property color background: "#13140c"
readonly property color error: "#ffb4ab"
readonly property color error_container: "#93000a"
readonly property color inverse_on_surface: "#313128"
readonly property color inverse_primary: "#5d631c"
readonly property color inverse_surface: "#e5e3d6"
readonly property color on_background: "#e5e3d6"
readonly property color on_error: "#690005"
readonly property color on_error_container: "#ffdad6"
readonly property color on_primary: "#2f3300"
readonly property color on_primary_container: "#e2e993"
readonly property color on_primary_fixed: "#1b1d00"
readonly property color on_primary_fixed_variant: "#454b03"
readonly property color on_secondary: "#30321a"
readonly property color on_secondary_container: "#e4e5c1"
readonly property color on_secondary_fixed: "#1b1d07"
readonly property color on_secondary_fixed_variant: "#47492e"
readonly property color on_surface: "#e5e3d6"
readonly property color on_surface_variant: "#c8c7b7"
readonly property color on_tertiary: "#07372c"
readonly property color on_tertiary_container: "#beecdc"
readonly property color on_tertiary_fixed: "#002019"
readonly property color on_tertiary_fixed_variant: "#234e42"
readonly property color outline: "#929182"
readonly property color outline_variant: "#47483b"
readonly property color primary: "#c5cc7a"
readonly property color primary_container: "#454b03"
readonly property color primary_fixed: "#e2e993"
readonly property color primary_fixed_dim: "#c5cc7a"
readonly property color scrim: "#000000"
readonly property color secondary: "#c8c9a6"
readonly property color secondary_container: "#47492e"
readonly property color secondary_fixed: "#e4e5c1"
readonly property color secondary_fixed_dim: "#c8c9a6"
readonly property color shadow: "#000000"
readonly property color surface: "#13140c"
readonly property color surface_bright: "#3a3a31"
readonly property color surface_container: "#202018"
readonly property color surface_container_high: "#2a2a22"
readonly property color surface_container_highest: "#35352c"
readonly property color surface_container_low: "#1c1c14"
readonly property color surface_container_lowest: "#0e0f08"
readonly property color surface_dim: "#13140c"
readonly property color surface_tint: "#c5cc7a"
readonly property color surface_variant: "#47483b"
readonly property color tertiary: "#a3d0c0"
readonly property color tertiary_container: "#234e42"
readonly property color tertiary_fixed: "#beecdc"
readonly property color tertiary_fixed_dim: "#a3d0c0"
function withAlpha(color: color, alpha: real): color {
return Qt.rgba(color.r, color.g, color.b, alpha);
}
}

View File

@@ -0,0 +1,7 @@
{
"isDarkTheme": true,
"avatarSource": "/home/imxyy/Pictures/icon.jpg",
"weatherLocation": "Dinslaken",
"displayTime": 6000,
"videoPath": "~/Videos/"
}

View File

@@ -0,0 +1,35 @@
import QtQuick
import QtQuick.Effects
import "root:/Data" as Data
import "root:/Widgets/System" as System
import "root:/Widgets/Calendar" as Calendar
// Vertical sidebar layout
Rectangle {
id: bar
// Clean bar background
color: Data.ThemeManager.bgColor
// Workspace indicator at top
System.NiriWorkspaces {
id: workspaceIndicator
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
horizontalCenterOffset: Data.Settings.borderWidth / 2
topMargin: 20
}
}
// Clock at bottom
Calendar.Clock {
id: clockWidget
anchors {
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
horizontalCenterOffset: Data.Settings.borderWidth / 2
bottomMargin: 20
}
}
}

View File

@@ -0,0 +1,575 @@
import QtQuick
import QtQuick.Shapes
import Qt5Compat.GraphicalEffects
import QtQuick.Effects
import "root:/Data" as Data
// Screen border with shadow effects
Shape {
id: borderShape
// Border dimensions
property real borderWidth: Data.Settings.borderWidth
property real radius: Data.Settings.cornerRadius
property real innerX: borderWidth
property real innerY: borderWidth
property real innerWidth: borderShape.width - (borderWidth * 2)
property real innerHeight: borderShape.height - (borderWidth * 2)
// Widget references for shadow positioning
property var workspaceIndicator: null
property var volumeOSD: null
property var clockWidget: null
// Initialization state to prevent ShaderEffect warnings
property bool effectsReady: false
// Burst effect properties - controlled by workspace indicator
property real masterProgress: workspaceIndicator ? workspaceIndicator.masterProgress : 0.0
property bool effectsActive: workspaceIndicator ? workspaceIndicator.effectsActive : false
property color effectColor: workspaceIndicator ? workspaceIndicator.effectColor : Data.ThemeManager.accent
// Delay graphics effects until component is fully loaded
Timer {
id: initTimer
interval: 100
running: true
onTriggered: borderShape.effectsReady = true
}
// Burst effect overlays (DISABLED - using unified overlay)
Item {
id: burstEffects
anchors.fill: parent
visible: false // Disabled in favor of unified overlay
z: 5
}
// Individual widget shadows (positioned separately)
// Workspace indicator shadow
Shape {
id: workspaceDropShadow
visible: borderShape.workspaceIndicator !== null
x: borderShape.workspaceIndicator ? borderShape.workspaceIndicator.x : 0 // Exact match
y: borderShape.workspaceIndicator ? borderShape.workspaceIndicator.y : 0
width: borderShape.workspaceIndicator ? borderShape.workspaceIndicator.width : 0 // Exact match
height: borderShape.workspaceIndicator ? borderShape.workspaceIndicator.height : 0
z: -1
layer.enabled: borderShape.workspaceIndicator !== null
layer.effect: DropShadow {
transparentBorder: true
horizontalOffset: 1
verticalOffset: 1
radius: 12 + (effectsActive && Data.Settings.workspaceGlowEnabled ? Math.sin(masterProgress * Math.PI) * 4 : 0)
samples: 25
color: {
if (!effectsActive)
return Qt.rgba(0, 0, 0, 0.4);
if (!Data.Settings.workspaceGlowEnabled)
return Qt.rgba(0, 0, 0, 0.4);
// Use accent color directly with reduced intensity
const intensity = Math.sin(masterProgress * Math.PI) * 0.4;
return Qt.rgba(effectColor.r * intensity + 0.08, effectColor.g * intensity + 0.08, effectColor.b * intensity + 0.08, 0.4 + intensity * 0.2);
}
cached: true
spread: 0.2 + (effectsActive && Data.Settings.workspaceGlowEnabled ? Math.sin(masterProgress * Math.PI) * 0.15 : 0)
}
ShapePath {
strokeWidth: 0
fillColor: "black"
startX: 12
startY: 0
// Right side - standard rounded corners
PathLine {
x: workspaceDropShadow.width - 16
y: 0
}
PathArc {
x: workspaceDropShadow.width
y: 16
radiusX: 16
radiusY: 16
direction: PathArc.Clockwise
}
PathLine {
x: workspaceDropShadow.width
y: workspaceDropShadow.height - 16
}
PathArc {
x: workspaceDropShadow.width - 16
y: workspaceDropShadow.height
radiusX: 16
radiusY: 16
direction: PathArc.Clockwise
}
PathLine {
x: 12
y: workspaceDropShadow.height
}
// Left side - concave curves for border integration
PathLine {
x: 0
y: workspaceDropShadow.height - 12
}
PathArc {
x: 12
y: workspaceDropShadow.height - 24
radiusX: 12
radiusY: 12
direction: PathArc.Clockwise
}
PathLine {
x: 12
y: 24
}
PathArc {
x: 0
y: 12
radiusX: 12
radiusY: 12
direction: PathArc.Clockwise
}
PathLine {
x: 12
y: 0
}
}
}
// Volume OSD shadow
Rectangle {
id: volumeOsdDropShadow
visible: borderShape.volumeOSD !== null && borderShape.volumeOSD.visible
opacity: borderShape.volumeOSD ? borderShape.volumeOSD.opacity : 0
x: parent.width - 45
y: (parent.height - 250) / 2
width: 45
height: 250
color: "black"
topLeftRadius: 20
bottomLeftRadius: 20
topRightRadius: 0
bottomRightRadius: 0
z: -1
// Sync opacity animations with volume OSD
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
layer.enabled: borderShape.volumeOSD !== null
layer.effect: DropShadow {
transparentBorder: true
horizontalOffset: -1
verticalOffset: 1
radius: 12 // Much more subtle
samples: 25
color: Qt.rgba(0, 0, 0, 0.4) // Very light shadow
cached: false
spread: 0.2 // Minimal spread
}
}
// Clock shadow
Rectangle {
id: clockDropShadow
visible: borderShape.clockWidget !== null
x: borderShape.clockWidget ? borderShape.clockWidget.x : 0
y: borderShape.clockWidget ? borderShape.clockWidget.y : 0
width: borderShape.clockWidget ? borderShape.clockWidget.width : 0
height: borderShape.clockWidget ? borderShape.clockWidget.height : 0
color: "black"
topLeftRadius: 0
topRightRadius: borderShape.clockWidget ? borderShape.clockWidget.height / 2 : 16
bottomLeftRadius: 0
bottomRightRadius: 0
z: -2 // Lower z-index to render behind border corners
layer.enabled: borderShape.clockWidget !== null
layer.effect: DropShadow {
transparentBorder: true
horizontalOffset: 1
verticalOffset: -1
radius: 12 // Much more subtle
samples: 25
color: Qt.rgba(0, 0, 0, 0.4) // Very light shadow
cached: false
spread: 0.2 // Minimal spread
}
}
// Shadow rendering source (hidden)
Item {
id: shadowSource
anchors.fill: parent
visible: false
Shape {
id: borderShadowShape
anchors.fill: parent
layer.enabled: true
layer.samples: 4
ShapePath {
fillColor: "black"
strokeWidth: 0
fillRule: ShapePath.OddEvenFill
// Outer rectangle (full screen)
PathMove {
x: 0
y: 0
}
PathLine {
x: shadowSource.width
y: 0
}
PathLine {
x: shadowSource.width
y: shadowSource.height
}
PathLine {
x: 0
y: shadowSource.height
}
PathLine {
x: 0
y: 0
}
// Inner rounded cutout creates border
PathMove {
x: borderShape.innerX + borderShape.radius
y: borderShape.innerY
}
PathLine {
x: borderShape.innerX + borderShape.innerWidth - borderShape.radius
y: borderShape.innerY
}
PathArc {
x: borderShape.innerX + borderShape.innerWidth
y: borderShape.innerY + borderShape.radius
radiusX: borderShape.radius
radiusY: borderShape.radius
direction: PathArc.Clockwise
}
PathLine {
x: borderShape.innerX + borderShape.innerWidth
y: borderShape.innerY + borderShape.innerHeight - borderShape.radius
}
PathArc {
x: borderShape.innerX + borderShape.innerWidth - borderShape.radius
y: borderShape.innerY + borderShape.innerHeight
radiusX: borderShape.radius
radiusY: borderShape.radius
direction: PathArc.Clockwise
}
PathLine {
x: borderShape.innerX + borderShape.radius
y: borderShape.innerY + borderShape.innerHeight
}
PathArc {
x: borderShape.innerX
y: borderShape.innerY + borderShape.innerHeight - borderShape.radius
radiusX: borderShape.radius
radiusY: borderShape.radius
direction: PathArc.Clockwise
}
PathLine {
x: borderShape.innerX
y: borderShape.innerY + borderShape.radius
}
PathArc {
x: borderShape.innerX + borderShape.radius
y: borderShape.innerY
radiusX: borderShape.radius
radiusY: borderShape.radius
direction: PathArc.Clockwise
}
}
}
// Workspace indicator shadow with concave curves
Shape {
id: workspaceShadowShape
visible: borderShape.workspaceIndicator !== null
x: borderShape.workspaceIndicator ? borderShape.workspaceIndicator.x : 0 // Exact match
y: borderShape.workspaceIndicator ? borderShape.workspaceIndicator.y : 0
width: borderShape.workspaceIndicator ? borderShape.workspaceIndicator.width : 0 // Exact match
height: borderShape.workspaceIndicator ? borderShape.workspaceIndicator.height : 0
preferredRendererType: Shape.CurveRenderer
layer.enabled: borderShape.workspaceIndicator !== null
layer.samples: 8
layer.effect: DropShadow {
transparentBorder: true
horizontalOffset: 2
verticalOffset: 3
radius: 25 + (effectsActive && Data.Settings.workspaceGlowEnabled ? Math.sin(masterProgress * Math.PI) * 6 : 0)
samples: 40
color: {
if (!effectsActive)
return Qt.rgba(0, 0, 0, 0.8);
if (!Data.Settings.workspaceGlowEnabled)
return Qt.rgba(0, 0, 0, 0.8);
// Accent color glow with reduced intensity
const intensity = Math.sin(masterProgress * Math.PI) * 0.3;
return Qt.rgba(effectColor.r * intensity + 0.1, effectColor.g * intensity + 0.1, effectColor.b * intensity + 0.1, 0.6 + intensity * 0.15);
}
cached: false
spread: 0.5 + (effectsActive && Data.Settings.workspaceGlowEnabled ? Math.sin(masterProgress * Math.PI) * 0.2 : 0)
}
ShapePath {
strokeWidth: 0
fillColor: "black"
strokeColor: "black"
startX: 12
startY: 0
// Right side - standard rounded corners
PathLine {
x: workspaceShadowShape.width - 16
y: 0
}
PathArc {
x: workspaceShadowShape.width
y: 16
radiusX: 16
radiusY: 16
direction: PathArc.Clockwise
}
PathLine {
x: workspaceShadowShape.width
y: workspaceShadowShape.height - 16
}
PathArc {
x: workspaceShadowShape.width - 16
y: workspaceShadowShape.height
radiusX: 16
radiusY: 16
direction: PathArc.Clockwise
}
PathLine {
x: 12
y: workspaceShadowShape.height
}
// Left side - concave curves for border integration
PathLine {
x: 0
y: workspaceShadowShape.height - 12
}
PathArc {
x: 12
y: workspaceShadowShape.height - 24
radiusX: 12
radiusY: 12
direction: PathArc.Clockwise
}
PathLine {
x: 12
y: 24
}
PathArc {
x: 0
y: 12
radiusX: 12
radiusY: 12
direction: PathArc.Clockwise
}
PathLine {
x: 12
y: 0
}
}
}
// Volume OSD shadow
Rectangle {
id: volumeOsdShadowShape
visible: borderShape.volumeOSD !== null && borderShape.volumeOSD.visible
x: shadowSource.width - 45
y: (shadowSource.height - 250) / 2
width: 45
height: 250
color: "black"
topLeftRadius: 20
bottomLeftRadius: 20
topRightRadius: 0
bottomRightRadius: 0
layer.enabled: borderShape.volumeOSD !== null && borderShape.volumeOSD.visible
layer.effect: DropShadow {
transparentBorder: true
horizontalOffset: -2 // Shadow to the left for right-side widget
verticalOffset: 3
radius: 25
samples: 40
color: Qt.rgba(0, 0, 0, 0.8)
cached: false
spread: 0.5
}
}
// Clock shadow
Rectangle {
id: clockShadowShape
visible: borderShape.clockWidget !== null
x: borderShape.clockWidget ? borderShape.clockWidget.x : 0
y: borderShape.clockWidget ? borderShape.clockWidget.y : 0
width: borderShape.clockWidget ? borderShape.clockWidget.width : 0
height: borderShape.clockWidget ? borderShape.clockWidget.height : 0
color: "black"
topLeftRadius: 0
topRightRadius: borderShape.clockWidget ? borderShape.clockWidget.height / 2 : 16
bottomLeftRadius: 0
bottomRightRadius: 0
layer.enabled: borderShape.clockWidget !== null
layer.effect: DropShadow {
transparentBorder: true
horizontalOffset: 2
verticalOffset: -2 // Shadow upward for bottom widget
radius: 25
samples: 40
color: Qt.rgba(0, 0, 0, 0.8)
cached: false
spread: 0.5
}
}
}
// Apply shadow effect to entire border shape
layer.enabled: true
layer.samples: 8
layer.smooth: true
layer.effect: DropShadow {
transparentBorder: true
horizontalOffset: 1
verticalOffset: 2
radius: 30 // Slightly less dramatic
samples: 45
color: Qt.rgba(0, 0, 0, 0.75) // A bit lighter
cached: false
spread: 0.5 // Less spread
}
// Main border shape
ShapePath {
fillColor: Data.ThemeManager.bgColor
strokeWidth: 0
fillRule: ShapePath.OddEvenFill
// Outer rectangle
PathMove {
x: 0
y: 0
}
PathLine {
x: borderShape.width
y: 0
}
PathLine {
x: borderShape.width
y: borderShape.height
}
PathLine {
x: 0
y: borderShape.height
}
PathLine {
x: 0
y: 0
}
// Inner rounded cutout
PathMove {
x: borderShape.innerX + borderShape.radius
y: borderShape.innerY
}
PathLine {
x: borderShape.innerX + borderShape.innerWidth - borderShape.radius
y: borderShape.innerY
}
PathArc {
x: borderShape.innerX + borderShape.innerWidth
y: borderShape.innerY + borderShape.radius
radiusX: borderShape.radius
radiusY: borderShape.radius
direction: PathArc.Clockwise
}
PathLine {
x: borderShape.innerX + borderShape.innerWidth
y: borderShape.innerY + borderShape.innerHeight - borderShape.radius
}
PathArc {
x: borderShape.innerX + borderShape.innerWidth - borderShape.radius
y: borderShape.innerY + borderShape.innerHeight
radiusX: borderShape.radius
radiusY: borderShape.radius
direction: PathArc.Clockwise
}
PathLine {
x: borderShape.innerX + borderShape.radius
y: borderShape.innerY + borderShape.innerHeight
}
PathArc {
x: borderShape.innerX
y: borderShape.innerY + borderShape.innerHeight - borderShape.radius
radiusX: borderShape.radius
radiusY: borderShape.radius
direction: PathArc.Clockwise
}
PathLine {
x: borderShape.innerX
y: borderShape.innerY + borderShape.radius
}
PathArc {
x: borderShape.innerX + borderShape.radius
y: borderShape.innerY
radiusX: borderShape.radius
radiusY: borderShape.radius
direction: PathArc.Clockwise
}
}
}

View File

@@ -0,0 +1,302 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
import Quickshell.Wayland
import Qt5Compat.GraphicalEffects
import "root:/Data" as Data
import "root:/Widgets/System" as System
import "root:/Core" as Core
import "root:/Widgets" as Widgets
import "root:/Widgets/Notifications" as Notifications
import "root:/Widgets/ControlPanel" as ControlPanel
// Desktop with borders and UI widgets
Scope {
id: desktop
property var shell
property var notificationService
// Desktop UI layer per screen
Variants {
model: Quickshell.screens
PanelWindow {
required property var modelData
screen: modelData
implicitWidth: Screen.width
implicitHeight: Screen.height
color: "transparent"
exclusiveZone: 0
WlrLayershell.namespace: "quickshell-desktop"
// Interactive mask for workspace indicator only
mask: Region {
item: workspaceIndicator
}
anchors {
top: true
left: true
bottom: true
right: true
}
// Workspace indicator at left border
System.NiriWorkspaces {
id: workspaceIndicator
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: Data.Settings.borderWidth
}
z: 10
width: 32
}
// Volume OSD at right border (primary screen only)
System.OSD {
id: osd
shell: desktop.shell
visible: modelData === Quickshell.primaryScreen
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: Data.Settings.borderWidth
}
z: 10
}
// Widget shadows (positioned behind border for proper layering)
// Workspace indicator shadow
Rectangle {
id: workspaceShadow
visible: workspaceIndicator !== null
x: workspaceIndicator.x
y: workspaceIndicator.y
width: workspaceIndicator.width
height: workspaceIndicator.height
color: "black"
radius: 16
z: -10 // Behind border
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true
horizontalOffset: 2
verticalOffset: 2
radius: 8 + (workspaceIndicator.effectsActive && Data.Settings.workspaceGlowEnabled ? Math.sin(workspaceIndicator.masterProgress * Math.PI) * 3 : 0)
samples: 17
color: {
if (!workspaceIndicator.effectsActive)
return Qt.rgba(0, 0, 0, 0.3);
if (!Data.Settings.workspaceGlowEnabled)
return Qt.rgba(0, 0, 0, 0.3);
// Use accent color glow with reduced intensity
const intensity = Math.sin(workspaceIndicator.masterProgress * Math.PI) * 0.3;
return Qt.rgba(workspaceIndicator.effectColor.r * intensity + 0.05, workspaceIndicator.effectColor.g * intensity + 0.05, workspaceIndicator.effectColor.b * intensity + 0.05, 0.3 + intensity * 0.15);
}
cached: true
spread: 0.1 + (workspaceIndicator.effectsActive && Data.Settings.workspaceGlowEnabled ? Math.sin(workspaceIndicator.masterProgress * Math.PI) * 0.1 : 0)
}
}
// Clock widget shadow
Rectangle {
id: clockShadow
visible: clockWidget !== null
x: clockWidget.x
y: clockWidget.y
width: clockWidget.width
height: clockWidget.height
color: "black"
topLeftRadius: 0
topRightRadius: clockWidget.height / 2
bottomLeftRadius: 0
bottomRightRadius: 0
z: -10 // Behind border
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true
horizontalOffset: 1
verticalOffset: -1
radius: 8
samples: 17
color: Qt.rgba(0, 0, 0, 0.3)
cached: true
spread: 0.1
}
}
// Border background with shadow
Border {
id: screenBorder
anchors.fill: parent
workspaceIndicator: workspaceIndicator
volumeOSD: volumeOsd
clockWidget: clockWidget
z: -5 // Behind UI elements to prevent shadow from covering control panel
}
// Unified Wave Overlay - simple burst effect
Item {
id: waveOverlay
anchors.fill: parent
visible: workspaceIndicator.effectsActive && Data.Settings.workspaceBurstEnabled
z: 15
property real progress: workspaceIndicator.masterProgress
property color waveColor: workspaceIndicator.effectColor
// Workspace indicator burst effects
Item {
x: workspaceIndicator.x
y: workspaceIndicator.y
width: workspaceIndicator.width
height: workspaceIndicator.height
// Expanding pill burst - positioned at current workspace index (mimics pill shape)
Rectangle {
x: parent.width / 2 - width / 2
y: {
// Find current workspace index directly from currentWorkspace
let focusedIndex = 0;
for (let i = 0; i < workspaceIndicator.workspaces.count; i++) {
const workspace = workspaceIndicator.workspaces.get(i);
if (workspace && workspace.id === workspaceIndicator.currentWorkspace) {
focusedIndex = i;
break;
}
}
// Calculate position accounting for Column centering and pill sizes
let cumulativeHeight = 0;
for (let i = 0; i < focusedIndex; i++) {
const ws = workspaceIndicator.workspaces.get(i);
cumulativeHeight += (ws && ws.isFocused ? 36 : 22) + 6; // pill height + spacing
}
// Current pill height
const currentWs = workspaceIndicator.workspaces.get(focusedIndex);
const currentPillHeight = (currentWs && currentWs.isFocused ? 36 : 22);
// Column is centered, so start from center and calculate offset
const columnHeight = parent.height - 24; // Total available height minus padding
const columnTop = 12; // Top padding
return columnTop + cumulativeHeight + currentPillHeight / 2 - height / 2;
}
width: 20 + waveOverlay.progress * 30
height: 36 + waveOverlay.progress * 20 // Pill-like height
radius: width / 2 // Pill-like rounded shape
color: "transparent"
border.width: 2
border.color: Qt.rgba(waveOverlay.waveColor.r, waveOverlay.waveColor.g, waveOverlay.waveColor.b, 1.0 - waveOverlay.progress)
opacity: Math.max(0, 1.0 - waveOverlay.progress)
}
// Secondary expanding pill burst - positioned at current workspace index
Rectangle {
x: parent.width / 2 - width / 2
y: {
// Find current workspace index directly from currentWorkspace
let focusedIndex = 0;
for (let i = 0; i < workspaceIndicator.workspaces.count; i++) {
const workspace = workspaceIndicator.workspaces.get(i);
if (workspace && workspace.id === workspaceIndicator.currentWorkspace) {
focusedIndex = i;
break;
}
}
// Calculate position accounting for Column centering and pill sizes
let cumulativeHeight = 0;
for (let i = 0; i < focusedIndex; i++) {
const ws = workspaceIndicator.workspaces.get(i);
cumulativeHeight += (ws && ws.isFocused ? 36 : 22) + 6; // pill height + spacing
}
// Current pill height
const currentWs = workspaceIndicator.workspaces.get(focusedIndex);
const currentPillHeight = (currentWs && currentWs.isFocused ? 36 : 22);
// Column is centered, so start from center and calculate offset
const columnHeight = parent.height - 24; // Total available height minus padding
const columnTop = 12; // Top padding
return columnTop + cumulativeHeight + currentPillHeight / 2 - height / 2;
}
width: 18 + waveOverlay.progress * 45
height: 30 + waveOverlay.progress * 35 // Pill-like height
radius: width / 2 // Pill-like rounded shape
color: "transparent"
border.width: 1.5
border.color: Qt.rgba(waveOverlay.waveColor.r, waveOverlay.waveColor.g, waveOverlay.waveColor.b, 0.6)
opacity: Math.max(0, 0.8 - waveOverlay.progress * 1.2)
visible: waveOverlay.progress > 0.2
}
}
}
// Clock at bottom-left corner
Widgets.Clock {
id: clockWidget
anchors {
bottom: parent.bottom
left: parent.left
bottomMargin: Data.Settings.borderWidth
leftMargin: Data.Settings.borderWidth
}
z: 10
}
// Notification popups (primary screen only)
Notifications.Notification {
id: notificationPopup
visible: (modelData === (Quickshell.primaryScreen || Quickshell.screens[0])) && calculatedHeight > 20
anchors {
top: parent.top
right: parent.right
rightMargin: Data.Settings.borderWidth + 20
topMargin: 0
}
width: 420
height: calculatedHeight
shell: desktop.shell
notificationServer: desktop.notificationService ? desktop.notificationService.notificationServer : null
z: 15
Component.onCompleted: {
let targetScreen = Quickshell.primaryScreen || Quickshell.screens[0];
if (modelData === targetScreen) {
desktop.shell.notificationWindow = notificationPopup;
}
}
}
// UI overlay layer for modal components
Item {
id: uiLayer
anchors.fill: parent
z: 20
ControlPanel.ControlPanel {
id: controlPanelComponent
shell: desktop.shell
}
}
}
}
// Handle dynamic screen configuration changes
Connections {
target: Quickshell
function onScreensChanged() {
// Screen changes handled by Variants automatically
}
}
}

View File

@@ -0,0 +1,394 @@
import QtQuick
import Quickshell
import Quickshell.Io
// App launcher service - discovers and manages applications
Item {
id: appService
property var applications: []
property bool isLoading: false
// Categories for apps
property var categories: {
"AudioVideo": "🎵",
"Audio": "🎵",
"Video": "🎬",
"Development": "💻",
"Education": "📚",
"Game": "🎮",
"Graphics": "🎨",
"Network": "🌐",
"Office": "📄",
"Science": "🔬",
"Settings": "⚙️",
"System": "🔧",
"Utility": "🛠️",
"Other": "📦"
}
property string userName: ""
property string homeDirectory: ""
property bool userInfoLoaded: false
property var currentApp: ({})
property var pendingSearchPaths: []
Component.onCompleted: {
// First get user info, then load applications
loadUserInfo();
}
function loadUserInfo() {
userNameProcess.running = true;
}
Process {
id: userNameProcess
command: ["whoami"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
if (line.trim()) {
userName = line.trim();
}
}
}
onExited: {
// Now get home directory
homeDirProcess.running = true;
}
}
Process {
id: homeDirProcess
command: ["sh", "-c", "echo $HOME"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
if (line.trim()) {
homeDirectory = line.trim();
}
}
}
onExited: {
// Now we have user info, start loading applications
userInfoLoaded = true;
loadApplications();
}
}
function loadApplications() {
if (!userInfoLoaded) {
console.log("User info not loaded yet, skipping application scan");
return;
}
isLoading = true;
applications = [];
console.log("DEBUG: Starting application scan with user:", userName, "home:", homeDirectory);
// Comprehensive search paths for maximum Linux compatibility
appService.pendingSearchPaths = [
// User-specific locations (highest priority)
homeDirectory + "/.local/share/applications/",
// Standard FreeDesktop.org locations
"/usr/share/applications/", "/usr/local/share/applications/",
// Flatpak locations
"/var/lib/flatpak/exports/share/applications/", homeDirectory + "/.local/share/flatpak/exports/share/applications/",
// Snap locations
"/var/lib/snapd/desktop/applications/", "/snap/bin/",
// AppImage locations (common user directories)
homeDirectory + "/Applications/", homeDirectory + "/AppImages/",
// Distribution-specific paths
"/opt/*/share/applications/" // For manually installed software
, "/usr/share/applications/kde4/" // KDE4 legacy
,
// NixOS-specific (will be ignored on non-NixOS systems)
"/run/current-system/sw/share/applications/", "/etc/profiles/per-user/" + userName + "/share/applications/"];
console.log("DEBUG: Starting with essential paths:", JSON.stringify(appService.pendingSearchPaths));
// Add XDG and home-manager paths
getXdgDataDirs.running = true;
}
Process {
id: getXdgDataDirs
command: ["sh", "-c", "echo $XDG_DATA_DIRS"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
if (line.trim()) {
var xdgDirs = line.trim().split(":");
for (var i = 0; i < xdgDirs.length; i++) {
if (xdgDirs[i].trim()) {
var xdgPath = xdgDirs[i].trim() + "/applications/";
if (appService.pendingSearchPaths.indexOf(xdgPath) === -1) {
appService.pendingSearchPaths.push(xdgPath);
console.log("DEBUG: Added XDG path:", xdgPath);
}
}
}
}
}
}
onExited: {
// Now add home-manager path
getHomeManagerPaths.running = true;
}
}
Process {
id: getHomeManagerPaths
command: ["sh", "-c", "find /nix/store -maxdepth 1 -name '*home-manager-path*' -type d 2>/dev/null | head -1"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
if (line.trim()) {
var homeManagerPath = line.trim() + "/share/applications/";
appService.pendingSearchPaths.push(homeManagerPath);
console.log("DEBUG: Added home-manager path:", homeManagerPath);
}
}
}
onExited: {
// CRITICAL: Always ensure these essential directories are included
var essentialPaths = ["/run/current-system/sw/share/applications/", "/usr/share/applications/", "/usr/local/share/applications/"];
for (var i = 0; i < essentialPaths.length; i++) {
var path = essentialPaths[i];
if (appService.pendingSearchPaths.indexOf(path) === -1) {
appService.pendingSearchPaths.push(path);
console.log("DEBUG: Added missing essential path:", path);
}
}
// Start bulk parsing with all paths including XDG and home-manager
startBulkParsing(appService.pendingSearchPaths);
}
}
function startBulkParsing(searchPaths) {
// BULLETPROOF: Ensure critical system directories are always included
var criticalPaths = ["/run/current-system/sw/share/applications/", "/usr/share/applications/", "/usr/local/share/applications/"];
for (var i = 0; i < criticalPaths.length; i++) {
var path = criticalPaths[i];
if (searchPaths.indexOf(path) === -1) {
searchPaths.push(path);
console.log("DEBUG: BULLETPROOF: Added missing critical path:", path);
}
}
console.log("DEBUG: Final directories to scan:", searchPaths.join(", "));
// Single command to parse all .desktop files at once
// Only parse fields from the main [Desktop Entry] section, ignore [Desktop Action] sections
var cmd = 'for dir in ' + searchPaths.map(p => "'" + p + "'").join(" ") + '; do ' + 'if [ -d "$dir" ]; then ' + 'find "$dir" -name "*.desktop" 2>/dev/null | while read file; do ' + 'echo "===FILE:$file"; ' + 'sed -n \'/^\\[Desktop Entry\\]/,/^\\[.*\\]/{/^\\[Desktop Entry\\]/d; /^\\[.*\\]/q; /^Name=/p; /^Exec=/p; /^Icon=/p; /^Comment=/p; /^Categories=/p; /^Hidden=/p; /^NoDisplay=/p}\' "$file" 2>/dev/null || true; ' + 'done; ' + 'fi; ' + 'done';
bulkParseProcess.command = ["sh", "-c", cmd];
bulkParseProcess.running = true;
}
Process {
id: bulkParseProcess
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
if (line.startsWith("===FILE:")) {
// Start of new file
if (appService.currentApp.name && appService.currentApp.exec && !appService.currentApp.hidden && !appService.currentApp.noDisplay) {
applications.push(appService.currentApp);
}
appService.currentApp = {
name: "",
exec: "",
icon: "",
comment: "",
categories: [],
hidden: false,
noDisplay: false,
filePath: line.substring(8) // Remove "===FILE:" prefix
};
} else if (line.startsWith("Name=")) {
appService.currentApp.name = line.substring(5);
} else if (line.startsWith("Exec=")) {
appService.currentApp.exec = line.substring(5);
} else if (line.startsWith("Icon=")) {
appService.currentApp.icon = line.substring(5);
} else if (line.startsWith("Comment=")) {
appService.currentApp.comment = line.substring(8);
} else if (line.startsWith("Categories=")) {
appService.currentApp.categories = line.substring(11).split(";").filter(cat => cat.length > 0);
} else if (line === "Hidden=true") {
appService.currentApp.hidden = true;
} else if (line === "NoDisplay=true") {
appService.currentApp.noDisplay = true;
}
}
}
onStarted: {
appService.currentApp = {};
}
onExited: {
// Process the last app
if (appService.currentApp.name && appService.currentApp.exec && !appService.currentApp.hidden && !appService.currentApp.noDisplay) {
applications.push(appService.currentApp);
}
console.log("DEBUG: Before deduplication: Found", applications.length, "applications");
// Deduplicate applications - prefer user installations over system ones
var uniqueApps = {};
var finalApps = [];
for (var i = 0; i < applications.length; i++) {
var app = applications[i];
var key = app.name + "|" + app.exec.split(" ")[0]; // Use name + base command as key
if (!uniqueApps[key]) {
// First occurrence of this app
uniqueApps[key] = app;
finalApps.push(app);
} else {
// Duplicate found - check if this version should replace the existing one
var existing = uniqueApps[key];
var shouldReplace = false;
// Priority order (higher priority replaces lower):
// 1. User local applications (highest priority)
// 2. Home-manager applications
// 3. User profile applications
// 4. System applications (lowest priority)
if (app.filePath.includes("/.local/share/applications/")) {
shouldReplace = true; // User local always wins
} else if (app.filePath.includes("home-manager-path") && !existing.filePath.includes("/.local/share/applications/")) {
shouldReplace = true; // Home-manager beats system
} else if (app.filePath.includes("/home/") && !existing.filePath.includes("/.local/share/applications/") && !existing.filePath.includes("home-manager-path")) {
shouldReplace = true; // User profile beats system
}
if (shouldReplace) {
// Replace the existing app in finalApps array
for (var j = 0; j < finalApps.length; j++) {
if (finalApps[j] === existing) {
finalApps[j] = app;
uniqueApps[key] = app;
break;
}
}
}
// If not replacing, just ignore the duplicate
}
}
applications = finalApps;
console.log("DEBUG: After deduplication: Found", applications.length, "unique applications");
isLoading = false;
applicationsChanged();
}
}
function launchApplication(app) {
if (!app || !app.exec)
return;
// Clean up the exec command (remove field codes like %f, %F, %u, %U)
var cleanExec = app.exec.replace(/%[fFuU]/g, "").trim();
launchProcess.command = ["sh", "-c", cleanExec];
launchProcess.running = true;
console.log("Launching:", app.name, "with command:", cleanExec);
}
Process {
id: launchProcess
running: false
onExited: {
if (exitCode !== 0) {
console.log("Failed to launch application, exit code:", exitCode);
}
}
}
// Fuzzy search function
function fuzzySearch(query, apps) {
if (!query || query.length === 0) {
return apps;
}
query = query.toLowerCase();
return apps.filter(app => {
var searchText = (app.name + " " + app.comment).toLowerCase();
// Simple fuzzy matching - check if all characters of query appear in order
var queryIndex = 0;
for (var i = 0; i < searchText.length && queryIndex < query.length; i++) {
if (searchText[i] === query[queryIndex]) {
queryIndex++;
}
}
return queryIndex === query.length;
}).sort((a, b) => {
// Sort by relevance - exact matches first, then by name
var aName = a.name.toLowerCase();
var bName = b.name.toLowerCase();
var aExact = aName.includes(query);
var bExact = bName.includes(query);
if (aExact && !bExact)
return -1;
if (!aExact && bExact)
return 1;
return aName.localeCompare(bName);
});
}
function getCategoryIcon(app) {
if (!app.categories || app.categories.length === 0) {
return categories["Other"];
}
// Find the first matching category
for (var i = 0; i < app.categories.length; i++) {
var category = app.categories[i];
if (categories[category]) {
return categories[category];
}
}
return categories["Other"];
}
}

View File

@@ -0,0 +1,155 @@
import QtQuick
import Quickshell.Io
import "root:/Data" as Data
// Matugen color integration service
Item {
id: service
property var shell
property var colors: ({})
property bool isLoaded: false
property int colorVersion: 0 // Increments every time colors update to force QML re-evaluation
// Signals to notify when colors change
signal matugenColorsUpdated
signal matugenColorsLoaded
// File watcher for the matugen quickshell-colors.qml
FileView {
id: matugenFile
path: "/home/imxyy/.config/quickshell/Data/quickshell-colors.qml"
blockWrites: true
onLoaded: {
parseColors(text());
}
onTextChanged: {
parseColors(text());
}
}
// Parse QML color definitions and map them to base16 colors
function parseColors(qmlText) {
if (!qmlText) {
console.warn("MatugenService: No QML content to parse");
return;
}
const lines = qmlText.split('\n');
const parsedColors = {};
// Extract readonly property color definitions
for (const line of lines) {
const match = line.match(/readonly\s+property\s+color\s+(\w+):\s*"(#[0-9a-fA-F]{6})"/);
if (match) {
const colorName = match[1];
const colorValue = match[2];
parsedColors[colorName] = colorValue;
}
}
// Detect if this is a light or dark theme based on surface luminance
const surfaceColor = parsedColors.surface || "#000000";
const isLightTheme = getLuminance(surfaceColor) > 0.5;
console.log(`MatugenService: Detected ${isLightTheme ? 'light' : 'dark'} theme from surface color: ${surfaceColor}`);
// Use Material Design 3 colors directly with better contrast
const baseMapping = {
base00: parsedColors.surface || (isLightTheme ? "#ffffff" : "#000000") // Background
,
base01: parsedColors.surface_container_low || (isLightTheme ? "#f8f9fa" : "#1a1a1a") // Panel bg
,
base02: parsedColors.surface_container || (isLightTheme ? "#e9ecef" : "#2a2a2a") // Selection
,
base03: parsedColors.surface_container_high || (isLightTheme ? "#dee2e6" : "#3a3a3a") // Border/separator
,
base04: parsedColors.on_surface_variant || (isLightTheme ? "#6c757d" : "#adb5bd") // Secondary text (better contrast)
,
base05: parsedColors.on_surface || (isLightTheme ? "#212529" : "#f8f9fa") // Primary text (high contrast)
,
base06: parsedColors.on_background || (isLightTheme ? "#000000" : "#ffffff") // Bright text
,
base07: isLightTheme ? parsedColors.surface_container_lowest || "#ffffff" : parsedColors.surface_bright || "#ffffff" // Brightest
,
base08: isLightTheme ? parsedColors.on_error || "#dc3545" : parsedColors.error || "#ff6b6b" // Error (theme appropriate)
,
base09: parsedColors.tertiary || (isLightTheme ? "#6f42c1" : "#a855f7") // Purple
,
base0A: parsedColors.primary_fixed || (isLightTheme ? "#fd7e14" : "#fbbf24") // Orange/Yellow
,
base0B: parsedColors.secondary || (isLightTheme ? "#198754" : "#10b981") // Green
,
base0C: parsedColors.surface_tint || (isLightTheme ? "#0dcaf0" : "#06b6d4") // Cyan
,
base0D: parsedColors.primary_container || (isLightTheme ? "#0d6efd" : "#3b82f6") // Blue
,
base0E: parsedColors.primary || (isLightTheme ? "#6610f2" : parsedColors.secondary || "#8b5cf6") // Accent - use primary for light, secondary for dark
,
base0F: parsedColors.scrim || "#000000" // Special/black
};
// Create the theme object
const theme = Object.assign({
name: isLightTheme ? "Matugen Light" : "Matugen Dark",
type: isLightTheme ? "light" : "dark"
}, baseMapping);
// Store colors in the appropriate theme slot
colors = {
raw: parsedColors,
[isLightTheme ? 'light' : 'dark']: theme,
// Keep the other theme as null or use fallback
[isLightTheme ? 'dark' : 'light']: null
};
isLoaded = true;
colorVersion++; // Increment version to force QML property updates
console.log("MatugenService: Colors loaded successfully from QML (version " + colorVersion + ")");
console.log("Available colors:", Object.keys(parsedColors).join(", "));
// Emit signals to notify theme system
matugenColorsUpdated();
matugenColorsLoaded();
}
// Calculate luminance of a hex color
function getLuminance(hexColor) {
// Remove # if present
const hex = hexColor.replace('#', '');
// Convert to RGB
const r = parseInt(hex.substr(0, 2), 16) / 255;
const g = parseInt(hex.substr(2, 2), 16) / 255;
const b = parseInt(hex.substr(4, 2), 16) / 255;
// Calculate relative luminance
const rs = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
const gs = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);
const bs = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}
// Reload colors from file
function reloadColors() {
matugenFile.reload();
}
// Get specific color by name
function getColor(colorName) {
return colors.raw ? colors.raw[colorName] : null;
}
// Check if matugen colors are available
function isAvailable() {
return isLoaded && colors.raw && Object.keys(colors.raw).length > 0;
}
Component.onCompleted: {
console.log("MatugenService: Initialized, watching quickshell-colors.qml");
}
}

View File

@@ -0,0 +1,97 @@
import QtQuick
import Quickshell
import Quickshell.Services.Notifications
import "root:/Data/" as Data
// Notification service with app filtering
Item {
id: service
property var shell
property alias notificationServer: notificationServer
property int maxHistorySize: Data.Settings.historyLimit
property int cleanupThreshold: maxHistorySize + 10
// Periodic cleanup every 30 minutes
Timer {
interval: 1800000
running: true
repeat: true
onTriggered: cleanupNotifications()
}
function cleanupNotifications() {
if (shell.notificationHistory && shell.notificationHistory.count > cleanupThreshold) {
const removeCount = shell.notificationHistory.count - maxHistorySize;
shell.notificationHistory.remove(maxHistorySize, removeCount);
}
// Remove invalid entries
if (shell.notificationHistory) {
for (let i = shell.notificationHistory.count - 1; i >= 0; i--) {
const item = shell.notificationHistory.get(i);
if (!item || !item.appName) {
shell.notificationHistory.remove(i);
}
}
}
}
NotificationServer {
id: notificationServer
actionsSupported: true
bodyMarkupSupported: true
imageSupported: true
keepOnReload: false
persistenceSupported: true
onNotification: notification => {
// Filter empty notifications
if (!notification.appName && !notification.summary && !notification.body) {
if (typeof notification.dismiss === 'function') {
notification.dismiss();
}
return;
}
// Filter ignored applications (case-insensitive)
var shouldIgnore = false;
if (notification.appName && Data.Settings.ignoredApps && Data.Settings.ignoredApps.length > 0) {
for (var i = 0; i < Data.Settings.ignoredApps.length; i++) {
if (Data.Settings.ignoredApps[i].toLowerCase() === notification.appName.toLowerCase()) {
shouldIgnore = true;
break;
}
}
}
if (shouldIgnore) {
if (typeof notification.dismiss === 'function') {
notification.dismiss();
}
return;
}
// Add to history and cleanup if needed
if (shell.notificationHistory) {
shell.addToNotificationHistory(notification, maxHistorySize);
if (shell.notificationHistory.count > cleanupThreshold) {
cleanupNotifications();
}
}
// Show notification window
if (shell.notificationWindow && shell.notificationWindow.screen === Quickshell.primaryScreen) {
shell.notificationWindow.visible = true;
}
}
}
Component.onDestruction: {
if (shell.notificationHistory) {
shell.notificationHistory.clear();
}
}
}

View File

@@ -0,0 +1,267 @@
import QtQuick
import "root:/Data" as Data
// Weather service using Open-Meteo API
Item {
id: service
property var shell
property string city: Data.Settings.weatherLocation
property bool isAmerican: Data.Settings.useFahrenheit
property int updateInterval: 3600 // 1 hour to reduce API calls
property string weatherDescription: ""
property var weather: null
property Timer retryTimer: Timer {
interval: 30000
repeat: false
running: false
onTriggered: getGeocoding()
}
Timer {
interval: service.updateInterval * 1000
running: true
repeat: true
triggeredOnStart: true
onTriggered: getGeocoding()
}
// Watch for settings changes and refresh weather data
Connections {
target: Data.Settings
function onWeatherLocationChanged() {
console.log("Weather location changed to:", Data.Settings.weatherLocation);
retryTimer.stop();
getGeocoding();
}
function onUseFahrenheitChanged() {
console.log("Temperature unit changed to:", Data.Settings.useFahrenheit ? "Fahrenheit" : "Celsius");
retryTimer.stop();
getGeocoding();
}
}
// WMO weather code descriptions (Open-Meteo standard)
property var weatherConsts: {
"omapiCodeDesc": {
0: "Clear sky",
1: "Mainly clear",
2: "Partly cloudy",
3: "Overcast",
45: "Fog",
48: "Depositing rime fog",
51: "Light drizzle",
53: "Moderate drizzle",
55: "Dense drizzle",
56: "Light freezing drizzle",
57: "Dense freezing drizzle",
61: "Slight rain",
63: "Moderate rain",
65: "Heavy rain",
66: "Light freezing rain",
67: "Heavy freezing rain",
71: "Slight snow fall",
73: "Moderate snow fall",
75: "Heavy snow fall",
77: "Snow grains",
80: "Slight rain showers",
81: "Moderate rain showers",
82: "Violent rain showers",
85: "Slight snow showers",
86: "Heavy snow showers",
95: "Thunderstorm",
96: "Thunderstorm with slight hail",
99: "Thunderstorm with heavy hail"
}
}
function getTemp(temp, tempUnit) {
return temp + tempUnit;
}
function updateWeather() {
if (!weather || !weather.current || !weather.current_units) {
console.warn("Weather data incomplete, skipping update");
return;
}
const weatherCode = weather.current.weather_code;
const temp = getTemp(Math.round(weather.current.temperature_2m || 0), weather.current_units.temperature_2m || "°C");
// Build 3-day forecast
const forecast = [];
const today = new Date();
if (weather.daily && weather.daily.time && weather.daily.weather_code && weather.daily.temperature_2m_min && weather.daily.temperature_2m_max) {
for (let i = 0; i < Math.min(3, weather.daily.time.length); i++) {
let dayName;
if (i === 0) {
dayName = "Today";
} else if (i === 1) {
dayName = "Tomorrow";
} else {
const futureDate = new Date(today);
futureDate.setDate(today.getDate() + i);
dayName = Qt.formatDate(futureDate, "ddd MMM d");
}
const dailyWeatherCode = weather.daily.weather_code[i];
const condition = weatherConsts.omapiCodeDesc[dailyWeatherCode] || "Unknown";
forecast.push({
dayName: dayName,
condition: condition,
minTemp: Math.round(weather.daily.temperature_2m_min[i]),
maxTemp: Math.round(weather.daily.temperature_2m_max[i])
});
}
}
// Update shell weather data in expected format
shell.weatherData = {
location: city,
currentTemp: temp,
currentCondition: weatherConsts.omapiCodeDesc[weatherCode] || "Unknown",
details: ["Wind: " + Math.round(weather.current.wind_speed_10m || 0) + " km/h"],
forecast: forecast
};
weatherDescription = weatherConsts.omapiCodeDesc[weatherCode] || "Unknown";
shell.weatherLoading = false;
}
// XHR pool to prevent memory leaks
property var activeXHRs: []
function cleanupXHR(xhr) {
if (xhr) {
xhr.abort();
xhr.onreadystatechange = null;
xhr.onerror = null;
const index = activeXHRs.indexOf(xhr);
if (index > -1) {
activeXHRs.splice(index, 1);
}
}
}
function getGeocoding() {
if (!city || city.trim() === "") {
console.warn("Weather location is empty, skipping weather request");
shell.weatherLoading = false;
return;
}
shell.weatherLoading = true;
const xhr = new XMLHttpRequest();
activeXHRs.push(xhr);
xhr.open("GET", `https://geocoding-api.open-meteo.com/v1/search?name=${city}&count=1&language=en&format=json`);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
try {
const geocoding = JSON.parse(xhr.responseText);
if (geocoding.results && geocoding.results.length > 0) {
const lat = geocoding.results[0].latitude;
const lng = geocoding.results[0].longitude;
getWeather(lat, lng);
} else {
console.warn("No geocoding results found for location:", city);
retryTimer.running = true;
shell.weatherLoading = false;
}
} catch (e) {
console.error("Failed to parse geocoding response:", e);
retryTimer.running = true;
shell.weatherLoading = false;
}
} else if (xhr.status === 0) {
// Silent handling of network issues
if (!retryTimer.running) {
console.warn("Weather service: Network unavailable, will retry automatically");
}
retryTimer.running = true;
shell.weatherLoading = false;
} else {
console.error("Geocoding request failed with status:", xhr.status);
retryTimer.running = true;
shell.weatherLoading = false;
}
cleanupXHR(xhr);
}
};
xhr.onerror = function () {
console.error("Geocoding request failed with network error");
retryTimer.running = true;
shell.weatherLoading = false;
cleanupXHR(xhr);
};
xhr.send();
}
function getWeather(lat, lng) {
const xhr = new XMLHttpRequest();
activeXHRs.push(xhr);
xhr.open("GET", `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lng}&current=temperature_2m,is_day,weather_code,wind_speed_10m&daily=temperature_2m_max,temperature_2m_min,weather_code&forecast_days=3&temperature_unit=` + (isAmerican ? "fahrenheit" : "celsius"));
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
try {
weather = JSON.parse(xhr.responseText);
updateWeather();
} catch (e) {
console.error("Failed to parse weather response:", e);
retryTimer.running = true;
shell.weatherLoading = false;
}
} else if (xhr.status === 0) {
// Silent handling of network issues
if (!retryTimer.running) {
console.warn("Weather service: Network unavailable for weather data");
}
retryTimer.running = true;
shell.weatherLoading = false;
} else {
console.error("Weather request failed with status:", xhr.status);
retryTimer.running = true;
shell.weatherLoading = false;
}
cleanupXHR(xhr);
}
};
xhr.onerror = function () {
console.error("Weather request failed with network error");
retryTimer.running = true;
shell.weatherLoading = false;
cleanupXHR(xhr);
};
xhr.send();
}
function loadWeather() {
getGeocoding();
}
Component.onCompleted: getGeocoding()
Component.onDestruction: {
// Cleanup all active XHR requests
for (let i = 0; i < activeXHRs.length; i++) {
if (activeXHRs[i]) {
activeXHRs[i].abort();
activeXHRs[i].onreadystatechange = null;
activeXHRs[i].onerror = null;
}
}
activeXHRs = [];
weather = null;
shell.weatherData = null;
weatherDescription = "";
}
}

View File

@@ -0,0 +1,119 @@
// Calendar.qml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "root:/Data" as Data
// Calendar widget with navigation
Rectangle {
id: calendarRoot
property var shell
radius: 20
color: Qt.lighter(Data.ThemeManager.bgColor, 1.2)
readonly property date currentDate: new Date()
property int month: currentDate.getMonth()
property int year: currentDate.getFullYear()
readonly property int currentDay: currentDate.getDate()
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 12
// Month/Year header
RowLayout {
Layout.fillWidth: true
spacing: 8
// Current month and year display
Text {
text: Qt.locale("en_US").monthName(calendarRoot.month) + " " + calendarRoot.year
color: Data.ThemeManager.accentColor
font.bold: true
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 18
}
}
// Weekday headers (Monday-Sunday)
Grid {
columns: 7
rowSpacing: 4
columnSpacing: 0
Layout.leftMargin: 2
Layout.fillWidth: true
Repeater {
model: ["M", "T", "W", "T", "F", "S", "S"]
delegate: Text {
text: modelData
color: Data.ThemeManager.fgColor
font.bold: true
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width / 7
font.pixelSize: 14
}
}
}
// Calendar grid
MonthGrid {
id: monthGrid
month: calendarRoot.month
year: calendarRoot.year
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 4
leftPadding: 0
rightPadding: 0
locale: Qt.locale("en_US")
implicitHeight: 400
delegate: Rectangle {
width: 30
height: 30
radius: 15
readonly property bool isCurrentMonth: model.month === calendarRoot.month
readonly property bool isToday: model.day === calendarRoot.currentDay && model.month === calendarRoot.currentDate.getMonth() && calendarRoot.year === calendarRoot.currentDate.getFullYear() && isCurrentMonth
// Dynamic styling: today = accent color, current month = normal, other months = dimmed
color: isToday ? Data.ThemeManager.accentColor : isCurrentMonth ? Data.ThemeManager.bgColor : Qt.darker(Data.ThemeManager.bgColor, 1.4)
Text {
text: model.day
anchors.centerIn: parent
color: isToday ? Data.ThemeManager.bgColor : isCurrentMonth ? Data.ThemeManager.fgColor : Qt.darker(Data.ThemeManager.fgColor, 1.5)
font.bold: isToday
font.pixelSize: 14
font.family: "monospace"
}
}
}
}
// Reusable navigation button
component NavButton: AbstractButton {
property alias buttonText: buttonLabel.text
implicitWidth: 30
implicitHeight: 30
background: Rectangle {
radius: 15
color: parent.down ? Qt.darker(Data.ThemeManager.accentColor, 1.2) : parent.hovered ? Qt.lighter(Data.ThemeManager.highlightBg, 1.1) : Data.ThemeManager.highlightBg
}
Text {
id: buttonLabel
anchors.centerIn: parent
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
}
}
}

View File

@@ -0,0 +1,127 @@
import QtQuick
import QtQuick.Controls
import "root:/Data" as Data
// Calendar popup with animations
Popup {
id: calendarPopup
property bool hovered: false
property bool clickMode: false // Persistent mode - stays open until clicked again
property var shell
property int targetX: 0
readonly property int targetY: Screen.height - height
width: 280
height: 280
modal: false
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
padding: 15
// Animation state properties
property bool _visible: false
property real animX: targetX - 20
property real animOpacity: 0
x: animX
y: targetY
opacity: animOpacity
visible: _visible
// Smooth slide-in animation
Behavior on animX {
NumberAnimation {
duration: 200
easing.type: Easing.InOutQuad
}
}
Behavior on animOpacity {
NumberAnimation {
duration: 200
easing.type: Easing.InOutQuad
}
}
// Hover mode: show/hide based on mouse state
onHoveredChanged: {
if (!clickMode) {
if (hovered) {
_visible = true;
animX = targetX;
animOpacity = 1;
} else {
animX = targetX - 20;
animOpacity = 0;
}
}
}
// Click mode: persistent visibility toggle
onClickModeChanged: {
if (clickMode) {
_visible = true;
animX = targetX;
animOpacity = 1;
} else {
animX = targetX - 20;
animOpacity = 0;
}
}
// Hide when animation completes
onAnimOpacityChanged: {
if (animOpacity === 0 && !hovered && !clickMode) {
_visible = false;
}
}
function setHovered(state) {
hovered = state;
}
function setClickMode(state) {
clickMode = state;
}
// Hover detection
MouseArea {
id: hoverArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
anchors.margins: 10 // Larger area to reduce flicker
onEntered: {
if (!clickMode) {
setHovered(true);
}
}
onExited: {
if (!clickMode) {
// Delayed exit check to prevent hover flicker
Qt.callLater(() => {
if (!hoverArea.containsMouse) {
setHovered(false);
}
});
}
}
}
// Lazy-loaded calendar content
Loader {
anchors.fill: parent
active: calendarPopup._visible
source: active ? "Calendar.qml" : ""
onLoaded: {
if (item) {
item.shell = calendarPopup.shell;
}
}
}
background: Rectangle {
color: Data.ThemeManager.bgColor
topRightRadius: 20
}
}

View File

@@ -0,0 +1,64 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import "root:/Data" as Data
import "root:/Core" as Core
// Clock with border integration
Item {
id: clockRoot
width: clockBackground.width
height: clockBackground.height
Rectangle {
id: clockBackground
width: clockText.implicitWidth + 24
height: 32
color: Data.ThemeManager.bgColor
// Rounded corner for border integration
topRightRadius: height / 2
Text {
id: clockText
anchors.centerIn: parent
font.family: "monospace"
font.pixelSize: 14
font.bold: true
color: Data.ThemeManager.accentColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: Qt.formatTime(new Date(), "HH:mm")
}
}
// Update every minute
Timer {
interval: 60000
running: true
repeat: true
onTriggered: clockText.text = Qt.formatTime(new Date(), "HH:mm")
}
// Border integration corner pieces
Core.Corners {
id: topLeftCorner
position: "topleft"
size: 1.3
fillColor: Data.ThemeManager.bgColor
offsetX: -39
offsetY: -26
z: 0 // Same z-level as clock background
}
Core.Corners {
id: topLeftCorner2
position: "topleft"
size: 1.3
fillColor: Data.ThemeManager.bgColor
offsetX: 20
offsetY: 6
z: 0 // Same z-level as clock background
}
}

View File

@@ -0,0 +1,140 @@
import QtQuick
import "root:/Data" as Data
import "root:/Core" as Core
// Main control panel coordinator - handles recording and system actions
Item {
id: controlPanelContainer
required property var shell
property bool isRecording: false
property int currentTab: 0 // 0=main, 1=calendar, 2=clipboard, 3=notifications, 4=music, 5=settings
property var tabIcons: ["widgets", "calendar_month", "content_paste", "notifications", "music_note", "settings"]
property bool isShown: false
property var recordingProcess: null
signal recordingRequested
signal stopRecordingRequested
signal systemActionRequested(string action)
signal performanceActionRequested(string action)
// Screen recording
onRecordingRequested: {
var currentDate = new Date();
var hours = String(currentDate.getHours()).padStart(2, '0');
var minutes = String(currentDate.getMinutes()).padStart(2, '0');
var day = String(currentDate.getDate()).padStart(2, '0');
var month = String(currentDate.getMonth() + 1).padStart(2, '0');
var year = currentDate.getFullYear();
var filename = hours + "-" + minutes + "-" + day + "-" + month + "-" + year + ".mp4";
var outputPath = Data.Settings.videoPath + filename;
var command = "gpu-screen-recorder -w portal -f 60 -a default_output -o " + outputPath;
var qmlString = 'import Quickshell.Io; Process { command: ["sh", "-c", "' + command + '"]; running: true }';
try {
recordingProcess = Qt.createQmlObject(qmlString, controlPanelContainer);
isRecording = true;
} catch (e) {
console.error("Failed to start recording:", e);
}
}
// Stop recording with cleanup
onStopRecordingRequested: {
if (recordingProcess && isRecording) {
var stopQmlString = 'import Quickshell.Io; Process { command: ["sh", "-c", "pkill -SIGINT -f \'gpu-screen-recorder.*portal\'"]; running: true; onExited: function() { destroy() } }';
try {
var stopProcess = Qt.createQmlObject(stopQmlString, controlPanelContainer);
var cleanupTimer = Qt.createQmlObject('import QtQuick; Timer { interval: 3000; running: true; repeat: false }', controlPanelContainer);
cleanupTimer.triggered.connect(function () {
if (recordingProcess) {
recordingProcess.running = false;
recordingProcess.destroy();
recordingProcess = null;
}
var forceKillQml = 'import Quickshell.Io; Process { command: ["sh", "-c", "pkill -9 -f \'gpu-screen-recorder.*portal\' 2>/dev/null || true"]; running: true; onExited: function() { destroy() } }';
var forceKillProcess = Qt.createQmlObject(forceKillQml, controlPanelContainer);
cleanupTimer.destroy();
});
} catch (e) {
console.error("Failed to stop recording:", e);
}
}
isRecording = false;
}
// System action routing
onSystemActionRequested: function (action) {
switch (action) {
case "lock":
Core.ProcessManager.lock();
break;
case "reboot":
Core.ProcessManager.reboot();
break;
case "shutdown":
Core.ProcessManager.shutdown();
break;
}
}
onPerformanceActionRequested: function (action) {
console.log("Performance action requested:", action);
}
// Control panel window component
ControlPanelWindow {
id: controlPanelWindow
// Pass through properties
shell: controlPanelContainer.shell
isRecording: controlPanelContainer.isRecording
currentTab: controlPanelContainer.currentTab
tabIcons: controlPanelContainer.tabIcons
isShown: controlPanelContainer.isShown
// Bind state changes back to parent
onCurrentTabChanged: controlPanelContainer.currentTab = currentTab
onIsShownChanged: controlPanelContainer.isShown = isShown
// Forward signals
onRecordingRequested: controlPanelContainer.recordingRequested()
onStopRecordingRequested: controlPanelContainer.stopRecordingRequested()
onSystemActionRequested: function (action) {
controlPanelContainer.systemActionRequested(action);
}
onPerformanceActionRequested: function (action) {
controlPanelContainer.performanceActionRequested(action);
}
}
// Clean up processes on destruction
Component.onDestruction: {
if (recordingProcess) {
try {
if (recordingProcess.running) {
recordingProcess.terminate();
}
recordingProcess.destroy();
} catch (e) {
console.warn("Error cleaning up recording process:", e);
}
recordingProcess = null;
}
// Force kill any remaining gpu-screen-recorder processes
var forceCleanupCmd = 'import Quickshell.Io; Process { command: ["sh", "-c", "pkill -9 -f gpu-screen-recorder 2>/dev/null || true"]; running: true; onExited: function() { destroy() } }';
try {
Qt.createQmlObject(forceCleanupCmd, controlPanelContainer);
} catch (e) {
console.warn("Error in force cleanup:", e);
}
}
}

View File

@@ -0,0 +1,110 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import "root:/Data" as Data
import "./components/navigation" as Navigation
// Panel content with tab layout - now clean and organized!
Item {
id: contentRoot
// Properties passed from parent
required property var shell
required property bool isRecording
property int currentTab: 0
property var tabIcons: []
required property var triggerMouseArea
// Signals to forward to parent
signal recordingRequested
signal stopRecordingRequested
signal systemActionRequested(string action)
signal performanceActionRequested(string action)
// Hover detection for auto-hide
property bool isHovered: {
const mouseStates = {
triggerHovered: triggerMouseArea.containsMouse,
backgroundHovered: backgroundMouseArea.containsMouse,
tabSidebarHovered: tabNavigation.containsMouse,
tabContainerHovered: tabContainer.isHovered,
tabContentActive: currentTab !== 0 // Non-main tabs stay open
,
tabNavigationActive: tabNavigation.containsMouse
};
return Object.values(mouseStates).some(state => state);
}
// Expose text input focus state for keyboard management
property bool textInputFocused: tabContainer.textInputFocused
// Panel background with bottom-only rounded corners
Rectangle {
anchors.fill: parent
color: Data.ThemeManager.bgColor
topLeftRadius: 0
topRightRadius: 0
bottomLeftRadius: 20
bottomRightRadius: 20
z: -10 // Far behind everything to avoid layering conflicts
}
// Main content container with tab layout
Rectangle {
id: mainContainer
anchors.fill: parent
anchors.margins: 9
color: "transparent"
radius: 12
MouseArea {
id: backgroundMouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
property alias containsMouse: backgroundMouseArea.containsMouse
}
// Left sidebar with tab navigation
Navigation.TabNavigation {
id: tabNavigation
width: 40
height: parent.height
anchors.left: parent.left
anchors.leftMargin: 9
anchors.top: parent.top
anchors.topMargin: 18
currentTab: contentRoot.currentTab
tabIcons: contentRoot.tabIcons
onCurrentTabChanged: contentRoot.currentTab = currentTab
}
// Main tab content area with sliding animation
Navigation.TabContainer {
id: tabContainer
width: parent.width - tabNavigation.width - 45
height: parent.height - 36
anchors.left: tabNavigation.right
anchors.leftMargin: 9
anchors.top: parent.top
anchors.topMargin: 18
shell: contentRoot.shell
isRecording: contentRoot.isRecording
triggerMouseArea: contentRoot.triggerMouseArea
currentTab: contentRoot.currentTab
onRecordingRequested: contentRoot.recordingRequested()
onStopRecordingRequested: contentRoot.stopRecordingRequested()
onSystemActionRequested: function (action) {
contentRoot.systemActionRequested(action);
}
onPerformanceActionRequested: function (action) {
contentRoot.performanceActionRequested(action);
}
}
}
}

View File

@@ -0,0 +1,185 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Wayland
import "root:/Data" as Data
import "root:/Core" as Core
// Control panel window and trigger
PanelWindow {
id: controlPanelWindow
// Properties passed from parent ControlPanel
required property var shell
required property bool isRecording
property int currentTab: 0
property var tabIcons: []
property bool isShown: false
// Signals to forward to parent
signal recordingRequested
signal stopRecordingRequested
signal systemActionRequested(string action)
signal performanceActionRequested(string action)
screen: Quickshell.primaryScreen || Quickshell.screens[0]
anchors.top: true
anchors.left: true
anchors.right: true
margins.bottom: 0
margins.left: (screen ? screen.width / 2 - 400 : 0) // Centered
margins.right: (screen ? screen.width / 2 - 400 : 0)
implicitWidth: 640
implicitHeight: isShown ? 400 : 8 // Expand/collapse animation
Behavior on implicitHeight {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
exclusiveZone: (panelContent && panelContent.textInputFocused) ? -1 : 0
color: "transparent"
visible: true
WlrLayershell.namespace: "quickshell-controlpanel"
WlrLayershell.keyboardFocus: (panelContent && panelContent.textInputFocused) ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand
// Hover trigger area at screen top
MouseArea {
id: triggerMouseArea
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
width: 600
height: 8
hoverEnabled: true
onContainsMouseChanged: {
if (containsMouse) {
show();
}
}
}
// Main panel content
ControlPanelContent {
id: panelContent
width: 600
height: 380
anchors.top: parent.top
anchors.topMargin: 8 // Trigger area space
anchors.horizontalCenter: parent.horizontalCenter
visible: isShown
opacity: isShown ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
// Pass through properties
shell: controlPanelWindow.shell
isRecording: controlPanelWindow.isRecording
currentTab: controlPanelWindow.currentTab
tabIcons: controlPanelWindow.tabIcons
triggerMouseArea: triggerMouseArea
// Bind state changes
onCurrentTabChanged: controlPanelWindow.currentTab = currentTab
// Forward signals
onRecordingRequested: controlPanelWindow.recordingRequested()
onStopRecordingRequested: controlPanelWindow.stopRecordingRequested()
onSystemActionRequested: function (action) {
controlPanelWindow.systemActionRequested(action);
}
onPerformanceActionRequested: function (action) {
controlPanelWindow.performanceActionRequested(action);
}
// Hover state management
onIsHoveredChanged: {
if (isHovered) {
hideTimer.stop();
} else {
hideTimer.restart();
}
}
}
// Border integration corners (positioned to match panel edges)
Core.Corners {
id: controlPanelLeftCorner
position: "bottomright"
size: 1.3
fillColor: Data.ThemeManager.bgColor
offsetX: -661
offsetY: -313
visible: isShown
z: 1 // Higher z-index to render above shadow effects
// Disable implicit animations to prevent corner sliding
Behavior on x {
enabled: false
}
Behavior on y {
enabled: false
}
}
Core.Corners {
id: controlPanelRightCorner
position: "bottomleft"
size: 1.3
fillColor: Data.ThemeManager.bgColor
offsetX: 661
offsetY: -313
visible: isShown
z: 1 // Higher z-index to render above shadow effects
Behavior on x {
enabled: false
}
Behavior on y {
enabled: false
}
}
// Auto-hide timer
Timer {
id: hideTimer
interval: 400
repeat: false
onTriggered: hide()
}
function show() {
if (isShown)
return;
isShown = true;
hideTimer.stop();
}
function hide() {
if (!isShown)
return;
// Only hide if on main tab and nothing is being hovered
if (currentTab === 0 && !panelContent.isHovered && !triggerMouseArea.containsMouse) {
isShown = false;
} else
// For non-main tabs, only hide if explicitly not hovered and no trigger hover
if (currentTab !== 0 && !panelContent.isHovered && !triggerMouseArea.containsMouse) {
// Add delay for non-main tabs to prevent accidental hiding
Qt.callLater(function () {
if (!panelContent.isHovered && !triggerMouseArea.containsMouse) {
isShown = false;
}
});
}
}
}

View File

@@ -0,0 +1,111 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/Data" as Data
import "." as Controls
// Dual-section control panel
Row {
id: root
spacing: 16
visible: true
height: 80
required property bool isRecording
required property var shell
signal performanceActionRequested(string action)
signal systemActionRequested(string action)
signal mouseChanged(bool containsMouse)
// Combined hover state from both sections
property bool containsMouse: performanceSection.containsMouse || systemSection.containsMouse
onContainsMouseChanged: mouseChanged(containsMouse)
// Performance controls section (left half)
Rectangle {
id: performanceSection
width: (parent.width - parent.spacing) / 2
height: parent.height
radius: 20
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
visible: true
// Hover tracking with coordination between background and content
property bool containsMouse: performanceMouseArea.containsMouse || performanceControls.containsMouse
MouseArea {
id: performanceMouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onContainsMouseChanged: {
if (containsMouse) {
performanceSection.containsMouse = true;
} else if (!performanceControls.containsMouse) {
performanceSection.containsMouse = false;
}
}
}
Controls.PerformanceControls {
id: performanceControls
anchors.fill: parent
anchors.margins: 12
shell: root.shell
onPerformanceActionRequested: function (action) {
root.performanceActionRequested(action);
}
onMouseChanged: function (containsMouse) {
if (containsMouse) {
performanceSection.containsMouse = true;
} else if (!performanceMouseArea.containsMouse) {
performanceSection.containsMouse = false;
}
}
}
}
// System controls section (right half)
Rectangle {
id: systemSection
width: (parent.width - parent.spacing) / 2
height: parent.height
radius: 20
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
visible: true
// Hover tracking with coordination between background and content
property bool containsMouse: systemMouseArea.containsMouse || systemControls.containsMouse
MouseArea {
id: systemMouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onContainsMouseChanged: {
if (containsMouse) {
systemSection.containsMouse = true;
} else if (!systemControls.containsMouse) {
systemSection.containsMouse = false;
}
}
}
Controls.SystemControls {
id: systemControls
anchors.fill: parent
anchors.margins: 12
shell: root.shell
onSystemActionRequested: function (action) {
root.systemActionRequested(action);
}
onMouseChanged: function (containsMouse) {
if (containsMouse) {
systemSection.containsMouse = true;
} else if (!systemMouseArea.containsMouse) {
systemSection.containsMouse = false;
}
}
}
}
}

View File

@@ -0,0 +1,122 @@
import QtQuick
import QtQuick.Controls
import Quickshell.Services.UPower
// Power profile controls
Column {
id: root
required property var shell
spacing: 8
signal performanceActionRequested(string action)
signal mouseChanged(bool containsMouse)
readonly property bool containsMouse: performanceButton.containsMouse || balancedButton.containsMouse || powerSaverButton.containsMouse
// Safe UPower service access with fallback checks
readonly property bool upowerReady: typeof PowerProfiles !== 'undefined' && PowerProfiles
readonly property int currentProfile: upowerReady ? PowerProfiles.profile : 0
onContainsMouseChanged: root.mouseChanged(containsMouse)
opacity: visible ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
Row {
spacing: 8
width: parent.width
// Performance mode button
SystemButton {
id: performanceButton
width: (parent.width - parent.spacing * 2) / 3
height: 52
shell: root.shell
iconText: "speed"
isActive: root.upowerReady && (typeof PowerProfile !== 'undefined') ? root.currentProfile === PowerProfile.Performance : false
onClicked: {
if (root.upowerReady && typeof PowerProfile !== 'undefined') {
PowerProfiles.profile = PowerProfile.Performance;
root.performanceActionRequested("performance");
} else {
console.warn("PowerProfiles not available");
}
}
onMouseChanged: function (containsMouse) {
if (!containsMouse && !root.containsMouse) {
root.mouseChanged(false);
}
}
}
// Balanced mode button
SystemButton {
id: balancedButton
width: (parent.width - parent.spacing * 2) / 3
height: 52
shell: root.shell
iconText: "balance"
isActive: root.upowerReady && (typeof PowerProfile !== 'undefined') ? root.currentProfile === PowerProfile.Balanced : false
onClicked: {
if (root.upowerReady && typeof PowerProfile !== 'undefined') {
PowerProfiles.profile = PowerProfile.Balanced;
root.performanceActionRequested("balanced");
} else {
console.warn("PowerProfiles not available");
}
}
onMouseChanged: function (containsMouse) {
if (!containsMouse && !root.containsMouse) {
root.mouseChanged(false);
}
}
}
// Power saver mode button
SystemButton {
id: powerSaverButton
width: (parent.width - parent.spacing * 2) / 3
height: 52
shell: root.shell
iconText: "battery_saver"
isActive: root.upowerReady && (typeof PowerProfile !== 'undefined') ? root.currentProfile === PowerProfile.PowerSaver : false
onClicked: {
if (root.upowerReady && typeof PowerProfile !== 'undefined') {
PowerProfiles.profile = PowerProfile.PowerSaver;
root.performanceActionRequested("powersaver");
} else {
console.warn("PowerProfiles not available");
}
}
onMouseChanged: function (containsMouse) {
if (!containsMouse && !root.containsMouse) {
root.mouseChanged(false);
}
}
}
}
// Ensure UPower service initialization
Component.onCompleted: {
Qt.callLater(function () {
if (!root.upowerReady) {
console.warn("UPower service not ready - performance controls may not work correctly");
}
});
}
}

View File

@@ -0,0 +1,118 @@
import QtQuick
import QtQuick.Controls
import "root:/Data" as Data
// System button
Rectangle {
id: root
required property var shell
required property string iconText
property string labelText: ""
property bool isActive: false
radius: 20
// Dynamic color based on active and hover states
color: {
if (isActive) {
return mouseArea.containsMouse ? Qt.lighter(Data.ThemeManager.accentColor, 1.1) : Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3);
} else {
return mouseArea.containsMouse ? Qt.lighter(Data.ThemeManager.accentColor, 1.2) : Qt.lighter(Data.ThemeManager.bgColor, 1.15);
}
}
border.width: isActive ? 2 : 1
border.color: isActive ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.3)
signal clicked
signal mouseChanged(bool containsMouse)
property bool isHovered: mouseArea.containsMouse
readonly property alias containsMouse: mouseArea.containsMouse
// Smooth color transitions
Behavior on color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
// Hover scale animation
scale: isHovered ? 1.05 : 1.0
Behavior on scale {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
// Button content with icon and optional label
Column {
anchors.centerIn: parent
spacing: 2
// System action icon
Text {
text: root.iconText
font.family: "Material Symbols Outlined"
font.pixelSize: 16
anchors.horizontalCenter: parent.horizontalCenter
color: {
if (root.isActive) {
return root.isHovered ? "#ffffff" : Data.ThemeManager.accentColor;
} else {
return root.isHovered ? "#ffffff" : Data.ThemeManager.accentColor;
}
}
Behavior on color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
// Optional text label
Label {
text: root.labelText
font.family: "monospace"
font.pixelSize: 8
color: {
if (root.isActive) {
return root.isHovered ? "#ffffff" : Data.ThemeManager.accentColor;
} else {
return root.isHovered ? "#ffffff" : Data.ThemeManager.accentColor;
}
}
anchors.horizontalCenter: parent.horizontalCenter
font.weight: root.isActive ? Font.Bold : Font.Medium
visible: root.labelText !== ""
Behavior on color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
}
// Click and hover handling
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: root.mouseChanged(containsMouse)
onClicked: root.clicked()
}
}

View File

@@ -0,0 +1,60 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
// System action buttons
RowLayout {
id: root
required property var shell
spacing: 8
signal systemActionRequested(string action)
signal mouseChanged(bool containsMouse)
readonly property bool containsMouse: lockButton.containsMouse || rebootButton.containsMouse || shutdownButton.containsMouse
onContainsMouseChanged: root.mouseChanged(containsMouse)
opacity: visible ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
// Reboot Button
SystemButton {
id: rebootButton
Layout.fillHeight: true
Layout.fillWidth: true
shell: root.shell
iconText: "restart_alt"
onClicked: root.systemActionRequested("reboot")
onMouseChanged: function (containsMouse) {
if (!containsMouse && !root.containsMouse) {
root.mouseChanged(false);
}
}
}
// Shutdown Button
SystemButton {
id: shutdownButton
Layout.fillHeight: true
Layout.fillWidth: true
shell: root.shell
iconText: "power_settings_new"
onClicked: root.systemActionRequested("shutdown")
onMouseChanged: function (containsMouse) {
if (!containsMouse && !root.containsMouse) {
root.mouseChanged(false);
}
}
}
}

View File

@@ -0,0 +1,666 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Services.Mpris
import "root:/Data" as Data
// Music player with MPRIS integration
Rectangle {
id: musicPlayer
property var shell
property var currentPlayer: null
property real currentPosition: 0
property int selectedPlayerIndex: 0
color: "transparent"
// Get all available players
function getAvailablePlayers() {
if (!Mpris.players || !Mpris.players.values) {
return [];
}
let allPlayers = Mpris.players.values;
let controllablePlayers = [];
for (let i = 0; i < allPlayers.length; i++) {
let player = allPlayers[i];
if (player && player.canControl) {
controllablePlayers.push(player);
}
}
return controllablePlayers;
}
// Find the active player (either selected or first available)
function findActivePlayer() {
let availablePlayers = getAvailablePlayers();
if (availablePlayers.length === 0) {
return null;
}
// Auto-switch to playing player if enabled
if (Data.Settings.autoSwitchPlayer) {
for (let i = 0; i < availablePlayers.length; i++) {
if (availablePlayers[i].isPlaying) {
selectedPlayerIndex = i;
return availablePlayers[i];
}
}
}
// Use selected player if valid, otherwise use first available
if (selectedPlayerIndex < availablePlayers.length) {
return availablePlayers[selectedPlayerIndex];
} else {
selectedPlayerIndex = 0;
return availablePlayers[0];
}
}
// Update current player
function updateCurrentPlayer() {
let newPlayer = findActivePlayer();
if (newPlayer !== currentPlayer) {
currentPlayer = newPlayer;
currentPosition = currentPlayer ? currentPlayer.position : 0;
}
}
// Timer to update progress bar position
Timer {
id: positionTimer
interval: 1000
running: currentPlayer && currentPlayer.isPlaying
repeat: true
onTriggered: {
if (currentPlayer) {
currentPosition = currentPlayer.position;
}
}
}
// Timer to check for auto-switching to playing players
Timer {
id: autoSwitchTimer
interval: 2000 // Check every 2 seconds
running: Data.Settings.autoSwitchPlayer
repeat: true
onTriggered: {
if (Data.Settings.autoSwitchPlayer) {
let availablePlayers = getAvailablePlayers();
for (let i = 0; i < availablePlayers.length; i++) {
if (availablePlayers[i].isPlaying && selectedPlayerIndex !== i) {
selectedPlayerIndex = i;
updateCurrentPlayer();
updatePlayerList();
break;
}
}
}
}
}
// Update player list for dropdown
function updatePlayerList() {
if (!playerComboBox)
return;
let availablePlayers = getAvailablePlayers();
let playerNames = availablePlayers.map(player => player.identity || "Unknown Player");
playerComboBox.model = playerNames;
if (selectedPlayerIndex >= playerNames.length) {
selectedPlayerIndex = 0;
}
playerComboBox.currentIndex = selectedPlayerIndex;
}
// Monitor for player changes
Connections {
target: Mpris.players
function onValuesChanged() {
updatePlayerList();
updateCurrentPlayer();
}
function onRowsInserted() {
updatePlayerList();
updateCurrentPlayer();
}
function onRowsRemoved() {
updatePlayerList();
updateCurrentPlayer();
}
function onObjectInsertedPost() {
updatePlayerList();
updateCurrentPlayer();
}
function onObjectRemovedPost() {
updatePlayerList();
updateCurrentPlayer();
}
}
// Monitor for settings changes
Connections {
target: Data.Settings
function onAutoSwitchPlayerChanged() {
console.log("Auto-switch player setting changed to:", Data.Settings.autoSwitchPlayer);
updateCurrentPlayer();
}
function onAlwaysShowPlayerDropdownChanged() {
console.log("Always show dropdown setting changed to:", Data.Settings.alwaysShowPlayerDropdown);
// Dropdown visibility is automatically handled by the binding
}
}
Component.onCompleted: {
updatePlayerList();
updateCurrentPlayer();
}
Column {
anchors.fill: parent
spacing: 10
// No music player available state
Item {
width: parent.width
height: parent.height
visible: !currentPlayer
Column {
anchors.centerIn: parent
spacing: 16
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "music_note"
font.family: "Material Symbols Outlined"
font.pixelSize: 48
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: getAvailablePlayers().length > 0 ? "No controllable player selected" : "No music player detected"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.family: "monospace"
font.pixelSize: 14
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: getAvailablePlayers().length > 0 ? "Select a player from the dropdown above" : "Start a music player to see controls"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.4)
font.family: "monospace"
font.pixelSize: 12
}
}
}
// Music player controls
Column {
width: parent.width
spacing: 12
visible: currentPlayer
// Player info and artwork
Rectangle {
width: parent.width
height: 130
radius: 20
color: Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2)
border.width: 1
Row {
anchors.fill: parent
anchors.margins: 16
spacing: 16
// Album artwork
Rectangle {
id: albumArtwork
width: 90
height: 90
radius: 20
color: Qt.darker(Data.ThemeManager.bgColor, 1.3)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
Image {
id: albumArt
anchors.fill: parent
anchors.margins: 2
fillMode: Image.PreserveAspectCrop
smooth: true
source: currentPlayer ? (currentPlayer.trackArtUrl || "") : ""
visible: source.toString() !== ""
// Rounded corners using layer
layer.enabled: true
layer.effect: OpacityMask {
cached: true // Cache to reduce ShaderEffect issues
maskSource: Rectangle {
width: albumArt.width
height: albumArt.height
radius: 20
visible: false
}
}
}
// Fallback music icon
Text {
anchors.centerIn: parent
text: "album"
font.family: "Material Symbols Outlined"
font.pixelSize: 32
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.4)
visible: !albumArt.visible
}
}
// Track info
Column {
width: parent.width - albumArtwork.width - parent.spacing
height: parent.height
spacing: 4
Text {
width: parent.width
text: currentPlayer ? (currentPlayer.trackTitle || "Unknown Track") : ""
color: Data.ThemeManager.fgColor
font.family: "monospace"
font.pixelSize: 18
font.bold: true
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
}
Text {
width: parent.width
text: currentPlayer ? (currentPlayer.trackArtist || "Unknown Artist") : ""
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.8)
font.family: "monospace"
font.pixelSize: 18
elide: Text.ElideRight
}
Text {
width: parent.width
text: currentPlayer ? (currentPlayer.trackAlbum || "Unknown Album") : ""
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.family: "monospace"
font.pixelSize: 15
elide: Text.ElideRight
}
}
}
}
// Interactive progress bar with seek functionality
Rectangle {
id: progressBarBackground
width: parent.width
height: 8
radius: 20
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.15)
property real progressRatio: currentPlayer && currentPlayer.length > 0 ? (currentPosition / currentPlayer.length) : 0
Rectangle {
id: progressFill
width: progressBarBackground.progressRatio * parent.width
height: parent.height
radius: parent.radius
color: Data.ThemeManager.accentColor
Behavior on width {
NumberAnimation {
duration: 200
}
}
}
// Interactive progress handle (circle)
Rectangle {
id: progressHandle
width: 16
height: 16
radius: 8
color: Data.ThemeManager.accentColor
border.color: Qt.lighter(Data.ThemeManager.accentColor, 1.3)
border.width: 1
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width / 2))
anchors.verticalCenter: parent.verticalCenter
visible: currentPlayer && currentPlayer.length > 0
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1.0
Behavior on scale {
NumberAnimation {
duration: 150
}
}
}
// Mouse area for seeking
MouseArea {
id: progressMouseArea
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && currentPlayer.length > 0 && currentPlayer.canSeek
onClicked: function (mouse) {
if (currentPlayer && currentPlayer.length > 0) {
let ratio = mouse.x / width;
let seekPosition = ratio * currentPlayer.length;
currentPlayer.position = seekPosition;
currentPosition = seekPosition;
}
}
onPositionChanged: function (mouse) {
if (pressed && currentPlayer && currentPlayer.length > 0) {
let ratio = Math.max(0, Math.min(1, mouse.x / width));
let seekPosition = ratio * currentPlayer.length;
currentPlayer.position = seekPosition;
currentPosition = seekPosition;
}
}
}
}
// Player selection dropdown (conditional visibility)
Rectangle {
width: parent.width
height: 38
radius: 20
color: Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2)
border.width: 1
visible: {
let playerCount = getAvailablePlayers().length;
let alwaysShow = Data.Settings.alwaysShowPlayerDropdown;
let shouldShow = alwaysShow || playerCount > 1;
return shouldShow;
}
Row {
anchors.fill: parent
anchors.margins: 6
anchors.leftMargin: 12
spacing: 8
Text {
anchors.verticalCenter: parent.verticalCenter
text: "Player:"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.family: "monospace"
font.pixelSize: 12
font.bold: true
}
ComboBox {
id: playerComboBox
anchors.verticalCenter: parent.verticalCenter
width: parent.width - parent.children[0].width - parent.spacing
height: 26
model: []
onActivated: function (index) {
selectedPlayerIndex = index;
updateCurrentPlayer();
}
background: Rectangle {
color: Qt.darker(Data.ThemeManager.bgColor, 1.3)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2)
border.width: 1
radius: 20
}
contentItem: Text {
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 22
anchors.verticalCenter: parent.verticalCenter
text: playerComboBox.currentText || "No players"
color: Data.ThemeManager.fgColor
font.family: "monospace"
font.pixelSize: 12
font.bold: true
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
indicator: Text {
anchors.right: parent.right
anchors.rightMargin: 4
anchors.verticalCenter: parent.verticalCenter
text: "expand_more"
font.family: "Material Symbols Outlined"
font.pixelSize: 12
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
}
popup: Popup {
y: playerComboBox.height + 2
width: playerComboBox.width
implicitHeight: contentItem.implicitHeight + 4
background: Rectangle {
color: Qt.darker(Data.ThemeManager.bgColor, 1.2)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
radius: 20
}
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: playerComboBox.popup.visible ? playerComboBox.delegateModel : null
currentIndex: playerComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {}
}
}
delegate: ItemDelegate {
width: playerComboBox.width
height: 28
background: Rectangle {
color: parent.hovered ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.15) : "transparent"
radius: 20
}
contentItem: Text {
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
text: modelData || ""
color: Data.ThemeManager.fgColor
font.family: "monospace"
font.pixelSize: 12
font.bold: true
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
}
}
}
// Media controls
Row {
width: parent.width
height: 35
spacing: 6
// Previous button
Rectangle {
width: (parent.width - parent.spacing * 4) * 0.2
height: parent.height
radius: height / 2
color: previousButton.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
MouseArea {
id: previousButton
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && currentPlayer.canGoPrevious
onClicked: if (currentPlayer)
currentPlayer.previous()
}
Text {
anchors.centerIn: parent
text: "skip_previous"
font.family: "Material Symbols Outlined"
font.pixelSize: 18
color: previousButton.enabled ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
}
// Play/Pause button
Rectangle {
width: (parent.width - parent.spacing * 4) * 0.3
height: parent.height
radius: height / 2
color: playButton.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: Data.ThemeManager.accentColor
border.width: 2
MouseArea {
id: playButton
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && (currentPlayer.canPlay || currentPlayer.canPause)
onClicked: {
if (currentPlayer) {
if (currentPlayer.isPlaying) {
currentPlayer.pause();
} else {
currentPlayer.play();
}
}
}
}
Text {
anchors.centerIn: parent
text: currentPlayer && currentPlayer.isPlaying ? "pause" : "play_arrow"
font.family: "Material Symbols Outlined"
font.pixelSize: 20
color: playButton.enabled ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
}
// Next button
Rectangle {
width: (parent.width - parent.spacing * 4) * 0.2
height: parent.height
radius: height / 2
color: nextButton.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
MouseArea {
id: nextButton
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && currentPlayer.canGoNext
onClicked: if (currentPlayer)
currentPlayer.next()
}
Text {
anchors.centerIn: parent
text: "skip_next"
font.family: "Material Symbols Outlined"
font.pixelSize: 18
color: nextButton.enabled ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
}
// Shuffle button
Rectangle {
width: (parent.width - parent.spacing * 4) * 0.15
height: parent.height
radius: height / 2
color: shuffleButton.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: currentPlayer && currentPlayer.shuffle ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
MouseArea {
id: shuffleButton
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && currentPlayer.canControl && currentPlayer.shuffleSupported
onClicked: {
if (currentPlayer && currentPlayer.shuffleSupported) {
currentPlayer.shuffle = !currentPlayer.shuffle;
}
}
}
Text {
anchors.centerIn: parent
text: "shuffle"
font.family: "Material Symbols Outlined"
font.pixelSize: 12
color: shuffleButton.enabled ? (currentPlayer && currentPlayer.shuffle ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
}
// Repeat button
Rectangle {
width: (parent.width - parent.spacing * 4) * 0.15
height: parent.height
radius: height / 2
color: repeatButton.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: currentPlayer && currentPlayer.loopState !== MprisLoopState.None ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
MouseArea {
id: repeatButton
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && currentPlayer.canControl && currentPlayer.loopSupported
onClicked: {
if (currentPlayer && currentPlayer.loopSupported) {
if (currentPlayer.loopState === MprisLoopState.None) {
currentPlayer.loopState = MprisLoopState.Track;
} else if (currentPlayer.loopState === MprisLoopState.Track) {
currentPlayer.loopState = MprisLoopState.Playlist;
} else {
currentPlayer.loopState = MprisLoopState.None;
}
}
}
}
Text {
anchors.centerIn: parent
text: currentPlayer && currentPlayer.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
font.family: "Material Symbols Outlined"
font.pixelSize: 12
color: repeatButton.enabled ? (currentPlayer && currentPlayer.loopState !== MprisLoopState.None ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
}
}
}
}
}

View File

@@ -0,0 +1,112 @@
import QtQuick
import "../../tabs" as Tabs
// Tab container with sliding animation
Item {
id: tabContainer
// Properties from parent
required property var shell
required property bool isRecording
required property var triggerMouseArea
property int currentTab: 0
// Signals to forward
signal recordingRequested
signal stopRecordingRequested
signal systemActionRequested(string action)
signal performanceActionRequested(string action)
// Hover detection combining all tab hovers
property bool isHovered: {
const tabHovers = [mainDashboard.isHovered, true // Calendar tab should stay open when active
, true // Clipboard tab should stay open when active
, true // Notification tab should stay open when active
, true // Music tab should stay open when active
, true // Settings tab should stay open when active
];
return tabHovers[currentTab] || false;
}
// Track when text inputs have focus for keyboard management
property bool textInputFocused: currentTab === 5 && settingsTab.anyTextInputFocused
clip: true
// Sliding content container
Row {
id: slidingRow
width: parent.width * 7 // 7 tabs wide
height: parent.height
spacing: 0
// Animate horizontal position based on current tab
x: -tabContainer.currentTab * tabContainer.width
Behavior on x {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
Tabs.MainDashboard {
id: mainDashboard
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isRecording: tabContainer.isRecording
triggerMouseArea: tabContainer.triggerMouseArea
onRecordingRequested: tabContainer.recordingRequested()
onStopRecordingRequested: tabContainer.stopRecordingRequested()
onSystemActionRequested: function (action) {
tabContainer.systemActionRequested(action);
}
onPerformanceActionRequested: function (action) {
tabContainer.performanceActionRequested(action);
}
}
Tabs.CalendarTab {
id: calendarTab
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isActive: tabContainer.currentTab === 1 || Math.abs(tabContainer.currentTab - 1) <= 1
}
Tabs.ClipboardTab {
id: clipboardTab
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isActive: tabContainer.currentTab === 2 || Math.abs(tabContainer.currentTab - 2) <= 1
}
Tabs.NotificationTab {
id: notificationTab
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isActive: tabContainer.currentTab === 3 || Math.abs(tabContainer.currentTab - 3) <= 1
}
Tabs.MusicTab {
id: musicTab
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isActive: tabContainer.currentTab === 5 || Math.abs(tabContainer.currentTab - 5) <= 1
}
Tabs.SettingsTab {
id: settingsTab
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isActive: tabContainer.currentTab === 6 || Math.abs(tabContainer.currentTab - 6) <= 1
}
}
}

View File

@@ -0,0 +1,139 @@
import QtQuick
import "root:/Data" as Data
// Tab navigation sidebar
Item {
id: tabNavigation
property int currentTab: 0
property var tabIcons: []
property bool containsMouse: sidebarMouseArea.containsMouse || tabColumn.containsMouse
MouseArea {
id: sidebarMouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
}
// Tab button background - matches system controls
Rectangle {
width: 38
height: tabColumn.height + 12
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
radius: 19
border.width: 1
border.color: Qt.lighter(Data.ThemeManager.bgColor, 1.3)
// Subtle inner shadow effect
Rectangle {
anchors.fill: parent
anchors.margins: 1
color: Qt.darker(Data.ThemeManager.bgColor, 1.05)
radius: parent.radius - 1
opacity: 0.3
}
}
// Tab icon buttons
Column {
id: tabColumn
spacing: 6
anchors.top: parent.top
anchors.topMargin: 6
anchors.horizontalCenter: parent.horizontalCenter
property bool containsMouse: {
for (let i = 0; i < tabRepeater.count; i++) {
const tab = tabRepeater.itemAt(i);
if (tab && tab.mouseArea && tab.mouseArea.containsMouse) {
return true;
}
}
return false;
}
Repeater {
id: tabRepeater
model: 7
delegate: Rectangle {
width: 30
height: 30
radius: 15
// Dynamic background based on state
color: {
if (tabNavigation.currentTab === index) {
return Data.ThemeManager.accentColor;
} else if (tabMouseArea.containsMouse) {
return Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.15);
} else {
return "transparent";
}
}
// Subtle shadow for active tab
Rectangle {
anchors.fill: parent
radius: parent.radius
color: "transparent"
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: tabNavigation.currentTab === index ? 0 : (tabMouseArea.containsMouse ? 1 : 0)
visible: tabNavigation.currentTab !== index
}
property alias mouseArea: tabMouseArea
MouseArea {
id: tabMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
tabNavigation.currentTab = index;
}
}
Text {
anchors.centerIn: parent
text: tabNavigation.tabIcons[index] || ""
font.family: "Material Symbols Outlined"
font.pixelSize: 16
color: {
if (tabNavigation.currentTab === index) {
return Data.ThemeManager.bgColor;
} else if (tabMouseArea.containsMouse) {
return Data.ThemeManager.accentColor;
} else {
return Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7);
}
}
// Smooth color transitions
Behavior on color {
ColorAnimation {
duration: 150
}
}
}
// Smooth transitions
Behavior on color {
ColorAnimation {
duration: 150
}
}
// Subtle scale effect on hover
scale: tabMouseArea.containsMouse ? 1.05 : 1.0
Behavior on scale {
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
}
}
}
}

View File

@@ -0,0 +1,758 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/Data" as Data
// Appearance settings content
Column {
width: parent.width
spacing: 20
// Theme Setting in Collapsible Section
SettingsCategory {
width: parent.width
title: "Theme Setting"
icon: "palette"
content: Component {
Column {
width: parent.width
spacing: 30 // Increased spacing between major sections
// Dark/Light Mode Switch
Column {
width: parent.width
spacing: 12
Text {
text: "Theme Mode"
color: Data.ThemeManager.fgColor
font.pixelSize: 15
font.bold: true
font.family: "monospace"
}
Row {
spacing: 16
anchors.horizontalCenter: parent.horizontalCenter
Text {
text: "Light"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.family: "monospace"
anchors.verticalCenter: parent.verticalCenter
}
// Toggle switch - enhanced design
Rectangle {
width: 64
height: 32
radius: 16
color: Data.ThemeManager.currentTheme.type === "dark" ? Qt.lighter(Data.ThemeManager.accentColor, 0.8) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.2)
border.width: 2
border.color: Data.ThemeManager.currentTheme.type === "dark" ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.4)
anchors.verticalCenter: parent.verticalCenter
// Inner track shadow
Rectangle {
anchors.fill: parent
anchors.margins: 2
radius: parent.radius - 2
color: "transparent"
border.width: 1
border.color: Qt.rgba(0, 0, 0, 0.1)
}
// Toggle handle
Rectangle {
id: toggleHandle
width: 26
height: 26
radius: 13
color: Data.ThemeManager.currentTheme.type === "dark" ? Data.ThemeManager.bgColor : Data.ThemeManager.panelBackground
border.width: 2
border.color: Data.ThemeManager.currentTheme.type === "dark" ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
anchors.verticalCenter: parent.verticalCenter
x: Data.ThemeManager.currentTheme.type === "dark" ? parent.width - width - 3 : 3
// Handle shadow
Rectangle {
anchors.centerIn: parent
anchors.verticalCenterOffset: 1
width: parent.width - 2
height: parent.height - 2
radius: parent.radius - 1
color: Qt.rgba(0, 0, 0, 0.1)
z: -1
}
// Handle highlight
Rectangle {
anchors.centerIn: parent
width: parent.width - 6
height: parent.height - 6
radius: parent.radius - 3
color: Qt.rgba(255, 255, 255, 0.15)
}
Behavior on x {
NumberAnimation {
duration: 250
easing.type: Easing.OutBack
easing.overshoot: 0.3
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
}
}
}
// Background color transition
Behavior on color {
ColorAnimation {
duration: 200
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
console.log("Theme switch clicked, current:", Data.ThemeManager.currentThemeId);
var currentFamily = Data.ThemeManager.currentThemeId.replace(/_dark$|_light$/, "");
var newType = Data.ThemeManager.currentTheme.type === "dark" ? "light" : "dark";
var newThemeId = currentFamily + "_" + newType;
console.log("Switching to:", newThemeId);
Data.ThemeManager.setTheme(newThemeId);
// Force update the settings if currentTheme isn't being saved properly
if (!Data.Settings.currentTheme) {
Data.Settings.currentTheme = newThemeId;
Data.Settings.saveSettings();
}
}
onEntered: {
parent.scale = 1.05;
}
onExited: {
parent.scale = 1.0;
}
}
Behavior on scale {
NumberAnimation {
duration: 150
easing.type: Easing.OutQuad
}
}
}
Text {
text: "Dark"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.family: "monospace"
anchors.verticalCenter: parent.verticalCenter
}
}
}
// Separator
Rectangle {
width: parent.width - 40
height: 1
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter
}
// Theme Selection
Column {
width: parent.width
spacing: 12
Text {
text: "Theme Family"
color: Data.ThemeManager.fgColor
font.pixelSize: 15
font.bold: true
font.family: "monospace"
}
Text {
text: "Choose your preferred theme family"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "monospace"
anchors.horizontalCenter: parent.horizontalCenter
}
// Compact 2x2 grid for themes
GridLayout {
columns: 2
columnSpacing: 8
rowSpacing: 8
anchors.horizontalCenter: parent.horizontalCenter
property var themeFamily: {
var currentFamily = Data.ThemeManager.currentThemeId.replace(/_dark$|_light$/, "");
return currentFamily;
}
property var themeFamilies: [
{
id: "oxocarbon",
name: "Oxocarbon",
description: "IBM Carbon"
},
{
id: "dracula",
name: "Dracula",
description: "Vibrant"
},
{
id: "gruvbox",
name: "Gruvbox",
description: "Retro"
},
{
id: "catppuccin",
name: "Catppuccin",
description: "Pastel"
},
{
id: "matugen",
name: "Matugen",
description: "Generated"
}
]
Repeater {
model: parent.themeFamilies
delegate: Rectangle {
Layout.preferredWidth: 140
Layout.preferredHeight: 50
radius: 10
color: parent.themeFamily === modelData.id ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: parent.themeFamily === modelData.id ? 2 : 1
border.color: parent.themeFamily === modelData.id ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 10
spacing: 6
// Compact theme preview colors
Row {
spacing: 1
property var previewTheme: Data.ThemeManager.themes[modelData.id + "_" + Data.ThemeManager.currentTheme.type] || Data.ThemeManager.themes[modelData.id + "_dark"]
Rectangle {
width: 4
height: 14
radius: 1
color: parent.previewTheme.base00
}
Rectangle {
width: 4
height: 14
radius: 1
color: parent.previewTheme.base0E
}
Rectangle {
width: 4
height: 14
radius: 1
color: parent.previewTheme.base0D
}
Rectangle {
width: 4
height: 14
radius: 1
color: parent.previewTheme.base0B
}
}
Column {
spacing: 1
anchors.verticalCenter: parent.verticalCenter
Text {
text: modelData.name
color: parent.parent.parent.parent.themeFamily === modelData.id ? Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
font.pixelSize: 12
font.bold: parent.parent.parent.parent.themeFamily === modelData.id
font.family: "monospace"
}
Text {
text: modelData.description
color: parent.parent.parent.parent.themeFamily === modelData.id ? Qt.rgba(Data.ThemeManager.bgColor.r, Data.ThemeManager.bgColor.g, Data.ThemeManager.bgColor.b, 0.8) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.pixelSize: 9
font.family: "monospace"
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
var themeType = Data.ThemeManager.currentTheme.type;
var newThemeId = modelData.id + "_" + themeType;
console.log("Theme card clicked:", newThemeId);
Data.ThemeManager.setTheme(newThemeId);
}
onEntered: {
parent.scale = 1.02;
}
onExited: {
parent.scale = 1.0;
}
}
Behavior on scale {
NumberAnimation {
duration: 150
easing.type: Easing.OutQuad
}
}
}
}
}
}
// Separator
Rectangle {
width: parent.width - 40
height: 1
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter
}
// Accent Colors
Column {
width: parent.width
spacing: 12
Text {
text: "Accent Colors"
color: Data.ThemeManager.fgColor
font.pixelSize: 15
font.bold: true
font.family: "monospace"
}
Text {
text: "Choose your preferred accent color for " + Data.ThemeManager.currentTheme.name
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "monospace"
wrapMode: Text.Wrap
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
}
// Compact flow layout for accent colors
Flow {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 20 // Margins to prevent clipping
spacing: 8
property var accentColors: {
var currentFamily = Data.ThemeManager.currentThemeId.replace(/_dark$|_light$/, "");
var themeColors = [];
// Theme-specific accent colors - reduced to 5 per theme for compactness
if (currentFamily === "dracula") {
themeColors.push({
name: "Magenta",
dark: "#ff79c6",
light: "#e91e63"
}, {
name: "Purple",
dark: "#bd93f9",
light: "#6c7ce0"
}, {
name: "Cyan",
dark: "#8be9fd",
light: "#17a2b8"
}, {
name: "Green",
dark: "#50fa7b",
light: "#27ae60"
}, {
name: "Orange",
dark: "#ffb86c",
light: "#f39c12"
});
} else if (currentFamily === "gruvbox") {
themeColors.push({
name: "Orange",
dark: "#fe8019",
light: "#d65d0e"
}, {
name: "Red",
dark: "#fb4934",
light: "#cc241d"
}, {
name: "Yellow",
dark: "#fabd2f",
light: "#d79921"
}, {
name: "Green",
dark: "#b8bb26",
light: "#98971a"
}, {
name: "Purple",
dark: "#d3869b",
light: "#b16286"
});
} else if (currentFamily === "catppuccin") {
themeColors.push({
name: "Mauve",
dark: "#cba6f7",
light: "#8839ef"
}, {
name: "Blue",
dark: "#89b4fa",
light: "#1e66f5"
}, {
name: "Teal",
dark: "#94e2d5",
light: "#179299"
}, {
name: "Green",
dark: "#a6e3a1",
light: "#40a02b"
}, {
name: "Peach",
dark: "#fab387",
light: "#fe640b"
});
} else if (currentFamily === "matugen") {
// Use dynamic matugen colors if available
if (Data.ThemeManager.matugen && Data.ThemeManager.matugen.isMatugenActive()) {
themeColors.push({
name: "Primary",
dark: Data.ThemeManager.matugen.getMatugenColor("primary") || "#adc6ff",
light: Data.ThemeManager.matugen.getMatugenColor("primary") || "#0f62fe"
}, {
name: "Secondary",
dark: Data.ThemeManager.matugen.getMatugenColor("secondary") || "#bfc6dc",
light: Data.ThemeManager.matugen.getMatugenColor("secondary") || "#6272a4"
}, {
name: "Tertiary",
dark: Data.ThemeManager.matugen.getMatugenColor("tertiary") || "#debcdf",
light: Data.ThemeManager.matugen.getMatugenColor("tertiary") || "#b16286"
}, {
name: "Surface",
dark: Data.ThemeManager.matugen.getMatugenColor("surface_tint") || "#adc6ff",
light: Data.ThemeManager.matugen.getMatugenColor("surface_tint") || "#0f62fe"
}, {
name: "Error",
dark: Data.ThemeManager.matugen.getMatugenColor("error") || "#ffb4ab",
light: Data.ThemeManager.matugen.getMatugenColor("error") || "#ba1a1a"
});
} else {
// Fallback matugen colors
themeColors.push({
name: "Primary",
dark: "#adc6ff",
light: "#0f62fe"
}, {
name: "Secondary",
dark: "#bfc6dc",
light: "#6272a4"
}, {
name: "Tertiary",
dark: "#debcdf",
light: "#b16286"
}, {
name: "Surface",
dark: "#adc6ff",
light: "#0f62fe"
}, {
name: "Error",
dark: "#ffb4ab",
light: "#ba1a1a"
});
}
} else {
// oxocarbon and fallback
themeColors.push({
name: "Purple",
dark: "#be95ff",
light: "#8a3ffc"
}, {
name: "Blue",
dark: "#78a9ff",
light: "#0f62fe"
}, {
name: "Cyan",
dark: "#3ddbd9",
light: "#007d79"
}, {
name: "Green",
dark: "#42be65",
light: "#198038"
}, {
name: "Pink",
dark: "#ff7eb6",
light: "#d12771"
});
}
return themeColors;
}
Repeater {
model: parent.accentColors
delegate: Rectangle {
width: 60
height: 50
radius: 10
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: Data.ThemeManager.accentColor.toString() === (Data.ThemeManager.currentTheme.type === "dark" ? modelData.dark : modelData.light) ? 3 : 1
border.color: Data.ThemeManager.accentColor.toString() === (Data.ThemeManager.currentTheme.type === "dark" ? modelData.dark : modelData.light) ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Column {
anchors.centerIn: parent
spacing: 4
Rectangle {
width: 20
height: 20
radius: 10
color: Data.ThemeManager.currentTheme.type === "dark" ? modelData.dark : modelData.light
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: modelData.name
color: Data.ThemeManager.fgColor
font.pixelSize: 9
font.family: "monospace"
font.bold: true
anchors.horizontalCenter: parent.horizontalCenter
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
// Set custom accent
Data.Settings.useCustomAccent = true;
Data.ThemeManager.setCustomAccent(modelData.dark, modelData.light);
}
onEntered: {
parent.scale = 1.05;
}
onExited: {
parent.scale = 1.0;
}
}
Behavior on scale {
NumberAnimation {
duration: 150
easing.type: Easing.OutQuad
}
}
}
}
}
}
}
}
}
// Animation Settings in Collapsible Section
SettingsCategory {
width: parent.width
title: "Animation Settings"
icon: "animation"
content: Component {
Column {
width: parent.width
spacing: 20
Text {
text: "Configure workspace change animations"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "monospace"
anchors.horizontalCenter: parent.horizontalCenter
}
// Workspace Burst Toggle
Row {
width: parent.width
height: 40
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 80
Text {
text: "Workspace Burst Effect"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "monospace"
}
Text {
text: "Expanding rings when switching workspaces"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.pixelSize: 11
font.family: "monospace"
}
}
// Toggle switch for burst
Rectangle {
width: 50
height: 25
radius: 12.5
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
color: Data.Settings.workspaceBurstEnabled ? Qt.lighter(Data.ThemeManager.accentColor, 0.8) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.2)
border.width: 1
border.color: Data.Settings.workspaceBurstEnabled ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.4)
Rectangle {
width: 20
height: 20
radius: 10
color: Data.ThemeManager.bgColor
border.width: 1.5
border.color: Data.Settings.workspaceBurstEnabled ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
anchors.verticalCenter: parent.verticalCenter
x: Data.Settings.workspaceBurstEnabled ? parent.width - width - 2.5 : 2.5
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
}
}
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.workspaceBurstEnabled = !Data.Settings.workspaceBurstEnabled;
}
}
}
}
// Workspace Glow Toggle
Row {
width: parent.width
height: 40
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 80
Text {
text: "Workspace Shadow Glow"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "monospace"
}
Text {
text: "Accent color glow in workspace shadow"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.pixelSize: 11
font.family: "monospace"
}
}
// Toggle switch for glow
Rectangle {
width: 50
height: 25
radius: 12.5
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
color: Data.Settings.workspaceGlowEnabled ? Qt.lighter(Data.ThemeManager.accentColor, 0.8) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.2)
border.width: 1
border.color: Data.Settings.workspaceGlowEnabled ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.4)
Rectangle {
width: 20
height: 20
radius: 10
color: Data.ThemeManager.bgColor
border.width: 1.5
border.color: Data.Settings.workspaceGlowEnabled ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
anchors.verticalCenter: parent.verticalCenter
x: Data.Settings.workspaceGlowEnabled ? parent.width - width - 2.5 : 2.5
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
}
Behavior on color {
ColorAnimation {
duration: 200
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
}
}
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.workspaceGlowEnabled = !Data.Settings.workspaceGlowEnabled;
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,129 @@
import QtQuick
import QtQuick.Controls
import "root:/Data" as Data
// Music Player settings content
Column {
width: parent.width
spacing: 20
// Auto-switch to active player
Column {
width: parent.width
spacing: 12
Text {
text: "Auto-switch to Active Player"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "monospace"
}
Text {
text: "Automatically switch to the player that starts playing music"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "monospace"
wrapMode: Text.Wrap
width: parent.width
}
Rectangle {
width: 200
height: 35
radius: 18
color: Data.Settings.autoSwitchPlayer ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Data.ThemeManager.accentColor
Behavior on color {
ColorAnimation {
duration: 200
}
}
Text {
anchors.centerIn: parent
text: Data.Settings.autoSwitchPlayer ? "Enabled" : "Disabled"
color: Data.Settings.autoSwitchPlayer ? Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "monospace"
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.autoSwitchPlayer = !Data.Settings.autoSwitchPlayer;
}
}
}
}
// Always show player dropdown
Column {
width: parent.width
spacing: 12
Text {
text: "Always Show Player Dropdown"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "monospace"
}
Text {
text: "Show the player selection dropdown even with only one player"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "monospace"
wrapMode: Text.Wrap
width: parent.width
}
Rectangle {
width: 200
height: 35
radius: 18
color: Data.Settings.alwaysShowPlayerDropdown ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Data.ThemeManager.accentColor
Behavior on color {
ColorAnimation {
duration: 200
}
}
Text {
anchors.centerIn: parent
text: Data.Settings.alwaysShowPlayerDropdown ? "Enabled" : "Disabled"
color: Data.Settings.alwaysShowPlayerDropdown ? Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "monospace"
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.alwaysShowPlayerDropdown = !Data.Settings.alwaysShowPlayerDropdown;
}
}
}
}
}

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