From 6af0cdbacfd24064acb46b5e959ab319b5efccc5 Mon Sep 17 00:00:00 2001 From: chayleaf Date: Tue, 7 Mar 2023 18:20:08 +0700 Subject: [PATCH] rofi steam game list... and lots of other stuff --- home/common/fish.nix | 47 +- home/common/general.nix | 8 +- home/common/gui.nix | 28 +- home/common/i3-sway.nix | 26 +- home/common/vim.nix | 32 +- home/flake.lock | 26 +- .../default.nix} | 0 home/hosts/nixmsi.nix | 2 +- home/rofi-steam-game-list/.gitignore | 1 + home/rofi-steam-game-list/Cargo.lock | 225 ++++++++ home/rofi-steam-game-list/Cargo.toml | 10 + home/rofi-steam-game-list/default.nix | 14 + home/rofi-steam-game-list/src/main.rs | 510 ++++++++++++++++++ system/common/vfio.nix | 5 +- system/flake.lock | 25 +- system/flake.nix | 3 +- system/hosts/nixmsi.nix | 1 + 17 files changed, 898 insertions(+), 65 deletions(-) rename home/{common/home-daemon.nix => home-daemon/default.nix} (100%) create mode 100644 home/rofi-steam-game-list/.gitignore create mode 100644 home/rofi-steam-game-list/Cargo.lock create mode 100644 home/rofi-steam-game-list/Cargo.toml create mode 100644 home/rofi-steam-game-list/default.nix create mode 100644 home/rofi-steam-game-list/src/main.rs diff --git a/home/common/fish.nix b/home/common/fish.nix index 665b25b..d80a35b 100644 --- a/home/common/fish.nix +++ b/home/common/fish.nix @@ -1,15 +1,56 @@ { config, pkgs, lib, ... }: { - # TODO: theme (it's using fish variables...) - programs.fish = { + programs.fish = + let nom-compat = pkgs.runCommand "any-shell-nom-compat" {} '' + mkdir -p $out/bin + for cmd in $(echo nix nix-shell nix-build); do + echo '#! ${pkgs.bash}/bin/bash' > $out/bin/$cmd + echo -n 'PATH=`echo $PATH | tr ":" "\n" | grep -v "any-shell-nom-compat" | tr "\n" ":"` ' >> $out/bin/$cmd + cmd1=$(echo $cmd | sed 's/nix/nom/') + echo "$cmd1"' "$@"' >> $out/bin/$cmd + chmod +x $out/bin/$cmd + done + ''; + in { enable = true; # not sure this is needed, but just in case shellInit = '' source /etc/fish/config.fish ''; interactiveShellInit = '' - ${pkgs.any-nix-shell}/bin/any-nix-shell fish --info-right | source + # ${pkgs.any-nix-shell}/bin/any-nix-shell fish | source + function nix-shell + ${pkgs.any-nix-shell}/bin/.any-nix-shell-wrapper fish $argv + end + function nix + if test $argv[1] = shell + set argv[1] fish + ${pkgs.any-nix-shell}/bin/.any-nix-wrapper $argv + else if test $argv[1] = develop + set argv[1] fish + command nix develop --command $argv + else + command nix $argv + end + end + + function nom-shell + PATH="${nom-compat}/bin:$PATH" ${pkgs.any-nix-shell}/bin/.any-nix-shell-wrapper fish $argv + end + function nom + if test $argv[1] = shell + set argv[1] fish + PATH="${nom-compat}/bin:$PATH" ${pkgs.any-nix-shell}/bin/.any-nix-wrapper $argv + else if test $argv[1] = develop + set argv[1] fish + ${pkgs.nix-output-monitor}/bin/nom develop --command $argv + else + command nix $argv + end + end + + export NOMCOMPAT=${nom-compat} # for posix compatibility set -gx SHELL zsh diff --git a/home/common/general.nix b/home/common/general.nix index addc02c..0ca2cef 100644 --- a/home/common/general.nix +++ b/home/common/general.nix @@ -56,12 +56,12 @@ # for preview exa bat ffmpeg ffmpegthumbnailer nsxiv imagemagick - libarchive unzip gnutar atool + libarchive atool libreoffice poppler_utils fontpreview djvulibre glow w3m # for opening - p7zip unrar-wrapper zathura mpv odt2txt - ]; + p7zip unrar-wrapper zathura odt2txt + ] ++ lib.optionals (!config.programs.mpv.enable) [ mpv ]; plugins = { src = pluginSrc; mappings = { @@ -144,6 +144,6 @@ home.packages = with pkgs; [ rclone sshfs fuse file jq python3Full killall - appimage-run comma + appimage-run comma nix-output-monitor ]; } diff --git a/home/common/gui.nix b/home/common/gui.nix index 7872386..1732d8e 100644 --- a/home/common/gui.nix +++ b/home/common/gui.nix @@ -1,9 +1,31 @@ { config, pkgs, lib, ... }: { imports = [ ./terminal.nix ]; + i18n.inputMethod = let fcitx5-qt = pkgs.libsForQt5.fcitx5-qt; in { + enabled = "fcitx5"; + fcitx5.addons = with pkgs; [ fcitx5-lua fcitx5-gtk fcitx5-mozc fcitx5-configtool fcitx5-qt ]; + }; home.sessionVariables = { + GTK_IM_MODULE = "fcitx"; + QT_IM_MODULE = "fcitx"; + XMODIFIERS = "@im=fcitx"; + SDL_IM_MODULE = "fcitx"; + XIM_SERVERS = "fcitx"; + INPUT_METHOD = "fcitx"; + SUDO_ASKPASS = pkgs.writeScript "sudo-askpass" '' + #! ${pkgs.bash}/bin/bash + ${pkgs.libsecret}/bin/secret-tool lookup root password + ''; + SDL_AUDIODRIVER = "pipewire,pulse,dsound"; + # SDL 3 + SDL_AUDIO_DRIVER = "pipewire,pulseaudio,dsound"; ALSOFT_CONF = "${config.xdg.configHome}/.config/alsoft.conf"; - SDL_AUDIODRIVER = "pipewire"; + # TODO: set to sdl3 compat when SDL3 releases + # this is for steam games, I set the launch options to: + # `SDL_DYNAMIC_API=$SDL2_DYNAMIC_API %command%` + # Steam itself doesn't work with SDL_DYNAMIC_API set, so it's + # a bad idea to set SDL_DYNAMIC_API globally + SDL2_DYNAMIC_API = "${pkgs.SDL2}/lib/libSDL2.so"; }; xdg.configFile."alsoft.conf".text = '' [general] @@ -189,10 +211,6 @@ ]; }; }; - i18n.inputMethod = let fcitx5-qt = pkgs.libsForQt5.fcitx5-qt; in { - enabled = "fcitx5"; - fcitx5.addons = with pkgs; [ fcitx5-lua fcitx5-gtk fcitx5-mozc fcitx5-configtool fcitx5-qt ]; - }; services.gammastep.enable = true; fonts.fontconfig.enable = true; gtk = { diff --git a/home/common/i3-sway.nix b/home/common/i3-sway.nix index 623e9a9..3e94f6d 100644 --- a/home/common/i3-sway.nix +++ b/home/common/i3-sway.nix @@ -80,7 +80,7 @@ commonConfig = { inherit modifier; startup = [ { command = builtins.toString (with pkgs; writeShellScript "init-wm" '' - ${callPackage ./home-daemon.nix {}}/bin/dotfiles-home-daemon system76-scheduler& + ${callPackage ../home-daemon {}}/bin/dotfiles-home-daemon system76-scheduler& ${gnome.zenity}/bin/zenity --password | (${keepassxc}/bin/keepassxc --pw-stdin ~/Nextcloud/keepass.kdbx&) # nextcloud and nheko need secret service access ${nextcloud-client}/bin/nextcloud --background& @@ -166,16 +166,6 @@ in imports = [ ./options.nix ./gui.nix ./waybar.nix ]; home.sessionVariables = { _JAVA_AWT_WM_NONREPARENTING = "1"; - GTK_IM_MODULE = "fcitx"; - QT_IM_MODULE = "fcitx"; - XMODIFIERS = "@im=fcitx"; - SDL_IM_MODULE = "fcitx"; - XIM_SERVERS = "fcitx"; - INPUT_METHOD = "fcitx"; - SUDO_ASKPASS = pkgs.writeScript "sudo-askpass" '' - #! ${pkgs.bash}/bin/bash - ${pkgs.libsecret}/bin/secret-tool lookup root password - ''; }; xdg.configFile."xdg-desktop-portal-wlr/config".source = (pkgs.formats.ini {}).generate "xdg-desktop-portal-wlr.ini" { screencast = { @@ -189,7 +179,7 @@ in systemd.user.services = lib.mkIf config.wayland.windowManager.sway.enable { gammastep.Unit.ConditionEnvironment = "WAYLAND_DISPLAY"; }; - programs.mako = { + services.mako = { enable = lib.mkDefault config.wayland.windowManager.sway.enable; # ms defaultTimeout = 7500; @@ -336,7 +326,9 @@ in }; in commonConfig // swayConfig; extraSessionCommands = '' export WLR_RENDERER=vulkan - export SDL_VIDEODRIVER=wayland + export SDL_VIDEODRIVER=wayland,x11,kmsdrm,windows,directx + # SDL3 + export SDL_VIDEO_DRIVER=wayland,x11,kmsdrm,windows export QT_QPA_PLATFORM=wayland export QT_WAYLAND_DISABLE_WINDOWDECORATION=1 export QT_QPA_PLATFORMTHEME=gnome @@ -345,12 +337,6 @@ in export GTK_USE_PORTAL=1 export XDG_CURRENT_DESKTOP=sway export XDG_SESSION_DESKTOP=sway - # TODO: set to sdl3 compat when SDL3 releases - # this is for steam games, I set the launch options to: - # `SDL_DYNAMIC_API=$SDL2_DYNAMIC_API %command%` - # Steam itself doesn't work with SDL_DYNAMIC_API set, so it's - # a bad idea to set SDL_DYNAMIC_API globally - export SDL2_DYNAMIC_API=${pkgs.SDL2.out}/lib/libSDL2.so ''; }; services.swayidle = let swaylock-start = builtins.toString (with pkgs; writeScript "swaylock-start" '' @@ -462,7 +448,7 @@ in }; terminal = config.terminalBin; extraConfig = { - modi = [ "calc" "drun" "run" "ssh" ]; + modi = [ "steam:${pkgs.callPackage ../rofi-steam-game-list {}}/bin/rofi-steam-game-list" "drun" "run" "ssh" ]; icon-theme = "hicolor"; drun-match-fields = [ "name" "generic" "exec" "keywords" ]; show-icons = true; diff --git a/home/common/vim.nix b/home/common/vim.nix index 29cfbc8..6da32bc 100644 --- a/home/common/vim.nix +++ b/home/common/vim.nix @@ -19,13 +19,13 @@ ]; # extraPython3Packages = pyPkgs: with pyPkgs; [ python-lsp-server ]; extraConfig = '' - autocmd BufReadPost * if @% !~# '\.git[\/\\]COMMIT_EDITMSG$' && line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g`\"" | endif syntax on au FileType markdown set colorcolumn=73 textwidth=72 au FileType gitcommit set colorcolumn=73 highlight NormalFloat guibg=NONE au BufReadPre * set foldmethod=syntax au BufReadPost * folddoc foldopen! + autocmd BufReadPost * if @% !~# '\.git[\/\\]COMMIT_EDITMSG$' && line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g`\"" | endif ''; viAlias = true; vimAlias = true; @@ -40,6 +40,36 @@ vim-svelte # TODO remove on next nvim update (0.8.3/0.9) vim-nix + { plugin = pkgs.vimUtils.buildVimPluginFrom2Nix { + pname = "vscode-nvim"; + version = "2023-02-10"; + src = pkgs.fetchFromGitHub { + owner = "Mofiqul"; + repo = "vscode.nvim"; + rev = "db9ee339b5556aa832ca58871fd18f9467a18520"; + sha256 = "sha256-X2IgIjO5NNq7vJdl09hBY1TFqHlsfF1xfllKr4osILI="; + }; + }; + config = lua '' + require("vscode").setup({ + transparent = true, + color_overrides = { + vscGray = "#745b5f", + vscViolet = "#${config.colors.magenta}", + vscBlue = "#6ddfd8", + vscDarkBlue = "#${config.colors.blue}", + vscGreen = "#${config.colors.green}", + vscBlueGreen = "#73bf88", + vscLightGreen = "#6acf6e", + vscRed = "#${config.colors.red}", + vscOrange = "#e89666", + vscLightRed = "#e64e4e", + vscYellowOrange = "#e8b166", + vscYellow = "#${config.colors.yellow}", + vscPink = "#cf83c4", + }, + }) + ''; } { plugin = nvim-web-devicons; config = lua '' require'nvim-web-devicons'.setup { diff --git a/home/flake.lock b/home/flake.lock index 8af6bd5..8baec9c 100644 --- a/home/flake.lock +++ b/home/flake.lock @@ -8,11 +8,11 @@ "utils": "utils" }, "locked": { - "lastModified": 1676367705, - "narHash": "sha256-un5UbRat9TwruyImtwUGcKF823rCEp4fQxnsaLFL7CM=", + "lastModified": 1677783711, + "narHash": "sha256-eq5mOVk3gv5HITtLhPjKwi8bFnOaQplA3X0WFgHnmxE=", "owner": "nix-community", "repo": "home-manager", - "rev": "da72e6fc6b7dc0c3f94edbd310aae7cd95c678b5", + "rev": "b9e3a29864798d55ec1d6579ab97876bb1ee9664", "type": "github" }, "original": { @@ -28,11 +28,11 @@ ] }, "locked": { - "lastModified": 1676564857, - "narHash": "sha256-E2O086asoR2sFhsEvPVCdneVYNoP1JbQ4B8OcIdpdPU=", + "lastModified": 1677773085, + "narHash": "sha256-TtNq5ooEUvyHMwOnFr1nUIpqzslM3pPGmbZKe+7BU4w=", "owner": "fufexan", "repo": "nix-gaming", - "rev": "dbe7029d83ff8ab3db3e379c7a1fb65a309c5c54", + "rev": "d59c534258d8d9779a5497e5a6a0f4e9616365ce", "type": "github" }, "original": { @@ -43,10 +43,10 @@ }, "nixpkgs": { "locked": { - "lastModified": 1676795730, - "narHash": "sha256-X69A9BdcPTySJJ7DqS4wc8b6eqGKi32jCSyaBsz4WB0=", - "path": "/nix/store/wyanhqy905a9n7zy3syq5aiikd8y3ksl-source", - "rev": "efc59894b1ba73cb745676616c56c780383d6788", + "lastModified": 1677932085, + "narHash": "sha256-+AB4dYllWig8iO6vAiGGYl0NEgmMgGHpy9gzWJ3322g=", + "path": "/nix/store/qilsyakhi0qmjq9iwxn49g3qbl1rp5m7-source", + "rev": "3c5319ad3aa51551182ac82ea17ab1c6b0f0df89", "type": "path" }, "original": { @@ -56,11 +56,11 @@ }, "nur": { "locked": { - "lastModified": 1676785553, - "narHash": "sha256-nJLp4LeU1MDfmyNkids+tIbpGx1tCwP4nI0gXOwKidg=", + "lastModified": 1678002043, + "narHash": "sha256-CKAoPQaUA+kitq4ChzlM5O3oTGHuQnlSV4hNSI1Ht0g=", "owner": "nix-community", "repo": "NUR", - "rev": "15ac68824a1d403aa0da7a92618b3ac379f3cf71", + "rev": "45ce037949e32a72bc65be6f20dc87fa73c5039d", "type": "github" }, "original": { diff --git a/home/common/home-daemon.nix b/home/home-daemon/default.nix similarity index 100% rename from home/common/home-daemon.nix rename to home/home-daemon/default.nix diff --git a/home/hosts/nixmsi.nix b/home/hosts/nixmsi.nix index 3b941ca..f8d0081 100644 --- a/home/hosts/nixmsi.nix +++ b/home/hosts/nixmsi.nix @@ -53,7 +53,7 @@ home.packages = with pkgs; [ openrgb piper steam-run steam - osu-lazer-bin + osu-lazer-bin taisei easyeffects # wineWowPackages.waylandFull winetricks diff --git a/home/rofi-steam-game-list/.gitignore b/home/rofi-steam-game-list/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/home/rofi-steam-game-list/.gitignore @@ -0,0 +1 @@ +/target diff --git a/home/rofi-steam-game-list/Cargo.lock b/home/rofi-steam-game-list/Cargo.lock new file mode 100644 index 0000000..2923b5a --- /dev/null +++ b/home/rofi-steam-game-list/Cargo.lock @@ -0,0 +1,225 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "fork" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9788ce090af4bf8d6e8f43d3f7d12305c787456387bd2d88856fcda3aa1f0dca" +dependencies = [ + "libc", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "keyvalues-parser" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d990301996c856ea07a84bc291e76f1273db52683663efc05c8d355976897e5" +dependencies = [ + "pest", + "pest_derive", + "thiserror", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "pest" +version = "2.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cbd939b234e95d72bc393d51788aec68aeeb5d51e748ca08ff3aad58cb722f7" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a81186863f3d0a27340815be8f2078dd8050b14cd71913db9fbda795e5f707d7" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a1ef20bf3193c15ac345acb32e26b3dc3223aff4d77ae4fc5359567683796b" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e3b284b1f13a20dc5ebc90aff59a51b8d7137c221131b52a7260c08cbc1cc80" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rofi-steam-game-list" +version = "0.1.0" +dependencies = [ + "fork", + "keyvalues-parser", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/home/rofi-steam-game-list/Cargo.toml b/home/rofi-steam-game-list/Cargo.toml new file mode 100644 index 0000000..14ac4ab --- /dev/null +++ b/home/rofi-steam-game-list/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rofi-steam-game-list" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +keyvalues-parser = "0.1" +fork = "0.1" diff --git a/home/rofi-steam-game-list/default.nix b/home/rofi-steam-game-list/default.nix new file mode 100644 index 0000000..dd2a353 --- /dev/null +++ b/home/rofi-steam-game-list/default.nix @@ -0,0 +1,14 @@ +{ lib, rustPlatform }: +rustPlatform.buildRustPackage { + pname = "rofi-steam-game-list"; + version = "0.1"; + + src = ../rofi-steam-game-list; + + cargoLock.lockFile = ../rofi-steam-game-list/Cargo.lock; + + meta = with lib; { + description = "A program to list Steam games for Rofi"; + license = licenses.bsd0; + }; +} diff --git a/home/rofi-steam-game-list/src/main.rs b/home/rofi-steam-game-list/src/main.rs new file mode 100644 index 0000000..8200553 --- /dev/null +++ b/home/rofi-steam-game-list/src/main.rs @@ -0,0 +1,510 @@ +//! I tried to create a proper parser, but those abstractions turned out to be not-so-zero cost! +//! Here's a simple version instead +#![allow(clippy::similar_names)] +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::needless_pass_by_value)] + +use fork::daemon; +use std::collections::{HashMap, HashSet}; +use std::io::{self, prelude::*}; +use std::sync::mpsc; +use std::time::{Duration, SystemTime}; + +fn read_file(p: impl AsRef) -> io::Result> { + let p = p.as_ref().to_owned(); + let mut vec = Vec::new(); + let mut file = std::fs::File::open(p)?; + file.read_to_end(&mut vec)?; + Ok(vec) +} +fn read_file_s(p: impl AsRef) -> io::Result { + let p = p.as_ref().to_owned(); + let mut s = String::new(); + let mut file = std::fs::File::open(p)?; + std::io::Read::read_to_string(&mut file, &mut s)?; + Ok(s) +} + +fn write_file(p: impl AsRef, data: Vec) -> io::Result<()> { + let p = p.as_ref().to_owned(); + let mut file = std::fs::File::create(p)?; + std::io::Write::write_all(&mut file, &data) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct AppInfoEntry { + pub app_id: u32, + pub info_state: u32, + pub last_updated: u32, + pub pics_token: u64, + pub text_vdf_sha1: [u8; 20], + pub change_number: u32, + pub info: HashMap, Value>, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct AppInfo { + pub magic: u32, + pub universe: u32, + pub entries: Vec, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + Map(HashMap, Value>), + String(Vec), +} + +#[allow(clippy::missing_const_for_fn)] +impl Value { + fn into_map(self) -> Option, Self>> { + if let Self::Map(map) = self { + Some(map) + } else { + None + } + } + fn into_string(self) -> Option> { + if let Self::String(s) = self { + Some(s) + } else { + None + } + } +} + +fn read_map(reader: &mut impl io::Read) -> io::Result, Value>> { + let mut ret = HashMap::new(); + let mut buf = [0u8]; + let mut buf2 = [0u8; 2]; + let mut buf4 = [0u8; 4]; + let mut buf8 = [0u8; 8]; + loop { + reader.read_exact(&mut buf)?; + let kind = buf[0]; + if kind == 8 || kind == 11 { + break Ok(ret); + } + let mut key = vec![]; + loop { + reader.read_exact(&mut buf)?; + if buf[0] == 0 { + break; + } + key.push(buf[0]); + } + #[allow(clippy::match_same_arms)] + match kind { + 0 => { + ret.insert(key, Value::Map(read_map(reader)?)); + } + 1 => { + let mut s = vec![]; + loop { + reader.read_exact(&mut buf)?; + if buf[0] == 0 { + break; + } + s.push(buf[0]); + } + ret.insert(key, Value::String(s)); + } + 2 => { + reader.read_exact(&mut buf4)?; + // ret.insert(key, Value::I32(i32::from_le_bytes(buf4)))?; + } + 3 => { + reader.read_exact(&mut buf4)?; + // ret.insert(key, Value::F32(f32::from_le_bytes(buf4)))?; + } + 4 => { + reader.read_exact(&mut buf4)?; + // ret.insert(key, Value::Pointer(i32::from_le_bytes(buf4)))?; + } + 5 => { + let mut s = vec![0u16; 2]; + loop { + reader.read_exact(&mut buf2)?; + if buf2 == [0u8, 0u8] { + break; + } + s.extend_from_slice(&[u16::from_le_bytes(buf2)]); + } + // utf-8 is used instead of utf-16 here + // ret.insert(key, Value::WideString(s))?; + } + 7 => { + reader.read_exact(&mut buf8)?; + // ret.insert(key, Value::U64(u64::from_le_bytes(buf8)))?; + } + 10 => { + reader.read_exact(&mut buf8)?; + // ret.insert(key, Value::I64(i64::from_le_bytes(buf8)))?; + } + n => panic!("invalid vdf data type: {n}"), + } + } +} + +fn read_app_info(reader: &mut impl io::Read) -> io::Result { + let mut buf4 = [0u8; 4]; + // let mut buf8 = [0u8; 8]; + // let mut buf20 = [0u8; 20]; + let mut buf64 = [0u8; 64]; + reader.read_exact(&mut buf4)?; + assert_eq!(buf4, [0x28, 0x44, 0x56, 0x07]); + reader.read_exact(&mut buf4)?; + assert_eq!(u32::from_le_bytes(buf4), 1); + let mut ret = AppInfo { + magic: 0x0756_4428, + universe: 1, + entries: vec![], + }; + loop { + reader.read_exact(&mut buf4)?; + let app_id = u32::from_le_bytes(buf4); + if app_id == 0 { + break Ok(ret); + } + let mut entry = AppInfoEntry { + app_id, + info_state: 0, + last_updated: 0, + pics_token: 0, + text_vdf_sha1: [0u8; 20], + change_number: 0, + info: HashMap::new(), + }; + reader.read_exact(&mut buf64[..4 * 3 + 8 + 20 + 4 + 20])?; + // reader.read_exact(&mut buf4)?; + // size + // reader.read_exact(&mut buf4)?; + // entry.info_state = u32::from_le_bytes(buf4); + // reader.read_exact(&mut buf4)?; + // entry.last_updated = u32::from_le_bytes(buf4); + // reader.read_exact(&mut buf8)?; + // entry.pics_token = u64::from_le_bytes(buf8); + // reader.read_exact(&mut buf20)?; + // entry.text_vdf_sha1 = buf20; + // reader.read_exact(&mut buf4)?; + // entry.change_number = u32::from_le_bytes(buf4); + // reader.read_exact(&mut buf20)?; + // bin sha1 + entry.info = read_map(reader)?; + ret.entries.push(entry); + } +} + +fn home() -> String { + std::env::var("HOME").unwrap() +} +fn xdg_home() -> String { + std::env::var("XDG_DATA_HOME").unwrap_or_else(|_| home() + "/.local/share") +} +fn xdg_cache() -> String { + std::env::var("XDG_CACHE_HOME").unwrap_or_else(|_| home() + "/.cache") +} +fn cache_dir() -> String { + let dir = xdg_cache() + "/rofi-steam-game-list"; + let _ = std::fs::create_dir_all(&dir); + dir +} +fn history_dir() -> String { + let dir = xdg_home() + "/rofi-steam-game-list"; + let _ = std::fs::create_dir_all(&dir); + dir +} +fn history(k: &str) -> HashMap { + let dir = history_dir(); + let mut ret = HashMap::new(); + let Ok(data) = read_file(dir + "/history_" + k) else { + return ret; + }; + if data.len() < 8 { + return ret; + } + let count = u32::from_le_bytes(data[4..8].try_into().unwrap()); + let data = &mut &data[8..]; + let mut buf4 = [0u8; 4]; + for _ in 0..count { + if std::io::Read::read_exact(data, &mut buf4).is_err() { + return ret; + } + let k = u32::from_le_bytes(buf4); + if std::io::Read::read_exact(data, &mut buf4).is_err() { + return ret; + } + let v = u32::from_le_bytes(buf4); + ret.insert(k, v); + } + ret +} +fn write_history(m: &HashMap, k: &str) { + let dir = history_dir(); + let mut data = vec![]; + data.extend_from_slice(&[0; 4]); + data.extend_from_slice(&(m.len() as u32).to_le_bytes()); + for (k, v) in m.iter() { + data.extend_from_slice(&k.to_le_bytes()); + data.extend_from_slice(&v.to_le_bytes()); + } + let _ = write_file(dir + "/history_" + k, data); +} + +fn read_time(s: String) -> io::Result { + std::fs::metadata(s + "/Steam/appcache/appinfo.vdf")?.modified() +} + +fn read_appinfo(target_type: String, s: String) -> io::Result<(SystemTime, Vec<(u32, String)>)> { + let time = read_time(s.clone())?; + let vec = read_file(s + "/Steam/appcache/appinfo.vdf")?; + let data = read_app_info(&mut &vec[..])?; + let mut ret = Vec::new(); + for mut info in data.entries { + if let Some(mut x) = info + .info + .remove(&b"appinfo"[..]) + .and_then(Value::into_map) + .and_then(|mut x| x.remove(&b"common"[..])) + .and_then(Value::into_map) + { + if let Some(mut t) = x + .remove(&b"type"[..]) + .and_then(Value::into_string) + .and_then(|x| String::from_utf8(x).ok()) + { + if let Some(n) = x + .remove(&b"name"[..]) + .and_then(Value::into_string) + .and_then(|x| String::from_utf8(x).ok()) + { + t.make_ascii_lowercase(); + if t == target_type { + ret.push((info.app_id, n)); + } + } + } + } + } + Ok((time, ret)) +} + +fn list_appids(s: &str) -> HashSet { + let Ok(data) = read_file_s(s.to_owned() + "/Steam/steamapps/libraryfolders.vdf") else { + return HashSet::new(); + }; + let ret = keyvalues_parser::Vdf::parse(&data) + .unwrap() + .value + .get_obj() + .unwrap() + .values() + .flat_map(|x| { + x.iter().flat_map(|x| { + x.get_obj().unwrap().get("apps").into_iter().flat_map(|x| { + x.iter().flat_map(|x| { + x.get_obj() + .unwrap() + .keys() + .filter_map(|x| x.parse::().ok()) + }) + }) + }) + }) + .collect::>(); + ret +} + +fn cache_time(k: &str) -> std::io::Result { + let path = cache_dir() + "/type_" + k; + let mut file = std::fs::File::open(path)?; + let mut data = Vec::new(); + file.read_to_end(&mut data)?; + if data.len() >= 12 { + let (_, data) = data.split_at(4); + let (first, _data) = data.split_at(8); + let time = SystemTime::UNIX_EPOCH + + Duration::from_millis(u64::from_le_bytes(first.try_into().unwrap())); + Ok(time) + } else { + Err(io::Error::from(io::ErrorKind::Other)) + } +} + +fn read_cache(k: &str) -> io::Result<(SystemTime, Vec<(u32, String)>)> { + let path = cache_dir() + "/type_" + k; + let mut file = std::fs::File::open(path)?; + let mut data = Vec::new(); + file.read_to_end(&mut data)?; + if data.len() >= 16 { + let (_, data) = data.split_at(4); + let (first, data) = data.split_at(8); + let time = SystemTime::UNIX_EPOCH + + Duration::from_millis(u64::from_le_bytes(first.try_into().unwrap())); + let (first, data) = data.split_at(4); + let count = u32::from_le_bytes(first.try_into().unwrap()); + let data = &mut &data[..]; + let mut buf4 = [0u8; 4]; + let mut buf1 = [0u8; 1]; + let mut ret = Vec::with_capacity(count as usize); + for _ in 0..count { + std::io::Read::read_exact(data, &mut buf4)?; + std::io::Read::read_exact(data, &mut buf1)?; + let len = if buf1[0] == 255 { + std::io::Read::read_exact(data, &mut buf1)?; + 255 + buf1[0] as usize + } else { + buf1[0] as usize + }; + let mut buf = vec![0; len]; + std::io::Read::read_exact(data, &mut buf)?; + if let Ok(s) = String::from_utf8(buf) { + ret.push((u32::from_le_bytes(buf4), s)); + } + } + Ok((time, ret)) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "invalid app id cache format", + )) + } +} + +fn write_cache(k: &str, time: SystemTime, ids: &[(u32, String)]) { + let mut data = Vec::new(); + data.extend_from_slice(&[0; 4]); + data.extend_from_slice( + &(time + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u64) + .to_le_bytes(), + ); + data.extend_from_slice(&(ids.len() as u32).to_le_bytes()); + for (id, s) in ids { + if s.len() > u8::MAX as usize + u8::MAX as usize { + continue; + } + data.extend_from_slice(&id.to_le_bytes()); + if s.len() > u8::MAX as usize { + data.extend_from_slice(&255u8.to_le_bytes()); + data.extend_from_slice(&((s.len() - u8::MAX as usize) as u8).to_le_bytes()); + } else { + data.extend_from_slice(&(s.len() as u8).to_le_bytes()); + } + data.extend_from_slice(s.as_bytes()); + } + let path = cache_dir() + "/type_" + k; + if let Ok(mut file) = std::fs::File::create(path) { + let _ = file.write_all(&data); + } +} + +struct PendingFut; +impl std::future::Future for PendingFut { + type Output = (); + fn poll(self: std::pin::Pin<&mut Self>, _: &mut std::task::Context<'_>) -> std::task::Poll<()> { + std::task::Poll::Pending + } +} + +fn main() { + let target_type = std::env::args().nth(1).map_or_else( + || "game".to_owned(), + |mut x| { + x.make_ascii_lowercase(); + x + }, + ); + if let Ok(appid) = std::env::var("ROFI_INFO") { + let _ = daemon(true, false); + let mut cmd = std::process::Command::new("xdg-open") + .arg(&format!("steam://rungameid/{appid}")) + .spawn() + .unwrap(); + if let Ok(x) = appid.parse::() { + let mut history = history(&target_type); + history.entry(x).and_modify(|curr| *curr += 1).or_insert(1); + write_history(&history, &target_type); + } + let _ = cmd.wait(); + return; + } + /* + * Flow1: read app info -> print app info -> write cache + * Flow2: read cache -> print cache -> check app info mod time -> write cache + * */ + let xdg_home = xdg_home(); + let xdg_home2 = xdg_home.clone(); + let xdg_home3 = xdg_home; + let target_type2 = target_type.clone(); + let target_type3 = target_type.clone(); + let target_type4 = target_type.clone(); + let a = std::thread::spawn(move || history(&target_type3)); + let (tx0, rx0) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let b = std::thread::spawn(move || { + let tx1 = tx0.clone(); + std::thread::spawn(move || { + tx0.send( + read_appinfo(target_type2, crate::xdg_home()).map_or_else(|_| { + let _ = tx2.send(None); + None + }, |info| { + let _ = tx2.send(Some(info.clone())); + Some((info, false)) + }) + ) + }); + std::thread::spawn(move || tx1.send(read_cache(&target_type4).ok().map(|x| (x, true)))); + #[allow(clippy::same_functions_in_if_condition)] + if let Ok(Some(x)) = rx0.recv() { + x + } else if let Ok(Some(x)) = rx0.recv() { + x + } else { + panic!() + } + }); + let c = std::thread::spawn(move || list_appids(&xdg_home2)); + let history = a.join().unwrap(); + let ((time, app_info), is_cache) = b.join().unwrap(); + let installed_games = c.join().unwrap(); + + let mut app_info_2 = app_info + .iter() + .filter_map(|x| { + if installed_games.contains(&x.0) { + Some(x.clone()) + } else { + None + } + }) + .collect::>(); + app_info_2.sort_by_key(|x| u32::MAX - history.get(&x.0).unwrap_or(&0)); + for (app_id, n) in &app_info_2 { + let icon = format!("{xdg_home3}/Steam/appcache/librarycache/{app_id}_icon.jpg"); + print!("{n}\0info\x1f{app_id}"); + if std::fs::metadata(&icon).is_ok() { + print!("\x1ficon\x1f{icon}"); + } + println!(); + } + let _ = daemon(true, false); + if is_cache { + if read_time(xdg_home3).unwrap() <= time { + return; + } + if let Some((time, app_info)) = rx2.recv().unwrap() { + write_cache(&target_type, time, &app_info); + } + } else { + if let Ok(ctime) = cache_time(&target_type) { + if time <= ctime { + return; + } + } + write_cache(&target_type, time, &app_info); + } +} diff --git a/system/common/vfio.nix b/system/common/vfio.nix index 5265f60..01d0ea2 100644 --- a/system/common/vfio.nix +++ b/system/common/vfio.nix @@ -94,13 +94,11 @@ "vfio" "vfio_iommu_type1" "vfio_pci" - "vfio_virqfd" ] else []); initrd.availableKernelModules = lib.mkIf (!cfg.passGpuAtBoot) [ "vfio" "vfio_iommu_type1" "vfio_pci" - "vfio_virqfd" ]; extraModulePackages = with config.boot.kernelPackages; @@ -118,8 +116,7 @@ ]; kernelModules = [ "vhost-net" - ] ++ (if cfg.passGpuAtBoot then [] else [ "vfio_virqfd" ]) - ++ (if enableIvshmem then [ "kvmfr" ] else []); + ] ++ (if enableIvshmem then [ "kvmfr" ] else []); }; services.udev.extraRules = lib.mkIf enableIvshmem (lib.concatStringsSep diff --git a/system/flake.lock b/system/flake.lock index 5b3dea5..59b14c4 100644 --- a/system/flake.lock +++ b/system/flake.lock @@ -52,11 +52,11 @@ ] }, "locked": { - "lastModified": 1676564857, - "narHash": "sha256-E2O086asoR2sFhsEvPVCdneVYNoP1JbQ4B8OcIdpdPU=", + "lastModified": 1677773085, + "narHash": "sha256-TtNq5ooEUvyHMwOnFr1nUIpqzslM3pPGmbZKe+7BU4w=", "owner": "fufexan", "repo": "nix-gaming", - "rev": "dbe7029d83ff8ab3db3e379c7a1fb65a309c5c54", + "rev": "d59c534258d8d9779a5497e5a6a0f4e9616365ce", "type": "github" }, "original": { @@ -67,11 +67,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1676775543, - "narHash": "sha256-VI0e60l94RY9Sc90OwDZpOf/nyLy41n2ULK6I6YkoP8=", + "lastModified": 1677949148, + "narHash": "sha256-dEdcn+UYs8TUK3VTNCQk9TsJapJLEq50A4q7eC3/PTU=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "525177a78023e1363bee482f520d4f2471ada03a", + "rev": "d63e86cbed3d399c4162594943bd8c1d8392e550", "type": "github" }, "original": { @@ -82,15 +82,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1676795730, - "narHash": "sha256-X69A9BdcPTySJJ7DqS4wc8b6eqGKi32jCSyaBsz4WB0=", + "lastModified": 1677932085, + "narHash": "sha256-+AB4dYllWig8iO6vAiGGYl0NEgmMgGHpy9gzWJ3322g=", "owner": "nixos", "repo": "nixpkgs", - "rev": "efc59894b1ba73cb745676616c56c780383d6788", + "rev": "3c5319ad3aa51551182ac82ea17ab1c6b0f0df89", "type": "github" }, "original": { "owner": "nixos", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -113,11 +114,11 @@ ] }, "locked": { - "lastModified": 1676773870, - "narHash": "sha256-RhG7QmA14xih1lv6SB2WDVER4fbJ1cLwr0ntCpIjKbQ=", + "lastModified": 1677983714, + "narHash": "sha256-2A5uDpF0vN4w9tvo5N+918bK0yRYfg4FdNZ/qccgH6s=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "a6fa42390d46ef1326fbe98288b65d3b586870da", + "rev": "1a9f6285d441ff438a6a1422dc3fde109d8615bf", "type": "github" }, "original": { diff --git a/system/flake.nix b/system/flake.nix index b39c9a0..d0b4880 100644 --- a/system/flake.nix +++ b/system/flake.nix @@ -2,8 +2,7 @@ description = "NixOS configuration"; inputs = { - # nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - nixpkgs.url = "github:nixos/nixpkgs"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; utils.url = "github:gytis-ivaskevicius/flake-utils-plus"; nixos-hardware.url = "github:NixOS/nixos-hardware"; impermanence.url = "github:nix-community/impermanence"; diff --git a/system/hosts/nixmsi.nix b/system/hosts/nixmsi.nix index 198c816..46defe1 100644 --- a/system/hosts/nixmsi.nix +++ b/system/hosts/nixmsi.nix @@ -43,6 +43,7 @@ in { # resume_offset = $(btrfs inspect-internal map-swapfile -r path/to/swapfile) "resume_offset=533760" "fbcon=font:TER16x32" + "consoleblank=60" ]; cleanTmpDir = true; loader = {