mirror of
https://github.com/danbulant/dotfiles
synced 2026-09-17 13:23:51 +00:00
Compare commits
2 commits
dd039cab6c
...
202e29c87c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
202e29c87c | ||
|
|
402a348601 |
7 changed files with 586 additions and 24 deletions
|
|
@ -41,6 +41,7 @@
|
|||
],
|
||||
"semantic_tokens": "combined",
|
||||
"cli_default_open_behavior": "existing_window",
|
||||
"file_scan_exclusions": ["**/node_modules", "..."],
|
||||
"languages": {
|
||||
"JavaScript": {
|
||||
"formatter": { "language_server": { "name": "biome" } },
|
||||
|
|
|
|||
18
flake.lock
18
flake.lock
|
|
@ -1689,11 +1689,11 @@
|
|||
},
|
||||
"nixpkgs-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1788179007,
|
||||
"narHash": "sha256-hn1oU2rue2SYK8dAr8+WNZWtbsz1S2W5mnHlSEuh3bo=",
|
||||
"lastModified": 1788531059,
|
||||
"narHash": "sha256-hLD4l3QOGBQhkVp3mQ2lJ/YbEi99qUgKapb40KovZ88=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "34ab99075ac4f7e40cf037eef32cb1c360bb85e9",
|
||||
"rev": "801bef6abd86b91e51083066b83fb354a11fc640",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -1766,11 +1766,11 @@
|
|||
},
|
||||
"nixpkgs_13": {
|
||||
"locked": {
|
||||
"lastModified": 1788179007,
|
||||
"narHash": "sha256-hn1oU2rue2SYK8dAr8+WNZWtbsz1S2W5mnHlSEuh3bo=",
|
||||
"lastModified": 1788531059,
|
||||
"narHash": "sha256-hLD4l3QOGBQhkVp3mQ2lJ/YbEi99qUgKapb40KovZ88=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "34ab99075ac4f7e40cf037eef32cb1c360bb85e9",
|
||||
"rev": "801bef6abd86b91e51083066b83fb354a11fc640",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -1961,11 +1961,11 @@
|
|||
"rust-overlay": "rust-overlay_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788263493,
|
||||
"narHash": "sha256-VCwnvc3sHbp8Ln4GugqbJHNl0CVelsxzdWMLfDaFXbw=",
|
||||
"lastModified": 1788547023,
|
||||
"narHash": "sha256-xr/YlrdPgCE/DouJdKVfMhEGldzsvgXs8k5heknctVY=",
|
||||
"owner": "can1357",
|
||||
"repo": "oh-my-pi",
|
||||
"rev": "45aed39ae01f1086f56983563b80c8c079df5cfd",
|
||||
"rev": "5964a0f7649275bcde818f20073193fd032451f2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
20
flake.nix
20
flake.nix
|
|
@ -168,6 +168,26 @@
|
|||
];
|
||||
};
|
||||
desktopCompatibilityOverlay = _: prev: {
|
||||
codex =
|
||||
let
|
||||
version = "0.153.4";
|
||||
src = prev.fetchFromGitHub {
|
||||
owner = "openai";
|
||||
repo = "codex";
|
||||
tag = "rust-v${version}";
|
||||
hash = "sha256-lHiDj5SodaM3mh8goMm6esfejeAT+Y3JJWrRnyj6sJo=";
|
||||
};
|
||||
sourceRoot = "${src.name}/codex-rs";
|
||||
in
|
||||
prev.codex.overrideAttrs (_: {
|
||||
inherit version src sourceRoot;
|
||||
cargoHash = "sha256-GG6kOXmCdq+bZLU2ul0DIVL8lDuweayvZvXn6+bcUZw=";
|
||||
cargoDeps = prev.rustPlatform.fetchCargoVendor {
|
||||
inherit src sourceRoot;
|
||||
hash = "sha256-GG6kOXmCdq+bZLU2ul0DIVL8lDuweayvZvXn6+bcUZw=";
|
||||
};
|
||||
});
|
||||
|
||||
# Hyprland 0.56.1 requires glaze >= 7 and < 8, while this
|
||||
# nixpkgs revision provides glaze 8.0.0.
|
||||
hyprland = prev.hyprland.override {
|
||||
|
|
|
|||
74
pkgs/delta-bin/package.nix
Normal file
74
pkgs/delta-bin/package.nix
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
alsa-lib,
|
||||
autoPatchelfHook,
|
||||
dbus,
|
||||
fontconfig,
|
||||
glib,
|
||||
lib,
|
||||
libGL,
|
||||
libsecret,
|
||||
makeWrapper,
|
||||
openssl,
|
||||
requireFile,
|
||||
stdenv,
|
||||
vulkan-loader,
|
||||
wayland,
|
||||
xkeyboard_config,
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "delta-bin";
|
||||
version = "0.6.1";
|
||||
|
||||
src = requireFile {
|
||||
name = "delta-linux-x86_64.tar.gz";
|
||||
url = "https://delta.dev/download";
|
||||
hash = "sha256-Q/CSFKlIDd47DwQk/QatfBs8AtM/ObTA3TFOTvggcsE=";
|
||||
message = ''
|
||||
Delta is beta-gated and cannot be downloaded without a Zed account.
|
||||
Download the Linux x86_64 archive from https://delta.dev/download, then run:
|
||||
|
||||
nix-store --add-fixed sha256 delta-linux-x86_64.tar.gz
|
||||
'';
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
autoPatchelfHook
|
||||
makeWrapper
|
||||
];
|
||||
runtimeDependencies = [
|
||||
alsa-lib
|
||||
dbus
|
||||
fontconfig
|
||||
glib
|
||||
libGL
|
||||
libsecret
|
||||
openssl
|
||||
vulkan-loader
|
||||
wayland
|
||||
];
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out
|
||||
cp -a ./. $out/
|
||||
rm $out/install.sh
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
postFixup = ''
|
||||
wrapProgram $out/bin/delta \
|
||||
--set-default XKB_CONFIG_ROOT "${xkeyboard_config}/share/X11/xkb"
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Collaborative agent workspace from the creators of Zed";
|
||||
homepage = "https://delta.dev/";
|
||||
license = lib.licenses.unfree;
|
||||
mainProgram = "delta";
|
||||
platforms = [ "x86_64-linux" ];
|
||||
sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
|
||||
};
|
||||
})
|
||||
452
scripts/app-usage-report.py
Executable file
452
scripts/app-usage-report.py
Executable file
|
|
@ -0,0 +1,452 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Report GUI packages declared for a Home Manager user but rarely launched.
|
||||
|
||||
The primary signal is DankMaterialShell's launcher history. Shell history,
|
||||
notification history, and configuration references are included as secondary
|
||||
signals so the report can expose likely false positives instead of silently
|
||||
classifying them as unused.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import datetime as dt
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
TEXT_SUFFIXES = {
|
||||
"",
|
||||
".conf",
|
||||
".desktop",
|
||||
".fish",
|
||||
".ini",
|
||||
".json",
|
||||
".kdl",
|
||||
".lua",
|
||||
".nix",
|
||||
".nu",
|
||||
".service",
|
||||
".sh",
|
||||
".toml",
|
||||
".yaml",
|
||||
".yml",
|
||||
}
|
||||
SKIP_DIRS = {
|
||||
".git",
|
||||
".mozilla",
|
||||
"Cache",
|
||||
"GPUCache",
|
||||
"IndexedDB",
|
||||
"Local Storage",
|
||||
"Service Worker",
|
||||
"cache",
|
||||
"node_modules",
|
||||
"storage",
|
||||
}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Find rarely launched GUI applications declared in a Home Manager profile."
|
||||
)
|
||||
parser.add_argument("--flake", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
parser.add_argument("--host", default="fern")
|
||||
parser.add_argument("--user", default=os.environ.get("USER", "dan"))
|
||||
parser.add_argument(
|
||||
"--days",
|
||||
type=int,
|
||||
default=90,
|
||||
help="consider a DMS launch recent within this many days (default: 90)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-launches",
|
||||
type=int,
|
||||
default=3,
|
||||
help="label an app rare when its lifetime DMS count is at most this value (default: 3)",
|
||||
)
|
||||
parser.add_argument("--all", action="store_true", help="include recently used applications")
|
||||
parser.add_argument("--json", action="store_true", help="emit machine-readable JSON")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def nix_packages(flake: Path, host: str, user: str) -> list[dict[str, Any]]:
|
||||
installable = (
|
||||
f".#nixosConfigurations.{host}.config.home-manager.users.{user}.home.packages"
|
||||
)
|
||||
result = subprocess.run(
|
||||
["nix", "eval", "--json", installable],
|
||||
cwd=flake,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
if result.returncode:
|
||||
sys.stderr.write(result.stderr)
|
||||
raise SystemExit(result.returncode)
|
||||
return [
|
||||
{
|
||||
"name": Path(out_path).name,
|
||||
"pname": None,
|
||||
"version": None,
|
||||
"outPath": out_path,
|
||||
}
|
||||
for out_path in json.loads(result.stdout)
|
||||
]
|
||||
|
||||
|
||||
def normalize(value: str) -> str:
|
||||
value = value.casefold().removesuffix(".desktop")
|
||||
return re.sub(r"[^a-z0-9]+", "", value)
|
||||
|
||||
|
||||
def executable_from(exec_line: str) -> str:
|
||||
try:
|
||||
words = shlex.split(exec_line)
|
||||
except ValueError:
|
||||
words = exec_line.split()
|
||||
words = [word for word in words if not word.startswith("%")]
|
||||
while words and ("=" in words[0] or words[0] in {"env", "nohup"}):
|
||||
words.pop(0)
|
||||
return Path(words[0]).name if words else ""
|
||||
|
||||
|
||||
def desktop_entry(path: Path) -> dict[str, str] | None:
|
||||
parser = configparser.ConfigParser(interpolation=None, strict=False)
|
||||
try:
|
||||
parser.read(path, encoding="utf-8")
|
||||
entry = parser["Desktop Entry"]
|
||||
except (OSError, UnicodeError, KeyError, configparser.Error):
|
||||
return None
|
||||
if entry.get("Type", "Application") != "Application":
|
||||
return None
|
||||
if entry.getboolean("Hidden", fallback=False) or entry.getboolean(
|
||||
"NoDisplay", fallback=False
|
||||
):
|
||||
return None
|
||||
return {
|
||||
"id": path.stem,
|
||||
"name": entry.get("Name", path.stem),
|
||||
"exec": executable_from(entry.get("Exec", "")),
|
||||
"wm_class": entry.get("StartupWMClass", ""),
|
||||
"desktop_file": str(path),
|
||||
}
|
||||
|
||||
|
||||
def package_key(store_name: str) -> str:
|
||||
name = re.sub(r"^[a-z0-9]{32}-", "", store_name)
|
||||
parts = name.split("-")
|
||||
for index, part in enumerate(parts[1:], start=1):
|
||||
if re.match(r"v?\d", part):
|
||||
return "-".join(parts[:index]).casefold()
|
||||
return name.casefold()
|
||||
|
||||
|
||||
def active_gui_packages(
|
||||
home: Path, user: str, declared_packages: list[dict[str, Any]]
|
||||
) -> tuple[list[dict[str, Any]], int]:
|
||||
profile = Path("/etc/profiles/per-user") / user
|
||||
if not profile.exists():
|
||||
profile = home / ".nix-profile"
|
||||
applications = profile / "share/applications"
|
||||
declared_keys = {package_key(package["name"]) for package in declared_packages}
|
||||
grouped: dict[Path, list[dict[str, str]]] = defaultdict(list)
|
||||
skipped = 0
|
||||
for path in applications.glob("*.desktop"):
|
||||
try:
|
||||
resolved = path.resolve(strict=True)
|
||||
owner = Path(*resolved.parts[:4])
|
||||
except OSError:
|
||||
continue
|
||||
if package_key(owner.name) not in declared_keys:
|
||||
skipped += 1
|
||||
continue
|
||||
entry = desktop_entry(path)
|
||||
if entry:
|
||||
grouped[owner].append(entry)
|
||||
return (
|
||||
[
|
||||
{
|
||||
"name": re.sub(r"^[a-z0-9]{32}-", "", owner.name),
|
||||
"pname": package_key(owner.name),
|
||||
"version": None,
|
||||
"outPath": str(owner),
|
||||
"entries": entries,
|
||||
}
|
||||
for owner, entries in grouped.items()
|
||||
],
|
||||
skipped,
|
||||
)
|
||||
|
||||
|
||||
def load_json(path: Path) -> Any:
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
return {}
|
||||
|
||||
|
||||
def usage_index(path: Path) -> tuple[dict[str, Any], dict[str, list[str]]]:
|
||||
records = load_json(path).get("appUsageRanking", {})
|
||||
indexes: dict[str, list[str]] = defaultdict(list)
|
||||
for key, record in records.items():
|
||||
for value in (key, record.get("name", ""), executable_from(record.get("exec", ""))):
|
||||
token = normalize(str(value))
|
||||
if token and key not in indexes[token]:
|
||||
indexes[token].append(key)
|
||||
return records, indexes
|
||||
|
||||
|
||||
def entry_tokens(entry: dict[str, str]) -> set[str]:
|
||||
return {
|
||||
token
|
||||
for token in (
|
||||
normalize(entry["id"]),
|
||||
normalize(entry["name"]),
|
||||
normalize(entry["exec"]),
|
||||
normalize(entry["wm_class"]),
|
||||
)
|
||||
if len(token) >= 3
|
||||
}
|
||||
|
||||
|
||||
def match_usage_keys(entry: dict[str, str], index: dict[str, list[str]]) -> set[str]:
|
||||
matches: set[str] = set()
|
||||
for token in entry_tokens(entry):
|
||||
matches.update(index.get(token, []))
|
||||
return matches
|
||||
|
||||
|
||||
def notification_counts(path: Path) -> dict[str, int]:
|
||||
data = load_json(path)
|
||||
notifications = data if isinstance(data, list) else data.get("notifications", [])
|
||||
counts: dict[str, int] = defaultdict(int)
|
||||
for item in notifications if isinstance(notifications, list) else []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
for field in ("desktopEntry", "appName"):
|
||||
token = normalize(str(item.get(field, "")))
|
||||
if token:
|
||||
counts[token] += 1
|
||||
return counts
|
||||
|
||||
|
||||
def history_text(home: Path) -> str:
|
||||
chunks: list[str] = []
|
||||
for path in (
|
||||
home / ".local/share/fish/fish_history",
|
||||
home / ".config/nushell/history.txt",
|
||||
):
|
||||
try:
|
||||
chunks.append(path.read_text(encoding="utf-8", errors="ignore"))
|
||||
except OSError:
|
||||
pass
|
||||
return "\n".join(chunks).casefold()
|
||||
|
||||
|
||||
def iter_config_files(roots: Iterable[Path]) -> Iterable[Path]:
|
||||
seen: set[Path] = set()
|
||||
for root in roots:
|
||||
if not root.is_dir():
|
||||
continue
|
||||
for current, dirs, files in os.walk(root):
|
||||
dirs[:] = [name for name in dirs if name not in SKIP_DIRS]
|
||||
for name in files:
|
||||
path = Path(current) / name
|
||||
if path in seen or path.suffix.casefold() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
seen.add(path)
|
||||
try:
|
||||
if path.stat().st_size <= 1_000_000:
|
||||
yield path
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
|
||||
def config_references(
|
||||
roots: Iterable[Path],
|
||||
home_config: Path,
|
||||
token_to_packages: dict[str, set[int]],
|
||||
) -> dict[int, list[str]]:
|
||||
references: dict[int, list[str]] = defaultdict(list)
|
||||
searchable = sorted(
|
||||
(token for token in token_to_packages if len(token) >= 4),
|
||||
key=len,
|
||||
reverse=True,
|
||||
)
|
||||
if not searchable:
|
||||
return references
|
||||
pattern = re.compile(
|
||||
r"(?<![A-Za-z0-9])(?:"
|
||||
+ "|".join(re.escape(token) for token in searchable)
|
||||
+ r")(?![A-Za-z0-9])",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
for path in iter_config_files(roots):
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8", errors="ignore")
|
||||
except OSError:
|
||||
continue
|
||||
matched_packages: set[int] = set()
|
||||
for match in pattern.finditer(text):
|
||||
matched_packages.update(token_to_packages.get(match.group(0).casefold(), set()))
|
||||
for package_id in matched_packages:
|
||||
references[package_id].append(str(path))
|
||||
|
||||
normalized_tokens: dict[str, set[int]] = defaultdict(set)
|
||||
for token, package_ids in token_to_packages.items():
|
||||
normalized_tokens[normalize(token)].update(package_ids)
|
||||
try:
|
||||
config_children = list(home_config.iterdir())
|
||||
except OSError:
|
||||
config_children = []
|
||||
for path in config_children:
|
||||
for package_id in normalized_tokens.get(normalize(path.name), set()):
|
||||
path_string = str(path)
|
||||
if path_string not in references[package_id]:
|
||||
references[package_id].append(path_string)
|
||||
return references
|
||||
|
||||
|
||||
def iso_time(timestamp_ms: int | float | None) -> str | None:
|
||||
if not timestamp_ms:
|
||||
return None
|
||||
return dt.datetime.fromtimestamp(float(timestamp_ms) / 1000, dt.timezone.utc).isoformat(
|
||||
timespec="seconds"
|
||||
)
|
||||
|
||||
|
||||
def analyze(args: argparse.Namespace) -> dict[str, Any]:
|
||||
home = Path.home() if args.user == os.environ.get("USER") else Path("/home") / args.user
|
||||
dms_state = home / ".local/state/DankMaterialShell/appusage.json"
|
||||
records, usage_by_token = usage_index(dms_state)
|
||||
notifications = notification_counts(home / ".cache/DankMaterialShell/notification_history.json")
|
||||
shell_history = history_text(home)
|
||||
|
||||
declared_packages = nix_packages(args.flake, args.host, args.user)
|
||||
gui_packages, skipped_profile_entries = active_gui_packages(
|
||||
home, args.user, declared_packages
|
||||
)
|
||||
|
||||
token_to_packages: dict[str, set[int]] = defaultdict(set)
|
||||
raw_tokens: dict[int, set[str]] = defaultdict(set)
|
||||
for package_id, package in enumerate(gui_packages):
|
||||
for entry in package["entries"]:
|
||||
for raw in (entry["id"], entry["exec"], entry["wm_class"]):
|
||||
token = raw.casefold().strip()
|
||||
if len(token) >= 4:
|
||||
raw_tokens[package_id].add(token)
|
||||
token_to_packages[token].add(package_id)
|
||||
|
||||
config_refs = config_references(
|
||||
(args.flake / ".config",),
|
||||
home / ".config",
|
||||
token_to_packages,
|
||||
)
|
||||
cutoff = dt.datetime.now(dt.timezone.utc).timestamp() * 1000 - args.days * 86_400_000
|
||||
report: list[dict[str, Any]] = []
|
||||
|
||||
for package_id, package in enumerate(gui_packages):
|
||||
usage_keys: set[str] = set()
|
||||
app_names: list[str] = []
|
||||
notify_count = 0
|
||||
for entry in package["entries"]:
|
||||
usage_keys.update(match_usage_keys(entry, usage_by_token))
|
||||
app_names.append(entry["name"])
|
||||
notify_count += sum(notifications.get(token, 0) for token in entry_tokens(entry))
|
||||
|
||||
launches = sum(int(records[key].get("usageCount", 0)) for key in usage_keys)
|
||||
last_used_ms = max(
|
||||
(float(records[key].get("lastUsed", 0)) for key in usage_keys), default=0
|
||||
)
|
||||
history_hits = sum(shell_history.count(token) for token in raw_tokens[package_id])
|
||||
recent = last_used_ms >= cutoff
|
||||
if recent:
|
||||
status = "recent"
|
||||
elif last_used_ms:
|
||||
status = (
|
||||
"stale-rare"
|
||||
if launches <= args.max_launches
|
||||
else "stale-frequent"
|
||||
)
|
||||
else:
|
||||
status = "configured-only" if config_refs[package_id] else "never"
|
||||
|
||||
report.append(
|
||||
{
|
||||
"package": package.get("pname") or package["name"],
|
||||
"version": package.get("version"),
|
||||
"applications": sorted(set(app_names)),
|
||||
"status": status,
|
||||
"dms_launches": launches,
|
||||
"last_dms_launch": iso_time(last_used_ms),
|
||||
"shell_history_hits": history_hits,
|
||||
"notification_hits": notify_count,
|
||||
"config_references": sorted(config_refs[package_id]),
|
||||
}
|
||||
)
|
||||
|
||||
order = {
|
||||
"never": 0,
|
||||
"configured-only": 1,
|
||||
"stale-rare": 2,
|
||||
"stale-frequent": 3,
|
||||
"recent": 4,
|
||||
}
|
||||
report.sort(
|
||||
key=lambda row: (
|
||||
order[row["status"]],
|
||||
row["dms_launches"],
|
||||
row["last_dms_launch"] or "",
|
||||
row["package"].casefold(),
|
||||
)
|
||||
)
|
||||
return {
|
||||
"host": args.host,
|
||||
"user": args.user,
|
||||
"window_days": args.days,
|
||||
"dms_usage_file": str(dms_state),
|
||||
"gui_package_count": len(gui_packages),
|
||||
"unmatched_profile_entry_count": skipped_profile_entries,
|
||||
"applications": report if args.all else [row for row in report if row["status"] != "recent"],
|
||||
}
|
||||
|
||||
|
||||
def print_report(result: dict[str, Any]) -> None:
|
||||
rows = result["applications"]
|
||||
print(
|
||||
f"{result['host']} GUI package usage: {len(rows)} shown / "
|
||||
f"{result['gui_package_count']} detected "
|
||||
f"({result['window_days']}-day DMS window)"
|
||||
)
|
||||
print("STATUS LAUNCHES LAST DMS LAUNCH HIST NOTIF CONFIG PACKAGE / APPLICATIONS")
|
||||
for row in rows:
|
||||
last = row["last_dms_launch"][:10] if row["last_dms_launch"] else "never"
|
||||
apps = ", ".join(row["applications"])
|
||||
print(
|
||||
f"{row['status']:<16} {row['dms_launches']:>8} {last:<17} "
|
||||
f"{row['shell_history_hits']:>5} {row['notification_hits']:>5} "
|
||||
f"{len(row['config_references']):>6} {row['package']} / {apps}"
|
||||
)
|
||||
print("\nDMS launches are authoritative only for apps opened through the DMS launcher.")
|
||||
print("HIST, NOTIF, and CONFIG are secondary evidence; inspect them before removing a package.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
result = analyze(args)
|
||||
if args.json:
|
||||
json.dump(result, sys.stdout, indent=2)
|
||||
print()
|
||||
else:
|
||||
print_report(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -118,6 +118,19 @@ in
|
|||
];
|
||||
|
||||
services.hardware.openrgb.enable = true;
|
||||
|
||||
# OpenRGB's GUI shutdown action runs too late during session teardown. Blank
|
||||
# the controllers while udev and the Nix store are still available instead.
|
||||
systemd.services.openrgb-shutdown = {
|
||||
description = "Turn off RGB lighting before shutdown";
|
||||
wantedBy = [ "shutdown.target" ];
|
||||
before = [ "shutdown.target" ];
|
||||
unitConfig.DefaultDependencies = false;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${lib.getExe pkgs.openrgb-with-all-plugins} --device B850 --mode static --color 000000 --device Wooting --color 000000";
|
||||
};
|
||||
};
|
||||
# The split RemoteDesktop/ScreenCast portal session emits malformed D-Bus
|
||||
# traffic with XDPH 1.4.1. Override capture without replacing Sunshine's
|
||||
# mutable web-UI configuration.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
}:
|
||||
let
|
||||
system = pkgs.stdenv.hostPlatform.system;
|
||||
delta-bin = pkgs.callPackage ../../pkgs/delta-bin/package.nix { };
|
||||
dmsShell = dms.packages.${system}.dms-shell.overrideAttrs (oldAttrs: {
|
||||
postPatch = (oldAttrs.postPatch or "") + ''
|
||||
if [[ -f quickshell/Services/PopoutService.qml ]]; then
|
||||
|
|
@ -142,6 +143,8 @@ in
|
|||
stateVersion = "25.11";
|
||||
|
||||
packages = with pkgs; [
|
||||
delta-bin
|
||||
kicad
|
||||
protontricks
|
||||
waypipe
|
||||
cinny
|
||||
|
|
@ -157,7 +160,7 @@ in
|
|||
wine
|
||||
codexbar.packages.${pkgs.system}.default
|
||||
codex
|
||||
jellyfin-desktop
|
||||
#jellyfin-desktop
|
||||
(kdePackages.qt6ct.overrideAttrs (oldAttrs: {
|
||||
patches = (oldAttrs.patches or [ ]) ++ [ ../../pkgs/qt6ct-0.11.patch ];
|
||||
name = "qt6ct-kde";
|
||||
|
|
@ -176,12 +179,12 @@ in
|
|||
nixd
|
||||
buck2
|
||||
(rusic.packages.${system}.default)
|
||||
affine
|
||||
#affine
|
||||
voxtype-vulkan
|
||||
#affinity-nix.packages.x86_64-linux.v3
|
||||
biome
|
||||
bun
|
||||
lenovo-legion
|
||||
#lenovo-legion
|
||||
itch
|
||||
filezilla
|
||||
nicotine-plus
|
||||
|
|
@ -203,11 +206,11 @@ in
|
|||
libxkbfile
|
||||
|
||||
# dioxus-cli
|
||||
cosmic-files
|
||||
cosmic-player
|
||||
cosmic-screenshot
|
||||
cosmic-applibrary
|
||||
cosmic-ext-calculator
|
||||
#cosmic-files
|
||||
#cosmic-player
|
||||
#cosmic-screenshot
|
||||
#cosmic-applibrary
|
||||
#cosmic-ext-calculator
|
||||
cosmic-icons
|
||||
examine
|
||||
|
||||
|
|
@ -227,7 +230,7 @@ in
|
|||
usbimager
|
||||
#bitwarden-desktop
|
||||
#metasploit
|
||||
lenovo-legion
|
||||
#lenovo-legion
|
||||
burpsuite
|
||||
zap
|
||||
kubernetes-helm
|
||||
|
|
@ -268,17 +271,16 @@ in
|
|||
lf
|
||||
rawtherapee
|
||||
syncthingtray
|
||||
anki-bin
|
||||
#anki-bin
|
||||
xournalpp
|
||||
simple-scan
|
||||
godot_4
|
||||
#rar
|
||||
wootility
|
||||
#wootility
|
||||
#surrealdb
|
||||
pico-sdk
|
||||
elf2uf2-rs
|
||||
obsidian
|
||||
betaflight-configurator
|
||||
home-manager
|
||||
#glxinfo
|
||||
pciutils
|
||||
|
|
@ -318,8 +320,8 @@ in
|
|||
datovka
|
||||
nwg-displays
|
||||
wireguard-tools
|
||||
mongodb-compass
|
||||
unstable.mongodb-tools
|
||||
#mongodb-compass
|
||||
#unstable.mongodb-tools
|
||||
#hashcat
|
||||
tldr
|
||||
#dunst
|
||||
|
|
@ -430,7 +432,7 @@ in
|
|||
graphviz
|
||||
|
||||
#blender
|
||||
warpinator
|
||||
#warpinator
|
||||
|
||||
awatcher
|
||||
tigervnc
|
||||
|
|
|
|||
Loading…
Reference in a new issue