Compare commits
47 Commits
quickshell
...
90cd4bdd84
| Author | SHA1 | Date | |
|---|---|---|---|
| 90cd4bdd84 | |||
| e1e3e96626 | |||
| 16f11262ff | |||
| a2b63259ed | |||
| ab539b82ca | |||
| 05c2bc3627 | |||
|
464a162703
|
|||
|
8385671c4f
|
|||
|
433e26edaa
|
|||
|
c28d386c29
|
|||
|
a149d2a3df
|
|||
|
ea02f5b023
|
|||
|
bf904ec38a
|
|||
|
c419ac3a30
|
|||
|
244844242f
|
|||
|
f730d8d118
|
|||
|
590343e5eb
|
|||
|
5da17890cb
|
|||
|
52692a4a47
|
|||
|
f2cb379100
|
|||
|
e6a2690baa
|
|||
|
f642f9c9cc
|
|||
|
7d08e1f5ef
|
|||
|
3cb73506f1
|
|||
|
4be7cd0c0f
|
|||
|
2d038b2ae2
|
|||
|
dd8afc8954
|
|||
|
e69600cc77
|
|||
|
17ef457181
|
|||
|
a39b966a91
|
|||
|
a0b044dbc9
|
|||
|
94764e7a5a
|
|||
|
fef786d317
|
|||
|
8ca0a6cc0f
|
|||
|
3263d72b59
|
|||
|
1c7e5b9f05
|
|||
|
1da34bf704
|
|||
|
88107f9fe7
|
|||
|
5d1f622782
|
|||
|
0ed99176ad
|
|||
|
4dddb0e803
|
|||
|
4353f9da34
|
|||
| 66e772ce62 | |||
| 422259838b | |||
| 390e75ce8e | |||
| a3b627359b | |||
| be75d12e9c |
@@ -1,8 +1,7 @@
|
|||||||
keys:
|
keys:
|
||||||
- &imxyy-nix age1jf5pg2x6ta8amj40xdy0stvcvrdlkwc2nrwtmkpymu0qclk0eg5qmm9kns
|
- &imxyy-nix ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEFLUkyeaK8ZPPZdVNEmtx8zvoxi7xqS2Z6oxRBuUPO
|
||||||
- &imxyy-nix-server age1hpgg6psejh4y6jcdd34wxuml75fnweqpe0kh8376yqsctsfn9qxs037kk6
|
- &imxyy-nix-server ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB28jpN+h5euh3NtdN+A+EtqgIatC22e4i1TPTioKire
|
||||||
- &imxyy-nix-x16 age1r0fv0tagxupfacv0aaxk5ss7sqvswv6kq8tk3x46ndqrj6f5afvqegahxq
|
- &imxyy-nix-x16 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMb5G/ieEYBOng66YeyttBQLThyM6W//z2POsNyq4Rw/
|
||||||
- &imxyy-cloudwin age1tp7th3rrv3x0l6jl76n0hjqjp223w2y586pkgr0hcjwdm254jd5shkj6a8
|
|
||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: secrets/.*\.(yaml|toml|json|env|dae|txt|conf)$
|
- path_regex: secrets/.*\.(yaml|toml|json|env|dae|txt|conf)$
|
||||||
key_groups:
|
key_groups:
|
||||||
@@ -10,4 +9,3 @@ creation_rules:
|
|||||||
- *imxyy-nix
|
- *imxyy-nix
|
||||||
- *imxyy-nix-server
|
- *imxyy-nix-server
|
||||||
- *imxyy-nix-x16
|
- *imxyy-nix-x16
|
||||||
- *imxyy-cloudwin
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
{ config, hostname, ... }:
|
{
|
||||||
|
config,
|
||||||
|
hostname,
|
||||||
|
...
|
||||||
|
}:
|
||||||
{
|
{
|
||||||
# I prefer this to the default issue text
|
# I prefer this to the default issue text
|
||||||
# ported from ArchLinux IIRC
|
# ported from ArchLinux IIRC
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
sopsRoot,
|
secrets,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
sops.secrets.et-imxyy-nix-server-nixremote = {
|
sops.secrets.et-imxyy-nix-server-nixremote = {
|
||||||
sopsFile = sopsRoot + /et-imxyy-nix-server-nixremote.toml;
|
sopsFile = secrets.et-imxyy-nix-server-nixremote;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
environment.systemPackages = [ pkgs.easytier ];
|
environment.systemPackages = [ pkgs.easytier ];
|
||||||
@@ -15,10 +14,8 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix-server-nixremote.path}";
|
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix-server-nixremote.path}";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Restart = lib.mkOverride 500 "always";
|
Restart = "always";
|
||||||
RestartMaxDelaySec = lib.mkOverride 500 "1m";
|
RestartSec = 30;
|
||||||
RestartSec = lib.mkOverride 500 "100ms";
|
|
||||||
RestartSteps = lib.mkOverride 500 9;
|
|
||||||
User = "root";
|
User = "root";
|
||||||
};
|
};
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
@@ -37,6 +34,7 @@
|
|||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWOy0QmAyxENg/O5m3cus8U3c9jCLioivwcWsh5/a82 imxyy-hisense-pad"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWOy0QmAyxENg/O5m3cus8U3c9jCLioivwcWsh5/a82 imxyy-hisense-pad"
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8pivvE8PMtsOxmccfNhH/4KehDKhBfUfJbQZxo/SZT imxyy-ace5"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8pivvE8PMtsOxmccfNhH/4KehDKhBfUfJbQZxo/SZT imxyy-ace5"
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKALTBn/QSGcSPgMg0ViSazFcaA0+nEF05EJpjbsI6dE imxyy_soope_@imxyy-cloudwin"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKALTBn/QSGcSPgMg0ViSazFcaA0+nEF05EJpjbsI6dE imxyy_soope_@imxyy-cloudwin"
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMb5G/ieEYBOng66YeyttBQLThyM6W//z2POsNyq4Rw/ imxyy@imxyy-nix-x16"
|
||||||
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIENauvvhVMLsUwH9cPYsvnOg7VCL3a4yEiKm8I524TE efl@efl-nix"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIENauvvhVMLsUwH9cPYsvnOg7VCL3a4yEiKm8I524TE efl@efl-nix"
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
services.postgresql.ensureDatabases = [ "coder" ];
|
services.postgresql.ensureDatabases = [ "coder" ];
|
||||||
virtualisation.oci-containers = {
|
virtualisation.oci-containers.containers.coder = {
|
||||||
containers = {
|
|
||||||
coder = {
|
|
||||||
image = "ghcr.io/coder/coder:latest";
|
image = "ghcr.io/coder/coder:latest";
|
||||||
environment = {
|
environment = {
|
||||||
CODER_ACCESS_URL = "https://coder.imxyy.top";
|
CODER_ACCESS_URL = "https://coder.imxyy.top";
|
||||||
@@ -26,8 +24,6 @@
|
|||||||
];
|
];
|
||||||
ports = [ "8086:8086" ];
|
ports = [ "8086:8086" ];
|
||||||
};
|
};
|
||||||
};
|
|
||||||
};
|
|
||||||
services.caddy.virtualHosts."coder.imxyy.top" = {
|
services.caddy.virtualHosts."coder.imxyy.top" = {
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
reverse_proxy :8086 {
|
reverse_proxy :8086 {
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
{
|
{
|
||||||
virtualisation.oci-containers = {
|
virtualisation.oci-containers.containers.sun-panel = {
|
||||||
containers = {
|
|
||||||
sun-panel = {
|
|
||||||
image = "hslr/sun-panel:latest";
|
image = "hslr/sun-panel:latest";
|
||||||
volumes = [
|
volumes = [
|
||||||
"/var/lib/sun-panel:/app/conf"
|
"/var/lib/sun-panel:/app/conf"
|
||||||
];
|
];
|
||||||
ports = [ "8085:3002" ];
|
ports = [ "8085:3002" ];
|
||||||
};
|
};
|
||||||
};
|
|
||||||
};
|
|
||||||
services.caddy.virtualHosts."home.imxyy.top" = {
|
services.caddy.virtualHosts."home.imxyy.top" = {
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
reverse_proxy :8085
|
reverse_proxy :8085
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
|
users.users.immich = {
|
||||||
|
home = "/mnt/nas/immich";
|
||||||
|
createHome = true;
|
||||||
|
};
|
||||||
services.immich = {
|
services.immich = {
|
||||||
enable = true;
|
enable = true;
|
||||||
host = "127.0.0.1";
|
host = "127.0.0.1";
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
WorkingDirectory = "/opt/minecraft/fabric1.20.6";
|
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";
|
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";
|
Restart = "always";
|
||||||
RestartSec = "10s";
|
RestartSec = 120;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
my.persist = {
|
my.persist = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{ config, sopsRoot, ... }:
|
{ config, secrets, ... }:
|
||||||
{
|
{
|
||||||
sops.secrets.minio-env = {
|
sops.secrets.minio-env = {
|
||||||
sopsFile = sopsRoot + /minio.env;
|
sopsFile = secrets.minio;
|
||||||
format = "dotenv";
|
format = "dotenv";
|
||||||
};
|
};
|
||||||
services.minio = {
|
services.minio = {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
username,
|
username,
|
||||||
sopsRoot,
|
secrets,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
@@ -131,17 +131,17 @@
|
|||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEFLUkyeaK8ZPPZdVNEmtx8zvoxi7xqS2Z6oxRBuUPO imxyy@imxyy-nix"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEFLUkyeaK8ZPPZdVNEmtx8zvoxi7xqS2Z6oxRBuUPO imxyy@imxyy-nix"
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWOy0QmAyxENg/O5m3cus8U3c9jCLioivwcWsh5/a82 imxyy-hisense-pad"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWOy0QmAyxENg/O5m3cus8U3c9jCLioivwcWsh5/a82 imxyy-hisense-pad"
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8pivvE8PMtsOxmccfNhH/4KehDKhBfUfJbQZxo/SZT imxyy-ace5"
|
"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"
|
||||||
];
|
];
|
||||||
users.users.${username}.openssh.authorizedKeys.keys = [
|
users.users.${username}.openssh.authorizedKeys.keys = [
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEFLUkyeaK8ZPPZdVNEmtx8zvoxi7xqS2Z6oxRBuUPO imxyy@imxyy-nix"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEFLUkyeaK8ZPPZdVNEmtx8zvoxi7xqS2Z6oxRBuUPO imxyy@imxyy-nix"
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWOy0QmAyxENg/O5m3cus8U3c9jCLioivwcWsh5/a82 imxyy-hisense-pad"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWOy0QmAyxENg/O5m3cus8U3c9jCLioivwcWsh5/a82 imxyy-hisense-pad"
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8pivvE8PMtsOxmccfNhH/4KehDKhBfUfJbQZxo/SZT imxyy-ace5"
|
"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"
|
||||||
];
|
];
|
||||||
|
|
||||||
sops.secrets.dae-imxyy-nix-server = {
|
sops.secrets.dae-imxyy-nix-server = {
|
||||||
sopsFile = sopsRoot + /dae-imxyy-nix-server.dae;
|
sopsFile = secrets.dae-imxyy-nix-server;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
services.dae = {
|
services.dae = {
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
};
|
};
|
||||||
systemd.services.dae.after = [ "sops-nix.service" ];
|
systemd.services.dae.after = [ "sops-nix.service" ];
|
||||||
sops.secrets.mihomo = {
|
sops.secrets.mihomo = {
|
||||||
sopsFile = sopsRoot + /mihomo.yaml;
|
sopsFile = secrets.mihomo;
|
||||||
format = "yaml";
|
format = "yaml";
|
||||||
key = "";
|
key = "";
|
||||||
};
|
};
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets.frp-env = {
|
sops.secrets.frp-env = {
|
||||||
sopsFile = sopsRoot + /frp.env;
|
sopsFile = secrets.frp;
|
||||||
format = "dotenv";
|
format = "dotenv";
|
||||||
};
|
};
|
||||||
systemd.services.frp.serviceConfig.EnvironmentFile = [
|
systemd.services.frp.serviceConfig.EnvironmentFile = [
|
||||||
@@ -407,6 +407,21 @@
|
|||||||
customDomains = [ "immich.imxyy.top" ];
|
customDomains = [ "immich.imxyy.top" ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "memo-http";
|
||||||
|
type = "http";
|
||||||
|
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 = "minecraft";
|
name = "minecraft";
|
||||||
type = "tcp";
|
type = "tcp";
|
||||||
@@ -449,12 +464,6 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
systemd.services."headscale" = {
|
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 = [
|
after = [
|
||||||
"podman-obligator.service"
|
"podman-obligator.service"
|
||||||
];
|
];
|
||||||
@@ -464,7 +473,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets.et-imxyy-nix-server = {
|
sops.secrets.et-imxyy-nix-server = {
|
||||||
sopsFile = sopsRoot + /et-imxyy-nix-server.toml;
|
sopsFile = secrets.et-imxyy-nix-server;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
environment.systemPackages = [ pkgs.easytier ];
|
environment.systemPackages = [ pkgs.easytier ];
|
||||||
@@ -472,10 +481,8 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix-server.path}";
|
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix-server.path}";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Restart = lib.mkOverride 500 "always";
|
Restart = "always";
|
||||||
RestartMaxDelaySec = lib.mkOverride 500 "1m";
|
RestartSec = 30;
|
||||||
RestartSec = lib.mkOverride 500 "100ms";
|
|
||||||
RestartSteps = lib.mkOverride 500 9;
|
|
||||||
User = "root";
|
User = "root";
|
||||||
};
|
};
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
@@ -485,9 +492,7 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
virtualisation.oci-containers = {
|
virtualisation.oci-containers.containers.obligator = {
|
||||||
containers = {
|
|
||||||
obligator = {
|
|
||||||
image = "anderspitman/obligator:latest";
|
image = "anderspitman/obligator:latest";
|
||||||
volumes = [
|
volumes = [
|
||||||
"/var/lib/obligator:/data"
|
"/var/lib/obligator:/data"
|
||||||
@@ -505,8 +510,6 @@
|
|||||||
"1616"
|
"1616"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
|
||||||
};
|
|
||||||
services.caddy.virtualHosts."headscale.imxyy.top" = {
|
services.caddy.virtualHosts."headscale.imxyy.top" = {
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
reverse_proxy :8080 {
|
reverse_proxy :8080 {
|
||||||
@@ -571,6 +574,7 @@
|
|||||||
"ai"
|
"ai"
|
||||||
"sy"
|
"sy"
|
||||||
"minio"
|
"minio"
|
||||||
|
"immich"
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
username,
|
username,
|
||||||
sopsRoot,
|
secrets,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
@@ -13,17 +13,16 @@
|
|||||||
timeout = 0;
|
timeout = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
hardware.bluetooth.enable = true;
|
|
||||||
hardware.bluetooth.powerOnBoot = true;
|
|
||||||
|
|
||||||
systemd.services.nix-daemon = {
|
systemd.services.nix-daemon = {
|
||||||
environment.TMPDIR = "/var/cache/nix";
|
environment.TMPDIR = "/var/cache/nix";
|
||||||
serviceConfig.CacheDirectory = "nix";
|
serviceConfig.CacheDirectory = "nix";
|
||||||
};
|
};
|
||||||
environment.variables.NIX_REMOTE = "daemon";
|
environment.variables.NIX_REMOTE = "daemon";
|
||||||
|
|
||||||
|
my.audio.enable = false;
|
||||||
|
|
||||||
sops.secrets.imxyy-nix-server-hashed-password = {
|
sops.secrets.imxyy-nix-server-hashed-password = {
|
||||||
sopsFile = sopsRoot + /imxyy-nix-server-hashed-password.txt;
|
sopsFile = secrets.imxyy-nix-server-hashed-password;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
neededForUsers = true;
|
neededForUsers = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
sopsRoot,
|
secrets,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
sops.secrets = {
|
sops.secrets = {
|
||||||
flatnote-env = {
|
flatnote-env = {
|
||||||
sopsFile = sopsRoot + /flatnote.env;
|
sopsFile = secrets.flatnote;
|
||||||
format = "dotenv";
|
format = "dotenv";
|
||||||
};
|
};
|
||||||
siyuan-env = {
|
siyuan-env = {
|
||||||
sopsFile = sopsRoot + /siyuan.env;
|
sopsFile = secrets.siyuan;
|
||||||
format = "dotenv";
|
format = "dotenv";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
virtualisation.oci-containers = {
|
virtualisation.oci-containers.containers = {
|
||||||
containers = {
|
|
||||||
flatnotes = {
|
flatnotes = {
|
||||||
image = "dullage/flatnotes:latest";
|
image = "dullage/flatnotes:latest";
|
||||||
volumes = [
|
volumes = [
|
||||||
@@ -44,6 +43,12 @@
|
|||||||
];
|
];
|
||||||
ports = [ "8095:6806" ];
|
ports = [ "8095:6806" ];
|
||||||
};
|
};
|
||||||
|
memos = {
|
||||||
|
image = "neosmemo/memos:stable";
|
||||||
|
volumes = [
|
||||||
|
"/mnt/nas/memos:/var/opt/memos"
|
||||||
|
];
|
||||||
|
ports = [ "8097:5230" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
services.caddy.virtualHosts = {
|
services.caddy.virtualHosts = {
|
||||||
@@ -57,5 +62,10 @@
|
|||||||
reverse_proxy :8095
|
reverse_proxy :8095
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
"memo.imxyy.top" = {
|
||||||
|
extraConfig = ''
|
||||||
|
reverse_proxy :8097
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{ config, sopsRoot, ... }:
|
{ config, secrets, ... }:
|
||||||
{
|
{
|
||||||
sops.secrets.vaultwarden-env = {
|
sops.secrets.vaultwarden-env = {
|
||||||
sopsFile = sopsRoot + /vaultwarden.env;
|
sopsFile = secrets.vaultwarden;
|
||||||
format = "dotenv";
|
format = "dotenv";
|
||||||
};
|
};
|
||||||
services.postgresql.ensureUsers = [
|
services.postgresql.ensureUsers = [
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
boot = {
|
boot = {
|
||||||
initrd.kernelModules = [
|
initrd.kernelModules = lib.mkBefore [
|
||||||
"vfio_pci"
|
"vfio_pci"
|
||||||
"vfio"
|
"vfio"
|
||||||
"vfio_iommu_type1"
|
"vfio_iommu_type1"
|
||||||
|
|||||||
@@ -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";
|
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 = {
|
my = {
|
||||||
sops.sshKeyPath = "/home/${username}/.ssh/id_ed25519";
|
sops.sshKeyFile = "/home/${username}/.ssh/id_ed25519";
|
||||||
coding.all.enable = true;
|
coding.all.enable = true;
|
||||||
coding.editor.vscode.enable = lib.mkForce false;
|
coding.editor.vscode.enable = lib.mkForce false;
|
||||||
cli.misc.enable = true;
|
cli.misc.enable = true;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
username,
|
username,
|
||||||
...
|
...
|
||||||
@@ -10,8 +8,6 @@
|
|||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
localsend
|
localsend
|
||||||
|
|
||||||
rclone
|
|
||||||
|
|
||||||
wpsoffice-cn
|
wpsoffice-cn
|
||||||
wps-office-fonts
|
wps-office-fonts
|
||||||
ttf-wps-fonts
|
ttf-wps-fonts
|
||||||
@@ -20,24 +16,32 @@
|
|||||||
anki
|
anki
|
||||||
|
|
||||||
ayugram-desktop
|
ayugram-desktop
|
||||||
telegram-desktop
|
|
||||||
signal-desktop
|
signal-desktop
|
||||||
discord
|
|
||||||
qq
|
qq
|
||||||
wechat
|
wechat
|
||||||
|
|
||||||
gnome-clocks
|
gnome-clocks
|
||||||
|
|
||||||
wineWowPackages.waylandFull
|
|
||||||
|
|
||||||
pavucontrol
|
|
||||||
pamixer
|
|
||||||
];
|
];
|
||||||
programs.zsh = {
|
programs.zsh = {
|
||||||
sessionVariables = {
|
sessionVariables = {
|
||||||
PATH = "/home/${username}/bin:$PATH";
|
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 = {
|
my = {
|
||||||
@@ -46,8 +50,6 @@
|
|||||||
coding.all.enable = true;
|
coding.all.enable = true;
|
||||||
desktop.all.enable = true;
|
desktop.all.enable = true;
|
||||||
|
|
||||||
desktop.browser.librewolf.enable = lib.mkForce false;
|
|
||||||
|
|
||||||
i18n.fcitx5.enable = true;
|
i18n.fcitx5.enable = true;
|
||||||
|
|
||||||
xdg = {
|
xdg = {
|
||||||
@@ -92,15 +94,6 @@
|
|||||||
"image/png" = imageviewer;
|
"image/png" = imageviewer;
|
||||||
"image/webp" = 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 = {
|
persist = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -118,23 +111,24 @@
|
|||||||
".local/state"
|
".local/state"
|
||||||
".local/share/Anki2"
|
".local/share/Anki2"
|
||||||
".local/share/shotwell"
|
".local/share/shotwell"
|
||||||
".local/share/cheat.sh"
|
|
||||||
".local/share/Kingsoft"
|
".local/share/Kingsoft"
|
||||||
|
|
||||||
".local/share/AyuGramDesktop"
|
".local/share/AyuGramDesktop"
|
||||||
".local/share/TelegramDesktop"
|
|
||||||
".config/Signal"
|
".config/Signal"
|
||||||
".config/discord"
|
|
||||||
".config/QQ"
|
".config/QQ"
|
||||||
".xwechat"
|
".xwechat"
|
||||||
|
|
||||||
".config/Kingsoft"
|
".config/Kingsoft"
|
||||||
".config/dconf"
|
".config/dconf"
|
||||||
".config/gh"
|
|
||||||
".config/pulse"
|
|
||||||
".config/pip"
|
".config/pip"
|
||||||
".config/libreoffice"
|
|
||||||
".config/sunshine"
|
".config/sunshine"
|
||||||
|
|
||||||
|
".gemini"
|
||||||
|
".claude"
|
||||||
|
".claude-code-router"
|
||||||
|
];
|
||||||
|
homeFiles = [
|
||||||
|
".claude.json"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
sopsRoot,
|
secrets,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
@@ -10,10 +9,51 @@
|
|||||||
"biosdevname=0"
|
"biosdevname=0"
|
||||||
"net.ifnames=0"
|
"net.ifnames=0"
|
||||||
];
|
];
|
||||||
networking.networkmanager.enable = true;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
sops.secrets.dae-imxyy-nix-x16 = {
|
sops.secrets.dae-imxyy-nix-x16 = {
|
||||||
sopsFile = sopsRoot + /dae-imxyy-nix-x16.dae;
|
sopsFile = secrets.dae-imxyy-nix-x16;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
services.dae = {
|
services.dae = {
|
||||||
@@ -22,7 +62,7 @@
|
|||||||
};
|
};
|
||||||
systemd.services.dae.after = [ "sops-nix.service" ];
|
systemd.services.dae.after = [ "sops-nix.service" ];
|
||||||
sops.secrets.mihomo = {
|
sops.secrets.mihomo = {
|
||||||
sopsFile = sopsRoot + /mihomo.yaml;
|
sopsFile = secrets.mihomo;
|
||||||
format = "yaml";
|
format = "yaml";
|
||||||
key = "";
|
key = "";
|
||||||
};
|
};
|
||||||
@@ -34,7 +74,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets.et-imxyy-nix-x16 = {
|
sops.secrets.et-imxyy-nix-x16 = {
|
||||||
sopsFile = sopsRoot + /et-imxyy-nix-x16.toml;
|
sopsFile = secrets.et-imxyy-nix-x16;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
@@ -44,10 +84,8 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix-x16.path}";
|
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix-x16.path}";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Restart = lib.mkOverride 500 "always";
|
Restart = "always";
|
||||||
RestartMaxDelaySec = lib.mkOverride 500 "1m";
|
RestartSec = 30;
|
||||||
RestartSec = lib.mkOverride 500 "100ms";
|
|
||||||
RestartSteps = lib.mkOverride 500 9;
|
|
||||||
User = "root";
|
User = "root";
|
||||||
};
|
};
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
lib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
config,
|
config,
|
||||||
username,
|
username,
|
||||||
sopsRoot,
|
secrets,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
@@ -36,36 +35,6 @@
|
|||||||
};
|
};
|
||||||
environment.variables.NIX_REMOTE = "daemon";
|
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 = {
|
fonts = {
|
||||||
enableDefaultPackages = false;
|
enableDefaultPackages = false;
|
||||||
fontDir.enable = true;
|
fontDir.enable = true;
|
||||||
@@ -102,6 +71,9 @@
|
|||||||
|
|
||||||
services.printing.enable = true;
|
services.printing.enable = true;
|
||||||
|
|
||||||
|
services.upower.enable = true;
|
||||||
|
services.power-profiles-daemon.enable = true;
|
||||||
|
|
||||||
services.keyd = {
|
services.keyd = {
|
||||||
enable = true;
|
enable = true;
|
||||||
keyboards.default.settings = {
|
keyboards.default.settings = {
|
||||||
@@ -134,7 +106,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
sops.secrets.imxyy-nix-rclone = {
|
sops.secrets.imxyy-nix-rclone = {
|
||||||
sopsFile = sopsRoot + /imxyy-nix-rclone.conf;
|
sopsFile = secrets.imxyy-nix-rclone;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
fileSystems = {
|
fileSystems = {
|
||||||
@@ -154,21 +126,6 @@
|
|||||||
"vfs-cache-mode=full"
|
"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" ];
|
my.persist.nixosDirs = [ "/etc/NetworkManager/system-connections" ];
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
username,
|
username,
|
||||||
...
|
...
|
||||||
@@ -10,8 +8,6 @@
|
|||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
localsend
|
localsend
|
||||||
|
|
||||||
rclone
|
|
||||||
|
|
||||||
wpsoffice-cn
|
wpsoffice-cn
|
||||||
wps-office-fonts
|
wps-office-fonts
|
||||||
ttf-wps-fonts
|
ttf-wps-fonts
|
||||||
@@ -20,18 +16,11 @@
|
|||||||
anki
|
anki
|
||||||
|
|
||||||
ayugram-desktop
|
ayugram-desktop
|
||||||
telegram-desktop
|
|
||||||
signal-desktop
|
signal-desktop
|
||||||
discord
|
|
||||||
qq
|
qq
|
||||||
wechat
|
wechat
|
||||||
|
|
||||||
gnome-clocks
|
gnome-clocks
|
||||||
|
|
||||||
wineWowPackages.waylandFull
|
|
||||||
|
|
||||||
pavucontrol
|
|
||||||
pamixer
|
|
||||||
];
|
];
|
||||||
programs.zsh = {
|
programs.zsh = {
|
||||||
shellAliases = {
|
shellAliases = {
|
||||||
@@ -91,8 +80,6 @@
|
|||||||
coding.all.enable = true;
|
coding.all.enable = true;
|
||||||
desktop.all.enable = true;
|
desktop.all.enable = true;
|
||||||
|
|
||||||
desktop.browser.librewolf.enable = lib.mkForce false;
|
|
||||||
|
|
||||||
i18n.fcitx5.enable = true;
|
i18n.fcitx5.enable = true;
|
||||||
|
|
||||||
xdg = {
|
xdg = {
|
||||||
@@ -137,16 +124,6 @@
|
|||||||
"image/png" = imageviewer;
|
"image/png" = imageviewer;
|
||||||
"image/webp" = 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 = {
|
persist = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -162,23 +139,19 @@
|
|||||||
".local/state"
|
".local/state"
|
||||||
".local/share/Anki2"
|
".local/share/Anki2"
|
||||||
".local/share/shotwell"
|
".local/share/shotwell"
|
||||||
".local/share/cheat.sh"
|
|
||||||
".local/share/Kingsoft"
|
".local/share/Kingsoft"
|
||||||
|
|
||||||
".local/share/AyuGramDesktop"
|
".local/share/AyuGramDesktop"
|
||||||
".local/share/TelegramDesktop"
|
|
||||||
".config/Signal"
|
".config/Signal"
|
||||||
".config/discord"
|
|
||||||
".config/QQ"
|
".config/QQ"
|
||||||
".xwechat"
|
".xwechat"
|
||||||
|
|
||||||
".config/Kingsoft"
|
".config/Kingsoft"
|
||||||
".config/dconf"
|
".config/dconf"
|
||||||
".config/gh"
|
|
||||||
".config/pulse"
|
|
||||||
".config/pip"
|
".config/pip"
|
||||||
".config/libreoffice"
|
|
||||||
".config/sunshine"
|
".config/sunshine"
|
||||||
|
|
||||||
|
".gemini"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
sopsRoot,
|
secrets,
|
||||||
username,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
@@ -86,7 +85,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets.dae-imxyy-nix = {
|
sops.secrets.dae-imxyy-nix = {
|
||||||
sopsFile = sopsRoot + /dae-imxyy-nix.dae;
|
sopsFile = secrets.dae-imxyy-nix;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
services.dae = {
|
services.dae = {
|
||||||
@@ -95,7 +94,7 @@
|
|||||||
};
|
};
|
||||||
systemd.services.dae.after = [ "sops-nix.service" ];
|
systemd.services.dae.after = [ "sops-nix.service" ];
|
||||||
sops.secrets.mihomo = {
|
sops.secrets.mihomo = {
|
||||||
sopsFile = sopsRoot + /mihomo.yaml;
|
sopsFile = secrets.mihomo;
|
||||||
format = "yaml";
|
format = "yaml";
|
||||||
key = "";
|
key = "";
|
||||||
};
|
};
|
||||||
@@ -107,7 +106,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
sops.secrets.et-imxyy-nix = {
|
sops.secrets.et-imxyy-nix = {
|
||||||
sopsFile = sopsRoot + /et-imxyy-nix.toml;
|
sopsFile = secrets.et-imxyy-nix;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
environment.systemPackages = [ pkgs.easytier ];
|
environment.systemPackages = [ pkgs.easytier ];
|
||||||
@@ -115,10 +114,8 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix.path}";
|
script = "${pkgs.easytier}/bin/easytier-core -c ${config.sops.secrets.et-imxyy-nix.path}";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Restart = lib.mkOverride 500 "always";
|
Restart = "always";
|
||||||
RestartMaxDelaySec = lib.mkOverride 500 "1m";
|
RestartSec = 30;
|
||||||
RestartSec = lib.mkOverride 500 "100ms";
|
|
||||||
RestartSteps = lib.mkOverride 500 9;
|
|
||||||
User = "root";
|
User = "root";
|
||||||
};
|
};
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
pkgs,
|
pkgs,
|
||||||
config,
|
config,
|
||||||
username,
|
username,
|
||||||
sopsRoot,
|
secrets,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
@@ -22,7 +22,7 @@ let
|
|||||||
temp = line.split(" ")
|
temp = line.split(" ")
|
||||||
bus = temp[1]
|
bus = temp[1]
|
||||||
device = temp[3][:-1]
|
device = temp[3][:-1]
|
||||||
subprocess.run(["${lib.getExe usbreset}", f"/dev/bus/usb/{bus}/{device}"])
|
subprocess.run(["${lib.getExe' pkgs.usbutils "usbreset"}", f"{bus}/{device}"])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -36,44 +36,6 @@ let
|
|||||||
with open("/tmp/.btreseted", "w"):
|
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
|
in
|
||||||
{
|
{
|
||||||
security.pam.loginLimits = [
|
security.pam.loginLimits = [
|
||||||
@@ -111,36 +73,6 @@ in
|
|||||||
};
|
};
|
||||||
environment.variables.NIX_REMOTE = "daemon";
|
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 = {
|
fonts = {
|
||||||
enableDefaultPackages = false;
|
enableDefaultPackages = false;
|
||||||
fontDir.enable = true;
|
fontDir.enable = true;
|
||||||
@@ -225,7 +157,7 @@ in
|
|||||||
];
|
];
|
||||||
|
|
||||||
sops.secrets.imxyy-nix-rclone = {
|
sops.secrets.imxyy-nix-rclone = {
|
||||||
sopsFile = sopsRoot + /imxyy-nix-rclone.conf;
|
sopsFile = secrets.imxyy-nix-rclone;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
};
|
};
|
||||||
fileSystems = {
|
fileSystems = {
|
||||||
|
|||||||
231
flake.lock
generated
231
flake.lock
generated
@@ -37,11 +37,11 @@
|
|||||||
"base16-helix": {
|
"base16-helix": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748408240,
|
"lastModified": 1752979451,
|
||||||
"narHash": "sha256-9M2b1rMyMzJK0eusea0x3lyh3mu5nMeEDSc4RZkGm+g=",
|
"narHash": "sha256-0CQM+FkYy0fOO/sMGhOoNL80ftsAzYCg9VhIrodqusM=",
|
||||||
"owner": "tinted-theming",
|
"owner": "tinted-theming",
|
||||||
"repo": "base16-helix",
|
"repo": "base16-helix",
|
||||||
"rev": "6c711ab1a9db6f51e2f6887cc3345530b33e152e",
|
"rev": "27cf1e66e50abc622fb76a3019012dc07c678fac",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -98,6 +98,26 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"catppuccin": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1754950929,
|
||||||
|
"narHash": "sha256-75hsUMshZ5ZlO/X2JWfZqKHPM66uhUNsDG/Zozwh/xs=",
|
||||||
|
"owner": "catppuccin",
|
||||||
|
"repo": "nix",
|
||||||
|
"rev": "45512611f1537c75e439d508409efc6901286645",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "catppuccin",
|
||||||
|
"repo": "nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"chaotic": {
|
"chaotic": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-schemas": "flake-schemas",
|
"flake-schemas": "flake-schemas",
|
||||||
@@ -107,11 +127,11 @@
|
|||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752141190,
|
"lastModified": 1754215849,
|
||||||
"narHash": "sha256-RHNq77Z84BtLTwyRtrBffm5V9006Dqw4vh3vrvULlxM=",
|
"narHash": "sha256-GHTc3TlGSJ1DsPujJHRlNQOgBeotW+RPY9fcc6d4bRs=",
|
||||||
"owner": "chaotic-cx",
|
"owner": "chaotic-cx",
|
||||||
"repo": "nyx",
|
"repo": "nyx",
|
||||||
"rev": "ef0794b8e94eea166407141f7e92da75f6df925a",
|
"rev": "8456c9ee811787540b33890120476a7ea8de0ec0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -128,11 +148,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1750940343,
|
"lastModified": 1754000084,
|
||||||
"narHash": "sha256-qmc/jreM09MOwQ8dOa/+yyh99rU7TowSqo8L33VHfto=",
|
"narHash": "sha256-m3UMp3dJfGptOR8WDGYgaHfax7Wpad0wKfOI8xZLC1s=",
|
||||||
"owner": "Bali10050",
|
"owner": "Bali10050",
|
||||||
"repo": "Darkly",
|
"repo": "Darkly",
|
||||||
"rev": "77770c8d3c35f7ad39da2c57122c360096df0aac",
|
"rev": "39b4100b86c4531a5729e00e1584df9970b02468",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -171,11 +191,11 @@
|
|||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752129689,
|
"lastModified": 1754290399,
|
||||||
"narHash": "sha256-0Xq5tZbvgZvxbbxv6kRHFuZE4Tq2za016NXh32nX0+Q=",
|
"narHash": "sha256-KwYm1/FeLqP9uE4Sbw+j2nI2/ErNbc9Mn+LPcrEOpX0=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "70bb04a7de606a75ba0a2ee9d47b99802780b35d",
|
"rev": "f53ddf7518d85d59b58df6e9955b25b0ac25f569",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -457,11 +477,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748528448,
|
"lastModified": 1751817769,
|
||||||
"narHash": "sha256-Tyn+PgBm78Ibq28/WbEz8+pYJZMdbJKsyXMCpT6TjrM=",
|
"narHash": "sha256-j7ORyAJFNxYWwF+6fs2z5IcJ40TpVwrkFYU9/CGHwfA=",
|
||||||
"owner": "imxyy1soope1",
|
"owner": "imxyy1soope1",
|
||||||
"repo": "go-musicfox",
|
"repo": "go-musicfox",
|
||||||
"rev": "1870fd3501631577ad4daef1489b03885abcb037",
|
"rev": "179b9440b6f209e7fd6ade589117b72bdd9042a6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -500,11 +520,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751824240,
|
"lastModified": 1754174776,
|
||||||
"narHash": "sha256-aDDC0CHTlL7QDKWWhdbEgVPK6KwWt+ca0QkmHYZxMzI=",
|
"narHash": "sha256-Sp3FRM6xNwNtGzYH/HByjzJYHSQvwsW+lDMMZNF43PQ=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "fd9e55f5fac45a26f6169310afca64d56b681935",
|
"rev": "e6e2f43a62b7dbc8aa8b1adb7101b0d8b9395445",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -520,11 +540,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752202894,
|
"lastModified": 1754263839,
|
||||||
"narHash": "sha256-knafgng4gCjZIUMyAEWjxxdols6n/swkYnbWr+oF+1w=",
|
"narHash": "sha256-ck7lILfCNuunsLvExPI4Pw9OOCJksxXwozum24W8b+8=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "fab659b346c0d4252208434c3c4b3983a4b38fec",
|
"rev": "1d7abbd5454db97e0af51416f4960b3fb64a4773",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -542,11 +562,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1743604125,
|
"lastModified": 1752603129,
|
||||||
"narHash": "sha256-ZD61DNbsBt1mQbinAaaEqKaJk2RFo9R/j+eYWeGMx7A=",
|
"narHash": "sha256-S+wmHhwNQ5Ru689L2Gu8n1OD6s9eU9n9mD827JNR+kw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "180fd43eea296e62ae68e079fcf56aba268b9a1a",
|
"rev": "e8c19a3cec2814c754f031ab3ae7316b64da085b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -595,11 +615,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751529406,
|
"lastModified": 1754110197,
|
||||||
"narHash": "sha256-jwKDHyUycp678zDYa5Hyfq3msO73YMXdZPxp96dU7po=",
|
"narHash": "sha256-N7GWK2084EsNdwzwg6FCIgMrSau1WwzxGSNdPHx5Tak=",
|
||||||
"owner": "Jovian-Experiments",
|
"owner": "Jovian-Experiments",
|
||||||
"repo": "Jovian-NixOS",
|
"repo": "Jovian-NixOS",
|
||||||
"rev": "b2e5ce654e4f5bf8905c2e07a96dcf4966e6277d",
|
"rev": "04ce5c103eb621220d69102bc0ee27c3abd89204",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -658,21 +678,17 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"niri-stable": "niri-stable",
|
"niri-stable": "niri-stable",
|
||||||
"niri-unstable": "niri-unstable",
|
"niri-unstable": "niri-unstable",
|
||||||
"nixpkgs": [
|
"nixpkgs": "nixpkgs_5",
|
||||||
"nixpkgs"
|
"nixpkgs-stable": "nixpkgs-stable",
|
||||||
],
|
|
||||||
"nixpkgs-stable": [
|
|
||||||
"nixpkgs-stable"
|
|
||||||
],
|
|
||||||
"xwayland-satellite-stable": "xwayland-satellite-stable",
|
"xwayland-satellite-stable": "xwayland-satellite-stable",
|
||||||
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
|
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752078530,
|
"lastModified": 1754303580,
|
||||||
"narHash": "sha256-TrRmlYdhWcadWvBpDjB9Xlry4uT4ZUIO46d+o5tjtCQ=",
|
"narHash": "sha256-nAfOEv4uo+v9QjDEqJhyg4QfesTjYNlI2pOE17Qums4=",
|
||||||
"owner": "sodiboo",
|
"owner": "sodiboo",
|
||||||
"repo": "niri-flake",
|
"repo": "niri-flake",
|
||||||
"rev": "d231d92313192d4d0c78d6ef04167fed9dee87cf",
|
"rev": "24251fba7cd98d47e02c597a9dff9b28e7a7e2db",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -701,11 +717,11 @@
|
|||||||
"niri-unstable": {
|
"niri-unstable": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1750791124,
|
"lastModified": 1753983552,
|
||||||
"narHash": "sha256-F5iVU/hjoSHSSe0gllxm0PcAaseEtGNanYK5Ha3k2Tg=",
|
"narHash": "sha256-xnPPjASWt4/u+GmRdrHADBKKSCtIhTFY53CSKM+Xtfk=",
|
||||||
"owner": "YaLTeR",
|
"owner": "YaLTeR",
|
||||||
"repo": "niri",
|
"repo": "niri",
|
||||||
"rev": "37458d94b288945f6cfbd3c5c233f634d59f246c",
|
"rev": "91b6a111cf228fadaabba706d8f4d155efc63b3a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -804,11 +820,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752199438,
|
"lastModified": 1754260137,
|
||||||
"narHash": "sha256-xSBMmGtq8K4Qv80TMqREmESCAsRLJRHAbFH2T/2Bf1Y=",
|
"narHash": "sha256-IViMH6Fwj8nwO1nuYCqOTpjm9OK9rQ0w8nmoOwPlo98=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "NixOS-WSL",
|
"repo": "NixOS-WSL",
|
||||||
"rev": "d34d9412556d3a896e294534ccd25f53b6822e80",
|
"rev": "57ba096649fa4e12dc564e8e3c529255baf89b35",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -819,11 +835,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751984180,
|
"lastModified": 1753939845,
|
||||||
"narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=",
|
"narHash": "sha256-K2ViRJfdVGE8tpJejs8Qpvvejks1+A4GQej/lBk5y7I=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0",
|
"rev": "94def634a20494ee057c76998843c015909d6311",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -847,11 +863,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-master": {
|
"nixpkgs-master": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752206449,
|
"lastModified": 1754308354,
|
||||||
"narHash": "sha256-NVAbC/s4CupABWGXF8M9mDiVw/n0YCftxwc1KatVjDk=",
|
"narHash": "sha256-d14QlvGnylLicAIyPmzaysXayTW49RJxl/fyVXCpU34=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1bd4d0d4a678d48b63eb18f457d74df2fcee6c69",
|
"rev": "fa154dda31ab1a92efb0d1b79bacc6c6174c5333",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -863,11 +879,27 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-stable": {
|
"nixpkgs-stable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752203688,
|
"lastModified": 1754028485,
|
||||||
"narHash": "sha256-uJ054F5PVGPu5SvLPMevhdY/EfK0X5DUyRtXhQYNUyo=",
|
"narHash": "sha256-IiiXB3BDTi6UqzAZcf2S797hWEPCRZOwyNThJIYhUfk=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "59e69648d345d6e8fef86158c555730fa12af9de",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-25.05",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-stable_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1754307408,
|
||||||
|
"narHash": "sha256-rt9CztaeSiXr1lwJ4O+mSpJc/T2QY3s7Zsp7qeKzQ8k=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a70a12c75e13aa546c20ce0fe515de634d52c39e",
|
"rev": "c33582214e37a77e2b36737ece61df25f505ac93",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -879,11 +911,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752124863,
|
"lastModified": 1754284898,
|
||||||
"narHash": "sha256-5rWuf6RAlMDp/CAEuyYEz7ryxzgjxOCgUDhWEef864c=",
|
"narHash": "sha256-wzM6HN0xxyooekXfl7p5P4Bn0LieOKOfsLg4DqY7XLk=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "40de82b434526744da778ed53c742c1282d9e75e",
|
"rev": "114484ca7213ac06fa7907e58dd8ef9d801d39f0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -943,11 +975,27 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_5": {
|
"nixpkgs_5": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751984180,
|
"lastModified": 1754214453,
|
||||||
"narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=",
|
"narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_6": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1754214453,
|
||||||
|
"narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0",
|
"rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -960,14 +1008,14 @@
|
|||||||
"nur": {
|
"nur": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts_3",
|
"flake-parts": "flake-parts_3",
|
||||||
"nixpkgs": "nixpkgs_5"
|
"nixpkgs": "nixpkgs_6"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752207112,
|
"lastModified": 1754303234,
|
||||||
"narHash": "sha256-dnVoQSGQqEGJQzS6iHAG95c0oFrezzBinwu1bDLj9J4=",
|
"narHash": "sha256-ZHx9RfMyPvW7efozz8JaMRoBG/2grSBIMi6o3PctOqU=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "NUR",
|
"repo": "NUR",
|
||||||
"rev": "f166dc14862dfec043f9545e8291cc4402f8b866",
|
"rev": "c6f06c7960982b40be721ccad3a4c295fce4d14d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -1001,28 +1049,9 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quickshell": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1752146885,
|
|
||||||
"narHash": "sha256-ZJK989GL+bTCQSxbG8v8/7tHMCEl/FPovkeDBNyClQE=",
|
|
||||||
"ref": "refs/heads/master",
|
|
||||||
"rev": "d7079b75241c6e2b67f2429996fa7679ffc052e2",
|
|
||||||
"revCount": 616,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"catppuccin": "catppuccin",
|
||||||
"chaotic": "chaotic",
|
"chaotic": "chaotic",
|
||||||
"darkly": "darkly",
|
"darkly": "darkly",
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
@@ -1037,10 +1066,9 @@
|
|||||||
"nixpkgs-unstable"
|
"nixpkgs-unstable"
|
||||||
],
|
],
|
||||||
"nixpkgs-master": "nixpkgs-master",
|
"nixpkgs-master": "nixpkgs-master",
|
||||||
"nixpkgs-stable": "nixpkgs-stable",
|
"nixpkgs-stable": "nixpkgs-stable_2",
|
||||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||||
"nur": "nur",
|
"nur": "nur",
|
||||||
"quickshell": "quickshell",
|
|
||||||
"sops-nix": "sops-nix",
|
"sops-nix": "sops-nix",
|
||||||
"stylix": "stylix",
|
"stylix": "stylix",
|
||||||
"zen": "zen"
|
"zen": "zen"
|
||||||
@@ -1049,11 +1077,11 @@
|
|||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752086493,
|
"lastModified": 1754218780,
|
||||||
"narHash": "sha256-USpVUdiWXDfPoh+agbvoBQaBhg3ZdKZgHXo/HikMfVo=",
|
"narHash": "sha256-M+bLCsYRYA7iudlZkeOf+Azm/1TUvihIq51OKia6KJ8=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "6e3abe164b9036048dce1a3aa65a7e7e5200c0d3",
|
"rev": "8d75311400a108d7ffe17dc9c38182c566952e6e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -1071,11 +1099,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751856221,
|
"lastModified": 1754189623,
|
||||||
"narHash": "sha256-/QE1eV0ckFvgRMcKjZqgdJDoXFNwSMepwRoBjaw2MCk=",
|
"narHash": "sha256-fstu5eb30UYwsxow0aQqkzxNxGn80UZjyehQVNVHuBk=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "34cae4b56929c5b340e1c5b10d9a98a425b2a51e",
|
"rev": "c582ff7f0d8a7ea689ae836dfb1773f1814f472a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -1091,15 +1119,16 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751606940,
|
"lastModified": 1752545238,
|
||||||
"narHash": "sha256-KrDPXobG7DFKTOteqdSVeL1bMVitDcy7otpVZWDE6MA=",
|
"narHash": "sha256-1BoqNz9ReFc5OBefSlwpp+FheotqK5pAPTEkkPqAlp0=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "3633fc4acf03f43b260244d94c71e9e14a2f6e0d",
|
"rev": "cb5bc3b04dc202201f3ab44cefc4a50db2325f20",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
|
"ref": "pull/779/merge",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -1125,11 +1154,11 @@
|
|||||||
"tinted-zed": "tinted-zed"
|
"tinted-zed": "tinted-zed"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752201883,
|
"lastModified": 1754264048,
|
||||||
"narHash": "sha256-SZVbQ4YThvYU50cJ4W4GNMy7/rVOJI8qmXqbEcRNsug=",
|
"narHash": "sha256-Yg1W0sFhBpnglfhWGlFmxzSmte1F157luHAADp5Hguk=",
|
||||||
"owner": "danth",
|
"owner": "danth",
|
||||||
"repo": "stylix",
|
"repo": "stylix",
|
||||||
"rev": "d395780b9c5c36f191b990b2021c71af180a1982",
|
"rev": "1b5e1c5642cf96e07daf14ae4c5ddd23d7ed5623",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -1269,11 +1298,11 @@
|
|||||||
"xwayland-satellite-unstable": {
|
"xwayland-satellite-unstable": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751228685,
|
"lastModified": 1752338000,
|
||||||
"narHash": "sha256-MENtauGBhJ+kDeFaawvWGXaFG3Il6qQzjaP0RmtfM0k=",
|
"narHash": "sha256-Fxlp/yKtynug0jyuauAmvZU2SzHCfwlwWf85j+IvQ0U=",
|
||||||
"owner": "Supreeeme",
|
"owner": "Supreeeme",
|
||||||
"repo": "xwayland-satellite",
|
"repo": "xwayland-satellite",
|
||||||
"rev": "557ebeb616e03d5e4a8049862bbbd1f02c6f020b",
|
"rev": "ba78881a68182ce338041846164cbfed0d70935c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -1290,11 +1319,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1752164817,
|
"lastModified": 1754193284,
|
||||||
"narHash": "sha256-LJFIx27IOUowLsJn5wci9mHZ4CesJsiAivQWDjnZPCc=",
|
"narHash": "sha256-ZGLXnpXy7vcTDLC1yLQSJOclsilvK+RNFVVEeO0ei/0=",
|
||||||
"owner": "0xc000022070",
|
"owner": "0xc000022070",
|
||||||
"repo": "zen-browser-flake",
|
"repo": "zen-browser-flake",
|
||||||
"rev": "9193992c4c2c4349b4280ec2b49648cae208fe63",
|
"rev": "ac2b25e76f5bfe1e30ae39409dfa450c5c1bc945",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
42
flake.nix
42
flake.nix
@@ -13,8 +13,9 @@
|
|||||||
# Nyxpkgs
|
# Nyxpkgs
|
||||||
chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable";
|
chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable";
|
||||||
|
|
||||||
# SOPS
|
# TODO: sops-nix: remove pr patch once merged
|
||||||
sops-nix.url = "github:Mic92/sops-nix";
|
# https://github.com/Mic92/sops-nix/pull/779
|
||||||
|
sops-nix.url = "github:Mic92/sops-nix/pull/779/merge";
|
||||||
sops-nix.inputs.nixpkgs.follows = "nixpkgs";
|
sops-nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
# Home manager
|
# Home manager
|
||||||
@@ -29,11 +30,6 @@
|
|||||||
|
|
||||||
# Niri
|
# Niri
|
||||||
niri.url = "github:sodiboo/niri-flake";
|
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.url = "github:Bali10050/Darkly";
|
||||||
darkly.inputs.nixpkgs.follows = "nixpkgs";
|
darkly.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
@@ -55,6 +51,9 @@
|
|||||||
zen.url = "github:0xc000022070/zen-browser-flake";
|
zen.url = "github:0xc000022070/zen-browser-flake";
|
||||||
zen.inputs.nixpkgs.follows = "nixpkgs";
|
zen.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
catppuccin.url = "github:catppuccin/nix";
|
||||||
|
catppuccin.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
infuse.url = "git+https://codeberg.org/amjoseph/infuse.nix";
|
infuse.url = "git+https://codeberg.org/amjoseph/infuse.nix";
|
||||||
infuse.flake = false;
|
infuse.flake = false;
|
||||||
|
|
||||||
@@ -71,10 +70,10 @@
|
|||||||
let
|
let
|
||||||
inherit (self) outputs;
|
inherit (self) outputs;
|
||||||
vars = import ./vars.nix;
|
vars = import ./vars.nix;
|
||||||
forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
|
forAllSystems = lib.genAttrs lib.systems.flakeExposed;
|
||||||
forAllHosts =
|
forAllHosts =
|
||||||
mkSystem:
|
mkSystem:
|
||||||
nixpkgs.lib.attrsets.mergeAttrsList (
|
lib.mergeAttrsList (
|
||||||
builtins.map (hostname: {
|
builtins.map (hostname: {
|
||||||
${hostname} = mkSystem hostname;
|
${hostname} = mkSystem hostname;
|
||||||
}) (builtins.attrNames (builtins.readDir ./config/hosts))
|
}) (builtins.attrNames (builtins.readDir ./config/hosts))
|
||||||
@@ -139,17 +138,6 @@
|
|||||||
darkly-qt5 = inputs.darkly.packages.${final.system}.darkly-qt5;
|
darkly-qt5 = inputs.darkly.packages.${final.system}.darkly-qt5;
|
||||||
darkly-qt6 = inputs.darkly.packages.${final.system}.darkly-qt6;
|
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: {
|
(final: prev: {
|
||||||
inherit lib;
|
inherit lib;
|
||||||
})
|
})
|
||||||
@@ -185,9 +173,16 @@
|
|||||||
outputs
|
outputs
|
||||||
hostname
|
hostname
|
||||||
;
|
;
|
||||||
sopsRoot = ./secrets;
|
secrets =
|
||||||
flake = ./.;
|
with lib.haumea;
|
||||||
} // vars;
|
load {
|
||||||
|
src = ./secrets;
|
||||||
|
loader = [
|
||||||
|
(matchers.always loaders.path)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// vars;
|
||||||
modules =
|
modules =
|
||||||
(lib.umport {
|
(lib.umport {
|
||||||
paths = [ ./modules ];
|
paths = [ ./modules ];
|
||||||
@@ -206,6 +201,7 @@
|
|||||||
inputs.impermanence.nixosModules.impermanence
|
inputs.impermanence.nixosModules.impermanence
|
||||||
inputs.home-manager.nixosModules.default
|
inputs.home-manager.nixosModules.default
|
||||||
inputs.niri.nixosModules.niri
|
inputs.niri.nixosModules.niri
|
||||||
|
inputs.catppuccin.nixosModules.catppuccin
|
||||||
home
|
home
|
||||||
pkgsConf
|
pkgsConf
|
||||||
];
|
];
|
||||||
|
|||||||
26
modules/audio.nix
Normal file
26
modules/audio.nix
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
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 ];
|
||||||
|
};
|
||||||
|
}
|
||||||
20
modules/bluetooth.nix
Normal file
20
modules/bluetooth.nix
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{ 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ lib.my.makeSwitch {
|
|||||||
];
|
];
|
||||||
config' = {
|
config' = {
|
||||||
my.cli.media = {
|
my.cli.media = {
|
||||||
cava.enable = true;
|
|
||||||
go-musicfox.enable = true;
|
go-musicfox.enable = true;
|
||||||
mpd.enable = true;
|
mpd.enable = true;
|
||||||
ffmpeg.enable = true;
|
ffmpeg.enable = true;
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
## 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'
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
## 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'
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -23,8 +23,3 @@ audio_output {
|
|||||||
type "pipewire"
|
type "pipewire"
|
||||||
name "pipewire"
|
name "pipewire"
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_output {
|
|
||||||
type "pulse"
|
|
||||||
name "pulseaudio"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ lib.my.makeSwitch {
|
|||||||
dnsutils
|
dnsutils
|
||||||
|
|
||||||
killall
|
killall
|
||||||
|
|
||||||
|
comma
|
||||||
];
|
];
|
||||||
|
|
||||||
programs.dconf.enable = true;
|
programs.dconf.enable = true;
|
||||||
@@ -80,6 +82,10 @@ lib.my.makeSwitch {
|
|||||||
enableAutoUpdates = true;
|
enableAutoUpdates = true;
|
||||||
settings.updates.auto_update = true;
|
settings.updates.auto_update = true;
|
||||||
};
|
};
|
||||||
|
programs.television = {
|
||||||
|
enable = true;
|
||||||
|
enableZshIntegration = true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ lib.my.makeSwitch {
|
|||||||
jj = {
|
jj = {
|
||||||
ignore_timeout = true;
|
ignore_timeout = true;
|
||||||
description = "The current jj status";
|
description = "The current jj status";
|
||||||
when = "jj root";
|
when = true;
|
||||||
symbol = " ";
|
|
||||||
command = ''
|
command = ''
|
||||||
jj log --revisions @ --no-graph --ignore-working-copy --color always --limit 1 --template '
|
jj log --revisions @ --no-graph --ignore-working-copy --color always --limit 1 --template '
|
||||||
separate(" ",
|
separate(" ",
|
||||||
|
" ",
|
||||||
change_id.shortest(4),
|
change_id.shortest(4),
|
||||||
bookmarks,
|
bookmarks,
|
||||||
"|",
|
"|",
|
||||||
@@ -64,19 +64,9 @@ lib.my.makeSwitch {
|
|||||||
"(no description set)",
|
"(no description set)",
|
||||||
) ++ raw_escape_sequence("\x1b[0m"),
|
) ++ 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_state.disabled = true;
|
||||||
git_commit.disabled = true;
|
git_commit.disabled = true;
|
||||||
@@ -88,7 +78,7 @@ lib.my.makeSwitch {
|
|||||||
};
|
};
|
||||||
programs.zsh = {
|
programs.zsh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
dotDir = ".config/zsh";
|
dotDir = "${config.my.home.xdg.configHome}/zsh";
|
||||||
history = {
|
history = {
|
||||||
path = "${stateHome}/zsh_history";
|
path = "${stateHome}/zsh_history";
|
||||||
ignorePatterns = [
|
ignorePatterns = [
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ lib.my.makeHomeProgramConfig {
|
|||||||
ui = {
|
ui = {
|
||||||
graph.style = "square";
|
graph.style = "square";
|
||||||
default-command = "status";
|
default-command = "status";
|
||||||
|
conflict-marker-style = "snapshot";
|
||||||
};
|
};
|
||||||
signing = {
|
signing = {
|
||||||
backend = "ssh";
|
backend = "ssh";
|
||||||
|
|||||||
@@ -44,12 +44,13 @@ lib.my.makeHomeProgramConfig {
|
|||||||
stylua
|
stylua
|
||||||
lua-language-server
|
lua-language-server
|
||||||
|
|
||||||
nodePackages.vscode-langservers-extracted
|
|
||||||
nodePackages.typescript-language-server
|
nodePackages.typescript-language-server
|
||||||
vue-language-server
|
vue-language-server
|
||||||
typescript
|
typescript
|
||||||
nodejs
|
nodejs
|
||||||
|
|
||||||
|
java-language-server
|
||||||
|
|
||||||
ripgrep
|
ripgrep
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,5 +4,3 @@ require("core.keymaps")
|
|||||||
require("langs.langs-setup")
|
require("langs.langs-setup")
|
||||||
|
|
||||||
require("plugins.plugins-setup")
|
require("plugins.plugins-setup")
|
||||||
|
|
||||||
require("core.autostart")
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
-- 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
|
|
||||||
@@ -13,27 +13,13 @@ keymap.set("n", "<leader>nh", ":nohl<CR>", opt)
|
|||||||
keymap.set("n", "<leader>sv", "<C-w>v", opt)
|
keymap.set("n", "<leader>sv", "<C-w>v", opt)
|
||||||
keymap.set("n", "<leader>sh", "<C-w>s", 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("v", ",", "<gv", opt)
|
keymap.set("v", ",", "<gv", opt)
|
||||||
|
|
||||||
keymap.set({ "n", "v" }, "<pageup>", "9k", opt)
|
keymap.set({ "n", "v" }, "<pageup>", "9k", opt)
|
||||||
keymap.set({ "n", "v" }, "<pagedown>", "9j", opt)
|
keymap.set({ "n", "v" }, "<pagedown>", "9j", opt)
|
||||||
keymap.set("i", "<pageup>", "<up><up><up><up><up><up><up><up><up>", opt)
|
keymap.set("i", "<pageup>", string.rep("<up>", 9), opt)
|
||||||
keymap.set("i", "<pagedown>", "<down><down><down><down><down><down><down><down><down>", opt)
|
keymap.set("i", "<pagedown>", string.rep("<down>", 9), opt)
|
||||||
|
|
||||||
keymap.set("n", "<leader>ww", ":w<CR>", opt)
|
keymap.set("n", "<leader>ww", ":w<CR>", opt)
|
||||||
keymap.set("n", "<leader>so", ":so<CR>", opt)
|
keymap.set("n", "<leader>so", ":so<CR>", opt)
|
||||||
@@ -54,15 +40,6 @@ keymap.set("n", "L", ":BufferLineCycleNext<CR>", opt)
|
|||||||
keymap.set("n", "<A-h>", ":BufferLineMovePrev<CR>", opt)
|
keymap.set("n", "<A-h>", ":BufferLineMovePrev<CR>", opt)
|
||||||
keymap.set("n", "<A-l>", ":BufferLineMoveNext<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
|
-- Neovide config
|
||||||
if vim.g.neovide then
|
if vim.g.neovide then
|
||||||
keymap.set("v", "<C-C>", '"+y', opt)
|
keymap.set("v", "<C-C>", '"+y', opt)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ local servers = {
|
|||||||
"cssls",
|
"cssls",
|
||||||
"nixd",
|
"nixd",
|
||||||
"html",
|
"html",
|
||||||
|
"java_language_server",
|
||||||
}
|
}
|
||||||
|
|
||||||
local extra_config = {
|
local extra_config = {
|
||||||
@@ -73,7 +74,6 @@ capabilities.textDocument.foldingRange = {
|
|||||||
dynamicRegistration = false,
|
dynamicRegistration = false,
|
||||||
lineFoldingOnly = true,
|
lineFoldingOnly = true,
|
||||||
}
|
}
|
||||||
local lspconfig = require("lspconfig")
|
|
||||||
for _, server in ipairs(servers) do
|
for _, server in ipairs(servers) do
|
||||||
local extra = extra_config[server] or {}
|
local extra = extra_config[server] or {}
|
||||||
local config = {
|
local config = {
|
||||||
@@ -82,5 +82,6 @@ for _, server in ipairs(servers) do
|
|||||||
for k, v in pairs(extra) do
|
for k, v in pairs(extra) do
|
||||||
config[k] = v
|
config[k] = v
|
||||||
end
|
end
|
||||||
lspconfig[server].setup(config)
|
vim.lsp.config(server, config)
|
||||||
|
vim.lsp.enable(server)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ lib.my.makeSwitch {
|
|||||||
python.enable = true;
|
python.enable = true;
|
||||||
rust.enable = true;
|
rust.enable = true;
|
||||||
lua.enable = true;
|
lua.enable = true;
|
||||||
|
java.enable = true;
|
||||||
qml.enable = true;
|
qml.enable = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
20
modules/coding/langs/java.nix
Normal file
20
modules/coding/langs/java.nix
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
lib.my.makeSwitch {
|
||||||
|
inherit config;
|
||||||
|
optionName = "java";
|
||||||
|
optionPath = [
|
||||||
|
"coding"
|
||||||
|
"langs"
|
||||||
|
"java"
|
||||||
|
];
|
||||||
|
config' = {
|
||||||
|
my.home.home.packages = with pkgs; [
|
||||||
|
openjdk24
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ lib.my.makeSwitch {
|
|||||||
my.home = {
|
my.home = {
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
nodejs
|
nodejs
|
||||||
nodePackages.npm
|
|
||||||
|
|
||||||
typescript
|
typescript
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ lib.my.makeSwitch {
|
|||||||
[net]
|
[net]
|
||||||
git-fetch-with-cli = true
|
git-fetch-with-cli = true
|
||||||
'';
|
'';
|
||||||
|
programs.zsh.initContent = lib.mkAfter ''
|
||||||
|
export PATH=$PATH:$HOME/.cargo/bin
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
my.persist.homeDirs = [
|
my.persist.homeDirs = [
|
||||||
".cargo"
|
".cargo"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ lib.my.makeSwitch {
|
|||||||
terminal.all.enable = true;
|
terminal.all.enable = true;
|
||||||
wm.all.enable = true;
|
wm.all.enable = true;
|
||||||
style.enable = true;
|
style.enable = true;
|
||||||
quickshell.enable = true;
|
|
||||||
wine.enable = true;
|
wine.enable = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ lib.my.makeSwitch {
|
|||||||
config' = {
|
config' = {
|
||||||
my.desktop.browser = {
|
my.desktop.browser = {
|
||||||
firefox.enable = true;
|
firefox.enable = true;
|
||||||
librewolf.enable = true;
|
|
||||||
chromium.enable = true;
|
chromium.enable = true;
|
||||||
zen.enable = true;
|
zen.enable = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
lib.my.makeHomeProgramConfig {
|
|
||||||
inherit config;
|
|
||||||
programName = "librewolf";
|
|
||||||
optionPath = [
|
|
||||||
"desktop"
|
|
||||||
"browser"
|
|
||||||
"librewolf"
|
|
||||||
];
|
|
||||||
extraConfig = {
|
|
||||||
my.persist.homeDirs = [
|
|
||||||
".librewolf"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -13,10 +13,8 @@ lib.my.makeSwitch {
|
|||||||
"minecraft"
|
"minecraft"
|
||||||
];
|
];
|
||||||
config' = {
|
config' = {
|
||||||
my.home.home.packages = with pkgs; [
|
my.home.home.packages = [
|
||||||
hmcl
|
pkgs.hmcl
|
||||||
|
|
||||||
openjdk21
|
|
||||||
];
|
];
|
||||||
|
|
||||||
my.persist.homeDirs = [
|
my.persist.homeDirs = [
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
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}"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
2
modules/desktop/quickshell/qml/.gitignore
vendored
2
modules/desktop/quickshell/qml/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
/debug.log
|
|
||||||
/quickshell.log
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB |
@@ -1,84 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,315 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"isDarkTheme": true,
|
|
||||||
"avatarSource": "/home/imxyy/Pictures/icon.jpg",
|
|
||||||
"weatherLocation": "Dinslaken",
|
|
||||||
"displayTime": 6000,
|
|
||||||
"videoPath": "~/Videos/"
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,575 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,394 +0,0 @@
|
|||||||
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"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
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}¤t=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 = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
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");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,666 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,758 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,531 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// Night Light settings content
|
|
||||||
Item {
|
|
||||||
id: nightLightSettings
|
|
||||||
width: parent.width
|
|
||||||
height: contentColumn.height
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: contentColumn
|
|
||||||
width: parent.width
|
|
||||||
spacing: 20
|
|
||||||
|
|
||||||
// Night Light Enable Toggle
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width - nightLightToggle.width - 16
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Enable Night Light"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Reduces blue light to help protect your eyes and improve sleep"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: nightLightToggle
|
|
||||||
width: 50
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: Data.Settings.nightLightEnabled ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 20
|
|
||||||
height: 20
|
|
||||||
radius: 10
|
|
||||||
color: Data.ThemeManager.bgColor
|
|
||||||
x: Data.Settings.nightLightEnabled ? parent.width - width - 4 : 4
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Behavior on x {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 200
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
Data.Settings.nightLightEnabled = !Data.Settings.nightLightEnabled;
|
|
||||||
}
|
|
||||||
onEntered: {
|
|
||||||
parent.scale = 1.05;
|
|
||||||
}
|
|
||||||
onExited: {
|
|
||||||
parent.scale = 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 150
|
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warmth Level Slider
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Warmth Level"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Adjust how warm the screen filter appears"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Cool"
|
|
||||||
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Slider {
|
|
||||||
id: warmthSlider
|
|
||||||
width: parent.width - 120
|
|
||||||
height: 30
|
|
||||||
from: 0.1
|
|
||||||
to: 1.0
|
|
||||||
value: Data.Settings.nightLightWarmth || 0.4
|
|
||||||
stepSize: 0.1
|
|
||||||
|
|
||||||
onValueChanged: {
|
|
||||||
Data.Settings.nightLightWarmth = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: parent.width
|
|
||||||
height: 6
|
|
||||||
radius: 3
|
|
||||||
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.2)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: warmthSlider.visualPosition * parent.width
|
|
||||||
height: parent.height
|
|
||||||
radius: parent.radius
|
|
||||||
color: Qt.rgba(1.0, 0.8 - warmthSlider.value * 0.3, 0.4, 1.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handle: Rectangle {
|
|
||||||
x: warmthSlider.leftPadding + warmthSlider.visualPosition * (warmthSlider.availableWidth - width)
|
|
||||||
y: warmthSlider.topPadding + warmthSlider.availableHeight / 2 - height / 2
|
|
||||||
width: 20
|
|
||||||
height: 20
|
|
||||||
radius: 10
|
|
||||||
color: Data.ThemeManager.accentColor
|
|
||||||
border.color: Qt.lighter(Data.ThemeManager.accentColor, 1.2)
|
|
||||||
border.width: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Warm"
|
|
||||||
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-enable Toggle
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width - autoToggle.width - 16
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Auto-enable Schedule"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Automatically turn on night light at sunset/bedtime"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: autoToggle
|
|
||||||
width: 50
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: Data.Settings.nightLightAuto ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 20
|
|
||||||
height: 20
|
|
||||||
radius: 10
|
|
||||||
color: Data.ThemeManager.bgColor
|
|
||||||
x: Data.Settings.nightLightAuto ? parent.width - width - 4 : 4
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Behavior on x {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 200
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
Data.Settings.nightLightAuto = !Data.Settings.nightLightAuto;
|
|
||||||
}
|
|
||||||
onEntered: {
|
|
||||||
parent.scale = 1.05;
|
|
||||||
}
|
|
||||||
onExited: {
|
|
||||||
parent.scale = 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 150
|
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule Time Controls - visible when auto-enable is on
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 16
|
|
||||||
visible: Data.Settings.nightLightAuto
|
|
||||||
opacity: Data.Settings.nightLightAuto ? 1.0 : 0.0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Schedule Times"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start and End Time Row
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 20
|
|
||||||
|
|
||||||
// Start Time
|
|
||||||
Column {
|
|
||||||
id: startTimeColumn
|
|
||||||
width: (parent.width - parent.spacing) / 2
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Start Time"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Night light turns on"
|
|
||||||
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: startTimeButton
|
|
||||||
width: parent.width
|
|
||||||
height: 40
|
|
||||||
radius: 8
|
|
||||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: (Data.Settings.nightLightStartHour || 20).toString().padStart(2, '0') + ":00"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
startTimePopup.open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start Time Popup
|
|
||||||
Popup {
|
|
||||||
id: startTimePopup
|
|
||||||
width: startTimeButton.width
|
|
||||||
height: 170
|
|
||||||
modal: true
|
|
||||||
focus: true
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
|
||||||
y: startTimeButton.y - height - 10
|
|
||||||
x: startTimeButton.x
|
|
||||||
dim: false
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: Data.ThemeManager.bgColor
|
|
||||||
radius: 12
|
|
||||||
border.width: 2
|
|
||||||
border.color: Data.ThemeManager.accentColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Select Start Hour"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
GridLayout {
|
|
||||||
columns: 6
|
|
||||||
columnSpacing: 6
|
|
||||||
rowSpacing: 6
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: 24
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: 24
|
|
||||||
height: 24
|
|
||||||
radius: 4
|
|
||||||
color: (Data.Settings.nightLightStartHour || 20) === modelData ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: modelData.toString().padStart(2, '0')
|
|
||||||
color: (Data.Settings.nightLightStartHour || 20) === modelData ? Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 10
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
Data.Settings.nightLightStartHour = modelData;
|
|
||||||
startTimePopup.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// End Time
|
|
||||||
Column {
|
|
||||||
id: endTimeColumn
|
|
||||||
width: (parent.width - parent.spacing) / 2
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "End Time"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Night light turns off"
|
|
||||||
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: endTimeButton
|
|
||||||
width: parent.width
|
|
||||||
height: 40
|
|
||||||
radius: 8
|
|
||||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: (Data.Settings.nightLightEndHour || 6).toString().padStart(2, '0') + ":00"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
endTimePopup.open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// End Time Popup
|
|
||||||
Popup {
|
|
||||||
id: endTimePopup
|
|
||||||
width: endTimeButton.width
|
|
||||||
height: 170
|
|
||||||
modal: true
|
|
||||||
focus: true
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
|
||||||
y: endTimeButton.y - height - 10
|
|
||||||
x: endTimeButton.x
|
|
||||||
dim: false
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: Data.ThemeManager.bgColor
|
|
||||||
radius: 12
|
|
||||||
border.width: 2
|
|
||||||
border.color: Data.ThemeManager.accentColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Select End Hour"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
GridLayout {
|
|
||||||
columns: 6
|
|
||||||
columnSpacing: 6
|
|
||||||
rowSpacing: 6
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: 24
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: 24
|
|
||||||
height: 24
|
|
||||||
radius: 4
|
|
||||||
color: (Data.Settings.nightLightEndHour || 6) === modelData ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: modelData.toString().padStart(2, '0')
|
|
||||||
color: (Data.Settings.nightLightEndHour || 6) === modelData ? Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 10
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
Data.Settings.nightLightEndHour = modelData;
|
|
||||||
endTimePopup.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,536 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// Notification settings content
|
|
||||||
Item {
|
|
||||||
id: notificationSettings
|
|
||||||
width: parent.width
|
|
||||||
height: contentColumn.height
|
|
||||||
|
|
||||||
// Expose the text input focus for parent keyboard management
|
|
||||||
property bool anyTextInputFocused: appNameInput.activeFocus
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: contentColumn
|
|
||||||
width: parent.width
|
|
||||||
spacing: 20
|
|
||||||
|
|
||||||
// Display Time Setting
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Display Time"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "How long notifications stay visible on screen"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: 16
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
Slider {
|
|
||||||
id: displayTimeSlider
|
|
||||||
width: parent.width - timeLabel.width - 16
|
|
||||||
height: 30
|
|
||||||
from: 2000
|
|
||||||
to: 15000
|
|
||||||
stepSize: 1000
|
|
||||||
value: Data.Settings.displayTime
|
|
||||||
|
|
||||||
onValueChanged: {
|
|
||||||
Data.Settings.displayTime = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
width: displayTimeSlider.availableWidth
|
|
||||||
height: 6
|
|
||||||
radius: 3
|
|
||||||
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: displayTimeSlider.visualPosition * parent.width
|
|
||||||
height: parent.height
|
|
||||||
radius: parent.radius
|
|
||||||
color: Data.ThemeManager.accentColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handle: Rectangle {
|
|
||||||
x: displayTimeSlider.leftPadding + displayTimeSlider.visualPosition * (displayTimeSlider.availableWidth - width)
|
|
||||||
y: displayTimeSlider.topPadding + displayTimeSlider.availableHeight / 2 - height / 2
|
|
||||||
width: 20
|
|
||||||
height: 20
|
|
||||||
radius: 10
|
|
||||||
color: Data.ThemeManager.accentColor
|
|
||||||
border.color: Qt.lighter(Data.ThemeManager.accentColor, 1.2)
|
|
||||||
border.width: 2
|
|
||||||
|
|
||||||
scale: displayTimeSlider.pressed ? 1.2 : 1.0
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: timeLabel
|
|
||||||
text: (displayTimeSlider.value / 1000).toFixed(1) + "s"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.family: "monospace"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 40
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max History Items
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "History Limit"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Maximum number of notifications to keep in history"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: 16
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
Slider {
|
|
||||||
id: historySlider
|
|
||||||
width: parent.width - historyLabel.width - 16
|
|
||||||
height: 30
|
|
||||||
from: 10
|
|
||||||
to: 100
|
|
||||||
stepSize: 5
|
|
||||||
value: Data.Settings.historyLimit
|
|
||||||
|
|
||||||
onValueChanged: {
|
|
||||||
Data.Settings.historyLimit = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
width: historySlider.availableWidth
|
|
||||||
height: 6
|
|
||||||
radius: 3
|
|
||||||
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: historySlider.visualPosition * parent.width
|
|
||||||
height: parent.height
|
|
||||||
radius: parent.radius
|
|
||||||
color: Data.ThemeManager.accentColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handle: Rectangle {
|
|
||||||
x: historySlider.leftPadding + historySlider.visualPosition * (historySlider.availableWidth - width)
|
|
||||||
y: historySlider.topPadding + historySlider.availableHeight / 2 - height / 2
|
|
||||||
width: 20
|
|
||||||
height: 20
|
|
||||||
radius: 10
|
|
||||||
color: Data.ThemeManager.accentColor
|
|
||||||
border.color: Qt.lighter(Data.ThemeManager.accentColor, 1.2)
|
|
||||||
border.width: 2
|
|
||||||
|
|
||||||
scale: historySlider.pressed ? 1.2 : 1.0
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: historyLabel
|
|
||||||
text: historySlider.value + " items"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.family: "monospace"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 60
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignored Apps Setting
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Ignored Applications"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Applications that won't show notifications"
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current ignored apps list
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: Math.max(100, ignoredAppsFlow.height + 16)
|
|
||||||
radius: 12
|
|
||||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Flow {
|
|
||||||
id: ignoredAppsFlow
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 8
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Data.Settings.ignoredApps
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: appNameText.width + removeButton.width + 16
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.15)
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: appNameText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: modelData
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: removeButton
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
radius: 9
|
|
||||||
color: removeMouseArea.containsMouse ? Qt.rgba(1, 0.3, 0.3, 0.8) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.5)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "×"
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: removeMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
Data.Settings.removeIgnoredApp(modelData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new app button
|
|
||||||
Rectangle {
|
|
||||||
width: addAppText.width + 36
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: addAppMouseArea.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.lighter(Data.ThemeManager.bgColor, 1.2)
|
|
||||||
border.width: 2
|
|
||||||
border.color: Data.ThemeManager.accentColor
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "add"
|
|
||||||
font.family: "Material Symbols Outlined"
|
|
||||||
font.pixelSize: 14
|
|
||||||
color: Data.ThemeManager.accentColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: addAppText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Add App"
|
|
||||||
color: Data.ThemeManager.accentColor
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: addAppMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: addAppPopup.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quick suggestions
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Common Apps"
|
|
||||||
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: ["Discord", "Spotify", "Steam", "Firefox", "Chrome", "VSCode", "Slack"]
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: suggestedAppText.width + 16
|
|
||||||
height: 24
|
|
||||||
radius: 12
|
|
||||||
color: suggestionMouseArea.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.1) : "transparent"
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: suggestedAppText
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: modelData
|
|
||||||
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
|
|
||||||
font.pixelSize: 11
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: suggestionMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
Data.Settings.addIgnoredApp(modelData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add app popup
|
|
||||||
Popup {
|
|
||||||
id: addAppPopup
|
|
||||||
parent: notificationSettings
|
|
||||||
width: 280
|
|
||||||
height: 160
|
|
||||||
x: (parent.width - width) / 2
|
|
||||||
y: (parent.height - height) / 2
|
|
||||||
modal: true
|
|
||||||
focus: true
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: Data.ThemeManager.bgColor
|
|
||||||
border.color: Data.ThemeManager.accentColor
|
|
||||||
border.width: 2
|
|
||||||
radius: 20
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 16
|
|
||||||
width: parent.width - 40
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
text: "Add Ignored App"
|
|
||||||
color: Data.ThemeManager.accentColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 40
|
|
||||||
radius: 20
|
|
||||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
border.width: appNameInput.activeFocus ? 2 : 1
|
|
||||||
border.color: appNameInput.activeFocus ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextInput {
|
|
||||||
id: appNameInput
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.family: "monospace"
|
|
||||||
selectByMouse: true
|
|
||||||
clip: true
|
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
|
||||||
focus: true
|
|
||||||
activeFocusOnTab: true
|
|
||||||
inputMethodHints: Qt.ImhNone
|
|
||||||
|
|
||||||
Keys.onPressed: function (event) {
|
|
||||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
|
||||||
addAppButton.clicked();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Placeholder text implementation
|
|
||||||
Text {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
text: "App name (e.g. Discord)"
|
|
||||||
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.5)
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.family: "monospace"
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
visible: appNameInput.text === ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 80
|
|
||||||
height: 32
|
|
||||||
radius: 16
|
|
||||||
color: cancelMouseArea.containsMouse ? Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.1) : "transparent"
|
|
||||||
border.width: 1
|
|
||||||
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Cancel"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cancelMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
appNameInput.text = "";
|
|
||||||
addAppPopup.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: addAppButton
|
|
||||||
width: 80
|
|
||||||
height: 32
|
|
||||||
radius: 16
|
|
||||||
color: addMouseArea.containsMouse ? Qt.lighter(Data.ThemeManager.accentColor, 1.1) : Data.ThemeManager.accentColor
|
|
||||||
|
|
||||||
signal clicked
|
|
||||||
onClicked: {
|
|
||||||
if (appNameInput.text.trim() !== "") {
|
|
||||||
if (Data.Settings.addIgnoredApp(appNameInput.text.trim())) {
|
|
||||||
appNameInput.text = "";
|
|
||||||
addAppPopup.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Add"
|
|
||||||
color: Data.ThemeManager.bgColor
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: addMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: parent.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onOpened: {
|
|
||||||
appNameInput.forceActiveFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// Reusable collapsible settings category component
|
|
||||||
Item {
|
|
||||||
id: categoryRoot
|
|
||||||
|
|
||||||
property string title: ""
|
|
||||||
property string icon: ""
|
|
||||||
property bool expanded: false
|
|
||||||
property alias content: contentLoader.sourceComponent
|
|
||||||
|
|
||||||
height: headerRect.height + (expanded ? contentLoader.height + 20 : 0)
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 250
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Category header
|
|
||||||
Rectangle {
|
|
||||||
id: headerRect
|
|
||||||
width: parent.width
|
|
||||||
height: 50
|
|
||||||
radius: 12
|
|
||||||
color: expanded ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.1) : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
border.width: expanded ? 2 : 1
|
|
||||||
border.color: expanded ? 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: 16
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: categoryRoot.icon
|
|
||||||
font.family: "Material Symbols Outlined"
|
|
||||||
font.pixelSize: 20
|
|
||||||
color: expanded ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: categoryRoot.title
|
|
||||||
color: expanded ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand/collapse arrow
|
|
||||||
Text {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.rightMargin: 16
|
|
||||||
text: expanded ? "expand_less" : "expand_more"
|
|
||||||
font.family: "Material Symbols Outlined"
|
|
||||||
font.pixelSize: 20
|
|
||||||
color: expanded ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
|
||||||
|
|
||||||
Behavior on rotation {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 250
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
categoryRoot.expanded = !categoryRoot.expanded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Category content
|
|
||||||
Loader {
|
|
||||||
id: contentLoader
|
|
||||||
anchors.top: headerRect.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.topMargin: expanded ? 20 : 0
|
|
||||||
anchors.leftMargin: 16
|
|
||||||
anchors.rightMargin: 16
|
|
||||||
|
|
||||||
visible: expanded
|
|
||||||
opacity: expanded ? 1 : 0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 250
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// System settings content
|
|
||||||
Item {
|
|
||||||
id: systemSettings
|
|
||||||
width: parent.width
|
|
||||||
height: contentColumn.height
|
|
||||||
|
|
||||||
// Expose the text input focus for parent keyboard management
|
|
||||||
property bool anyTextInputFocused: videoPathInput.activeFocus
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: contentColumn
|
|
||||||
width: parent.width
|
|
||||||
spacing: 20
|
|
||||||
|
|
||||||
// Video Recording Path
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Video Recording Path"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 40
|
|
||||||
radius: 8
|
|
||||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
border.width: videoPathInput.activeFocus ? 2 : 1
|
|
||||||
border.color: videoPathInput.activeFocus ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextInput {
|
|
||||||
id: videoPathInput
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
text: Data.Settings.videoPath
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.family: "monospace"
|
|
||||||
selectByMouse: true
|
|
||||||
clip: true
|
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
|
||||||
focus: true
|
|
||||||
activeFocusOnTab: true
|
|
||||||
inputMethodHints: Qt.ImhNone
|
|
||||||
|
|
||||||
onTextChanged: {
|
|
||||||
Data.Settings.videoPath = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: function (event) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
videoPathInput.forceActiveFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// Weather settings content
|
|
||||||
Item {
|
|
||||||
id: weatherSettings
|
|
||||||
width: parent.width
|
|
||||||
height: contentColumn.height
|
|
||||||
|
|
||||||
required property var shell
|
|
||||||
|
|
||||||
// Expose the text input focus for parent keyboard management
|
|
||||||
property bool anyTextInputFocused: locationInput.activeFocus
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: contentColumn
|
|
||||||
width: parent.width
|
|
||||||
spacing: 20
|
|
||||||
|
|
||||||
// Location Setting
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Location"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width - applyButton.width - 12
|
|
||||||
height: 40
|
|
||||||
radius: 8
|
|
||||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
border.width: locationInput.activeFocus ? 2 : 1
|
|
||||||
border.color: locationInput.activeFocus ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextInput {
|
|
||||||
id: locationInput
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
text: Data.Settings.weatherLocation
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.family: "monospace"
|
|
||||||
selectByMouse: true
|
|
||||||
clip: true
|
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
|
||||||
focus: true
|
|
||||||
activeFocusOnTab: true
|
|
||||||
inputMethodHints: Qt.ImhNone
|
|
||||||
|
|
||||||
Keys.onPressed: function (event) {
|
|
||||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
|
||||||
applyButton.clicked();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
locationInput.forceActiveFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: applyButton
|
|
||||||
width: 80
|
|
||||||
height: 40
|
|
||||||
radius: 8
|
|
||||||
color: applyMouseArea.containsMouse ? Qt.lighter(Data.ThemeManager.accentColor, 1.1) : Data.ThemeManager.accentColor
|
|
||||||
|
|
||||||
signal clicked
|
|
||||||
onClicked: {
|
|
||||||
Data.Settings.weatherLocation = locationInput.text;
|
|
||||||
weatherSettings.shell.weatherService.loadWeather();
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "Apply"
|
|
||||||
color: Data.ThemeManager.bgColor
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: applyMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: parent.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temperature Units
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "Temperature Units"
|
|
||||||
color: Data.ThemeManager.fgColor
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 80
|
|
||||||
height: 35
|
|
||||||
radius: 18
|
|
||||||
color: !Data.Settings.useFahrenheit ? 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: "°C"
|
|
||||||
color: !Data.Settings.useFahrenheit ? 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.useFahrenheit = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 80
|
|
||||||
height: 35
|
|
||||||
radius: 18
|
|
||||||
color: Data.Settings.useFahrenheit ? 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: "°F"
|
|
||||||
color: Data.Settings.useFahrenheit ? 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.useFahrenheit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// Dual-button notification and clipboard history toggle bar
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
width: 42
|
|
||||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
radius: 12
|
|
||||||
z: 2 // Above notification history overlay
|
|
||||||
|
|
||||||
required property bool notificationHistoryVisible
|
|
||||||
required property bool clipboardHistoryVisible
|
|
||||||
required property var notificationHistory
|
|
||||||
signal notificationToggleRequested
|
|
||||||
signal clipboardToggleRequested
|
|
||||||
|
|
||||||
// Combined hover state for parent component tracking
|
|
||||||
property bool containsMouse: notifButtonMouseArea.containsMouse || clipButtonMouseArea.containsMouse
|
|
||||||
|
|
||||||
property real buttonHeight: 38
|
|
||||||
height: buttonHeight * 2 + 4 // Two buttons with spacing
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 2
|
|
||||||
|
|
||||||
// Notifications toggle button (top half)
|
|
||||||
Rectangle {
|
|
||||||
id: notificationPill
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: parent.verticalCenter
|
|
||||||
bottomMargin: 2 // Half of button spacing
|
|
||||||
}
|
|
||||||
radius: 12
|
|
||||||
color: notifButtonMouseArea.containsMouse || root.notificationHistoryVisible ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.05)
|
|
||||||
border.color: notifButtonMouseArea.containsMouse || root.notificationHistoryVisible ? Data.ThemeManager.accentColor : "transparent"
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: notifButtonMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: root.notificationToggleRequested()
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "notifications"
|
|
||||||
font.family: "Material Symbols Outlined"
|
|
||||||
font.pixelSize: 16
|
|
||||||
color: notifButtonMouseArea.containsMouse || root.notificationHistoryVisible ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clipboard toggle button (bottom half)
|
|
||||||
Rectangle {
|
|
||||||
id: clipboardPill
|
|
||||||
anchors {
|
|
||||||
top: parent.verticalCenter
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: parent.bottom
|
|
||||||
topMargin: 2 // Half of button spacing
|
|
||||||
}
|
|
||||||
radius: 12
|
|
||||||
color: clipButtonMouseArea.containsMouse || root.clipboardHistoryVisible ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.05)
|
|
||||||
border.color: clipButtonMouseArea.containsMouse || root.clipboardHistoryVisible ? Data.ThemeManager.accentColor : "transparent"
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: clipButtonMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: root.clipboardToggleRequested()
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "content_paste"
|
|
||||||
font.family: "Material Symbols Outlined"
|
|
||||||
font.pixelSize: 16
|
|
||||||
color: clipButtonMouseArea.containsMouse || root.clipboardHistoryVisible ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
|
|
||||||
// Top-edge hover trigger
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
width: 360
|
|
||||||
height: 1
|
|
||||||
color: "red"
|
|
||||||
anchors.top: parent.top
|
|
||||||
|
|
||||||
signal triggered
|
|
||||||
|
|
||||||
// Hover detection area at screen top edge
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
property bool isHovered: containsMouse
|
|
||||||
|
|
||||||
// Timer coordination
|
|
||||||
onIsHoveredChanged: {
|
|
||||||
if (isHovered) {
|
|
||||||
showTimer.start();
|
|
||||||
hideTimer.stop();
|
|
||||||
} else {
|
|
||||||
hideTimer.start();
|
|
||||||
showTimer.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onEntered: hideTimer.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delayed show trigger to prevent accidental activation
|
|
||||||
Timer {
|
|
||||||
id: showTimer
|
|
||||||
interval: 200
|
|
||||||
onTriggered: root.triggered()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide delay timer (controlled by parent)
|
|
||||||
Timer {
|
|
||||||
id: hideTimer
|
|
||||||
interval: 500
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public interface
|
|
||||||
readonly property alias containsMouse: mouseArea.containsMouse
|
|
||||||
function stopHideTimer() {
|
|
||||||
hideTimer.stop();
|
|
||||||
}
|
|
||||||
function startHideTimer() {
|
|
||||||
hideTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// System tray context menu
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
width: parent.width
|
|
||||||
height: visible ? calculatedHeight : 0
|
|
||||||
visible: false
|
|
||||||
enabled: visible
|
|
||||||
clip: true
|
|
||||||
color: Data.ThemeManager.bgColor
|
|
||||||
border.color: Data.ThemeManager.accentColor
|
|
||||||
border.width: 2
|
|
||||||
radius: 20
|
|
||||||
|
|
||||||
required property var menu
|
|
||||||
required property var systemTrayY
|
|
||||||
required property var systemTrayHeight
|
|
||||||
|
|
||||||
property bool containsMouse: trayMenuMouseArea.containsMouse
|
|
||||||
property bool menuJustOpened: false
|
|
||||||
property point triggerPoint: Qt.point(0, 0)
|
|
||||||
property Item originalParent
|
|
||||||
property int totalCount: opener.children ? opener.children.values.length : 0
|
|
||||||
|
|
||||||
signal hideRequested
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: trayMenuMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
preventStealing: true
|
|
||||||
propagateComposedEvents: false
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible) {
|
|
||||||
menuJustOpened = true;
|
|
||||||
Qt.callLater(function () {
|
|
||||||
menuJustOpened = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle() {
|
|
||||||
visible = !visible;
|
|
||||||
if (visible) {
|
|
||||||
menuJustOpened = true;
|
|
||||||
Qt.callLater(function () {
|
|
||||||
menuJustOpened = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function show(point, parentItem) {
|
|
||||||
visible = true;
|
|
||||||
menuJustOpened = true;
|
|
||||||
Qt.callLater(function () {
|
|
||||||
menuJustOpened = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hide() {
|
|
||||||
visible = false;
|
|
||||||
menuJustOpened = false;
|
|
||||||
// Small delay before notifying hide to prevent control panel flicker
|
|
||||||
Qt.callLater(function () {
|
|
||||||
hideRequested();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Smart positioning to avoid screen edges
|
|
||||||
y: {
|
|
||||||
var preferredY = systemTrayY + systemTrayHeight + 10;
|
|
||||||
var availableSpace = parent.height - preferredY - 20;
|
|
||||||
if (calculatedHeight > availableSpace) {
|
|
||||||
return systemTrayY - height - 10;
|
|
||||||
}
|
|
||||||
return preferredY;
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 200
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dynamic height calculation based on menu item count and types
|
|
||||||
property int calculatedHeight: {
|
|
||||||
if (totalCount === 0)
|
|
||||||
return 40;
|
|
||||||
var separatorCount = 0;
|
|
||||||
var regularItemCount = 0;
|
|
||||||
|
|
||||||
if (opener.children && opener.children.values) {
|
|
||||||
for (var i = 0; i < opener.children.values.length; i++) {
|
|
||||||
if (opener.children.values[i].isSeparator) {
|
|
||||||
separatorCount++;
|
|
||||||
} else {
|
|
||||||
regularItemCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate total height: separators + grid rows + margins
|
|
||||||
var separatorHeight = separatorCount * 12;
|
|
||||||
var regularItemRows = Math.ceil(regularItemCount / 2);
|
|
||||||
var regularItemHeight = regularItemRows * 32;
|
|
||||||
return Math.max(80, 35 + separatorHeight + regularItemHeight + 40);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu opener handles the native menu integration
|
|
||||||
QsMenuOpener {
|
|
||||||
id: opener
|
|
||||||
menu: root.menu
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grid layout for menu items (2 columns)
|
|
||||||
GridView {
|
|
||||||
id: gridView
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 20
|
|
||||||
cellWidth: width / 2
|
|
||||||
cellHeight: 32
|
|
||||||
interactive: false
|
|
||||||
flow: GridView.FlowLeftToRight
|
|
||||||
layoutDirection: Qt.LeftToRight
|
|
||||||
|
|
||||||
model: ScriptModel {
|
|
||||||
values: opener.children ? [...opener.children.values] : []
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
id: entry
|
|
||||||
required property var modelData
|
|
||||||
required property int index
|
|
||||||
|
|
||||||
width: gridView.cellWidth - 4
|
|
||||||
height: modelData.isSeparator ? 12 : 30
|
|
||||||
|
|
||||||
// Separator line
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.leftMargin: 8
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
anchors.topMargin: 4
|
|
||||||
anchors.bottomMargin: 4
|
|
||||||
visible: modelData.isSeparator
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width * 0.8
|
|
||||||
height: 1
|
|
||||||
color: Qt.darker(Data.ThemeManager.accentColor, 1.5)
|
|
||||||
opacity: 0.6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular menu item
|
|
||||||
Rectangle {
|
|
||||||
id: itemBackground
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 2
|
|
||||||
visible: !modelData.isSeparator
|
|
||||||
color: "transparent"
|
|
||||||
radius: 6
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.leftMargin: 8
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
spacing: 6
|
|
||||||
|
|
||||||
Image {
|
|
||||||
Layout.preferredWidth: 16
|
|
||||||
Layout.preferredHeight: 16
|
|
||||||
source: modelData?.icon ?? ""
|
|
||||||
visible: (modelData?.icon ?? "") !== ""
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
color: mouseArea.containsMouse ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
|
||||||
text: modelData?.text ?? ""
|
|
||||||
font.pixelSize: 11
|
|
||||||
font.family: "monospace"
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
enabled: (modelData?.enabled ?? true) && root.visible && !modelData.isSeparator
|
|
||||||
|
|
||||||
onEntered: itemBackground.color = Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.15)
|
|
||||||
onExited: itemBackground.color = "transparent"
|
|
||||||
onClicked: {
|
|
||||||
modelData.triggered();
|
|
||||||
root.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty state indicator
|
|
||||||
Item {
|
|
||||||
anchors.centerIn: gridView
|
|
||||||
visible: gridView.count === 0
|
|
||||||
|
|
||||||
Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "No tray items available"
|
|
||||||
color: Qt.darker(Data.ThemeManager.fgColor, 2)
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.family: "monospace"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// Calendar button
|
|
||||||
Rectangle {
|
|
||||||
id: calendarButton
|
|
||||||
width: 40
|
|
||||||
height: 80
|
|
||||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
radius: 20
|
|
||||||
|
|
||||||
property bool containsMouse: calendarMouseArea.containsMouse
|
|
||||||
property bool calendarVisible: false
|
|
||||||
property var calendarPopup: null
|
|
||||||
property var shell: null // Shell reference from parent
|
|
||||||
|
|
||||||
signal entered
|
|
||||||
signal exited
|
|
||||||
|
|
||||||
// Hover state management
|
|
||||||
onContainsMouseChanged: {
|
|
||||||
if (containsMouse) {
|
|
||||||
entered();
|
|
||||||
} else {
|
|
||||||
exited();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: calendarMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
toggleCalendar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calendar icon
|
|
||||||
Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "calendar_month"
|
|
||||||
font.pixelSize: 24
|
|
||||||
font.family: "Material Symbols Outlined"
|
|
||||||
color: calendarButton.containsMouse || calendarButton.calendarVisible ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle calendar popup
|
|
||||||
function toggleCalendar() {
|
|
||||||
if (!calendarPopup) {
|
|
||||||
var component = Qt.createComponent("root:/Widgets/Calendar/CalendarPopup.qml");
|
|
||||||
if (component.status === Component.Ready) {
|
|
||||||
calendarPopup = component.createObject(calendarButton.parent, {
|
|
||||||
"targetX": calendarButton.x + calendarButton.width + 10,
|
|
||||||
"shell": calendarButton.shell
|
|
||||||
});
|
|
||||||
} else if (component.status === Component.Error) {
|
|
||||||
console.log("Error loading calendar:", component.errorString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (calendarPopup) {
|
|
||||||
calendarVisible = !calendarVisible;
|
|
||||||
calendarPopup.setClickMode(calendarVisible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideCalendar() {
|
|
||||||
if (calendarPopup) {
|
|
||||||
calendarVisible = false;
|
|
||||||
calendarPopup.setClickMode(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// Night light widget with pure Qt overlay (no external dependencies)
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
property var shell: null
|
|
||||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
radius: 20
|
|
||||||
|
|
||||||
property bool containsMouse: nightLightMouseArea.containsMouse
|
|
||||||
property bool isActive: Data.Settings.nightLightEnabled
|
|
||||||
property real warmth: Data.Settings.nightLightWarmth // 0=no filter, 1=very warm (0-1 scale)
|
|
||||||
property real strength: isActive ? warmth : 0
|
|
||||||
property bool autoSchedulerActive: false // Flag to prevent manual override during auto changes
|
|
||||||
|
|
||||||
signal entered
|
|
||||||
signal exited
|
|
||||||
|
|
||||||
// Night light overlay window
|
|
||||||
property var overlayWindow: null
|
|
||||||
|
|
||||||
// Hover state management for parent components
|
|
||||||
onContainsMouseChanged: {
|
|
||||||
if (containsMouse) {
|
|
||||||
entered();
|
|
||||||
} else {
|
|
||||||
exited();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background with warm tint when active
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: parent.radius
|
|
||||||
color: isActive ? Qt.rgba(1.0, 0.6, 0.2, 0.15) : "transparent"
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 300
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: nightLightMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
// Right-click to cycle through warmth levels
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
onClicked: function (mouse) {
|
|
||||||
if (mouse.button === Qt.RightButton) {
|
|
||||||
cycleWarmth();
|
|
||||||
} else {
|
|
||||||
toggleNightLight();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Night light icon with dynamic color
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: isActive ? "light_mode" : "dark_mode"
|
|
||||||
font.pixelSize: 24
|
|
||||||
font.family: "Material Symbols Outlined"
|
|
||||||
color: isActive ? Qt.rgba(1.0, 0.8 - strength * 0.3, 0.4 - strength * 0.2, 1.0) : // Warm orange when active
|
|
||||||
(containsMouse ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warmth indicator dots
|
|
||||||
Row {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: 6
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: 3
|
|
||||||
visible: isActive && containsMouse
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: 3
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: 4
|
|
||||||
height: 4
|
|
||||||
radius: 2
|
|
||||||
color: index < Math.ceil(warmth * 3) ? Qt.rgba(1.0, 0.7 - index * 0.2, 0.3, 0.8) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for settings changes
|
|
||||||
Connections {
|
|
||||||
target: Data.Settings
|
|
||||||
function onNightLightEnabledChanged() {
|
|
||||||
if (Data.Settings.nightLightEnabled) {
|
|
||||||
createOverlay();
|
|
||||||
} else {
|
|
||||||
removeOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set manual override flag if this wasn't an automatic change
|
|
||||||
if (!autoSchedulerActive) {
|
|
||||||
Data.Settings.nightLightManualOverride = true;
|
|
||||||
Data.Settings.nightLightManuallyEnabled = Data.Settings.nightLightEnabled;
|
|
||||||
console.log("Manual night light change detected - override enabled, manually set to:", Data.Settings.nightLightEnabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function onNightLightWarmthChanged() {
|
|
||||||
updateOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Functions to control night light
|
|
||||||
function toggleNightLight() {
|
|
||||||
Data.Settings.nightLightEnabled = !Data.Settings.nightLightEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cycleWarmth() {
|
|
||||||
// Cycle through warmth levels: 0.2 -> 0.4 -> 0.6 -> 1.0 -> 0.2
|
|
||||||
var newWarmth = warmth >= 1.0 ? 0.2 : (warmth >= 0.6 ? 1.0 : warmth + 0.2);
|
|
||||||
Data.Settings.nightLightWarmth = newWarmth;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createOverlay() {
|
|
||||||
if (overlayWindow)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var qmlString = `
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: nightLightOverlay
|
|
||||||
screen: Quickshell.primaryScreen || Quickshell.screens[0]
|
|
||||||
|
|
||||||
anchors.top: true
|
|
||||||
anchors.left: true
|
|
||||||
anchors.right: true
|
|
||||||
anchors.bottom: true
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
WlrLayershell.namespace: "quickshell-nightlight"
|
|
||||||
exclusiveZone: 0
|
|
||||||
|
|
||||||
// Click-through overlay
|
|
||||||
mask: Region {}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: overlayRect
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "transparent" // Initial color, will be set by parent
|
|
||||||
|
|
||||||
// Smooth transitions when warmth changes
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation { duration: 300 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to update overlay color
|
|
||||||
function updateColor(newWarmth) {
|
|
||||||
overlayRect.color = Qt.rgba(1.0, 0.8 - newWarmth * 0.4, 0.3 - newWarmth * 0.25, 0.1 + newWarmth * 0.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
try {
|
|
||||||
overlayWindow = Qt.createQmlObject(qmlString, root);
|
|
||||||
// Set initial color
|
|
||||||
updateOverlay();
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to create night light overlay:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateOverlay() {
|
|
||||||
if (overlayWindow && overlayWindow.updateColor) {
|
|
||||||
overlayWindow.updateColor(warmth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeOverlay() {
|
|
||||||
if (overlayWindow) {
|
|
||||||
overlayWindow.destroy();
|
|
||||||
overlayWindow = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preset warmth levels for easy access
|
|
||||||
function setLow() {
|
|
||||||
Data.Settings.nightLightWarmth = 0.2;
|
|
||||||
} // Light warmth
|
|
||||||
function setMedium() {
|
|
||||||
Data.Settings.nightLightWarmth = 0.4;
|
|
||||||
} // Medium warmth
|
|
||||||
function setHigh() {
|
|
||||||
Data.Settings.nightLightWarmth = 0.6;
|
|
||||||
} // High warmth
|
|
||||||
function setMax() {
|
|
||||||
Data.Settings.nightLightWarmth = 1.0;
|
|
||||||
} // Maximum warmth
|
|
||||||
|
|
||||||
// Auto-enable based on time (basic sunset/sunrise simulation)
|
|
||||||
Timer {
|
|
||||||
interval: 60000 // Check every minute
|
|
||||||
running: true
|
|
||||||
repeat: true
|
|
||||||
onTriggered: checkAutoEnable()
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkAutoEnable() {
|
|
||||||
if (!Data.Settings.nightLightAuto)
|
|
||||||
return;
|
|
||||||
var now = new Date();
|
|
||||||
var hour = now.getHours();
|
|
||||||
var minute = now.getMinutes();
|
|
||||||
var startHour = Data.Settings.nightLightStartHour || 20;
|
|
||||||
var endHour = Data.Settings.nightLightEndHour || 6;
|
|
||||||
|
|
||||||
// Handle overnight schedules (e.g., 20:00 to 6:00)
|
|
||||||
var shouldBeActive = false;
|
|
||||||
if (startHour > endHour) {
|
|
||||||
// Overnight: active from startHour onwards OR before endHour
|
|
||||||
shouldBeActive = (hour >= startHour || hour < endHour);
|
|
||||||
} else if (startHour < endHour) {
|
|
||||||
// Same day: active between startHour and endHour
|
|
||||||
shouldBeActive = (hour >= startHour && hour < endHour);
|
|
||||||
} else {
|
|
||||||
// startHour === endHour: never auto-enable
|
|
||||||
shouldBeActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug logging
|
|
||||||
console.log(`Night Light Auto Check: ${hour}:${minute.toString().padStart(2, '0')} - Should be active: ${shouldBeActive}, Currently active: ${Data.Settings.nightLightEnabled}, Manual override: ${Data.Settings.nightLightManualOverride}`);
|
|
||||||
|
|
||||||
// Smart override logic - only block conflicting actions
|
|
||||||
if (Data.Settings.nightLightManualOverride) {
|
|
||||||
// If user manually enabled, allow auto-disable but block auto-enable
|
|
||||||
if (Data.Settings.nightLightManuallyEnabled && !shouldBeActive && Data.Settings.nightLightEnabled) {
|
|
||||||
console.log("Auto-disabling night light (respecting schedule after manual enable)");
|
|
||||||
autoSchedulerActive = true;
|
|
||||||
Data.Settings.nightLightEnabled = false;
|
|
||||||
Data.Settings.nightLightManualOverride = false; // Reset after respecting schedule
|
|
||||||
autoSchedulerActive = false;
|
|
||||||
return;
|
|
||||||
} else
|
|
||||||
// If user manually disabled, block auto-enable until next cycle
|
|
||||||
if (!Data.Settings.nightLightManuallyEnabled && shouldBeActive && !Data.Settings.nightLightEnabled) {
|
|
||||||
// Check if this is the start of a new schedule cycle
|
|
||||||
var isNewCycle = (hour === startHour && minute === 0);
|
|
||||||
if (isNewCycle) {
|
|
||||||
console.log("New schedule cycle starting - resetting manual override");
|
|
||||||
Data.Settings.nightLightManualOverride = false;
|
|
||||||
} else {
|
|
||||||
console.log("Manual disable override active - skipping auto-enable");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
// Other cases - reset override and continue
|
|
||||||
{
|
|
||||||
Data.Settings.nightLightManualOverride = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-enable when schedule starts
|
|
||||||
if (shouldBeActive && !Data.Settings.nightLightEnabled) {
|
|
||||||
console.log("Auto-enabling night light");
|
|
||||||
autoSchedulerActive = true;
|
|
||||||
Data.Settings.nightLightEnabled = true;
|
|
||||||
autoSchedulerActive = false;
|
|
||||||
} else
|
|
||||||
// Auto-disable when schedule ends
|
|
||||||
if (!shouldBeActive && Data.Settings.nightLightEnabled) {
|
|
||||||
console.log("Auto-disabling night light");
|
|
||||||
autoSchedulerActive = true;
|
|
||||||
Data.Settings.nightLightEnabled = false;
|
|
||||||
autoSchedulerActive = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup on destruction
|
|
||||||
Component.onDestruction: {
|
|
||||||
removeOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize overlay state based on settings
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (Data.Settings.nightLightEnabled) {
|
|
||||||
createOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import QtQuick.Controls
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// Screen recording toggle button
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
required property var shell
|
|
||||||
required property bool isRecording
|
|
||||||
radius: 20
|
|
||||||
|
|
||||||
signal recordingRequested
|
|
||||||
signal stopRecordingRequested
|
|
||||||
signal mouseChanged(bool containsMouse)
|
|
||||||
|
|
||||||
// Dynamic color: accent when recording/hovered, gray otherwise
|
|
||||||
color: isRecording ? Data.ThemeManager.accentColor : (mouseArea.containsMouse ? Data.ThemeManager.accentColor : Qt.darker(Data.ThemeManager.bgColor, 1.15))
|
|
||||||
|
|
||||||
property bool isHovered: mouseArea.containsMouse
|
|
||||||
readonly property alias containsMouse: mouseArea.containsMouse
|
|
||||||
|
|
||||||
// Button content with icon and text
|
|
||||||
RowLayout {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
// Recording state icon
|
|
||||||
Text {
|
|
||||||
text: isRecording ? "stop_circle" : "radio_button_unchecked"
|
|
||||||
font.family: "Material Symbols Outlined"
|
|
||||||
font.pixelSize: 16
|
|
||||||
color: isRecording || mouseArea.containsMouse ? "#ffffff" : Data.ThemeManager.fgColor
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recording state label
|
|
||||||
Label {
|
|
||||||
text: isRecording ? "Stop Recording" : "Start Recording"
|
|
||||||
font.family: "monospace"
|
|
||||||
font.pixelSize: 13
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: isRecording || mouseArea.containsMouse ? "#ffffff" : Data.ThemeManager.fgColor
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Click handling and hover detection
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
onContainsMouseChanged: root.mouseChanged(containsMouse)
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (isRecording) {
|
|
||||||
root.stopRecordingRequested();
|
|
||||||
} else {
|
|
||||||
root.recordingRequested();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import "root:/Data" as Data
|
|
||||||
|
|
||||||
// Simple theme toggle button with hover feedback
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
property var shell: null
|
|
||||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
|
||||||
radius: 20
|
|
||||||
|
|
||||||
property bool containsMouse: themeMouseArea.containsMouse
|
|
||||||
property bool menuJustOpened: false
|
|
||||||
|
|
||||||
signal entered
|
|
||||||
signal exited
|
|
||||||
|
|
||||||
// Hover state management for parent components
|
|
||||||
onContainsMouseChanged: {
|
|
||||||
if (containsMouse) {
|
|
||||||
entered();
|
|
||||||
} else if (!menuJustOpened) {
|
|
||||||
exited();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: themeMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked: {
|
|
||||||
Data.ThemeManager.toggleTheme();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theme toggle icon with color feedback
|
|
||||||
Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "contrast"
|
|
||||||
font.pixelSize: 24
|
|
||||||
font.family: "Material Symbols Outlined"
|
|
||||||
color: containsMouse ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
import Quickshell.Io
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import Qt5Compat.GraphicalEffects
|
|
||||||
import "root:/Data/" as Data
|
|
||||||
|
|
||||||
// User profile card
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
required property var shell
|
|
||||||
property url avatarSource: Data.Settings.avatarSource
|
|
||||||
property string userName: "" // will be set by process output
|
|
||||||
property string userInfo: "" // will hold uptime string
|
|
||||||
|
|
||||||
property bool isActive: false
|
|
||||||
property bool isHovered: false // track hover state
|
|
||||||
|
|
||||||
radius: 20
|
|
||||||
width: 220
|
|
||||||
height: 80
|
|
||||||
|
|
||||||
// Dynamic color based on hover and active states
|
|
||||||
color: {
|
|
||||||
if (isActive) {
|
|
||||||
return isHovered ? 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 isHovered ? 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)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 14
|
|
||||||
spacing: 12
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
// Avatar
|
|
||||||
Rectangle {
|
|
||||||
id: avatarCircle
|
|
||||||
width: 52
|
|
||||||
height: 52
|
|
||||||
radius: 20
|
|
||||||
clip: true
|
|
||||||
border.color: Data.ThemeManager.accentColor
|
|
||||||
border.width: 3
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Image {
|
|
||||||
id: avatarImage
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 2
|
|
||||||
source: Data.Settings.avatarSource
|
|
||||||
fillMode: Image.PreserveAspectCrop
|
|
||||||
cache: false
|
|
||||||
visible: false // Hidden for masking
|
|
||||||
asynchronous: true
|
|
||||||
sourceSize.width: 48 // Memory optimization
|
|
||||||
sourceSize.height: 48
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply circular mask to avatar
|
|
||||||
OpacityMask {
|
|
||||||
anchors.fill: avatarImage
|
|
||||||
source: avatarImage
|
|
||||||
cached: true // Cache to reduce ShaderEffect issues
|
|
||||||
maskSource: Rectangle {
|
|
||||||
width: avatarImage.width
|
|
||||||
height: avatarImage.height
|
|
||||||
radius: 18 // Proportional to parent radius
|
|
||||||
visible: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User information text
|
|
||||||
Column {
|
|
||||||
spacing: 4
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: parent.width - avatarCircle.width - gifContainer.width - parent.spacing * 2
|
|
||||||
|
|
||||||
Text {
|
|
||||||
width: parent.width
|
|
||||||
text: root.userName === "" ? "Loading..." : root.userName
|
|
||||||
font.family: "monospace"
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
color: isHovered || root.isActive ? "#ffffff" : Data.ThemeManager.accentColor
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
width: parent.width
|
|
||||||
text: root.userInfo === "" ? "Loading uptime..." : root.userInfo
|
|
||||||
font.family: "monospace"
|
|
||||||
font.pixelSize: 11
|
|
||||||
font.bold: true
|
|
||||||
color: isHovered || root.isActive ? "#cccccc" : Qt.lighter(Data.ThemeManager.accentColor, 1.6)
|
|
||||||
elide: Text.ElideRight
|
|
||||||
maximumLineCount: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animated GIF with rounded corners
|
|
||||||
Rectangle {
|
|
||||||
id: gifContainer
|
|
||||||
width: 80
|
|
||||||
height: 80
|
|
||||||
radius: 12
|
|
||||||
color: "transparent"
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
AnimatedImage {
|
|
||||||
id: animatedImage
|
|
||||||
source: "root:/Assets/UserProfile.gif"
|
|
||||||
anchors.fill: parent
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
playing: true
|
|
||||||
cache: false
|
|
||||||
speed: 1.0
|
|
||||||
asynchronous: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply rounded corner mask to GIF
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: OpacityMask {
|
|
||||||
cached: true // Cache to reduce ShaderEffect issues
|
|
||||||
maskSource: Rectangle {
|
|
||||||
width: gifContainer.width
|
|
||||||
height: gifContainer.height
|
|
||||||
radius: gifContainer.radius
|
|
||||||
visible: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onEntered: root.isHovered = true
|
|
||||||
onExited: root.isHovered = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current username
|
|
||||||
Process {
|
|
||||||
id: usernameProcess
|
|
||||||
running: true // Always run to get username
|
|
||||||
command: ["sh", "-c", "whoami"]
|
|
||||||
|
|
||||||
stdout: SplitParser {
|
|
||||||
splitMarker: "\n"
|
|
||||||
onRead: data => {
|
|
||||||
const line = data.trim();
|
|
||||||
if (line.length > 0) {
|
|
||||||
root.userName = line.charAt(0).toUpperCase() + line.slice(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get system uptime with parsing for readable format
|
|
||||||
Process {
|
|
||||||
id: uptimeProcess
|
|
||||||
running: false
|
|
||||||
command: ["sh", "-c", "uptime"] // Use basic uptime command
|
|
||||||
|
|
||||||
stdout: SplitParser {
|
|
||||||
splitMarker: "\n"
|
|
||||||
onRead: data => {
|
|
||||||
const line = data.trim();
|
|
||||||
if (line.length > 0) {
|
|
||||||
// Parse uptime output: " 10:30:00 up 1:23, 2 users, load average: 0.08, 0.02, 0.01"
|
|
||||||
const match = line.match(/up\s+(.+?),\s+\d+\s+user/);
|
|
||||||
if (match && match[1]) {
|
|
||||||
root.userInfo = "Up: " + match[1].trim();
|
|
||||||
} else {
|
|
||||||
// Fallback parsing for different uptime formats
|
|
||||||
const upIndex = line.indexOf("up ");
|
|
||||||
if (upIndex !== -1) {
|
|
||||||
const afterUp = line.substring(upIndex + 3);
|
|
||||||
const commaIndex = afterUp.indexOf(",");
|
|
||||||
if (commaIndex !== -1) {
|
|
||||||
root.userInfo = "Up: " + afterUp.substring(0, commaIndex).trim();
|
|
||||||
} else {
|
|
||||||
root.userInfo = "Up: " + afterUp.trim();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
root.userInfo = "Uptime unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
root.userInfo = "Uptime unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stderr: SplitParser {
|
|
||||||
splitMarker: "\n"
|
|
||||||
onRead: data => {
|
|
||||||
console.log("Uptime error:", data);
|
|
||||||
root.userInfo = "Uptime error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update uptime every 5 minutes
|
|
||||||
Timer {
|
|
||||||
id: uptimeTimer
|
|
||||||
interval: 300000 // Update every 5 minutes
|
|
||||||
running: true // Always run the uptime timer
|
|
||||||
repeat: true
|
|
||||||
onTriggered: {
|
|
||||||
uptimeProcess.running = false;
|
|
||||||
uptimeProcess.running = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
uptimeProcess.running = true; // Start uptime process on component load
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onDestruction: {
|
|
||||||
if (usernameProcess.running) {
|
|
||||||
usernameProcess.running = false;
|
|
||||||
}
|
|
||||||
if (uptimeProcess.running) {
|
|
||||||
uptimeProcess.running = false;
|
|
||||||
}
|
|
||||||
if (uptimeTimer.running) {
|
|
||||||
uptimeTimer.running = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user