├── tests ├── profiles │ ├── nvidia-test-ids.ids │ ├── multiple-profile-raw-escaped-strings-test.toml │ ├── multiple-profile-raw-escaped-strings-test-parsed.toml │ ├── extra-check-root-profile.toml │ ├── profile-raw-escaped-strings-test.toml │ ├── graphic_drivers-profiles-test.toml │ └── graphic_drivers-invalid-profiles-test.toml └── chwd_spec.lua ├── .luarc.json ├── i18n.toml ├── libpci-sys ├── wrapper.h ├── .gitignore ├── wrapper.c ├── Cargo.toml ├── src │ └── lib.rs ├── build.rs └── Cargo.lock ├── ids ├── intel-vaapi.ids ├── nvidia-470.ids ├── nvidia-390.ids ├── intel-media-sdk.ids ├── nvidia-580.ids └── nouveau.ids ├── .gitignore ├── scripts ├── chwd-kernel │ ├── .gitignore │ ├── Cargo.toml │ ├── src │ │ ├── kernel.rs │ │ └── main.rs │ └── Cargo.lock ├── nvidia-pci-ids-dumper │ └── nvidia-pci-ids-dumper ├── intel-pci-ids-dumper │ └── intel-pci-ids-dumper.lua └── chwd ├── libpci ├── .gitignore ├── Cargo.toml ├── examples │ └── example1.rs ├── Cargo.lock └── src │ └── lib.rs ├── README.md ├── rustfmt.toml ├── .github ├── dependabot.yml └── workflows │ ├── lua.yml │ └── rust.yml ├── profiles └── pci │ ├── network_drivers │ └── profiles.toml │ ├── ai_sdk │ └── profiles.toml │ ├── t2-macbook │ └── profiles.toml │ ├── handhelds │ └── profiles.toml │ └── graphic_drivers │ └── profiles.toml ├── src ├── lib.rs ├── consts.rs ├── localization.rs ├── logger.rs ├── profile_misc.rs ├── device.rs ├── device_misc.rs ├── args.rs ├── misc.rs ├── hwd_misc.rs ├── console_writer.rs ├── main.rs └── profile.rs ├── Cargo.toml └── i18n ├── en └── chwd.ftl ├── cs └── chwd.ftl ├── sk └── chwd.ftl ├── sv └── chwd.ftl ├── ru └── chwd.ftl └── de └── chwd.ftl /tests/profiles/nvidia-test-ids.ids: -------------------------------------------------------------------------------- 1 | 12 23 53 33 2 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "Lua.runtime.special": { 3 | "die": "os.exit" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /i18n.toml: -------------------------------------------------------------------------------- 1 | # The language identifier of the language used in the 2 | # source code for gettext system, and the primary fallback language 3 | # (for which all strings must be present) when using the fluent 4 | # system. 5 | fallback_language = "en" 6 | 7 | [fluent] 8 | # The path to the assets directory. 9 | assets_dir = "i18n" 10 | -------------------------------------------------------------------------------- /libpci-sys/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern void pci_lookup_class_helper(struct pci_access* pacc, char* class_str, size_t size_of, struct pci_dev* dev); 4 | extern void pci_lookup_vendor_helper(struct pci_access* pacc, char* vendor, size_t size_of, struct pci_dev* dev); 5 | extern void pci_lookup_device_helper(struct pci_access* pacc, char* device, size_t size_of, struct pci_dev* dev); 6 | -------------------------------------------------------------------------------- /ids/intel-vaapi.ids: -------------------------------------------------------------------------------- 1 | 0042 0046 0102 0106 010a 0112 0116 0122 0126 0152 0156 015a 0162 0166 016a 0402 0406 040a 040b 040e 0412 0416 041a 041b 041e 0422 0426 042a 042b 042e 0a02 0a06 0a0a 0a0b 0a0e 0a12 0a16 0a1a 0a1b 0a1e 0a22 0a26 0a2a 0a2b 0a2e 0c02 0c06 0c0a 0c0b 0c0e 0c12 0c16 0c1a 0c1b 0c1e 0c22 0c26 0c2a 0c2b 0c2e 0d02 0d06 0d0a 0d0b 0d0e 0d12 0d16 0d1a 0d1b 0d1e 0d22 0d26 0d2a 0d2b 0d2e 0f30 0f31 0f32 0f33 2a42 2e02 2e12 2e22 2e32 2e42 2e92 a001 a011 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.dump 3 | *.ctu-info 4 | .cache 5 | .idea 6 | target 7 | 8 | # Prerequisites 9 | *.d 10 | 11 | # Compiled Object files 12 | *.slo 13 | *.lo 14 | *.o 15 | *.obj 16 | 17 | # Precompiled Headers 18 | *.gch 19 | *.pch 20 | 21 | # Compiled Dynamic libraries 22 | *.so 23 | *.dylib 24 | 25 | # Fortran module files 26 | *.mod 27 | *.smod 28 | 29 | # Compiled Static libraries 30 | *.lai 31 | *.la 32 | *.a 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | -------------------------------------------------------------------------------- /scripts/chwd-kernel/.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.dump 3 | .idea 4 | build 5 | target 6 | 7 | # Prerequisites 8 | *.d 9 | 10 | # Compiled Object files 11 | *.slo 12 | *.lo 13 | *.o 14 | *.obj 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Compiled Dynamic libraries 21 | *.so 22 | *.dylib 23 | 24 | # Fortran module files 25 | *.mod 26 | *.smod 27 | 28 | # Compiled Static libraries 29 | *.lai 30 | *.la 31 | *.a 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | -------------------------------------------------------------------------------- /libpci/.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.dump 3 | *.ctu-info 4 | .cache 5 | .idea 6 | target 7 | 8 | # Prerequisites 9 | *.d 10 | 11 | # Compiled Object files 12 | *.slo 13 | *.lo 14 | *.o 15 | *.obj 16 | 17 | # Precompiled Headers 18 | *.gch 19 | *.pch 20 | 21 | # Compiled Dynamic libraries 22 | *.so 23 | *.dylib 24 | 25 | # Fortran module files 26 | *.mod 27 | *.smod 28 | 29 | # Compiled Static libraries 30 | *.lai 31 | *.la 32 | *.a 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | -------------------------------------------------------------------------------- /libpci-sys/.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.dump 3 | *.ctu-info 4 | .cache 5 | .idea 6 | target 7 | 8 | # Prerequisites 9 | *.d 10 | 11 | # Compiled Object files 12 | *.slo 13 | *.lo 14 | *.o 15 | *.obj 16 | 17 | # Precompiled Headers 18 | *.gch 19 | *.pch 20 | 21 | # Compiled Dynamic libraries 22 | *.so 23 | *.dylib 24 | 25 | # Fortran module files 26 | *.mod 27 | *.smod 28 | 29 | # Compiled Static libraries 30 | *.lai 31 | *.la 32 | *.a 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

chwd

3 |

4 | CachyOS Hardware Detection Tool written in Rust 5 |

6 |

7 | 8 | [![Dependency Status](https://deps.rs/repo/github/cachyos/chwd/status.svg)](https://deps.rs/repo/github/cachyos/chwd) 9 |
10 | [![CI](https://github.com/cachyos/chwd/actions/workflows/rust.yml/badge.svg)](https://github.com/cachyos/chwd/actions/workflows/rust.yml) 11 | 12 |

13 |
14 | 15 | chwd for CachyOS and Arch Linux. 16 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | format_code_in_doc_comments = true 3 | match_block_trailing_comma = true 4 | condense_wildcard_suffixes = true 5 | use_field_init_shorthand = true 6 | normalize_doc_attributes = true 7 | overflow_delimited_expr = true 8 | imports_granularity = "Module" 9 | use_small_heuristics = "Max" 10 | normalize_comments = true 11 | reorder_impl_items = true 12 | use_try_shorthand = true 13 | newline_style = "Unix" 14 | format_strings = true 15 | wrap_comments = true 16 | comment_width = 100 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /ids/nvidia-470.ids: -------------------------------------------------------------------------------- 1 | 0fc6 0fc8 0fc9 0fcd 0fce 0fd1 0fd2 0fd3 0fd4 0fd5 0fd8 0fd9 0fdf 0fe0 0fe1 0fe2 0fe3 0fe4 0fe9 0fea 0fec 0fed 0fee 0ff6 0ff8 0ff9 0ffa 0ffb 0ffc 0ffd 0ffe 0fff 1001 1004 1005 1007 1008 100a 100c 1021 1022 1023 1024 1026 1027 1028 1029 102a 102d 103a 103c 1180 1183 1184 1185 1187 1188 1189 118a 118e 118f 1193 1194 1195 1198 1199 119a 119d 119e 119f 11a0 11a1 11a2 11a3 11a7 11b4 11b6 11b7 11b8 11ba 11bc 11bd 11be 11c0 11c2 11c3 11c4 11c5 11c6 11c8 11cb 11e0 11e1 11e2 11e3 11fa 11fc 1280 1281 1282 1284 1286 1287 1288 1289 128b 1290 1291 1292 1293 1295 1296 1298 1299 129a 12b9 12ba 2 | -------------------------------------------------------------------------------- /ids/nvidia-390.ids: -------------------------------------------------------------------------------- 1 | 06c0 06c4 06ca 06cd 06d1 06d2 06d8 06d9 06da 06dc 06dd 06de 06df 0dc0 0dc4 0dc5 0dc6 0dcd 0dce 0dd1 0dd2 0dd3 0dd6 0dd8 0dda 0de0 0de1 0de2 0de3 0de4 0de5 0de7 0de8 0de9 0dea 0deb 0dec 0ded 0dee 0def 0df0 0df1 0df2 0df3 0df4 0df5 0df6 0df7 0df8 0df9 0dfa 0dfc 0e22 0e23 0e24 0e30 0e31 0e3a 0e3b 0f00 0f01 0f02 0f03 1040 1042 1048 1049 104a 104b 104c 1050 1051 1052 1054 1055 1056 1057 1058 1059 105a 105b 107c 107d 1080 1081 1082 1084 1086 1087 1088 1089 108b 1091 1094 1096 109a 109b 1140 1200 1201 1203 1205 1206 1207 1208 1210 1211 1212 1213 1241 1243 1244 1245 1246 1247 1248 1249 124b 124d 1251 2 | -------------------------------------------------------------------------------- /.github/workflows/lua.yml: -------------------------------------------------------------------------------- 1 | name: Lua tests 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'LICENSE' 7 | - '*.md' 8 | - '*.sh' 9 | branches: 10 | - master 11 | pull_request: 12 | branches: 13 | - master 14 | 15 | jobs: 16 | sile: 17 | runs-on: ubuntu-latest 18 | container: cachyos/cachyos:latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | - name: Install dependencies 23 | run: | 24 | pacman -Syu --noconfirm lua lua-filesystem busted lua-busted 25 | - name: Run Busted 26 | run: busted . --no-keep-going 27 | -------------------------------------------------------------------------------- /libpci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libpci" 3 | version = "0.1.3" 4 | edition = "2021" 5 | authors = ["Vladislav Nepogodin "] 6 | license = "GPL-3.0-only" 7 | rust-version = "1.81.0" 8 | 9 | description = "Rust bindings for libpci" 10 | homepage = "https://github.com/CachyOS/chwd" 11 | repository = "https://github.com/CachyOS/chwd" 12 | categories = ["api-bindings"] 13 | 14 | [dependencies] 15 | libpci-c-sys = { path = "../libpci-sys", default-features = false, version = "0.1.3" } 16 | libc = { default-features = false, version = "0.2" } 17 | 18 | [features] 19 | default = [] 20 | 21 | std = ["libpci-c-sys/std"] 22 | 23 | [[example]] 24 | name = "example1" 25 | -------------------------------------------------------------------------------- /libpci/examples/example1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut pacc = libpci::PCIAccess::new(true); 3 | 4 | let mut i = 1; 5 | let devices = pacc.devices().expect("Failed"); 6 | for mut item in devices.iter_mut() { 7 | item.fill_info(libpci::Fill::IDENT as u32 | libpci::Fill::CLASS as u32); 8 | let item_class = item.class().unwrap(); 9 | let item_vendor = item.vendor().unwrap(); 10 | let item_device = item.device().unwrap(); 11 | println!( 12 | "class := '{}', vendor := '{}', device := '{}'", 13 | item_class, item_vendor, item_device 14 | ); 15 | i += 1; 16 | } 17 | println!("i := '{i}'"); 18 | } 19 | -------------------------------------------------------------------------------- /libpci-sys/wrapper.c: -------------------------------------------------------------------------------- 1 | #include "wrapper.h" 2 | 3 | void pci_lookup_class_helper(struct pci_access* pacc, char* class_str, size_t size_of, struct pci_dev* dev) { 4 | pci_lookup_name(pacc, class_str, size_of, PCI_LOOKUP_CLASS, dev->device_class); 5 | } 6 | 7 | void pci_lookup_vendor_helper(struct pci_access* pacc, char* vendor, size_t size_of, struct pci_dev* dev) { 8 | pci_lookup_name(pacc, vendor, size_of, PCI_LOOKUP_VENDOR, dev->vendor_id, dev->device_id); 9 | } 10 | 11 | void pci_lookup_device_helper(struct pci_access* pacc, char* device, size_t size_of, struct pci_dev* dev) { 12 | pci_lookup_name(pacc, device, size_of, PCI_LOOKUP_DEVICE, dev->vendor_id, dev->device_id); 13 | } 14 | -------------------------------------------------------------------------------- /libpci-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libpci-c-sys" 3 | version = "0.1.3" 4 | edition = "2021" 5 | authors = ["Vladislav Nepogodin "] 6 | license = "GPL-3.0-only" 7 | rust-version = "1.81.0" 8 | 9 | description = "Raw bindings for libpci" 10 | homepage = "https://github.com/CachyOS/chwd" 11 | repository = "https://github.com/CachyOS/chwd" 12 | categories = ["api-bindings"] 13 | 14 | [dependencies] 15 | libc = { default-features = false, version = "0.2" } 16 | 17 | [build-dependencies] 18 | bindgen = { version = "0.72", default-features = false, features = ["runtime"] } 19 | cc = "1" 20 | 21 | [features] 22 | default = [] 23 | 24 | std = [] # Use std types instead of libc in bindgen 25 | -------------------------------------------------------------------------------- /ids/intel-media-sdk.ids: -------------------------------------------------------------------------------- 1 | 0a84 1602 1606 160a 160b 160d 160e 1612 1616 161a 161b 161d 161e 1622 1626 162a 162b 162d 162e 1632 1636 163a 163b 163d 163e 1902 1906 190a 190b 190e 1912 1913 1915 1916 1917 191a 191b 191d 191e 1921 1923 1926 1927 192a 192b 192d 1932 193a 193b 193d 1a84 1a85 22b0 22b1 22b2 22b3 3184 3185 3e90 3e91 3e92 3e93 3e94 3e96 3e98 3e99 3e9a 3e9b 3e9c 3ea0 3ea1 3ea2 3ea3 3ea4 3ea5 3ea6 3ea7 3ea8 3ea9 5902 5906 5908 590a 590b 590e 5912 5913 5915 5916 5917 591a 591b 591c 591d 591e 5921 5923 5926 5927 593b 5a40 5a41 5a42 5a44 5a49 5a4a 5a4c 5a50 5a51 5a52 5a54 5a59 5a5a 5a5c 5a84 5a85 87c0 87ca 8a50 8a51 8a52 8a53 8a54 8a56 8a57 8a58 8a59 8a5a 8a5b 8a5c 8a5d 8a70 8a71 9b21 9b41 9ba2 9ba4 9ba5 9ba8 9baa 9bac 9bc2 9bc4 9bc5 9bc6 9bc8 9bca 9bcc 9be6 9bf6 -------------------------------------------------------------------------------- /scripts/chwd-kernel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chwd-kernel" 3 | version = "0.1.0" 4 | authors = ["Vladislav Nepogodin "] 5 | license = "GPLv3" 6 | edition = "2021" 7 | 8 | [dependencies] 9 | alpm = { default-features = false, version = "5" } 10 | alpm-utils = { features = ["conf"], default-features = false, version = "5" } 11 | pacmanconf = "3.1" 12 | 13 | subprocess = "0.2" 14 | clap = { features = ["derive"], version = "4" } 15 | nix = { features = ["user"], default-features = false, version = "0.30" } 16 | dialoguer = { default-features = false, version = "0.12" } 17 | itertools = "0.14" 18 | 19 | [profile.release] 20 | strip = "symbols" 21 | panic = "abort" 22 | lto = true 23 | opt-level = 3 24 | codegen-units = 1 25 | -------------------------------------------------------------------------------- /ids/nvidia-580.ids: -------------------------------------------------------------------------------- 1 | 1340 1341 1344 1346 1347 1348 1349 134b 134d 134e 134f 137a 137b 137d 1380 1381 1382 1390 1391 1392 1393 1398 1399 139a 139b 139c 139d 13b0 13b1 13b2 13b3 13b4 13b6 13b9 13ba 13bb 13bc 13c0 13c2 13d7 13d8 13d9 13da 13f0 13f1 13f2 13f3 13f8 13f9 13fa 13fb 1401 1402 1406 1407 1427 1430 1431 1436 15f0 15f7 15f8 15f9 1617 1618 1619 161a 1667 174d 174e 179c 17c2 17c8 17f0 17f1 17fd 1b00 1b02 1b06 1b30 1b38 1b80 1b81 1b82 1b83 1b84 1b87 1ba0 1ba1 1ba2 1bb0 1bb1 1bb3 1bb4 1bb5 1bb6 1bb7 1bb8 1bb9 1bbb 1bc7 1be0 1be1 1c02 1c03 1c04 1c06 1c07 1c09 1c20 1c21 1c22 1c23 1c30 1c31 1c60 1c61 1c62 1c81 1c82 1c83 1c8c 1c8d 1c8f 1c90 1c91 1c92 1c94 1c96 1cb1 1cb2 1cb3 1cb6 1cba 1cbb 1cbc 1cbd 1cfa 1cfb 1d01 1d02 1d10 1d11 1d12 1d13 1d16 1d33 1d34 1d52 1d81 1db1 1db3 1db4 1db5 1db6 1db7 1db8 1dba 1df0 1df2 1df5 1df6 2 | -------------------------------------------------------------------------------- /profiles/pci/network_drivers/profiles.toml: -------------------------------------------------------------------------------- 1 | [broadcom-wl] 2 | desc = 'Broadcom 802.11 Linux STA wireless driver' 3 | class_ids = "0200 0280 0282" 4 | vendor_ids = "14E4 14A4" 5 | device_ids = "4311 4312 4315 4727 4328 4329 432A 432B 432C 432D 0576 4353 4357 4358 4359 4365 4331 43B1 43A0 4360" 6 | priority = 1 7 | packages = 'broadcom-wl-dkms linux-firmware-broadcom' 8 | post_install = """ 9 | cat </etc/modprobe.d/01-chwd-net-blacklist.conf 10 | # Do not load modules on boot. 11 | blacklist b43 12 | blacklist b43legacy 13 | blacklist ssb 14 | blacklist bcm43xx 15 | blacklist brcm80211 16 | blacklist brcmfmac 17 | blacklist brcmsmac 18 | blacklist bcma 19 | EOF 20 | mkinitcpio -P 21 | """ 22 | post_remove = """ 23 | rm -f /etc/modprobe.d/01-chwd-net-blacklist.conf 24 | mkinitcpio -P 25 | """ 26 | -------------------------------------------------------------------------------- /tests/profiles/multiple-profile-raw-escaped-strings-test.toml: -------------------------------------------------------------------------------- 1 | [case] 2 | ai_sdk = false 3 | class_ids = "0300" 4 | desc = "Test profile" 5 | 6 | [case.test-profile] 7 | device_ids = "1435 163f" 8 | device_name_pattern = '(AD)\w+' 9 | hwd_product_name_pattern = '(Ally)\w+' 10 | packages = "opencl-mesa lib32-opencl-mesa rocm-opencl-runtime" 11 | post_install = ''' 12 | echo "Steam Deck chwd installing..." 13 | ''' 14 | post_remove = ''' 15 | echo "Steam deck chwd removing..." 16 | ''' 17 | priority = 6 18 | vendor_ids = "1002" 19 | 20 | [case.test-profile-2] 21 | device_ids = "1432" 22 | device_name_pattern = '(ADW)\w+' 23 | hwd_product_name_pattern = '(AllyX)\w+' 24 | packages = "opencl-mesa lib32-opencl-mesa rocm-opencl-runtime mesa" 25 | post_install = ''' 26 | echo "Test installing..." 27 | ''' 28 | post_remove = ''' 29 | echo "Test removing..." 30 | ''' 31 | priority = 7 32 | vendor_ids = "1005" 33 | -------------------------------------------------------------------------------- /tests/profiles/multiple-profile-raw-escaped-strings-test-parsed.toml: -------------------------------------------------------------------------------- 1 | [case.test-profile] 2 | ai_sdk = false 3 | class_ids = "0300" 4 | desc = "Test profile" 5 | device_ids = "1435 163f" 6 | device_name_pattern = '(AD)\w+' 7 | hwd_product_name_pattern = '(Ally)\w+' 8 | packages = "opencl-mesa lib32-opencl-mesa rocm-opencl-runtime" 9 | post_install = ''' 10 | echo "Steam Deck chwd installing..." 11 | ''' 12 | post_remove = ''' 13 | echo "Steam deck chwd removing..." 14 | ''' 15 | priority = 6 16 | vendor_ids = "1002" 17 | 18 | [case.test-profile-2] 19 | ai_sdk = false 20 | class_ids = "0300" 21 | desc = "Test profile" 22 | device_ids = "1432" 23 | device_name_pattern = '(ADW)\w+' 24 | hwd_product_name_pattern = '(AllyX)\w+' 25 | packages = "opencl-mesa lib32-opencl-mesa rocm-opencl-runtime mesa" 26 | post_install = ''' 27 | echo "Test installing..." 28 | ''' 29 | post_remove = ''' 30 | echo "Test removing..." 31 | ''' 32 | priority = 7 33 | vendor_ids = "1005" 34 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS chwd. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | pub mod consts; 20 | pub mod data; 21 | pub mod device; 22 | pub mod hwd_misc; 23 | pub mod profile; 24 | -------------------------------------------------------------------------------- /libpci-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![no_std] 5 | //! Low-level bindings to the [libpci] library. 6 | //! 7 | //! [libpci]: https://mj.ucw.cz/sw/pciutils/ 8 | 9 | #[cfg(feature = "std")] 10 | extern crate std; 11 | 12 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 13 | 14 | /// This function returns either ptr to next element or null if data is null. 15 | /// 16 | /// # Safety 17 | /// 18 | /// The caller must take care of null ptr. 19 | pub unsafe fn pci_get_next_device(data: *const pci_dev) -> *const pci_dev { 20 | if data.is_null() { 21 | data 22 | } else { 23 | (*data).next 24 | } 25 | } 26 | 27 | /// This function returns either ptr to next element or null if data is null. 28 | /// 29 | /// # Safety 30 | /// 31 | /// The caller must take care of null ptr. 32 | pub unsafe fn pci_get_next_device_mut(data: *mut pci_dev) -> *mut pci_dev { 33 | if data.is_null() { 34 | data 35 | } else { 36 | (*data).next 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/profiles/extra-check-root-profile.toml: -------------------------------------------------------------------------------- 1 | [nvidia-dkms] 2 | ai_sdk = false 3 | class_ids = "0300 0302 0380" 4 | desc = "Closed source NVIDIA drivers for Linux (Latest)" 5 | device_ids = "*" 6 | device_name_pattern = '((GM|GP)+[0-9]+[^M]*\s.*)' 7 | packages = "nvidia-utils egl-wayland nvidia-settings opencl-nvidia lib32-opencl-nvidia lib32-nvidia-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader" 8 | pre_install = ''' 9 | echo preinstall 10 | ''' 11 | post_install = ''' 12 | cat </etc/mkinitcpio.conf.d/10-chwd.conf 13 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 14 | MODULES+=(nvidia nvidia_modeset nvidia_uvm nvidia_drm) 15 | EOF 16 | mkinitcpio -P 17 | 18 | # Add libva-nvidia-driver to profile 19 | echo "export LIBVA_DRIVER_NAME=nvidia" > /etc/profile.d/nvidia-vaapi.sh 20 | ''' 21 | pre_remove = ''' 22 | echo preremove 23 | ''' 24 | post_remove = """ 25 | rm -f /etc/mkinitcpio.conf.d/10-chwd.conf 26 | rm -f /etc/profile.d/nvidia-vaapi.sh 27 | mkinitcpio -P 28 | """ 29 | priority = 12 30 | vendor_ids = "10de" 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chwd" 3 | version = "1.17.1" 4 | authors = ["Vladislav Nepogodin "] 5 | license = "GPL-3.0-only" 6 | edition = "2021" 7 | 8 | [dependencies] 9 | toml = "0.9" 10 | libpci = { path = "libpci", version = "0.1.3" } 11 | subprocess = "0.2" 12 | colored = "3" 13 | log = "0.4" 14 | clap = { features = ["derive"], version = "4" } 15 | anyhow = "1" 16 | nix = { features = ["user"], default-features = false, version = "0.30" } 17 | comfy-table = { default-features = false, version = "7" } 18 | regex = "1.12" 19 | once_cell = { features = ["std"], default-features = false, version = "1" } 20 | i18n-embed = { version = "0.16", features = ["fluent-system", "desktop-requester"] } 21 | i18n-embed-fl = "0.10" 22 | rust-embed = { version = "8", features = ["debug-embed", "include-exclude"] } 23 | glob = "0.3" 24 | 25 | [build-dependencies] 26 | clap = { features = ["derive"], version = "4" } 27 | clap_complete = "4" 28 | 29 | [profile.release] 30 | strip = "symbols" 31 | panic = "abort" 32 | lto = true 33 | opt-level = 3 34 | codegen-units = 1 35 | 36 | [lib] 37 | name = "chwd" 38 | path = "src/lib.rs" 39 | -------------------------------------------------------------------------------- /i18n/en/chwd.ftl: -------------------------------------------------------------------------------- 1 | # Headers 2 | name-header = Name 3 | desc-header = Desc 4 | priority-header = Priority 5 | classids-header = ClassIDS 6 | vendorids-header = VendorIDS 7 | 8 | # main 9 | root-operation = You cannot perform this operation unless you are root! 10 | profile-not-exist = profile '{$profile_name}' does not exist! 11 | no-matching-device = no matching device for profile '{$profile_name}' found! 12 | profile-not-installed = profile '{$profile_name}' is not installed! 13 | script-failed = script failed! 14 | failed-set-db = failed to set database! 15 | pass-profile-no-match-install = passed profile does not match with installed profile! 16 | 17 | # device 18 | available = AVAILABLE 19 | installed = INSTALLED 20 | device = Device 21 | no-profile-device = no profiles for PCI devices found! 22 | 23 | # console writer 24 | invalid-profile = profile '{$invalid_profile}' is invalid! 25 | all-pci-profiles = All PCI profiles: 26 | installed-pci-profiles = Installed PCI profiles: 27 | pci-profiles-not-found = No PCI profiles found! 28 | no-installed-pci-profiles = No installed PCI profiles! 29 | no-installed-profile-device = no installed profile for PCI devices found! 30 | -------------------------------------------------------------------------------- /i18n/cs/chwd.ftl: -------------------------------------------------------------------------------- 1 | # Hlavička 2 | name-header = Jméno 3 | desc-header = Popis 4 | priority-header = Priorita 5 | classids-header = ID Třídy 6 | vendorids-header = ID Prodejce 7 | 8 | # Hlavní 9 | root-operation = Tuto akci nemůžete provést pokud nejste root! 10 | profile-not-exist = profil '{$profile_name}' neexistuje! 11 | no-matching-device = Žádné nalezené zařízení pro '{$profile_name}'! 12 | profile-not-installed = profil '{$profile_name}' není nainstalovaný! 13 | script-failed = script selhal! 14 | failed-set-db = nepodařilo se nastavit databázy! 15 | pass-profile-no-match-install = uvedený profil se neshoduje s nainstalovaným profilem! 16 | 17 | # Zařízení 18 | available = DOSTUPNÉ 19 | installed = NAINSTALOVÁNO 20 | device = Zařízení 21 | no-profile-device = nebyly nalezeny žádné profily pro zařízení PCI! 22 | 23 | # Výstup v konzoly 24 | invalid-profile = profil '{$invalid_profile}' je neplatný! 25 | all-pci-profiles = Všechny PCI profily: 26 | installed-pci-profiles = Instalované PCI profily: 27 | pci-profiles-not-found = Žádné PCI profily nebyly nalezeny! 28 | no-installed-pci-profiles = Žádné PCI profili nejsou nainstalovány! 29 | no-installed-profile-device = žádný nainstalovaný profil pro zařízení PCI nebyl nalezen ! 30 | -------------------------------------------------------------------------------- /i18n/sk/chwd.ftl: -------------------------------------------------------------------------------- 1 | # Hlavička 2 | name-header = Meno 3 | desc-header = Popis 4 | priority-header = Priorita 5 | classids-header = ID Triedy 6 | vendorids-header = ID Predajcu 7 | 8 | # Hlavné 9 | root-operation = Túto akciu nemôžete vykonať pokiaľ nie ste root! 10 | profile-not-exist = profil '{$profile_name}' neexistuje! 11 | no-matching-device = Žiadne nájdené zariadenie pre '{$profile_name}'! 12 | profile-not-installed = profil '{$profile_name}' nie je nainštalovaný! 13 | script-failed = script zlyhal! 14 | failed-set-db = nepodarilo sa nastaviť databázy! 15 | pass-profile-no-match-install = uvedený profil sa nezhoduje s nainštalovaným profilom! 16 | 17 | # Zariadenie 18 | available = DOSTUPNÉ 19 | installed = NAINŠTALOVANÉ 20 | device = Zariadenie 21 | no-profile-device = neboli nájdené žiadne profily pre zariadenia PCI! 22 | 23 | # Výstup v konzoly 24 | invalid-profile = profil '{$invalid_profile}' je neplatný! 25 | all-pci-profiles = Všetky PCI profily: 26 | installed-pci-profiles = Inštalované PCI profily: 27 | pci-profiles-not-found = Žiadne PCI profily neboli nájdené! 28 | no-installed-pci-profiles = Žiadne PCI profily nie sú nainštalované! 29 | no-installed-profile-device = žiadny nainštalovaný profil pre zariadenie PCI nebol nájdený ! 30 | -------------------------------------------------------------------------------- /i18n/sv/chwd.ftl: -------------------------------------------------------------------------------- 1 | # Headers 2 | name-header = Namn 3 | desc-header = Beskr 4 | priority-header = Prioritet 5 | classids-header = ClassIDS 6 | vendorids-header = VendorIDS 7 | 8 | # main 9 | root-operation = Du kan inte genomföra denna åtgärd om du inte är root! 10 | profile-not-exist = profilen '{$profile_name}' finns inte! 11 | no-matching-device = ingen matchande enhet för profilen '{$profile_name}' hittades! 12 | profile-not-installed = profilen '{$profile_name}' är inte installerad! 13 | script-failed = skriptet misslyckades! 14 | failed-set-db = misslyckades med att ställa in databas! 15 | pass-profile-no-match-install = skickad profil matchar inte med installerad profil! 16 | 17 | # device 18 | available = TILLGÄNGLIG 19 | installed = INSTALLERAD 20 | device = Enhet 21 | no-profile-device = inga profiler för PCI-enheter hittades! 22 | 23 | # console writer 24 | invalid-profile = profilen '{$invalid_profile}' är ogiltig! 25 | all-pci-profiles = Alla PCI-profiler: 26 | installed-pci-profiles = Installerade PCI-profiler: 27 | pci-profiles-not-found = Inga PCI-profiler hittades! 28 | no-installed-pci-profiles = Inga installerade PCI-profiler! 29 | no-installed-profile-device = ingen installerad profil för PCI-enheter hittades! -------------------------------------------------------------------------------- /i18n/ru/chwd.ftl: -------------------------------------------------------------------------------- 1 | # Headers 2 | name-header = Имя 3 | desc-header = Описание 4 | priority-header = Приоритет 5 | classids-header = IDS класса 6 | vendorids-header = IDS вендора 7 | 8 | # main 9 | root-operation = вы не сможете выполнить эту операцию, если не являетесь пользователем root! 10 | profile-not-exist = профиль '{$profile_name}' не существует! 11 | no-matching-device = не найдено ни одного подходящего устройства для профиля '{$profile_name}'! 12 | profile-not-installed = профиль '{$profile_name}' не установлен! 13 | script-failed = скрипт не сработал! 14 | failed-set-db = не удалось задать БД! 15 | pass-profile-no-match-install = переданный профиль не совпадает с установленным профилем! 16 | 17 | # device 18 | available = ДОСТУПНО 19 | installed = УСТАНОВЛЕНО 20 | device = Устройство 21 | no-profile-device = профили для устройств PCI не найдены! 22 | 23 | # console writer 24 | invalid-profile = профиль '{$invalid_profile}' недействительный! 25 | all-pci-profiles = Все PCI профили: 26 | installed-pci-profiles = Установленные PCI профили: 27 | pci-profiles-not-found = PCI профили не найдены! 28 | no-installed-pci-profiles = Нет установленных PCI профилей! 29 | no-installed-profile-device = не найдено ни одного установленного профиля для устройств PCI! 30 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023-2024 Vladislav Nepogodin 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License along 14 | // with this program; if not, write to the Free Software Foundation, Inc., 15 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 | 17 | pub const CHWD_CONFIG_FILE: &str = "profiles.toml"; 18 | pub const CHWD_PCI_CONFIG_DIR: &str = "/var/lib/chwd/db/pci/"; 19 | pub const CHWD_PCI_DATABASE_DIR: &str = "/var/lib/chwd/local/pci/"; 20 | pub const CHWD_SCRIPT_PATH: &str = "/var/lib/chwd/scripts/chwd"; 21 | 22 | pub const CHWD_PM_CACHE_DIR: &str = "/var/cache/pacman/pkg"; 23 | pub const CHWD_PM_CONFIG: &str = "/etc/pacman.conf"; 24 | pub const CHWD_PM_ROOT: &str = "/"; 25 | -------------------------------------------------------------------------------- /i18n/de/chwd.ftl: -------------------------------------------------------------------------------- 1 | # Headers 2 | name-header = Name 3 | desc-header = Beschr. 4 | priority-header = Priorität 5 | classids-header = ClassIDS 6 | vendorids-header = VendorIDS 7 | 8 | # main 9 | root-operation = Sie können diese Operation nur durchführen, wenn Sie root sind! 10 | profile-not-exist = Das Profil '{$profile_name}' existiert nicht! 11 | no-matching-device = Kein passendes Gerät für Profil '{$profile_name}' gefunden! 12 | profile-not-installed = Das Profil '{$profile_name}' ist nicht installiert! 13 | script-failed = Skript fehlgeschlagen! 14 | failed-set-db = Datenbank kann nicht gesetzt werden! 15 | pass-profile-no-match-install = Das eingegebene Profil stimmt nicht mit dem installierten Profil überein! 16 | 17 | # device 18 | available = VERFÜGBAR 19 | installed = INSTALLIERT 20 | device = Gerät 21 | no-profile-device = Keine Profile für PCI-Geräte gefunden! 22 | 23 | # console writer 24 | invalid-profile = Das Profil '{$invalid_profile}' ist ungültig! 25 | all-pci-profiles = Alle PCI-Profile: 26 | installed-pci-profiles = Installierte PCI-Profile: 27 | pci-profiles-not-found = Keine PCI-Profile gefunden! 28 | no-installed-pci-profiles = Keine installierten PCI-Profile! 29 | no-installed-profile-device = Keine installierten Profile für PCI-Geräte gefunden! 30 | -------------------------------------------------------------------------------- /src/localization.rs: -------------------------------------------------------------------------------- 1 | use i18n_embed::fluent::{fluent_language_loader, FluentLanguageLoader}; 2 | use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer}; 3 | use once_cell::sync::Lazy; 4 | use rust_embed::RustEmbed; 5 | 6 | #[derive(RustEmbed)] 7 | #[folder = "i18n"] // path to the compiled localization resources 8 | struct Localizations; 9 | 10 | pub static LANGUAGE_LOADER: Lazy = Lazy::new(|| { 11 | let loader: FluentLanguageLoader = fluent_language_loader!(); 12 | 13 | loader.load_fallback_language(&Localizations).expect("Error while loading fallback language"); 14 | 15 | loader 16 | }); 17 | 18 | #[macro_export] 19 | macro_rules! fl { 20 | ($message_id:literal) => {{ 21 | i18n_embed_fl::fl!($crate::localization::LANGUAGE_LOADER, $message_id) 22 | }}; 23 | 24 | ($message_id:literal, $($args:expr),*) => {{ 25 | i18n_embed_fl::fl!($crate::localization::LANGUAGE_LOADER, $message_id, $($args), *) 26 | }}; 27 | } 28 | 29 | /// Get the `Localizer` to be used for localizing this library. 30 | pub fn localizer() -> Box { 31 | Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations)) 32 | } 33 | 34 | /// Get translated text 35 | pub fn get_locale_text(message_id: &str) -> String { 36 | LANGUAGE_LOADER.get(message_id) 37 | } 38 | -------------------------------------------------------------------------------- /profiles/pci/ai_sdk/profiles.toml: -------------------------------------------------------------------------------- 1 | # VENDOR AMD=1002 INTEL=8086 NVIDIA=10de 2 | # CLASSID 03=Display controller 3 | # 00=VGA compatible controller 02=3D controller 80=Display controller 4 | 5 | # NVIDIA cards 6 | #CLASSIDS="0300 0302" 7 | #VENDORIDS="10de" 8 | #DEVICEIDS=">/var/lib/mhwd/ids/pci/nvidia.ids" 9 | 10 | [nvidia-ai-sdk] 11 | desc = 'NVIDIA AI SDK and related tools' 12 | ai_sdk = true 13 | #class_ids = "*" 14 | class_ids = "0300 0302" 15 | vendor_ids = "10de" 16 | priority = 9 17 | packages = 'cuda cudnn nccl python-pytorch-opt-cuda ollama-cuda tensorflow-opt-cuda python-tensorflow-opt-cuda chatbox' 18 | device_name_pattern = '(GB|AD|GV|TU|GA|GH|GM|GP)\w+' 19 | #device_ids = '*' 20 | post_install = """ 21 | systemctl enable ollama.service 22 | """ 23 | post_remove = """ 24 | systemctl disable ollama.service 25 | """ 26 | 27 | [rocm-ai-sdk] 28 | desc = 'AMD AI SDK and related tools' 29 | ai_sdk = true 30 | class_ids = "*" 31 | vendor_ids = "1002" 32 | device_ids = '*' 33 | priority = 9 34 | packages = 'rocm-hip-sdk python-pytorch-opt-rocm ollama-rocm tensorflow-opt python-tensorflow-opt chatbox' 35 | # https://docs.kernel.org/gpu/amdgpu/driver-misc.html 36 | gc_versions = '11.0.0 11.0.3 10.3.0 9.4.1 9.4.2 9.4.3' 37 | post_install = """ 38 | systemctl enable ollama.service 39 | """ 40 | post_remove = """ 41 | systemctl disable ollama.service 42 | """ 43 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'LICENSE' 7 | - '*.md' 8 | - '*.sh' 9 | branches: 10 | - master 11 | pull_request: 12 | branches: 13 | - master 14 | 15 | env: 16 | CARGO_TERM_COLOR: always 17 | 18 | jobs: 19 | build: 20 | name: CI 21 | runs-on: ubuntu-latest 22 | container: 23 | image: archlinux 24 | steps: 25 | - name: Install Packages 26 | run: pacman -Syu clang gcc pciutils --noconfirm --needed 27 | 28 | - uses: actions/checkout@v4 29 | 30 | - name: Setup toolchain 31 | uses: actions-rs/toolchain@v1 32 | with: 33 | profile: minimal 34 | toolchain: nightly 35 | override: true 36 | components: rustfmt, clippy 37 | 38 | - name: Run cargo build 39 | uses: actions-rs/cargo@v1 40 | with: 41 | command: build 42 | 43 | - name: Run cargo test 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: test 47 | 48 | - name: Run cargo clippy 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: clippy 52 | args: --all-targets -- -D warnings 53 | 54 | - name: Run cargo fmt 55 | uses: actions-rs/cargo@v1 56 | with: 57 | command: fmt 58 | args: --all -- --check 59 | -------------------------------------------------------------------------------- /tests/profiles/profile-raw-escaped-strings-test.toml: -------------------------------------------------------------------------------- 1 | [test-profile] 2 | ai_sdk = false 3 | class_ids = "0300" 4 | desc = "Test profile" 5 | device_ids = "1435 163f" 6 | device_name_pattern = '(AD)\w+' 7 | hwd_product_name_pattern = '(Ally)\w+' 8 | packages = "opencl-mesa lib32-opencl-mesa rocm-opencl-runtime" 9 | post_install = ''' 10 | echo "Steam Deck chwd installing..." 11 | username=$(id -nu 1000) 12 | services=("steam-powerbuttond") 13 | kernelparams="amd_iommu=off amdgpu.gttsize=8128 spi_amd.speed_dev=1 audit=0 iomem=relaxed amdgpu.ppfeaturemask=0xffffffff" 14 | echo "Enabling services..." 15 | for service in ${services[@]}; do 16 | systemctl enable --now "${service}.service" 17 | done 18 | echo "Adding required kernel parameters..." 19 | sed -i "s/LINUX_OPTIONS="[^"]*/& ${kernelparams}/" /etc/sdboot-manage.conf 20 | ''' 21 | post_remove = ''' 22 | echo "Steam deck chwd removing..." 23 | username=$(id -nu 1000) 24 | services=("steam-powerbuttond") 25 | kernelparams="amd_iommu=off amdgpu.gttsize=8128 spi_amd.speed_dev=1 audit=0 iomem=relaxed amdgpu.ppfeaturemask=0xffffffff" 26 | echo "Disabling services..." 27 | for service in ${services[@]}; do 28 | systemctl disable "${service}.service" 29 | done 30 | echo "Removing kernel parameters..." 31 | sed -i "s/${kernelparams}//" /etc/sdboot-manage.conf 32 | ''' 33 | priority = 6 34 | vendor_ids = "1002" 35 | -------------------------------------------------------------------------------- /src/logger.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use colored::Colorize; 4 | use log::{Level, Metadata, Record}; 5 | 6 | struct SimpleLogger; 7 | 8 | static LOGGER: SimpleLogger = SimpleLogger; 9 | 10 | impl log::Log for SimpleLogger { 11 | fn enabled(&self, _: &Metadata) -> bool { 12 | true 13 | } 14 | 15 | fn log(&self, record: &Record) { 16 | if self.enabled(record.metadata()) { 17 | let level_str = match record.level() { 18 | Level::Error => "Error:".red(), 19 | Level::Warn => "Warning:".yellow(), 20 | Level::Info => ">".red(), 21 | Level::Debug => "Debug:".white(), 22 | Level::Trace => "Trace:".black(), 23 | }; 24 | if record.level() == Level::Error { 25 | eprintln!("{level_str} {}", record.args()); 26 | } else { 27 | println!("{level_str} {}", record.args()); 28 | } 29 | } 30 | } 31 | 32 | fn flush(&self) { 33 | // use std::io::Write; 34 | // io::stdout().flush().unwrap(); 35 | } 36 | } 37 | 38 | pub fn init_logger() -> Result<(), log::SetLoggerError> { 39 | // set log level 40 | let max_log_level = if let Ok(env_log) = env::var("RUST_LOG") { 41 | let env_log = env_log.to_lowercase(); 42 | match env_log.as_str() { 43 | "trace" => log::LevelFilter::Trace, 44 | "debug" => log::LevelFilter::Debug, 45 | "warn" => log::LevelFilter::Warn, 46 | "error" => log::LevelFilter::Error, 47 | _ => log::LevelFilter::Info, 48 | } 49 | } else { 50 | log::LevelFilter::Info 51 | }; 52 | 53 | log::set_logger(&LOGGER).map(|()| log::set_max_level(max_log_level)) 54 | } 55 | -------------------------------------------------------------------------------- /src/profile_misc.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023-2024 Vladislav Nepogodin 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License along 14 | // with this program; if not, write to the Free Software Foundation, Inc., 15 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 | 17 | use crate::fl; 18 | use crate::profile::Profile; 19 | 20 | use comfy_table::modifiers::UTF8_ROUND_CORNERS; 21 | use comfy_table::presets::UTF8_FULL; 22 | use comfy_table::*; 23 | 24 | pub fn print_profile_details(profile: &Profile) { 25 | let mut class_ids = String::new(); 26 | let mut vendor_ids = String::new(); 27 | for hwd_id in profile.hwd_ids.iter() { 28 | vendor_ids.push_str(&hwd_id.vendor_ids.join(" ")); 29 | class_ids.push_str(&hwd_id.class_ids.join(" ")); 30 | } 31 | 32 | let desc_formatted = if profile.desc.is_empty() { "-" } else { &profile.desc }; 33 | 34 | let mut table = Table::new(); 35 | table 36 | .load_preset(UTF8_FULL) 37 | .apply_modifier(UTF8_ROUND_CORNERS) 38 | .set_content_arrangement(ContentArrangement::Dynamic) 39 | .add_row(vec![&fl!("name-header"), &profile.name]) 40 | .add_row(vec![&fl!("desc-header"), desc_formatted]) 41 | .add_row(vec![&fl!("priority-header"), &profile.priority.to_string()]) 42 | .add_row(vec![&fl!("classids-header"), &class_ids]) 43 | .add_row(vec![&fl!("vendorids-header"), &vendor_ids]); 44 | 45 | println!("{table}\n"); 46 | } 47 | -------------------------------------------------------------------------------- /tests/profiles/graphic_drivers-profiles-test.toml: -------------------------------------------------------------------------------- 1 | [nvidia-dkms.40xxcards] 2 | desc = 'Closed source NVIDIA drivers(40xx series) for Linux (Latest)' 3 | priority = 9 4 | conditional_packages = """ 5 | kernels="$(pacman -Qqs "^linux-cachyos")" 6 | modules="" 7 | 8 | for kernel in $kernels; do 9 | case "$kernel" in 10 | *-headers|*-zfs);; 11 | *-nvidia) modules+=" ${kernel}";; 12 | *) modules+=" ${kernel}-nvidia";; 13 | esac 14 | done 15 | 16 | # Fallback if there are no kernels with pre-built modules 17 | [ -z "$modules" ] && modules="nvidia-dkms" 18 | 19 | echo "$modules" 20 | """ 21 | post_install = """ 22 | cat </etc/mkinitcpio.conf.d/10-chwd.conf 23 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 24 | MODULES+=(nvidia nvidia_modeset nvidia_uvm nvidia_drm) 25 | EOF 26 | mkinitcpio -P 27 | """ 28 | device_name_pattern = '(AD)\w+' 29 | 30 | [nvidia-dkms] 31 | desc = 'Closed source NVIDIA drivers for Linux (Latest)' 32 | class_ids = "0300 0380 0302" 33 | vendor_ids = "10de" 34 | priority = 8 35 | packages = 'nvidia-utils egl-wayland nvidia-settings opencl-nvidia lib32-opencl-nvidia lib32-nvidia-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader' 36 | conditional_packages = """ 37 | kernels="$(pacman -Qqs "^linux-cachyos")" 38 | modules="" 39 | 40 | for kernel in $kernels; do 41 | case "$kernel" in 42 | *-headers|*-zfs);; 43 | *-nvidia) modules+=" ${kernel}";; 44 | *) modules+=" ${kernel}-nvidia";; 45 | esac 46 | done 47 | 48 | # Fallback if there are no kernels with pre-built modules 49 | [ -z "$modules" ] && modules="nvidia-dkms" 50 | 51 | echo "$modules" 52 | """ 53 | post_install = """ 54 | cat </etc/mkinitcpio.conf.d/10-chwd.conf 55 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 56 | MODULES+=(nvidia nvidia_modeset nvidia_uvm nvidia_drm) 57 | EOF 58 | mkinitcpio -P 59 | """ 60 | post_remove = """ 61 | rm -f /etc/mkinitcpio.conf.d/10-chwd.conf 62 | mkinitcpio -P 63 | """ 64 | device_ids = '>tests/profiles/nvidia-test-ids.ids' 65 | hwd_product_name_pattern = '(Ally)\w+' 66 | -------------------------------------------------------------------------------- /libpci-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | fn main() { 5 | println!("cargo:rustc-link-lib=pci"); 6 | 7 | // Tell cargo to invalidate the built crate whenever the wrapper changes 8 | println!("cargo:rerun-if-changed=wrapper.h"); 9 | 10 | // Compile static helper library 11 | compile_helper_lib(); 12 | 13 | // The bindgen::Builder is the main entry point 14 | // to bindgen, and lets you build up options for 15 | // the resulting bindings. 16 | let bindings = bindgen::Builder::default() 17 | // The input header we would like to generate 18 | // bindings for. 19 | .header("wrapper.h") 20 | // Tell cargo to invalidate the built crate whenever any of the 21 | // included header files changed. 22 | .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) 23 | .size_t_is_usize(true) 24 | .use_core() 25 | .allowlist_function("pci_.*") 26 | .allowlist_var("PCI_.*") 27 | .allowlist_var("pci_.*") 28 | .allowlist_type("pci_.*") 29 | .blocklist_type("timespec") 30 | //.blocklist_type("stat") 31 | //.default_macro_constant_type(bindgen::EnumVariation::Rust) 32 | //.default_enum_style(bindgen::EnumVariation::Rust { 33 | // non_exhaustive: false, 34 | //}) 35 | .default_macro_constant_type(bindgen::MacroTypeVariation::Signed); 36 | 37 | // Finish the builder and generate the bindings. 38 | let bindings = bindings 39 | .generate() 40 | // Unwrap the Result and panic on failure. 41 | .expect("Unable to generate bindings"); 42 | 43 | // Write the bindings to the $OUT_DIR/bindings.rs file. 44 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 45 | bindings.write_to_file(out_path.join("bindings.rs")).expect("Couldn't write bindings!"); 46 | } 47 | 48 | fn compile_helper_lib() { 49 | cc::Build::new() 50 | // Add file 51 | .file("wrapper.c") 52 | // Some extra parameters 53 | .flag_if_supported("-ffunction-sections") 54 | .flag_if_supported("-fdata-sections") 55 | .flag_if_supported("-fmerge-all-constants") 56 | // Compile! 57 | .compile("libpci_utils.a"); 58 | } 59 | -------------------------------------------------------------------------------- /profiles/pci/t2-macbook/profiles.toml: -------------------------------------------------------------------------------- 1 | # MacBook T2 2 | 3 | [macbook-t2] 4 | desc = 'Packages and device configuration for the T2 Macbook' 5 | priority = 9 6 | class_ids = "0000" 7 | vendor_ids = "106b" 8 | device_ids = "1801 1802" 9 | packages = 'apple-t2-audio-config t2fanrd' 10 | post_install = """ 11 | cat </etc/mkinitcpio.conf.d/11-chwd.conf 12 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 13 | MODULES+=(apple-bce) 14 | EOF 15 | mkinitcpio -P 16 | 17 | echo apple-bce > /etc/modules-load.d/t2.conf 18 | kernelparams="intel_iommu=on iommu=pt pcie_ports=compat" 19 | echo "Adding required kernel parameters..." 20 | 21 | if [ -f /etc/sdboot-manage.conf ]; then 22 | sed -i "s/LINUX_OPTIONS=\"[^\"]*/& ${kernelparams}/" /etc/sdboot-manage.conf 23 | fi 24 | 25 | if pacman -Qs grub &> /dev/null ; then 26 | echo "GRUB is installed. Generating the GRUB configuration file..." 27 | sed -i "s/GRUB_CMDLINE_LINUX_DEFAULT=\"[^\"]*/& ${kernelparams}/" /etc/default/grub 28 | grub-mkconfig -o /boot/grub/grub.cfg 29 | fi 30 | 31 | cat </etc/modprobe.d/brcmfmac.conf 32 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 33 | options brcmfmac feature_disable=0x82000 34 | EOF 35 | 36 | cat < /dev/null ; then 60 | sed -i "s/${kernelparams}//" /etc/default/grub 61 | fi 62 | """ 63 | -------------------------------------------------------------------------------- /src/device.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023-2024 Vladislav Nepogodin 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License along 14 | // with this program; if not, write to the Free Software Foundation, Inc., 15 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 | 17 | use crate::profile::Profile; 18 | 19 | use std::sync::Arc; 20 | 21 | #[derive(Debug, Default, Clone)] 22 | pub struct Device { 23 | pub class_name: String, 24 | pub device_name: String, 25 | pub vendor_name: String, 26 | pub class_id: String, 27 | pub device_id: String, 28 | pub vendor_id: String, 29 | pub sysfs_busid: String, 30 | pub sysfs_id: String, 31 | pub available_profiles: Vec>, 32 | pub installed_profiles: Vec>, 33 | } 34 | 35 | impl Device { 36 | pub fn get_available_profiles(&self) -> Vec { 37 | let smth_handle_arc = |profile: &Profile| profile.clone(); 38 | self.available_profiles.iter().map(|x| smth_handle_arc(x)).collect() 39 | } 40 | 41 | pub fn device_info(&self) -> String { 42 | format!( 43 | "{} ({}:{}:{}) {} {} {}", 44 | self.sysfs_busid, 45 | self.class_id, 46 | self.vendor_id, 47 | self.device_id, 48 | self.class_name, 49 | self.vendor_name, 50 | self.device_name 51 | ) 52 | } 53 | } 54 | 55 | pub fn get_unique_devices(devices: &[Device]) -> Vec { 56 | let mut uniq_devices = vec![]; 57 | for device in devices.iter() { 58 | // Check if already in list 59 | let found = uniq_devices.iter().any(|x: &Device| { 60 | (device.sysfs_busid == x.sysfs_busid) && (device.sysfs_id == x.sysfs_id) 61 | }); 62 | 63 | if !found { 64 | uniq_devices.push(device.clone()); 65 | } 66 | } 67 | 68 | uniq_devices 69 | } 70 | -------------------------------------------------------------------------------- /src/device_misc.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023-2024 Vladislav Nepogodin 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License along 14 | // with this program; if not, write to the Free Software Foundation, Inc., 15 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 | 17 | use crate::device::Device; 18 | use crate::{console_writer, fl, profile_misc}; 19 | 20 | pub fn print_available_profiles_in_detail(devices: &[Device]) { 21 | let mut config_found = false; 22 | for device in devices.iter() { 23 | let available_profiles = &device.available_profiles; 24 | let installed_profiles = &device.installed_profiles; 25 | if available_profiles.is_empty() && installed_profiles.is_empty() { 26 | continue; 27 | } 28 | config_found = true; 29 | 30 | log::info!( 31 | "{} {}: {} ({}:{}:{})", 32 | "PCI", 33 | fl!("device"), 34 | device.sysfs_id, 35 | device.class_id, 36 | device.vendor_id, 37 | device.device_id 38 | ); 39 | println!(" {} {} {}", device.class_name, device.vendor_name, device.device_name); 40 | println!(); 41 | if !installed_profiles.is_empty() { 42 | println!(" > {}:\n", fl!("installed")); 43 | for installed_profile in installed_profiles.iter() { 44 | profile_misc::print_profile_details(installed_profile); 45 | } 46 | println!("\n"); 47 | } 48 | if !available_profiles.is_empty() { 49 | println!(" > {}:\n", fl!("available")); 50 | for available_profile in available_profiles.iter() { 51 | profile_misc::print_profile_details(available_profile); 52 | } 53 | println!("\n"); 54 | } 55 | } 56 | 57 | if !config_found { 58 | console_writer::print_warn_msg!("no-profile-device"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/profiles/graphic_drivers-invalid-profiles-test.toml: -------------------------------------------------------------------------------- 1 | [nvidia-dkms.40xxcards] 2 | desc = 'Closed source NVIDIA drivers(40xx series) for Linux (Latest)' 3 | priority = 9 4 | packages = 'nvidia-utils egl-wayland nvidia-settings opencl-nvidia lib32-opencl-nvidia lib32-nvidia-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader' 5 | conditional_packages = """ 6 | kernels="$(pacman -Qqs "^linux-cachyos")" 7 | modules="" 8 | 9 | for kernel in $kernels; do 10 | case "$kernel" in 11 | *-headers|*-zfs);; 12 | *-nvidia) modules+=" ${kernel}";; 13 | *) modules+=" ${kernel}-nvidia";; 14 | esac 15 | done 16 | 17 | # Fallback if there are no kernels with pre-built modules 18 | [ -z "$modules" ] && modules="nvidia-dkms" 19 | 20 | echo "$modules" 21 | """ 22 | post_install = """ 23 | cat </etc/mkinitcpio.conf.d/10-chwd.conf 24 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 25 | MODULES+=(nvidia nvidia_modeset nvidia_uvm nvidia_drm) 26 | EOF 27 | mkinitcpio -P 28 | """ 29 | post_remove = """ 30 | rm -f /etc/mkinitcpio.conf.d/10-chwd.conf 31 | mkinitcpio -P 32 | """ 33 | device_ids = '*' 34 | 35 | [nvidia-dkms] 36 | desc = 'Closed source NVIDIA drivers for Linux (Latest)' 37 | class_ids = "0300 0380 0302" 38 | vendor_ids = "10de" 39 | priority = 8 40 | packages = 'nvidia-utils egl-wayland nvidia-settings opencl-nvidia lib32-opencl-nvidia lib32-nvidia-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader' 41 | conditional_packages = """ 42 | kernels="$(pacman -Qqs "^linux-cachyos")" 43 | modules="" 44 | 45 | for kernel in $kernels; do 46 | case "$kernel" in 47 | *-headers|*-zfs);; 48 | *-nvidia) modules+=" ${kernel}";; 49 | *) modules+=" ${kernel}-nvidia";; 50 | esac 51 | done 52 | 53 | # Fallback if there are no kernels with pre-built modules 54 | [ -z "$modules" ] && modules="nvidia-dkms" 55 | 56 | echo "$modules" 57 | """ 58 | post_install = """ 59 | cat </etc/mkinitcpio.conf.d/10-chwd.conf 60 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 61 | MODULES+=(nvidia nvidia_modeset nvidia_uvm nvidia_drm) 62 | EOF 63 | mkinitcpio -P 64 | """ 65 | post_remove = """ 66 | rm -f /etc/mkinitcpio.conf.d/10-chwd.conf 67 | mkinitcpio -P 68 | """ 69 | device_ids = '>/some/path/to/file/nvidia-invalid-ids.ids' 70 | 71 | [invalid] 72 | desc = "Invalid profile for testing only" 73 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023-2024 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS chwd. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | use clap::builder::ArgPredicate; 20 | use clap::Parser; 21 | 22 | #[derive(Parser, Debug)] 23 | #[command(author, version, about, long_about = None)] 24 | pub struct Args { 25 | /// Check profile 26 | #[arg(short, long, value_name = "profile")] 27 | pub check: Option, 28 | 29 | /// Install profile 30 | #[arg(short, long, value_name = "profile", conflicts_with("remove"))] 31 | pub install: Option, 32 | 33 | /// Remove profile 34 | #[arg(short, long, value_name = "profile", conflicts_with("install"))] 35 | pub remove: Option, 36 | 37 | /// Show detailed info for listings 38 | #[arg(short, long, requires_if(ArgPredicate::IsPresent, "listings"))] 39 | pub detail: bool, 40 | 41 | /// Force reinstall 42 | #[arg(short, long)] 43 | pub force: bool, 44 | 45 | /// List installed kernels 46 | #[arg(long, group = "listings")] 47 | pub list_installed: bool, 48 | 49 | /// List available profiles for all devices 50 | #[arg(long = "list", group = "listings")] 51 | pub list_available: bool, 52 | 53 | /// List all profiles 54 | #[arg(long, group = "listings")] 55 | pub list_all: bool, 56 | 57 | /// Autoconfigure 58 | #[arg(short, long, value_name = "classid", conflicts_with_all(["install", "remove"]), default_missing_value = "any", num_args(0..=1))] 59 | pub autoconfigure: Option, 60 | 61 | /// Toggle AI SDK profiles 62 | #[arg(long = "ai_sdk")] 63 | pub is_ai_sdk: bool, 64 | 65 | #[arg(long, default_value_t = String::from("/var/cache/pacman/pkg"))] 66 | pub pmcachedir: String, 67 | #[arg(long, default_value_t = String::from("/etc/pacman.conf"))] 68 | pub pmconfig: String, 69 | #[arg(long, default_value_t = String::from("/"))] 70 | pub pmroot: String, 71 | } 72 | -------------------------------------------------------------------------------- /ids/nouveau.ids: -------------------------------------------------------------------------------- 1 | 0020 0028 0029 002c 002d 0040 0041 0042 0043 0044 0045 0046 0047 0048 004e 0090 0091 0092 0093 0095 0098 0099 009d 00a0 00c0 00c1 00c2 00c3 00c8 00c9 00cc 00cd 00ce 00f1 00f2 00f3 00f4 00f5 00f6 00f8 00f9 00fa 00fb 00fc 00fd 00fe 0100 0101 0103 0110 0111 0112 0113 0140 0141 0142 0143 0144 0145 0146 0147 0148 0149 014a 014c 014d 014e 014f 0150 0151 0152 0153 0160 0161 0162 0163 0164 0165 0166 0167 0168 0169 016a 0170 0171 0172 0173 0174 0175 0176 0177 0178 0179 017a 017c 017d 0181 0182 0183 0185 0188 018a 018b 018c 0191 0193 0194 0197 019d 019e 01a0 01d0 01d1 01d2 01d3 01d6 01d7 01d8 01da 01db 01dc 01dd 01de 01df 01f0 0200 0201 0202 0203 0211 0212 0215 0218 0221 0222 0240 0241 0242 0244 0245 0247 0250 0251 0253 0258 0259 025b 0280 0281 0282 0286 0288 0289 028c 0290 0291 0292 0293 0294 0295 0297 0298 0299 029a 029b 029c 029d 029e 029f 02e0 02e1 02e2 02e3 02e4 0301 0302 0308 0309 0311 0312 0314 031a 031b 031c 0320 0321 0322 0323 0324 0325 0326 0327 0328 032a 032b 032c 032d 0330 0331 0332 0333 0334 0338 033f 0341 0342 0343 0344 0347 0348 034c 034e 038b 0390 0391 0392 0393 0394 0395 0397 0398 0399 039c 039e 03d0 03d1 03d2 03d5 03d6 0400 0401 0402 0403 0404 0405 0406 0407 0408 0409 040a 040b 040c 040d 040e 040f 0410 0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f 0531 0533 053a 053b 053e 05e0 05e1 05e2 05e3 05e6 05e7 05ea 05eb 05ed 05f8 05f9 05fd 05fe 05ff 0600 0601 0602 0603 0604 0605 0606 0607 0608 0609 060a 060b 060c 060d 060f 0610 0611 0612 0613 0614 0615 0617 0618 0619 061a 061b 061c 061d 061e 061f 0621 0622 0623 0625 0626 0627 0628 062a 062b 062c 062d 062e 0630 0631 0632 0635 0637 0638 063a 0640 0641 0643 0644 0645 0646 0647 0648 0649 064a 064b 064c 0651 0652 0653 0654 0655 0656 0658 0659 065a 065b 065c 06e0 06e1 06e2 06e3 06e4 06e5 06e6 06e7 06e8 06e9 06ea 06eb 06ec 06ef 06f1 06f8 06f9 06fa 06fb 06fd 06ff 07e0 07e1 07e2 07e3 07e5 0840 0844 0845 0846 0847 0848 0849 084a 084b 084c 084d 084f 0860 0861 0862 0863 0864 0865 0866 0867 0868 0869 086a 086c 086d 086e 086f 0870 0871 0872 0873 0874 0876 087a 087d 087e 087f 08a0 08a2 08a3 08a4 08a5 0a20 0a22 0a23 0a26 0a27 0a28 0a29 0a2a 0a2b 0a2c 0a2d 0a32 0a34 0a35 0a38 0a3c 0a60 0a62 0a63 0a64 0a65 0a66 0a67 0a68 0a69 0a6a 0a6c 0a6e 0a6f 0a70 0a71 0a72 0a73 0a74 0a75 0a76 0a78 0a7a 0a7c 0ca0 0ca2 0ca3 0ca4 0ca5 0ca7 0ca8 0ca9 0cac 0caf 0cb0 0cb1 0cbc 0fef 0ff2 10c0 10c3 10c5 10d8 11bf 06c0 06c4 06ca 06cd 06d1 06d2 06d8 06d9 06da 06dc 06dd 06de 06df 0dc0 0dc4 0dc5 0dc6 0dcd 0dce 0dd1 0dd2 0dd3 0dd6 0dd8 0dda 0de0 0de1 0de2 0de3 0de4 0de5 0de7 0de8 0de9 0dea 0deb 0dec 0ded 0dee 0def 0df0 0df1 0df2 0df3 0df4 0df5 0df6 0df7 0df8 0df9 0dfa 0dfc 0e22 0e23 0e24 0e30 0e31 0e3a 0e3b 0f00 0f01 0f02 0f03 1040 1042 1048 1049 104a 104b 104c 1050 1051 1052 1054 1055 1056 1057 1058 1059 105a 105b 107c 107d 1080 1081 1082 1084 1086 1087 1088 1089 108b 1091 1094 1096 109a 109b 1140 1200 1201 1203 1205 1206 1207 1208 1210 1211 1212 1213 1241 1243 1244 1245 1246 1247 1248 1249 124b 124d 1251 2 | -------------------------------------------------------------------------------- /src/misc.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023-2024 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS chwd. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | use std::path::Path; 20 | use std::sync::Arc; 21 | 22 | use crate::consts; 23 | use crate::profile::Profile; 24 | 25 | #[derive(Debug, PartialEq)] 26 | pub enum Transaction { 27 | Install, 28 | Remove, 29 | } 30 | 31 | #[derive(Debug, PartialEq)] 32 | pub enum Status { 33 | Success, 34 | ErrorNotInstalled, 35 | ErrorAlreadyInstalled, 36 | ErrorNoMatchLocalConfig, 37 | ErrorScriptFailed, 38 | ErrorSetDatabase, 39 | } 40 | 41 | #[derive(Debug)] 42 | pub enum Message { 43 | InstallStart, 44 | InstallEnd, 45 | RemoveStart, 46 | RemoveEnd, 47 | } 48 | 49 | #[inline] 50 | pub fn get_current_cmdname(cmd_line: &str) -> &str { 51 | if let Some(trim_pos) = cmd_line.rfind('/') { 52 | return cmd_line.get((trim_pos + 1)..).unwrap(); 53 | } 54 | cmd_line 55 | } 56 | 57 | pub fn find_profile(profile_name: &str, profiles: &[Profile]) -> Option> { 58 | let found_profile = profiles.iter().find(|x| x.name == profile_name); 59 | if let Some(found_profile) = found_profile { 60 | return Some(Arc::new(found_profile.clone())); 61 | } 62 | None 63 | } 64 | 65 | pub fn check_environment() -> Vec { 66 | let mut missing_dirs = vec![]; 67 | 68 | if !Path::new(consts::CHWD_PCI_CONFIG_DIR).exists() { 69 | missing_dirs.push(consts::CHWD_PCI_CONFIG_DIR.to_owned()); 70 | } 71 | if !Path::new(consts::CHWD_PCI_DATABASE_DIR).exists() { 72 | missing_dirs.push(consts::CHWD_PCI_DATABASE_DIR.to_owned()); 73 | } 74 | 75 | missing_dirs 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use crate::{misc, profile}; 81 | 82 | #[test] 83 | fn cmdline() { 84 | assert_eq!(misc::get_current_cmdname("../../../testchwd"), "testchwd"); 85 | assert_eq!(misc::get_current_cmdname("/usr/bin/testchwd"), "testchwd"); 86 | assert_eq!(misc::get_current_cmdname("testchwd"), "testchwd"); 87 | } 88 | 89 | #[test] 90 | fn profile_find() { 91 | let prof_path = "tests/profiles/graphic_drivers-profiles-test.toml"; 92 | let profiles = profile::parse_profiles(prof_path).expect("failed"); 93 | 94 | assert!(misc::find_profile("nvidia-dkms", &profiles).is_some()); 95 | assert!(misc::find_profile("nvidia-dkm", &profiles).is_none()); 96 | assert!(misc::find_profile("nvidia-dkms.40xxcards", &profiles).is_some()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /scripts/nvidia-pci-ids-dumper/nvidia-pci-ids-dumper: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | --[[ 3 | Copyright (C) 2025 Vasiliy Stelmachenok for CachyOS 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program; if not, write to the Free Software Foundation, Inc., 17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | --]] 19 | local found, json = pcall(require, 'dkjson') 20 | 21 | local function die(msg, ...) 22 | print(msg:format(...)) 23 | os.exit(1) 24 | end 25 | 26 | if not found then 27 | die("lua-dkjson dependency is missing, please install it from repositories") 28 | end 29 | 30 | local function write(generations) 31 | for branch, ids in pairs(generations) do 32 | local fname = branch .. ".ids" 33 | 34 | if branch ~= "nouveau" then 35 | fname = "nvidia-" .. branch:match("([0-9]+).xx") .. ".ids" 36 | end 37 | 38 | local devices = {} 39 | 40 | for id in pairs(ids) do 41 | devices[#devices+1] = id 42 | end 43 | 44 | devices = table.sort(devices) or devices 45 | 46 | local hwdb, errmsg = io.open(fname, "w+") 47 | if hwdb then 48 | local content = table.concat(devices, " ") 49 | hwdb:write(content .. "\n") 50 | hwdb:close() 51 | else 52 | die("Failed to write PCI ids: %s", errmsg) 53 | end 54 | end 55 | end 56 | 57 | local function parse(chips) 58 | local drivers = { 59 | ["390.xx"] = {}, 60 | ["470.xx"] = {}, 61 | ["580.xx"] = {}, 62 | ["nouveau"] = {} 63 | } 64 | for _, chip in ipairs(chips) do 65 | local id = chip.devid:gsub("0x", ""):lower() 66 | if chip.legacybranch then 67 | local branch = chip.legacybranch 68 | if branch == "580.xx" or branch == "470.xx" or branch == "390.xx" then 69 | drivers[branch][id] = true 70 | else 71 | drivers["nouveau"][id] = true 72 | end 73 | end 74 | end 75 | 76 | return drivers 77 | end 78 | 79 | local function main() 80 | if #arg < 1 then 81 | die("Specify path to the supported-gpu.json file") 82 | end 83 | 84 | local path = arg[1] 85 | local file, errmsg = io.open(path, "r") 86 | 87 | if not file then 88 | die("Failed to open %s file: %s", path, errmsg) 89 | return 90 | end 91 | 92 | local raw = file:read() 93 | file:close() 94 | 95 | if not raw then 96 | die("Failed to read file %s", path) 97 | end 98 | 99 | local supported_gpus = json.decode(raw) 100 | 101 | if not supported_gpus.chips then 102 | die("Failed to parse supported_gpus.json") 103 | end 104 | 105 | local generations = parse(supported_gpus.chips) 106 | write(generations) 107 | end 108 | 109 | main() 110 | -------------------------------------------------------------------------------- /scripts/chwd-kernel/src/kernel.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022-2024 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS kernel manager. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | const IGNORED_PKG: &str = "linux-api-headers"; 20 | const REPLACE_PART: &str = "-headers"; 21 | // const NEEDLE: &str = "linux[^ ]*-headers"; 22 | 23 | #[derive(Debug)] 24 | pub struct Kernel<'a> { 25 | pub name: String, 26 | pub repo: String, 27 | pub raw: String, 28 | alpm_pkg: Option<&'a alpm::Package>, 29 | alpm_handle: Option<&'a alpm::Alpm>, 30 | } 31 | 32 | impl Kernel<'_> { 33 | // Name must be without any repo name (e.g. core/linux) 34 | pub fn is_installed(&self) -> Option { 35 | let local_db = self.alpm_handle.as_ref()?.localdb(); 36 | Some(local_db.pkg(self.name.as_bytes()).is_ok()) 37 | } 38 | 39 | pub fn version(&self) -> Option { 40 | if !self.is_installed().unwrap() { 41 | return Some(self.alpm_pkg.as_ref()?.version().to_string()); 42 | } 43 | 44 | let local_db = self.alpm_handle.as_ref()?.localdb(); 45 | let local_pkg = local_db.pkg(self.name.as_bytes()); 46 | 47 | Some(local_pkg.ok()?.version().to_string()) 48 | } 49 | } 50 | 51 | /// Find kernel packages by finding packages which have words 'linux' and 'headers'. 52 | /// From the output of 'pacman -Sl' 53 | /// - find lines that have words: 'linux' and 'headers' 54 | /// - drop lines containing 'testing' (=testing repo, causes duplicates) and 'linux-api-headers' 55 | /// (=not a kernel header) 56 | /// - show the (header) package names 57 | /// Now we have names of the kernel headers. 58 | /// Then add the kernel packages to proper places and output the result. 59 | /// Then display possible kernels and headers added by the user. 60 | 61 | /// The output consists of a list of reponame and a package name formatted as: "reponame/pkgname" 62 | /// For example: 63 | /// reponame/linux-xxx reponame/linux-xxx-headers 64 | /// reponame/linux-yyy reponame/linux-yyy-headers 65 | /// ... 66 | pub fn get_kernels(alpm_handle: &alpm::Alpm) -> Vec { 67 | let mut kernels = Vec::new(); 68 | let needles: &[String] = &["linux-[a-z]".into(), "headers".into()]; 69 | // let needles: &[String] = &["linux[^ ]*-headers".into()]; 70 | 71 | for db in alpm_handle.syncdbs() { 72 | let db_name = db.name(); 73 | // search each database for packages matching the regex "linux-[a-z]" AND "headers" 74 | for pkg_headers in db.search(needles.iter()).unwrap() { 75 | let mut pkg_name = pkg_headers.name().to_owned(); 76 | if pkg_name.contains(IGNORED_PKG) { 77 | continue; 78 | } 79 | pkg_name = pkg_name.replace(REPLACE_PART, ""); 80 | 81 | // Skip if the actual kernel package is not found 82 | if let Ok(pkg) = db.pkg(pkg_name.as_bytes()) { 83 | kernels.push(Kernel { 84 | name: pkg_name.clone(), 85 | repo: db_name.to_string(), 86 | raw: format!("{}/{}", db_name, pkg_name), 87 | 88 | alpm_pkg: Some(pkg), 89 | alpm_handle: Some(alpm_handle), 90 | }); 91 | } 92 | } 93 | } 94 | 95 | kernels 96 | } 97 | -------------------------------------------------------------------------------- /src/hwd_misc.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023-2024 Vladislav Nepogodin 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License along 14 | // with this program; if not, write to the Free Software Foundation, Inc., 15 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 | 17 | pub fn get_sysfs_busid_from_amdgpu_path(amdgpu_path: &str) -> &str { 18 | amdgpu_path.split('/') 19 | // Extract the 7th element (amdgpu id) 20 | .nth(6) 21 | .unwrap_or_default() 22 | } 23 | 24 | // returns Vec of ( sysfs busid, formatted GC version ) 25 | pub fn get_gc_versions() -> Option> { 26 | use std::fs; 27 | 28 | let ip_match_paths = glob::glob("/sys/bus/pci/drivers/amdgpu/*/ip_discovery/die/*/GC/*/") 29 | .expect("Failed to read glob pattern"); 30 | 31 | let gc_versions = ip_match_paths 32 | .filter_map(Result::ok) 33 | .filter_map(|path| path.to_str().map(|s| s.to_owned())) 34 | .filter_map(|ip_match_path| { 35 | let sysfs_busid = get_sysfs_busid_from_amdgpu_path(&ip_match_path).to_owned(); 36 | 37 | let major = 38 | fs::read_to_string(format!("{ip_match_path}/major")).ok()?.trim().to_owned(); 39 | let minor = 40 | fs::read_to_string(format!("{ip_match_path}/minor")).ok()?.trim().to_owned(); 41 | let revision = 42 | fs::read_to_string(format!("{ip_match_path}/revision")).ok()?.trim().to_owned(); 43 | 44 | Some((sysfs_busid, format!("{major}.{minor}.{revision}"))) 45 | }) 46 | .collect::>(); 47 | 48 | // Correctly check for empty Vec: 49 | if gc_versions.is_empty() { 50 | None 51 | } else { 52 | Some(gc_versions) 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use crate::hwd_misc; 59 | 60 | #[test] 61 | fn gpu_from_amdgpu_path() { 62 | assert_eq!( 63 | hwd_misc::get_sysfs_busid_from_amdgpu_path( 64 | "/sys/bus/pci/drivers/amdgpu/0000:c2:00.0/ip_discovery/die/0/GC/0/" 65 | ), 66 | "0000:c2:00.0" 67 | ); 68 | assert_eq!( 69 | hwd_misc::get_sysfs_busid_from_amdgpu_path( 70 | "/sys/bus/pci/drivers/amdgpu/0000:c2:00.0/ip_discovery/die//" 71 | ), 72 | "0000:c2:00.0" 73 | ); 74 | assert_eq!( 75 | hwd_misc::get_sysfs_busid_from_amdgpu_path("/sys/bus/pci/drivers/amdgpu/0000:c2:00.0/"), 76 | "0000:c2:00.0" 77 | ); 78 | assert_eq!( 79 | hwd_misc::get_sysfs_busid_from_amdgpu_path( 80 | "/sys/bus/pci/drivers/amdgpu/0000:30:00.0/ip_discovery/die/0/GC/0" 81 | ), 82 | "0000:30:00.0" 83 | ); 84 | assert_eq!( 85 | hwd_misc::get_sysfs_busid_from_amdgpu_path( 86 | "/sys/bus/pci/drivers/amdgpu/0000:30:00.0/ip_discovery/die//" 87 | ), 88 | "0000:30:00.0" 89 | ); 90 | assert_eq!( 91 | hwd_misc::get_sysfs_busid_from_amdgpu_path("/sys/bus/pci/drivers/amdgpu/0000:30:00.0/"), 92 | "0000:30:00.0" 93 | ); 94 | assert_eq!( 95 | hwd_misc::get_sysfs_busid_from_amdgpu_path( 96 | "/sys/bus/pci/drivers/amdgpu/0000:04:00.0/ip_discovery/die/0/GC/0" 97 | ), 98 | "0000:04:00.0" 99 | ); 100 | assert_eq!( 101 | hwd_misc::get_sysfs_busid_from_amdgpu_path( 102 | "/sys/bus/pci/drivers/amdgpu/0000:04:00.0/ip_discovery/die//" 103 | ), 104 | "0000:04:00.0" 105 | ); 106 | assert_eq!( 107 | hwd_misc::get_sysfs_busid_from_amdgpu_path("/sys/bus/pci/drivers/amdgpu/0000:04:00.0/"), 108 | "0000:04:00.0" 109 | ); 110 | 111 | assert_eq!(hwd_misc::get_sysfs_busid_from_amdgpu_path("/sys/bus/pci/drivers/amdgpu/"), ""); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /scripts/intel-pci-ids-dumper/intel-pci-ids-dumper.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | --[[ 3 | Copyright (C) 2025 Vasiliy Stelmachenok for CachyOS 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program; if not, write to the Free Software Foundation, Inc., 17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | --]] 19 | 20 | local GENERATION_PATTERN = "^#define INTEL_([^_]+)_?.*_IDS.*" 21 | local DEVICE_ID_PATTERN = "^%s*MACRO__%(0x([0-9A-Za-z]+),.*" 22 | 23 | local function printf(msg, ...) 24 | print(msg:format(...)) 25 | end 26 | 27 | local function die(msg, ...) 28 | printf(msg, ...) 29 | os.exit(1) 30 | end 31 | 32 | local function parse(file) 33 | local line = file:read("*l") 34 | local gen, id 35 | local result = {} 36 | 37 | while line do 38 | if line == "" then 39 | gen = nil 40 | else 41 | if gen == nil then 42 | gen = line:match(GENERATION_PATTERN) 43 | 44 | if gen and not result[gen] then 45 | result[gen] = {} 46 | end 47 | else 48 | id = line:match(DEVICE_ID_PATTERN) 49 | 50 | if id then 51 | result[gen][#result[gen] + 1] = string.lower(id) 52 | end 53 | end 54 | end 55 | 56 | line = file:read("*l") 57 | end 58 | 59 | return result 60 | end 61 | 62 | local function help() 63 | print([[ 64 | Dump PCI IDs of Intel GPUs from pciids.h 65 | 66 | Usage: intel-pci-ids-dumper [OPTIONS] [GEN...] 67 | 68 | Arguments: 69 | [GEN] - name of GPU generation 70 | 71 | Options: 72 | -i, --input Input file with PCI IDs 73 | -o, --output Output file for PCI IDs of specified generations 74 | -h, --help Show this message 75 | ]] 76 | ) 77 | os.exit(0) 78 | end 79 | 80 | local function main() 81 | local input, output 82 | local gens = {} 83 | 84 | if #arg < 1 then 85 | help() 86 | end 87 | 88 | local i = 1 89 | while i < #arg + 1 do 90 | if arg[i] == "--input" or arg[i] == "-i" then 91 | input = arg[i + 1] 92 | i = i + 1 93 | elseif arg[i] == "--help" or arg[i] == "-h" then 94 | help() 95 | elseif arg[i] == "--output" or arg[i] == "-o" then 96 | output = arg[i + 1] 97 | if not output then 98 | die("Output file must be specified") 99 | end 100 | i = i + 1 101 | else 102 | gens[#gens + 1] = arg[i] 103 | end 104 | i = i + 1 105 | end 106 | 107 | if not input then 108 | die("Input file must be specified") 109 | end 110 | 111 | local file, errmsg = io.open(input, "r") 112 | 113 | if not file then 114 | die("Failed to open %s file: %s", input, errmsg) 115 | end 116 | 117 | local result = parse(file) 118 | file:close() 119 | 120 | if not next(result) then 121 | die("Specified file is not valid and does not contain PCI IDs") 122 | end 123 | 124 | local dump = {} 125 | 126 | if #gens > 0 then 127 | for _, gen in ipairs(gens) do 128 | if result[gen] then 129 | printf("Found %s ids for %s generation", #result[gen], gen) 130 | for _, id in pairs(result[gen]) do 131 | dump[#dump + 1] = id 132 | end 133 | else 134 | printf("No PCI IDs were found for %s generation", gen) 135 | end 136 | end 137 | else 138 | for gen, ids in pairs(result) do 139 | printf("Found %s ids for %s generation", #result[gen], gen) 140 | for _, id in pairs(ids) do 141 | dump[#dump + 1] = id 142 | end 143 | end 144 | end 145 | 146 | table.sort(dump) 147 | 148 | if output then 149 | file, errmsg = io.open(output, "w") 150 | 151 | if not file then 152 | die("Failed to open %s file: %s", output, errmsg) 153 | end 154 | 155 | printf("Saving %d ids to %s file", #dump, output) 156 | file:write(table.concat(dump, " ")) 157 | file:close() 158 | end 159 | end 160 | 161 | main() 162 | -------------------------------------------------------------------------------- /src/console_writer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023-2024 Vladislav Nepogodin 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License along 14 | // with this program; if not, write to the Free Software Foundation, Inc., 15 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 | 17 | use crate::data::Data; 18 | use crate::fl; 19 | use crate::misc::Message; 20 | use crate::profile::Profile; 21 | 22 | use comfy_table::modifiers::UTF8_ROUND_CORNERS; 23 | use comfy_table::presets::UTF8_FULL; 24 | use comfy_table::*; 25 | 26 | pub fn handle_arguments_listing(data: &Data, args: &crate::args::Args) { 27 | // Check for invalid profiles 28 | for invalid_profile in data.invalid_profiles.iter() { 29 | print_warn_msg!("invalid-profile", invalid_profile = invalid_profile.as_str()); 30 | } 31 | 32 | // List all profiles 33 | if args.list_all { 34 | let all_profiles = &data.all_profiles; 35 | if !all_profiles.is_empty() { 36 | list_profiles(all_profiles, &fl!("all-pci-profiles")); 37 | } else { 38 | print_warn_msg!("pci-profiles-not-found"); 39 | } 40 | } 41 | 42 | // List installed profiles 43 | if args.list_installed { 44 | let installed_profiles = &data.installed_profiles; 45 | if args.detail { 46 | print_installed_profiles(installed_profiles); 47 | } else if !installed_profiles.is_empty() { 48 | list_profiles(installed_profiles, &fl!("installed-pci-profiles")); 49 | } else { 50 | print_warn_msg!("no-installed-pci-profiles"); 51 | } 52 | } 53 | 54 | // List available profiles 55 | if args.list_available { 56 | let pci_devices = &data.pci_devices; 57 | if args.detail { 58 | crate::device_misc::print_available_profiles_in_detail(pci_devices); 59 | } else { 60 | for pci_device in pci_devices.iter() { 61 | let available_profiles = &pci_device.get_available_profiles(); 62 | if available_profiles.is_empty() { 63 | continue; 64 | } 65 | 66 | list_profiles( 67 | available_profiles, 68 | &format!( 69 | "{} ({}:{}:{}) {} {}:", 70 | pci_device.sysfs_busid, 71 | pci_device.class_id, 72 | pci_device.vendor_id, 73 | pci_device.device_id, 74 | pci_device.class_name, 75 | pci_device.vendor_name 76 | ), 77 | ); 78 | } 79 | } 80 | } 81 | } 82 | 83 | pub fn list_profiles(profiles: &[Profile], header_msg: &str) { 84 | log::info!("{header_msg}"); 85 | println!(); 86 | 87 | let mut table = Table::new(); 88 | table 89 | .load_preset(UTF8_FULL) 90 | .apply_modifier(UTF8_ROUND_CORNERS) 91 | .set_content_arrangement(ContentArrangement::Dynamic) 92 | .set_header(vec![&fl!("name-header"), &fl!("priority-header")]); 93 | 94 | for profile in profiles.iter() { 95 | table.add_row(vec![&profile.name, &profile.priority.to_string()]); 96 | } 97 | 98 | println!("{table}\n"); 99 | } 100 | 101 | pub fn print_installed_profiles(installed_profiles: &[Profile]) { 102 | if installed_profiles.is_empty() { 103 | print_warn_msg!("no-installed-profile-device"); 104 | return; 105 | } 106 | 107 | for profile in installed_profiles.iter() { 108 | crate::profile_misc::print_profile_details(profile); 109 | } 110 | println!(); 111 | } 112 | 113 | pub fn print_message(msg_type: Message, msg_str: &str) { 114 | match msg_type { 115 | Message::InstallStart => log::info!("Installing {msg_str} ..."), 116 | Message::InstallEnd => log::info!("Successfully installed {msg_str}"), 117 | Message::RemoveStart => log::info!("Removing {msg_str} ..."), 118 | Message::RemoveEnd => log::info!("Successfully removed {msg_str}"), 119 | } 120 | } 121 | 122 | #[macro_export] 123 | macro_rules! print_error_msg { 124 | ($message_id:literal) => {{ 125 | log::error!("{}", fl!($message_id)); 126 | }}; 127 | ($message_id:literal, $($args:expr),*) => {{ 128 | log::error!("{}", fl!($message_id, $($args), *)); 129 | }}; 130 | } 131 | 132 | #[macro_export] 133 | macro_rules! print_warn_msg { 134 | ($message_id:literal) => {{ 135 | log::warn!("{}", fl!($message_id)); 136 | }}; 137 | ($message_id:literal, $($args:expr),*) => {{ 138 | log::warn!("{}", fl!($message_id, $($args), *)); 139 | }}; 140 | } 141 | pub(crate) use {print_error_msg, print_warn_msg}; 142 | -------------------------------------------------------------------------------- /tests/chwd_spec.lua: -------------------------------------------------------------------------------- 1 | describe("Profile parsing", function() 2 | _G._TEST = true 3 | package.path = 'scripts/?;' .. package.path 4 | local chwd = require("chwd") 5 | 6 | describe("Valid cases", function() 7 | local profiles = chwd.parse_profiles("tests/profiles/graphic_drivers-profiles-test.toml") 8 | local name = "nvidia-dkms" 9 | 10 | it("Profiles are available", function() 11 | assert.are_not.same(profiles, {}) 12 | end) 13 | 14 | local profile = profiles[name] 15 | it("Search for profile", function() 16 | assert.truthy(profile) 17 | end) 18 | 19 | describe("Attributes", function() 20 | local packages, hooks = chwd.get_profile(profiles, name) 21 | it("Packages", function() 22 | assert.are.equals(packages, 23 | "nvidia-utils egl-wayland nvidia-settings opencl-nvidia lib32-opencl-nvidia lib32-nvidia-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader") 24 | end) 25 | it("Hooks", function() 26 | assert.truthy(hooks) 27 | end) 28 | it("Post remove hook", function() 29 | assert.are.equals(hooks['post_remove'], [[ 30 | rm -f /etc/mkinitcpio.conf.d/10-chwd.conf 31 | mkinitcpio -P 32 | ]]) 33 | end) 34 | it("Post install hook", function() 35 | assert.are.equals(hooks['post_install'], [[ 36 | cat </etc/mkinitcpio.conf.d/10-chwd.conf 37 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 38 | MODULES+=(nvidia nvidia_modeset nvidia_uvm nvidia_drm) 39 | EOF 40 | mkinitcpio -P 41 | ]]) 42 | end) 43 | it("Conditional packages hook", function() 44 | assert.are.equals(hooks['conditional_packages'], [[ 45 | kernels="$(pacman -Qqs "^linux-cachyos")" 46 | modules="" 47 | 48 | for kernel in $kernels; do 49 | case "$kernel" in 50 | *-headers|*-zfs);; 51 | *-nvidia) modules+=" ${kernel}";; 52 | *) modules+=" ${kernel}-nvidia";; 53 | esac 54 | done 55 | 56 | # Fallback if there are no kernels with pre-built modules 57 | [ -z "$modules" ] && modules="nvidia-dkms" 58 | 59 | echo "$modules" 60 | ]]) 61 | end) 62 | end) 63 | 64 | local child_name = "nvidia-dkms.40xxcards" 65 | local child_profile = profiles[child_name] 66 | it("Search for child profile", function() 67 | assert.truthy(child_profile) 68 | end) 69 | 70 | describe("Inheritance", function() 71 | local packages, hooks = chwd.get_profile(profiles, child_name) 72 | it("Inherit parent packages", function() 73 | assert.are.equals(packages, 74 | "nvidia-utils egl-wayland nvidia-settings opencl-nvidia lib32-opencl-nvidia lib32-nvidia-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader") 75 | end) 76 | it("Inherit some parent hook", function() 77 | assert.are.equals(hooks['post_remove'], [[ 78 | rm -f /etc/mkinitcpio.conf.d/10-chwd.conf 79 | mkinitcpio -P 80 | ]]) 81 | end) 82 | end) 83 | 84 | describe("Packages inspection", function() 85 | local lfs = require("lfs") 86 | 87 | local function search(path, t) 88 | for file in lfs.dir(path) do 89 | if file ~= "." and file ~= ".." then 90 | local f = path .. '/' .. file 91 | local attr = lfs.attributes(f) 92 | assert(type(attr) == "table") 93 | if attr.mode == "directory" then 94 | search(f, t) 95 | else 96 | if f:match('.toml$') then 97 | t[#t + 1] = f 98 | end 99 | end 100 | end 101 | end 102 | end 103 | 104 | local available_profiles = {} 105 | search("./profiles", available_profiles) 106 | 107 | it("Packages are available in repo", function() 108 | for _, file in ipairs(available_profiles) do 109 | local profiles = chwd.parse_profiles(file) 110 | for pname, _ in pairs(profiles) do 111 | local packages = chwd.get_profile(profiles, pname) 112 | print(string.format("Checking profile %s for available packages: %s...", pname, packages)) 113 | local _, _, exitcode = os.execute("pacman -Sp " .. packages .. " 1>/dev/null") 114 | assert.True(exitcode == 0) 115 | end 116 | end 117 | end) 118 | end) 119 | end) 120 | 121 | describe("Invalid cases", function() 122 | it("Profiles are not available", function() 123 | assert.are.same(chwd.parse_profiles("/dev/null"), {}) 124 | end) 125 | 126 | local profiles = chwd.parse_profiles("tests/profiles/graphic_drivers-invalid-profiles-test.toml") 127 | it("Non-existing profile", function() 128 | assert.is.falsy(profiles['unknown']) 129 | end) 130 | it("Unspecified packages", function() 131 | assert.is.falsy(profiles['invalid'].packages) 132 | end) 133 | end) 134 | end) 135 | -------------------------------------------------------------------------------- /profiles/pci/handhelds/profiles.toml: -------------------------------------------------------------------------------- 1 | [vangogh-steam-deck] 2 | desc = 'Valve Steam Deck' 3 | class_ids = "0300" 4 | vendor_ids = "1002" 5 | device_ids = "1435 163f" 6 | hwd_product_name_pattern = '(Jupiter|Galileo)' 7 | priority = 6 8 | packages = 'steamos-manager steamos-powerbuttond jupiter-fan-control steamdeck-dsp cachyos-handheld mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon opencl-mesa lib32-opencl-mesa' 9 | conditional_packages = """ 10 | if grep -q 'Galileo' /sys/devices/virtual/dmi/id/product_name; then 11 | echo 'galileo-mura' 12 | fi 13 | """ 14 | post_install = """ 15 | echo "Steam Deck chwd installing..." 16 | services=("jupiter-fan-control") 17 | echo "Enabling services..." 18 | for service in ${services[@]}; do 19 | systemctl enable --now "${service}.service" 20 | done 21 | 22 | if [ -d /etc/limine-entry-tool.d ]; then 23 | cat << 'EOF' > /etc/limine-entry-tool.d/20-chwd.conf 24 | KERNEL_CMDLINE[default]+="amd_iommu=off amdgpu.gttsize=8128 spi_amd.speed_dev=1 audit=0 iomem=relaxed amdgpu.ppfeaturemask=0xffffffff" 25 | EOF 26 | fi 27 | """ 28 | post_remove = """ 29 | echo "Steam Deck chwd removing..." 30 | services=("jupiter-fan-control") 31 | echo "Disabling services..." 32 | for service in ${services[@]}; do 33 | systemctl disable "${service}.service" 34 | done 35 | rm -f /etc/limine-entry-tool.d/20-chwd.conf 36 | """ 37 | 38 | [phoenix-rog-ally] 39 | desc = 'ASUS ROG Ally & Ally X' 40 | class_ids = "0300" 41 | vendor_ids = "1002" 42 | device_ids = "15bf 15c8" 43 | hwd_product_name_pattern = '(ROG Ally).*' 44 | priority = 6 45 | packages = 'steamos-manager steamos-powerbuttond inputplumber cachyos-handheld mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon opencl-mesa lib32-opencl-mesa' 46 | post_install = """ 47 | echo "Ally chwd installing..." 48 | echo "Installing audio profile..." 49 | product_name="$(cat /sys/devices/virtual/dmi/id/product_name)" 50 | mkdir -p /etc/pipewire/pipewire.conf.d /etc/wireplumber/wireplumber.conf.d/ 51 | ln -s /usr/share/cachyos-handheld/rog-ally/pipewire/filter-chain.conf \ 52 | /etc/pipewire/pipewire.conf.d 53 | if [[ "$product_name" =~ RC71L ]]; then 54 | ln -s /usr/share/cachyos-handheld/rog-ally/wireplumber/alsa-card0.conf \ 55 | /etc/wireplumber/wireplumber.conf.d 56 | else 57 | ln -s /usr/share/cachyos-handheld/rog-ally/wireplumber/alsa-card0-x.conf \ 58 | /etc/wireplumber/wireplumber.conf.d 59 | fi 60 | ln -s /usr/share/cachyos-handheld/common/wireplumber/alsa-card1.conf \ 61 | /etc/wireplumber/wireplumber.conf.d 62 | """ 63 | post_remove = """ 64 | echo "Ally chwd removing..." 65 | echo "Removing audio profile..." 66 | rm -f /etc/pipewire/pipewire.conf.d/filter-chain.conf 67 | rm -f /etc/wireplumber/wireplumber.conf.d/alsa-card0{,-x}.conf 68 | rm -f /etc/wireplumber/wireplumber.conf.d/alsa-card1.conf 69 | """ 70 | 71 | [phoenix-legion-go] 72 | desc = 'Lenovo Legion Go' 73 | class_ids = "0300" 74 | vendor_ids = "1002" 75 | device_ids = "15bf" 76 | hwd_product_name_pattern = '(83E1)' 77 | priority = 6 78 | packages = 'hhd hhd-ui adjustor cachyos-handheld mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon opencl-mesa lib32-opencl-mesa' 79 | post_install = """ 80 | echo "Legion go chwd installing..." 81 | username=$(id -nu 1000) 82 | services=("hhd@${username}") 83 | echo "Enabling services..." 84 | for service in ${services[@]}; do 85 | systemctl enable --now "${service}.service" 86 | done 87 | 88 | if [ -d /etc/limine-entry-tool.d ]; then 89 | cat << 'EOF' > /etc/limine-entry-tool.d/20-chwd.conf 90 | KERNEL_CMDLINE[default]+="amdgpu.sg_display=0" 91 | EOF 92 | fi 93 | """ 94 | post_remove = """ 95 | echo "Legion go chwd removing..." 96 | username=$(id -nu 1000) 97 | services=("hhd@${username}") 98 | echo "Disabling services..." 99 | for service in ${services[@]}; do 100 | systemctl disable "${service}.service" 101 | done 102 | rm -f /etc/limine-entry-tool.d/20-chwd.conf 103 | """ 104 | 105 | [amd-legion-go-s] 106 | desc = 'Lenovo Legion Go S' 107 | class_ids = "0300" 108 | vendor_ids = "1002" 109 | device_ids = "1681 15bf" 110 | hwd_product_name_pattern = '(83L3|83N6|83Q2|83Q3)' 111 | priority = 6 112 | packages = 'inputplumber steamos-manager steamos-powerbuttond cachyos-handheld mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon opencl-mesa lib32-opencl-mesa' 113 | post_install = """ 114 | if [ -d /etc/limine-entry-tool.d ]; then 115 | cat << 'EOF' > /etc/limine-entry-tool.d/20-chwd.conf 116 | KERNEL_CMDLINE[default]+="amdgpu.sg_display=0" 117 | EOF 118 | fi 119 | """ 120 | post_remove = """ 121 | rm -f /etc/limine-entry-tool.d/20-chwd.conf 122 | """ 123 | 124 | [amd-xbox-rog-ally] 125 | desc = 'ASUS XBox ROG Ally & Ally X' 126 | class_ids = "0300 0380" 127 | vendor_ids = "1002" 128 | device_ids = "163f 150e" 129 | hwd_product_name_pattern = '(RC73YA|RC73XA)' 130 | priority = 6 131 | packages = 'steamos-manager steamos-powerbuttond inputplumber cachyos-handheld mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon opencl-mesa lib32-opencl-mesa' 132 | 133 | [intel-msi-claw] 134 | desc = 'MSI Claw Intel' 135 | class_ids = "0300" 136 | vendor_ids = "8086" 137 | device_ids = "64a0 7d55" 138 | hwd_product_name_pattern = '(A2VM|A1M)' 139 | priority = 6 140 | packages = 'inputplumber steamos-manager steamos-powerbuttond cachyos-handheld mesa lib32-mesa vulkan-intel lib32-vulkan-intel intel-media-driver opencl-mesa lib32-opencl-mesa' 141 | post_install = """ 142 | echo "Claw chwd installing..." 143 | echo msi-wmi-platform > /usr/lib/modules-load.d/chwd-msi-claw.conf 144 | echo "Installing audio profile..." 145 | mkdir -p /etc/wireplumber/wireplumber.conf.d 146 | ln -s /usr/share/cachyos-handheld/msi-claw/wireplumber/alsa-card0.conf \ 147 | /etc/wireplumber/wireplumber.conf.d 148 | echo "Installing OpenCL profile..." 149 | echo "export RUSTICL_ENABLE=iris" > /etc/profile.d/opencl.sh 150 | mkdir -p /etc/environment.d 151 | echo "RUSTICL_ENABLE=iris" > /etc/environment.d/30-opencl.conf 152 | """ 153 | post_remove = """ 154 | echo "Claw chwd removing..." 155 | rm -f /usr/lib/modules-load.d/chwd-msi-claw.conf 156 | echo "Removing audio profile..." 157 | rm -f /etc/wireplumber/wireplumber.conf.d/alsa-card0.conf 158 | echo "Removing Opencl profile..." 159 | rm -f /etc/profile.d/opencl.sh 160 | rm -f /etc/environment.d/30-opencl.conf 161 | """ 162 | -------------------------------------------------------------------------------- /libpci-sys/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "bindgen" 16 | version = "0.72.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" 19 | dependencies = [ 20 | "bitflags", 21 | "cexpr", 22 | "clang-sys", 23 | "itertools", 24 | "proc-macro2", 25 | "quote", 26 | "regex", 27 | "rustc-hash", 28 | "shlex", 29 | "syn", 30 | ] 31 | 32 | [[package]] 33 | name = "bitflags" 34 | version = "2.10.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 37 | 38 | [[package]] 39 | name = "cc" 40 | version = "1.2.49" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" 43 | dependencies = [ 44 | "find-msvc-tools", 45 | "shlex", 46 | ] 47 | 48 | [[package]] 49 | name = "cexpr" 50 | version = "0.6.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 53 | dependencies = [ 54 | "nom", 55 | ] 56 | 57 | [[package]] 58 | name = "cfg-if" 59 | version = "1.0.4" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 62 | 63 | [[package]] 64 | name = "clang-sys" 65 | version = "1.8.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 68 | dependencies = [ 69 | "glob", 70 | "libc", 71 | "libloading", 72 | ] 73 | 74 | [[package]] 75 | name = "either" 76 | version = "1.15.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 79 | 80 | [[package]] 81 | name = "find-msvc-tools" 82 | version = "0.1.5" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 85 | 86 | [[package]] 87 | name = "glob" 88 | version = "0.3.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 91 | 92 | [[package]] 93 | name = "itertools" 94 | version = "0.13.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 97 | dependencies = [ 98 | "either", 99 | ] 100 | 101 | [[package]] 102 | name = "libc" 103 | version = "0.2.178" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" 106 | 107 | [[package]] 108 | name = "libloading" 109 | version = "0.8.9" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" 112 | dependencies = [ 113 | "cfg-if", 114 | "windows-link", 115 | ] 116 | 117 | [[package]] 118 | name = "libpci-c-sys" 119 | version = "0.1.3" 120 | dependencies = [ 121 | "bindgen", 122 | "cc", 123 | "libc", 124 | ] 125 | 126 | [[package]] 127 | name = "memchr" 128 | version = "2.7.6" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 131 | 132 | [[package]] 133 | name = "minimal-lexical" 134 | version = "0.2.1" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 137 | 138 | [[package]] 139 | name = "nom" 140 | version = "7.1.3" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 143 | dependencies = [ 144 | "memchr", 145 | "minimal-lexical", 146 | ] 147 | 148 | [[package]] 149 | name = "proc-macro2" 150 | version = "1.0.103" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 153 | dependencies = [ 154 | "unicode-ident", 155 | ] 156 | 157 | [[package]] 158 | name = "quote" 159 | version = "1.0.42" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 162 | dependencies = [ 163 | "proc-macro2", 164 | ] 165 | 166 | [[package]] 167 | name = "regex" 168 | version = "1.12.2" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 171 | dependencies = [ 172 | "aho-corasick", 173 | "memchr", 174 | "regex-automata", 175 | "regex-syntax", 176 | ] 177 | 178 | [[package]] 179 | name = "regex-automata" 180 | version = "0.4.13" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 183 | dependencies = [ 184 | "aho-corasick", 185 | "memchr", 186 | "regex-syntax", 187 | ] 188 | 189 | [[package]] 190 | name = "regex-syntax" 191 | version = "0.8.8" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 194 | 195 | [[package]] 196 | name = "rustc-hash" 197 | version = "2.1.1" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 200 | 201 | [[package]] 202 | name = "shlex" 203 | version = "1.3.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 206 | 207 | [[package]] 208 | name = "syn" 209 | version = "2.0.111" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" 212 | dependencies = [ 213 | "proc-macro2", 214 | "quote", 215 | "unicode-ident", 216 | ] 217 | 218 | [[package]] 219 | name = "unicode-ident" 220 | version = "1.0.22" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 223 | 224 | [[package]] 225 | name = "windows-link" 226 | version = "0.2.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 229 | -------------------------------------------------------------------------------- /libpci/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "bindgen" 16 | version = "0.72.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" 19 | dependencies = [ 20 | "bitflags", 21 | "cexpr", 22 | "clang-sys", 23 | "itertools", 24 | "proc-macro2", 25 | "quote", 26 | "regex", 27 | "rustc-hash", 28 | "shlex", 29 | "syn", 30 | ] 31 | 32 | [[package]] 33 | name = "bitflags" 34 | version = "2.10.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 37 | 38 | [[package]] 39 | name = "cc" 40 | version = "1.2.49" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" 43 | dependencies = [ 44 | "find-msvc-tools", 45 | "shlex", 46 | ] 47 | 48 | [[package]] 49 | name = "cexpr" 50 | version = "0.6.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 53 | dependencies = [ 54 | "nom", 55 | ] 56 | 57 | [[package]] 58 | name = "cfg-if" 59 | version = "1.0.4" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 62 | 63 | [[package]] 64 | name = "clang-sys" 65 | version = "1.8.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 68 | dependencies = [ 69 | "glob", 70 | "libc", 71 | "libloading", 72 | ] 73 | 74 | [[package]] 75 | name = "either" 76 | version = "1.15.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 79 | 80 | [[package]] 81 | name = "find-msvc-tools" 82 | version = "0.1.5" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 85 | 86 | [[package]] 87 | name = "glob" 88 | version = "0.3.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 91 | 92 | [[package]] 93 | name = "itertools" 94 | version = "0.13.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 97 | dependencies = [ 98 | "either", 99 | ] 100 | 101 | [[package]] 102 | name = "libc" 103 | version = "0.2.178" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" 106 | 107 | [[package]] 108 | name = "libloading" 109 | version = "0.8.9" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" 112 | dependencies = [ 113 | "cfg-if", 114 | "windows-link", 115 | ] 116 | 117 | [[package]] 118 | name = "libpci" 119 | version = "0.1.3" 120 | dependencies = [ 121 | "libc", 122 | "libpci-c-sys", 123 | ] 124 | 125 | [[package]] 126 | name = "libpci-c-sys" 127 | version = "0.1.3" 128 | dependencies = [ 129 | "bindgen", 130 | "cc", 131 | "libc", 132 | ] 133 | 134 | [[package]] 135 | name = "memchr" 136 | version = "2.7.6" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 139 | 140 | [[package]] 141 | name = "minimal-lexical" 142 | version = "0.2.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 145 | 146 | [[package]] 147 | name = "nom" 148 | version = "7.1.3" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 151 | dependencies = [ 152 | "memchr", 153 | "minimal-lexical", 154 | ] 155 | 156 | [[package]] 157 | name = "proc-macro2" 158 | version = "1.0.103" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 161 | dependencies = [ 162 | "unicode-ident", 163 | ] 164 | 165 | [[package]] 166 | name = "quote" 167 | version = "1.0.42" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 170 | dependencies = [ 171 | "proc-macro2", 172 | ] 173 | 174 | [[package]] 175 | name = "regex" 176 | version = "1.12.2" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 179 | dependencies = [ 180 | "aho-corasick", 181 | "memchr", 182 | "regex-automata", 183 | "regex-syntax", 184 | ] 185 | 186 | [[package]] 187 | name = "regex-automata" 188 | version = "0.4.13" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 191 | dependencies = [ 192 | "aho-corasick", 193 | "memchr", 194 | "regex-syntax", 195 | ] 196 | 197 | [[package]] 198 | name = "regex-syntax" 199 | version = "0.8.8" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 202 | 203 | [[package]] 204 | name = "rustc-hash" 205 | version = "2.1.1" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 208 | 209 | [[package]] 210 | name = "shlex" 211 | version = "1.3.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 214 | 215 | [[package]] 216 | name = "syn" 217 | version = "2.0.111" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" 220 | dependencies = [ 221 | "proc-macro2", 222 | "quote", 223 | "unicode-ident", 224 | ] 225 | 226 | [[package]] 227 | name = "unicode-ident" 228 | version = "1.0.22" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 231 | 232 | [[package]] 233 | name = "windows-link" 234 | version = "0.2.1" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 237 | -------------------------------------------------------------------------------- /scripts/chwd-kernel/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022-2024 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS chwd. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | mod kernel; 20 | 21 | use clap::Parser; 22 | use dialoguer::Confirm; 23 | use itertools::Itertools; 24 | use subprocess::{Exec, Redirection}; 25 | 26 | #[derive(Parser, Debug)] 27 | #[command(author, version, about, long_about = None)] 28 | #[command(arg_required_else_help(true))] 29 | struct Args { 30 | /// List available kernels 31 | #[arg(long)] 32 | list: bool, 33 | 34 | /// List installed kernels 35 | #[arg(long)] 36 | list_installed: bool, 37 | 38 | /// Show running kernel 39 | #[arg(long)] 40 | running_kernel: bool, 41 | 42 | /// Install kernels 43 | #[arg(short, long = "install", conflicts_with("remove_kernels"))] 44 | install_kernels: Vec, 45 | 46 | /// Uninstall kernels 47 | #[arg(short, long = "remove", conflicts_with("install_kernels"))] 48 | remove_kernels: Vec, 49 | } 50 | 51 | enum WorkingMode { 52 | KernelInstall, 53 | KernelRemove, 54 | } 55 | 56 | fn new_alpm() -> alpm::Result { 57 | let pacman = pacmanconf::Config::with_opts(None, Some("/etc/pacman.conf"), Some("/")).unwrap(); 58 | let alpm = alpm_utils::alpm_with_conf(&pacman)?; 59 | 60 | Ok(alpm) 61 | } 62 | 63 | fn simple_shell_exec(cmd: &str) -> String { 64 | let mut exec_out = Exec::shell(cmd).stdout(Redirection::Pipe).capture().unwrap().stdout_str(); 65 | exec_out.pop(); 66 | exec_out 67 | } 68 | 69 | #[inline] 70 | fn get_kernel_running() -> String { 71 | simple_shell_exec( 72 | r#"(grep -Po '(?<=initrd\=\\initramfs-)(.+)(?=\.img)|(?<=boot\/vmlinuz-)([^ $]+)' /proc/cmdline)"#, 73 | ) 74 | } 75 | 76 | fn root_check() { 77 | if nix::unistd::geteuid().is_root() { 78 | return; 79 | } 80 | occur_err("Please run as root."); 81 | } 82 | 83 | fn occur_err(msg: &str) { 84 | eprintln!("\x1B[31mError:\x1B[0m {}", msg); 85 | std::process::exit(1); 86 | } 87 | 88 | fn show_installed_kernels(kernels: &[kernel::Kernel]) { 89 | let current_kernel = get_kernel_running(); 90 | println!( 91 | "\x1B[32mCurrently running:\x1B[0m {} ({})", 92 | simple_shell_exec("uname -r"), 93 | current_kernel 94 | ); 95 | println!("The following kernels are installed in your system:"); 96 | for kernel in kernels.iter().unique_by(|p| &p.name) { 97 | if !kernel.is_installed().unwrap() { 98 | continue; 99 | } 100 | println!("local/{} {}", kernel.name, kernel.version().unwrap()); 101 | } 102 | } 103 | 104 | fn show_available_kernels(kernels: &[kernel::Kernel]) { 105 | println!("\x1B[32mavailable kernels:\x1B[0m"); 106 | for kernel in kernels { 107 | println!("{} {}", kernel.raw, kernel.version().unwrap()); 108 | } 109 | } 110 | 111 | fn kernel_install(available_kernels: &[kernel::Kernel], kernel_names: &[String]) -> bool { 112 | let mut pkginstall = String::new(); 113 | let mut rmc = false; 114 | 115 | let current_kernel = get_kernel_running(); 116 | for kernel_name in kernel_names { 117 | if kernel_name == "rmc" { 118 | rmc = true; 119 | continue; 120 | } else if ¤t_kernel == kernel_name { 121 | occur_err( 122 | "You can't reinstall your current kernel. Please use 'pacman -Syu' instead to \ 123 | update.", 124 | ); 125 | } else if available_kernels.iter().all(|elem| &elem.name != kernel_name) { 126 | eprintln!("\x1B[31mError:\x1B[0m Please make sure if the given kernel(s) exist(s)."); 127 | show_available_kernels(available_kernels); 128 | return false; 129 | } 130 | 131 | pkginstall.push_str(&format!("{} ", &kernel_name)); 132 | } 133 | let _ = Exec::shell("pacman -Syy").join(); 134 | 135 | let outofdate = simple_shell_exec("pacman -Qqu | tr '\n' ' '"); 136 | if !outofdate.is_empty() { 137 | eprintln!( 138 | "The following packages are out of date, please update your system first: {}", 139 | outofdate 140 | ); 141 | if !Confirm::new() 142 | .with_prompt("Do you want to continue anyway?") 143 | .default(true) 144 | .interact() 145 | .unwrap() 146 | { 147 | return false; 148 | } 149 | } 150 | 151 | let exit_status = Exec::shell(format!("pacman -Syu {}", pkginstall)).join().unwrap(); 152 | if rmc && exit_status.success() { 153 | let _ = Exec::shell(format!("pacman -R {}", current_kernel)).join(); 154 | } else if rmc && !exit_status.success() { 155 | occur_err("\n'rmc' aborted because the kernel failed to install or canceled on removal."); 156 | } 157 | true 158 | } 159 | 160 | fn kernel_remove(available_kernels: &[kernel::Kernel], kernel_names: &[String]) -> bool { 161 | let mut pkgremove = String::new(); 162 | 163 | let current_kernel = get_kernel_running(); 164 | for kernel_name in kernel_names { 165 | if ¤t_kernel == kernel_name { 166 | occur_err("You can't remove your current kernel."); 167 | } else if !available_kernels 168 | .iter() 169 | .any(|elem| elem.is_installed().unwrap() && (&elem.name == kernel_name)) 170 | { 171 | eprintln!("\x1B[31mError:\x1B[0m Kernel is not installed."); 172 | show_installed_kernels(available_kernels); 173 | return false; 174 | } 175 | 176 | pkgremove.push_str(&format!("{} ", kernel_name)); 177 | } 178 | 179 | let exit_status = Exec::shell(format!("pacman -R {}", pkgremove)).join().unwrap(); 180 | exit_status.success() 181 | } 182 | 183 | fn init_alpm_handle() -> alpm::Alpm { 184 | new_alpm().expect("Unable to initialize Alpm") 185 | } 186 | 187 | fn main() { 188 | let args = Args::parse(); 189 | 190 | if args.list { 191 | let alpm_handle = init_alpm_handle(); 192 | let kernels = kernel::get_kernels(&alpm_handle); 193 | show_available_kernels(&kernels); 194 | return; 195 | } 196 | 197 | if args.list_installed { 198 | let alpm_handle = init_alpm_handle(); 199 | let kernels = kernel::get_kernels(&alpm_handle); 200 | show_installed_kernels(&kernels); 201 | return; 202 | } 203 | 204 | if args.running_kernel { 205 | println!("\x1B[32mrunning kernel:\x1B[0m '{}'", get_kernel_running()); 206 | return; 207 | } 208 | 209 | let working_mode = if !args.install_kernels.is_empty() { 210 | Some(WorkingMode::KernelInstall) 211 | } else if !args.remove_kernels.is_empty() { 212 | Some(WorkingMode::KernelRemove) 213 | } else { 214 | None 215 | }; 216 | 217 | match working_mode { 218 | Some(WorkingMode::KernelInstall) => { 219 | root_check(); 220 | 221 | let alpm_handle = init_alpm_handle(); 222 | let kernels = kernel::get_kernels(&alpm_handle); 223 | kernel_install(&kernels, &args.install_kernels); 224 | }, 225 | Some(WorkingMode::KernelRemove) => { 226 | root_check(); 227 | 228 | let alpm_handle = init_alpm_handle(); 229 | let kernels = kernel::get_kernels(&alpm_handle); 230 | kernel_remove(&kernels, &args.remove_kernels); 231 | }, 232 | _ => occur_err("Invalid argument (use -h for help)."), 233 | }; 234 | } 235 | -------------------------------------------------------------------------------- /scripts/chwd-kernel/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "alpm" 7 | version = "5.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "65194002f3ee42412a21c9a4edf2de285cc0680575bcc7f026e698b52784fa95" 10 | dependencies = [ 11 | "alpm-sys", 12 | "bitflags", 13 | ] 14 | 15 | [[package]] 16 | name = "alpm-sys" 17 | version = "5.0.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "7364aaf7673e679a2616e26c78af26b922b69afeba965333386f7d0400daf3a3" 20 | dependencies = [ 21 | "pkg-config", 22 | ] 23 | 24 | [[package]] 25 | name = "alpm-utils" 26 | version = "5.0.0" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "a59a6ffb120311a82dd5569a3bfa0098db8a1c1d8b5ec568e97b61cff3b10729" 29 | dependencies = [ 30 | "alpm", 31 | "pacmanconf", 32 | ] 33 | 34 | [[package]] 35 | name = "anstream" 36 | version = "0.6.21" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 39 | dependencies = [ 40 | "anstyle", 41 | "anstyle-parse", 42 | "anstyle-query", 43 | "anstyle-wincon", 44 | "colorchoice", 45 | "is_terminal_polyfill", 46 | "utf8parse", 47 | ] 48 | 49 | [[package]] 50 | name = "anstyle" 51 | version = "1.0.13" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 54 | 55 | [[package]] 56 | name = "anstyle-parse" 57 | version = "0.2.7" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 60 | dependencies = [ 61 | "utf8parse", 62 | ] 63 | 64 | [[package]] 65 | name = "anstyle-query" 66 | version = "1.1.5" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 69 | dependencies = [ 70 | "windows-sys", 71 | ] 72 | 73 | [[package]] 74 | name = "anstyle-wincon" 75 | version = "3.0.11" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 78 | dependencies = [ 79 | "anstyle", 80 | "once_cell_polyfill", 81 | "windows-sys", 82 | ] 83 | 84 | [[package]] 85 | name = "bitflags" 86 | version = "2.10.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 89 | 90 | [[package]] 91 | name = "cfg-if" 92 | version = "1.0.4" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 95 | 96 | [[package]] 97 | name = "cfg_aliases" 98 | version = "0.2.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 101 | 102 | [[package]] 103 | name = "chwd-kernel" 104 | version = "0.1.0" 105 | dependencies = [ 106 | "alpm", 107 | "alpm-utils", 108 | "clap", 109 | "dialoguer", 110 | "itertools", 111 | "nix", 112 | "pacmanconf", 113 | "subprocess", 114 | ] 115 | 116 | [[package]] 117 | name = "cini" 118 | version = "1.0.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "8628d1f5b9a7b1196ce1aa660e3ba7e2559d350649cbe94993519c127df667f2" 121 | 122 | [[package]] 123 | name = "clap" 124 | version = "4.5.53" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" 127 | dependencies = [ 128 | "clap_builder", 129 | "clap_derive", 130 | ] 131 | 132 | [[package]] 133 | name = "clap_builder" 134 | version = "4.5.53" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" 137 | dependencies = [ 138 | "anstream", 139 | "anstyle", 140 | "clap_lex", 141 | "strsim", 142 | ] 143 | 144 | [[package]] 145 | name = "clap_derive" 146 | version = "4.5.49" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 149 | dependencies = [ 150 | "heck", 151 | "proc-macro2", 152 | "quote", 153 | "syn", 154 | ] 155 | 156 | [[package]] 157 | name = "clap_lex" 158 | version = "0.7.6" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" 161 | 162 | [[package]] 163 | name = "colorchoice" 164 | version = "1.0.4" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 167 | 168 | [[package]] 169 | name = "console" 170 | version = "0.16.1" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" 173 | dependencies = [ 174 | "encode_unicode", 175 | "libc", 176 | "once_cell", 177 | "unicode-width", 178 | "windows-sys", 179 | ] 180 | 181 | [[package]] 182 | name = "dialoguer" 183 | version = "0.12.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" 186 | dependencies = [ 187 | "console", 188 | "shell-words", 189 | ] 190 | 191 | [[package]] 192 | name = "either" 193 | version = "1.15.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 196 | 197 | [[package]] 198 | name = "encode_unicode" 199 | version = "1.0.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 202 | 203 | [[package]] 204 | name = "heck" 205 | version = "0.5.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 208 | 209 | [[package]] 210 | name = "is_terminal_polyfill" 211 | version = "1.70.2" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 214 | 215 | [[package]] 216 | name = "itertools" 217 | version = "0.14.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 220 | dependencies = [ 221 | "either", 222 | ] 223 | 224 | [[package]] 225 | name = "libc" 226 | version = "0.2.178" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" 229 | 230 | [[package]] 231 | name = "nix" 232 | version = "0.30.1" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" 235 | dependencies = [ 236 | "bitflags", 237 | "cfg-if", 238 | "cfg_aliases", 239 | "libc", 240 | ] 241 | 242 | [[package]] 243 | name = "once_cell" 244 | version = "1.21.3" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 247 | 248 | [[package]] 249 | name = "once_cell_polyfill" 250 | version = "1.70.2" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 253 | 254 | [[package]] 255 | name = "pacmanconf" 256 | version = "3.1.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "1087f8994e545eed9f7453376282f2964f18ca4b739e42f0dc7f2fed246d76c3" 259 | dependencies = [ 260 | "cini", 261 | ] 262 | 263 | [[package]] 264 | name = "pkg-config" 265 | version = "0.3.32" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 268 | 269 | [[package]] 270 | name = "proc-macro2" 271 | version = "1.0.103" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 274 | dependencies = [ 275 | "unicode-ident", 276 | ] 277 | 278 | [[package]] 279 | name = "quote" 280 | version = "1.0.42" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 283 | dependencies = [ 284 | "proc-macro2", 285 | ] 286 | 287 | [[package]] 288 | name = "shell-words" 289 | version = "1.1.1" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" 292 | 293 | [[package]] 294 | name = "strsim" 295 | version = "0.11.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 298 | 299 | [[package]] 300 | name = "subprocess" 301 | version = "0.2.9" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" 304 | dependencies = [ 305 | "libc", 306 | "winapi", 307 | ] 308 | 309 | [[package]] 310 | name = "syn" 311 | version = "2.0.111" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" 314 | dependencies = [ 315 | "proc-macro2", 316 | "quote", 317 | "unicode-ident", 318 | ] 319 | 320 | [[package]] 321 | name = "unicode-ident" 322 | version = "1.0.22" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 325 | 326 | [[package]] 327 | name = "unicode-width" 328 | version = "0.2.2" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" 331 | 332 | [[package]] 333 | name = "utf8parse" 334 | version = "0.2.2" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 337 | 338 | [[package]] 339 | name = "winapi" 340 | version = "0.3.9" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 343 | dependencies = [ 344 | "winapi-i686-pc-windows-gnu", 345 | "winapi-x86_64-pc-windows-gnu", 346 | ] 347 | 348 | [[package]] 349 | name = "winapi-i686-pc-windows-gnu" 350 | version = "0.4.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 353 | 354 | [[package]] 355 | name = "winapi-x86_64-pc-windows-gnu" 356 | version = "0.4.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 359 | 360 | [[package]] 361 | name = "windows-link" 362 | version = "0.2.1" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 365 | 366 | [[package]] 367 | name = "windows-sys" 368 | version = "0.61.2" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 371 | dependencies = [ 372 | "windows-link", 373 | ] 374 | -------------------------------------------------------------------------------- /libpci/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[cfg(feature = "std")] 4 | extern crate std; 5 | 6 | // Re-export libpci-c-sys 7 | pub use libpci_c_sys; 8 | 9 | #[cfg(feature = "std")] 10 | use std::os::raw::c_char; 11 | 12 | #[cfg(not(feature = "std"))] 13 | use libc::c_char; 14 | 15 | #[cfg(not(feature = "std"))] 16 | #[macro_use] 17 | extern crate alloc; 18 | 19 | use alloc::string::String; 20 | use core::marker::PhantomData; 21 | use core::{mem, ptr, str}; 22 | 23 | /// Returns the LIBPCI version. 24 | pub fn version_number() -> u32 { 25 | libpci_c_sys::PCI_LIB_VERSION as u32 26 | } 27 | 28 | #[repr(u32)] 29 | #[derive(Clone, PartialEq)] 30 | pub enum Fill { 31 | IDENT = libpci_c_sys::PCI_FILL_IDENT as u32, 32 | IRQ = libpci_c_sys::PCI_FILL_IRQ as u32, 33 | BASES = libpci_c_sys::PCI_FILL_BASES as u32, 34 | RomBase = libpci_c_sys::PCI_FILL_ROM_BASE as u32, 35 | SIZES = libpci_c_sys::PCI_FILL_SIZES as u32, 36 | CLASS = libpci_c_sys::PCI_FILL_CLASS as u32, 37 | CAPS = libpci_c_sys::PCI_FILL_CAPS as u32, 38 | ExtCaps = libpci_c_sys::PCI_FILL_EXT_CAPS as u32, 39 | PhysSlot = libpci_c_sys::PCI_FILL_PHYS_SLOT as u32, 40 | ModuleAlias = libpci_c_sys::PCI_FILL_MODULE_ALIAS as u32, 41 | LABEL = libpci_c_sys::PCI_FILL_LABEL as u32, 42 | NumaNode = libpci_c_sys::PCI_FILL_NUMA_NODE as u32, 43 | IoFlags = libpci_c_sys::PCI_FILL_IO_FLAGS as u32, 44 | DtNode = libpci_c_sys::PCI_FILL_DT_NODE as u32, 45 | IommuGroup = libpci_c_sys::PCI_FILL_IOMMU_GROUP as u32, 46 | BridgeBases = libpci_c_sys::PCI_FILL_BRIDGE_BASES as u32, 47 | RESCAN = libpci_c_sys::PCI_FILL_RESCAN as u32, 48 | ClassExt = libpci_c_sys::PCI_FILL_CLASS_EXT as u32, 49 | SUBSYS = libpci_c_sys::PCI_FILL_SUBSYS as u32, 50 | PARENT = libpci_c_sys::PCI_FILL_PARENT as u32, 51 | DRIVER = libpci_c_sys::PCI_FILL_DRIVER as u32, 52 | } 53 | 54 | #[repr(u32)] 55 | #[derive(Clone, PartialEq)] 56 | pub enum AccessType { 57 | Auto = libpci_c_sys::pci_access_type_PCI_ACCESS_AUTO, 58 | SysBusPci = libpci_c_sys::pci_access_type_PCI_ACCESS_SYS_BUS_PCI, 59 | ProcBusPci = libpci_c_sys::pci_access_type_PCI_ACCESS_PROC_BUS_PCI, 60 | I386Type1 = libpci_c_sys::pci_access_type_PCI_ACCESS_I386_TYPE1, 61 | I386Type2 = libpci_c_sys::pci_access_type_PCI_ACCESS_I386_TYPE2, 62 | FbsdDevice = libpci_c_sys::pci_access_type_PCI_ACCESS_FBSD_DEVICE, 63 | AixDevice = libpci_c_sys::pci_access_type_PCI_ACCESS_AIX_DEVICE, 64 | NbsdLibpci = libpci_c_sys::pci_access_type_PCI_ACCESS_NBSD_LIBPCI, 65 | ObsdDevice = libpci_c_sys::pci_access_type_PCI_ACCESS_OBSD_DEVICE, 66 | Dump = libpci_c_sys::pci_access_type_PCI_ACCESS_DUMP, 67 | Darwin = libpci_c_sys::pci_access_type_PCI_ACCESS_DARWIN, 68 | SylixosDevice = libpci_c_sys::pci_access_type_PCI_ACCESS_SYLIXOS_DEVICE, 69 | HURD = libpci_c_sys::pci_access_type_PCI_ACCESS_HURD, 70 | Win32Cfgmgr32 = libpci_c_sys::pci_access_type_PCI_ACCESS_WIN32_CFGMGR32, 71 | Win32Kldbg = libpci_c_sys::pci_access_type_PCI_ACCESS_WIN32_KLDBG, 72 | Win32Sysdbg = libpci_c_sys::pci_access_type_PCI_ACCESS_WIN32_SYSDBG, 73 | MmioType1 = libpci_c_sys::pci_access_type_PCI_ACCESS_MMIO_TYPE1, 74 | Type1Ext = libpci_c_sys::pci_access_type_PCI_ACCESS_MMIO_TYPE1_EXT, 75 | Max = libpci_c_sys::pci_access_type_PCI_ACCESS_MAX, 76 | } 77 | 78 | impl From for AccessType { 79 | fn from(value: u32) -> Self { 80 | if value > AccessType::Max as u32 || value < AccessType::Auto as u32 { 81 | return AccessType::Auto; 82 | } 83 | unsafe { mem::transmute(value) } 84 | } 85 | } 86 | 87 | /// PCI access structure. 88 | #[derive(Clone, Debug)] 89 | pub struct PCIAccess<'a> { 90 | handle: *mut libpci_c_sys::pci_access, 91 | _phantom: PhantomData<&'a ()>, 92 | } 93 | 94 | /// Holds device data found on this bus. 95 | pub struct PCIDevice<'a>(*mut libpci_c_sys::pci_dev, PhantomData<&'a ()>); 96 | 97 | unsafe impl<'a> Send for PCIAccess<'a> {} 98 | unsafe impl<'a> Send for PCIDevice<'a> {} 99 | 100 | impl<'a> Drop for PCIDevice<'a> { 101 | fn drop(&mut self) {} 102 | } 103 | 104 | impl<'a> Drop for PCIAccess<'a> { 105 | fn drop(&mut self) { 106 | if self.handle.is_null() { 107 | return; 108 | } 109 | // Safety: Just FFI 110 | unsafe { 111 | libpci_c_sys::pci_cleanup(self.handle); 112 | } 113 | } 114 | } 115 | 116 | impl<'a> PCIAccess<'a> { 117 | /// Tries to create a new data. 118 | /// 119 | /// Safety: Just FFI 120 | /// 121 | /// Returns `None` if libpci_c_sys::pci_alloc returns a NULL pointer - may happen if allocation 122 | /// fails. 123 | pub fn try_new(do_scan: bool) -> Option { 124 | let ptr: *mut libpci_c_sys::pci_access = unsafe { libpci_c_sys::pci_alloc() }; 125 | if ptr.is_null() { 126 | None 127 | } else { 128 | unsafe { 129 | libpci_c_sys::pci_init(ptr); 130 | } 131 | if do_scan { 132 | unsafe { 133 | libpci_c_sys::pci_scan_bus(ptr); 134 | } 135 | } 136 | Some(Self { handle: ptr, _phantom: PhantomData }) 137 | } 138 | } 139 | 140 | /// Create a new data. 141 | /// 142 | /// # Panics 143 | /// 144 | /// Panics if failed to allocate required memory. 145 | pub fn new(do_scan: bool) -> Self { 146 | // Safety: Just FFI 147 | Self::try_new(do_scan).expect("returned null pointer when allocating memory") 148 | } 149 | 150 | /// Scan to get the list of devices 151 | pub fn scan_bus(&mut self) { 152 | if !self.handle.is_null() { 153 | unsafe { 154 | libpci_c_sys::pci_scan_bus(self.handle); 155 | } 156 | } 157 | } 158 | 159 | /// Get linked list of devices 160 | pub fn devices(&mut self) -> Option { 161 | if self.handle.is_null() { 162 | None 163 | } else { 164 | Some(unsafe { PCIDevice::from_raw((*self.handle).devices) }) 165 | } 166 | } 167 | } 168 | 169 | impl<'a> Default for PCIDevice<'a> { 170 | fn default() -> Self { 171 | Self::new() 172 | } 173 | } 174 | 175 | impl<'a> PCIDevice<'a> { 176 | /// Create a new data. 177 | pub fn new() -> Self { 178 | // Safety: Just FFI 179 | Self(ptr::null_mut::(), PhantomData) 180 | } 181 | 182 | /// Constructs from raw C type 183 | /// 184 | /// # Safety 185 | /// 186 | /// the caller must guarantee that `self` is valid 187 | /// for a reference if it isn't null. 188 | pub unsafe fn from_raw(data: *mut libpci_c_sys::pci_dev) -> Self { 189 | Self(data, PhantomData) 190 | } 191 | 192 | /// Scan to get the list of devices 193 | pub fn fill_info(&mut self, fill: u32) { 194 | if !self.0.is_null() { 195 | unsafe { 196 | libpci_c_sys::pci_fill_info(self.0, fill as _); 197 | } 198 | } 199 | } 200 | 201 | /// Get class str. 202 | pub fn class(&self) -> Option { 203 | if self.0.is_null() { 204 | None 205 | } else { 206 | let mut class = vec![0_u8; 1024]; 207 | let size = (class.len() * mem::size_of::()) as usize; 208 | 209 | unsafe { 210 | libpci_c_sys::pci_lookup_class_helper( 211 | (*self.0).access, 212 | class.as_mut_ptr() as _, 213 | size, 214 | self.0, 215 | ); 216 | } 217 | Some(String::from(unsafe { c_char_to_str(class.as_ptr() as _) })) 218 | } 219 | } 220 | 221 | /// Get vendor str. 222 | pub fn vendor(&self) -> Option { 223 | if self.0.is_null() { 224 | None 225 | } else { 226 | let mut vendor = vec![0_u8; 256]; 227 | let size = (vendor.len() * mem::size_of::()) as usize; 228 | 229 | unsafe { 230 | libpci_c_sys::pci_lookup_vendor_helper( 231 | (*self.0).access, 232 | vendor.as_mut_ptr() as _, 233 | size, 234 | self.0, 235 | ); 236 | } 237 | Some(String::from(unsafe { c_char_to_str(vendor.as_ptr() as _) })) 238 | } 239 | } 240 | 241 | /// Get device str. 242 | pub fn device(&self) -> Option { 243 | if self.0.is_null() { 244 | None 245 | } else { 246 | let mut device = vec![0_u8; 256]; 247 | let size = (device.len() * mem::size_of::()) as usize; 248 | 249 | unsafe { 250 | libpci_c_sys::pci_lookup_device_helper( 251 | (*self.0).access, 252 | device.as_mut_ptr() as _, 253 | size, 254 | self.0, 255 | ); 256 | } 257 | Some(String::from(unsafe { c_char_to_str(device.as_ptr() as _) })) 258 | } 259 | } 260 | 261 | /// Class ID. 262 | pub fn class_id(&self) -> Option { 263 | if self.0.is_null() { 264 | None 265 | } else { 266 | let inner_obj = unsafe { *self.0 }; 267 | Some(inner_obj.device_class) 268 | } 269 | } 270 | 271 | /// Vendor ID. 272 | pub fn vendor_id(&self) -> Option { 273 | if self.0.is_null() { 274 | None 275 | } else { 276 | let inner_obj = unsafe { *self.0 }; 277 | Some(inner_obj.vendor_id) 278 | } 279 | } 280 | 281 | /// Device ID. 282 | pub fn device_id(&self) -> Option { 283 | if self.0.is_null() { 284 | None 285 | } else { 286 | let inner_obj = unsafe { *self.0 }; 287 | Some(inner_obj.device_id) 288 | } 289 | } 290 | 291 | /// Domain (host bridge). 292 | pub fn domain(&self) -> Option { 293 | if self.0.is_null() { 294 | None 295 | } else { 296 | let inner_obj = unsafe { *self.0 }; 297 | Some(inner_obj.domain) 298 | } 299 | } 300 | 301 | /// Bus inside domain. 302 | pub fn bus(&self) -> Option { 303 | if self.0.is_null() { 304 | None 305 | } else { 306 | let inner_obj = unsafe { *self.0 }; 307 | Some(inner_obj.bus) 308 | } 309 | } 310 | 311 | /// Bus inside device. 312 | pub fn dev(&self) -> Option { 313 | if self.0.is_null() { 314 | None 315 | } else { 316 | let inner_obj = unsafe { *self.0 }; 317 | Some(inner_obj.dev) 318 | } 319 | } 320 | 321 | /// Bus inside func. 322 | pub fn func(&self) -> Option { 323 | if self.0.is_null() { 324 | None 325 | } else { 326 | let inner_obj = unsafe { *self.0 }; 327 | Some(inner_obj.func) 328 | } 329 | } 330 | 331 | pub fn iter_mut(&self) -> IterMut<'_> { 332 | IterMut { ptr: self.0 as _, _phantom: PhantomData } 333 | } 334 | } 335 | 336 | pub struct IterMut<'a> { 337 | ptr: *mut libpci_c_sys::pci_dev, 338 | _phantom: PhantomData<&'a mut ()>, 339 | } 340 | 341 | impl<'a> Iterator for IterMut<'a> { 342 | type Item = PCIDevice<'a>; 343 | 344 | // next() is the only required method 345 | fn next(&mut self) -> Option> { 346 | let ptr = self.ptr; 347 | self.ptr = unsafe { libpci_c_sys::pci_get_next_device_mut(self.ptr) }; 348 | if ptr.is_null() { 349 | None 350 | } else { 351 | Some(unsafe { PCIDevice::from_raw(ptr) }) 352 | } 353 | } 354 | } 355 | 356 | unsafe fn c_char_to_str(text: *const c_char) -> &'static str { 357 | if text.is_null() { 358 | return ""; 359 | } 360 | #[cfg(not(feature = "std"))] 361 | { 362 | // To be safe, we need to compute right now its length 363 | let len = libc::strlen(text); 364 | // Cast it to a slice 365 | let slice = core::slice::from_raw_parts(text as *mut u8, len); 366 | // And hope it's still text. 367 | str::from_utf8(slice).expect("bad error message from libpci") 368 | } 369 | 370 | #[cfg(feature = "std")] 371 | { 372 | std::ffi::CStr::from_ptr(text).to_str().expect("bad error message from libpci") 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /scripts/chwd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | --[[ 3 | Copyright (C) 2023-2024 CachyOS 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program; if not, write to the Free Software Foundation, Inc., 17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | --]] 19 | local pacman, pmconfig, pmroot, cachedir, sync 20 | 21 | ---@alias hookAlias {pre_install: string?, post_install: string?, post_remove: string?, pre_remove: string?, conditional_packages: string?} 22 | 23 | ---@param s string 24 | ---@param ... any 25 | local function printf(s, ...) 26 | print(s:format(...)) 27 | end 28 | 29 | ---@param err string 30 | ---@param ... any 31 | local function die(err, ...) 32 | printf(err, ...) 33 | os.exit(1) 34 | end 35 | 36 | ---@param path string 37 | ---@return boolean 38 | local function file_exists(path) 39 | local file = io.open(path, "r") 40 | if file then 41 | file:close() 42 | return true 43 | else 44 | return false 45 | end 46 | end 47 | 48 | ---@return boolean 49 | local function check_on_multilib() 50 | local multilib_pattern = "^%[multilib%]" 51 | for line in io.lines(pmconfig) do 52 | if line:match(multilib_pattern) then 53 | return true 54 | end 55 | end 56 | return false 57 | end 58 | 59 | ---@param str string 60 | ---@return string[] 61 | local function split(str) 62 | local t = {} 63 | for found in str:gmatch("([^%s]+)") do 64 | t[#t + 1] = found 65 | end 66 | return t 67 | end 68 | 69 | ---@param args string[] 70 | ---@return table 71 | local function get_opts(args) 72 | local options = {} 73 | local option_pattern = "-%-?(.+)" 74 | 75 | for i = 1, #args do 76 | local option = args[i] 77 | local match = option:match(option_pattern) 78 | 79 | if match then 80 | options[match] = i 81 | end 82 | end 83 | return options 84 | end 85 | 86 | ---@param package_name string 87 | ---@return boolean 88 | local function is_installed(package_name) 89 | local handle = io.popen("/bin/pacman -Qi " .. package_name) 90 | 91 | if handle then 92 | ---@type string? 93 | local line 94 | ---@type string? 95 | local provider 96 | repeat 97 | line = handle:read("*l") 98 | if line then 99 | provider = line:match("Name%s+:%s+([^%s]+)") 100 | end 101 | until provider or line == nil 102 | handle:close() 103 | 104 | if provider and provider == package_name then 105 | return true 106 | end 107 | end 108 | 109 | return false 110 | end 111 | 112 | ---@param action string 113 | ---@param pkgs string 114 | ---@return number? 115 | local function pacman_handle(action, pkgs) 116 | local cmd = table.concat({ pacman, action, pkgs }, " ") 117 | local _, _, code = os.execute(cmd) 118 | return code 119 | end 120 | 121 | ---@param packages string 122 | ---@return number? 123 | local function install(packages) 124 | if sync then 125 | return pacman_handle("--needed -Sy", packages) 126 | end 127 | return pacman_handle("--needed -S", packages) 128 | end 129 | 130 | ---@param packages string 131 | ---@return number? 132 | local function remove(packages) 133 | local pkgs = "" 134 | for _, pkg in ipairs(split(packages)) do 135 | if is_installed(pkg) and (pkg ~= "mesa" or pkg ~= "lib32-mesa") then 136 | pkgs = pkgs .. " " .. pkg 137 | end 138 | end 139 | 140 | if #pkgs == 0 then 141 | print("Nothing to remove...") 142 | return 0 143 | else 144 | return pacman_handle("-Rdd", pkgs) 145 | end 146 | end 147 | 148 | ---@param hooks hookAlias 149 | ---@param name string 150 | ---@return string? 151 | local function exec_hook(hooks, name) 152 | local hook = hooks[name] 153 | 154 | if not hook then 155 | print("WARNING: An unknown hook is being called") 156 | return 157 | end 158 | 159 | if hook == "" then 160 | return 161 | end 162 | 163 | local handle, errmsg = io.popen(hook, "r") 164 | 165 | if not handle then 166 | printf("ERROR: Unknown shell invocation error for %s hook: %s", name, errmsg) 167 | return 168 | end 169 | 170 | local output = handle:read("*a") 171 | local success, _, exitcode = handle:close() 172 | 173 | if not success then 174 | printf("ERROR: Error occurred while executing %s hook: %s", name, exitcode) 175 | end 176 | 177 | return output 178 | end 179 | 180 | ---@param text string 181 | ---@return string, integer 182 | local function escape_pattern(text) 183 | return text:gsub("([^%w])", "%%%1") 184 | end 185 | 186 | ---@param path string 187 | ---@return table 188 | local function parse_profiles(path) 189 | local profile_name_pattern = "^%[([A-Za-z0-9-. ]+)%]" 190 | local packages_pattern = "^packages%s*=%s*'?\"?([A-Za-z0-9- ]+)'?\"?" 191 | local profiles = {} 192 | ---@type string? 193 | local profile 194 | ---@type string? 195 | local captured_hook 196 | 197 | local profile_file, errmsg = io.open(path, "r") 198 | 199 | if not profile_file then 200 | die("Failed to open %s: %s", path, errmsg) 201 | end 202 | 203 | local line = profile_file:read("l") 204 | 205 | while line do 206 | local profile_found = line:match(profile_name_pattern) 207 | 208 | if profile_found and not captured_hook then 209 | profile = profile_found 210 | profiles[profile] = { 211 | ["packages"] = nil, 212 | ["hooks"] = { 213 | ["pre_install"] = "", 214 | ["post_install"] = "", 215 | ["post_remove"] = "", 216 | ["pre_remove"] = "", 217 | ["conditional_packages"] = "" 218 | } 219 | } 220 | else 221 | if profile then 222 | if not profiles[profile].packages then 223 | profiles[profile].packages = line:match(packages_pattern) 224 | else 225 | local hooks = profiles[profile]["hooks"] 226 | if captured_hook == nil then 227 | for hook in pairs(hooks) do 228 | local hook_pattern = '^' .. escape_pattern(hook) .. '%s*=%s*"""' 229 | if line:match(hook_pattern) then 230 | captured_hook = hook 231 | end 232 | end 233 | else 234 | local hook_end = line:match('(.*)"""') 235 | if hook_end then 236 | hooks[captured_hook] = hooks[captured_hook] .. hook_end 237 | captured_hook = nil 238 | else 239 | hooks[captured_hook] = hooks[captured_hook] .. line .. "\n" 240 | end 241 | end 242 | end 243 | end 244 | end 245 | 246 | line = profile_file:read("l") 247 | end 248 | 249 | return profiles 250 | end 251 | 252 | 253 | ---@param profiles table 254 | ---@param name string 255 | ---@return string?, hookAlias 256 | local function get_profile(profiles, name) 257 | local packages 258 | local hooks = {} 259 | local keys = {} 260 | 261 | for tname in name:gmatch("([^.]*)") do 262 | keys[#keys + 1] = tname 263 | local key = table.concat(keys, ".") 264 | local profile = profiles[key] 265 | 266 | if not profile then 267 | die("Parent profile not found: %s", key) 268 | end 269 | 270 | if profile.packages then 271 | packages = profile.packages 272 | end 273 | 274 | for hook_name, hook in pairs(profile.hooks) do 275 | if hooks[hook_name] ~= "" then 276 | if hook ~= "" then 277 | hooks[hook_name] = hook 278 | end 279 | else 280 | hooks[hook_name] = hook 281 | end 282 | end 283 | end 284 | 285 | return packages, hooks 286 | end 287 | 288 | ---@param options table 289 | ---@param option string 290 | ---@param default string? 291 | ---@return string 292 | local function get_opt_argument(options, option, default) 293 | local index = options[option] 294 | if index == nil then 295 | if default then 296 | return default 297 | else 298 | die("The mandatory option --%s is omitted", option) 299 | end 300 | else 301 | local option_argument = arg[index + 1] 302 | if option_argument == nil or options[option_argument:gsub("-%-", "")] then 303 | die("Missing argument for option %s", option) 304 | else 305 | return option_argument 306 | end 307 | end 308 | end 309 | 310 | local function main() 311 | local options = get_opts(arg) 312 | 313 | cachedir = get_opt_argument(options, "cachedir", "/var/cache/pacman/pkg") 314 | pmroot = get_opt_argument(options, "pmroot", "/") 315 | pmconfig = get_opt_argument(options, "pmconfig", "/etc/pacman.conf") 316 | pacman = table.concat({ "pacman --noconfirm", "--cachedir", cachedir, "-r", pmroot, "--config", pmconfig }, " ") 317 | local profile_name = get_opt_argument(options, "profile") 318 | local path = get_opt_argument(options, "path") 319 | 320 | if not path or not profile_name then 321 | return 322 | end 323 | 324 | if options.sync then 325 | sync = true 326 | end 327 | 328 | if not file_exists(path) then 329 | die("Profiles file is not found: %s", path) 330 | end 331 | 332 | local profiles = parse_profiles(path) 333 | 334 | if not next(profiles) then 335 | die("Couldn't find any profiles in %s", path) 336 | end 337 | 338 | if not profiles[profile_name] then 339 | die("Profile not found") 340 | end 341 | 342 | local packages, hooks = get_profile(profiles, profile_name) 343 | 344 | if not packages then 345 | die("Profile %s is not valid", profile_name) 346 | end 347 | 348 | if packages and not check_on_multilib() then 349 | packages = packages:gsub("%s?(lib32-[A-Za-z0-9-]+)", "") 350 | end 351 | 352 | if options.install then 353 | exec_hook(hooks, "pre_install") 354 | 355 | local conditional_packages = exec_hook(hooks, "conditional_packages") 356 | 357 | if conditional_packages then 358 | packages = packages .. " " .. conditional_packages 359 | end 360 | 361 | local code = install(packages) 362 | if code ~= 0 then 363 | exec_hook(hooks, "pre_remove") 364 | die("ERROR: Pacman command was failed! Exit code: %s", code) 365 | else 366 | exec_hook(hooks, "post_install") 367 | end 368 | elseif options.remove then 369 | exec_hook(hooks, "pre_remove") 370 | 371 | local conditional_packages = exec_hook(hooks, "conditional_packages") 372 | 373 | if conditional_packages then 374 | packages = packages .. " " .. conditional_packages 375 | end 376 | 377 | local code = remove(packages) 378 | if code ~= 0 then 379 | die("ERROR: Pacman command was failed! Exit code: %s", code) 380 | else 381 | exec_hook(hooks, "post_remove") 382 | end 383 | else 384 | die("Action is missing, exit...") 385 | end 386 | end 387 | 388 | ---@diagnostic disable-next-line 389 | if _TEST then -- luacheck: ignore 390 | return { 391 | get_profile = get_profile, 392 | parse_profiles = parse_profiles 393 | } 394 | else 395 | main() 396 | end 397 | -------------------------------------------------------------------------------- /profiles/pci/graphic_drivers/profiles.toml: -------------------------------------------------------------------------------- 1 | # chwd has a sequential search for the desired profile. First it goes through 2 | # all class_ids that indicate a device type, like GPU or network adapter. Then 3 | # the profile specifies which manufacturer's driver we need via vendor_id, 4 | # after which chwd select specific device in device_id or device_pattern, which 5 | # is how much wider and includes devices of the same generation. For example: 6 | # 7 | # Searched for 3D controllers 8 | # class_ids = "0302" 9 | # 10 | # Filters only NVIDIA GPUs 11 | # vendor_ids = "10de" 12 | # 13 | # A certain device or set of ids: 14 | # device_ids = "25a2" 15 | # 16 | # Or set of devices depending on the generation: 17 | # device_name_pattern = '(AD)\w+' 18 | 19 | [nvidia-open-dkms] 20 | desc = 'Open source NVIDIA drivers for Linux (Latest)' 21 | class_ids = "0300 0302 0380" 22 | vendor_ids = "10de" 23 | device_ids = "*" 24 | priority = 10 25 | packages = 'nvidia-utils egl-wayland nvidia-settings opencl-nvidia lib32-opencl-nvidia lib32-nvidia-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader' 26 | conditional_packages = """ 27 | kernels="$(pacman -Qqs "^linux-cachyos")" 28 | packages="" 29 | 30 | for kernel in $kernels; do 31 | case "$kernel" in 32 | *-nvidia-open|*-nvidia) modules+=" ${kernel}";; 33 | *-headers|*-zfs|*-nvidia|*-dbg);; 34 | *) packages+=" ${kernel}-nvidia-open";; 35 | esac 36 | done 37 | 38 | # Fallback if there are no kernels with pre-built modules 39 | [ -z "$packages" ] && packages="nvidia-open-dkms" 40 | 41 | # Trying to determine the laptop 42 | device_type="$(cat /sys/devices/virtual/dmi/id/chassis_type)" 43 | if ((device_type >= 8 && device_type <= 11)); then 44 | packages+=" nvidia-prime switcheroo-control" 45 | fi 46 | 47 | echo "$packages" 48 | 49 | """ 50 | pre_install = """ 51 | cat </etc/mkinitcpio.conf.d/10-chwd.conf 52 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 53 | MODULES+=(nvidia nvidia_modeset nvidia_uvm nvidia_drm) 54 | EOF 55 | """ 56 | post_install = """ 57 | # Trying to determine the laptop 58 | device_type="$(cat /sys/devices/virtual/dmi/id/chassis_type)" 59 | if ((device_type >= 8 && device_type <= 11)); then 60 | # nvidia-powerd is not supported by Turing GPUs 61 | if ! lspci -d "10de:*:030x" -vm | grep -q 'Device:\\tTU.*'; then 62 | systemctl enable nvidia-powerd 63 | fi 64 | 65 | systemctl enable switcheroo-control 66 | 67 | cat << 'EOF' >/etc/profile.d/nvidia-rtd3-workaround.sh 68 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 69 | if [ -n "$(lspci -d "10de:*:0302")" ]; then 70 | export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json 71 | fi 72 | EOF 73 | 74 | cat << 'EOF' | install -Dm755 /dev/stdin /usr/lib/systemd/user-environment-generators/20-nvidia-rtd3-workaround 75 | #!/usr/bin/env sh 76 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 77 | if [ -n "$(lspci -d "10de:*:0302")" ]; then 78 | echo "__EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json" 79 | fi 80 | EOF 81 | 82 | else 83 | # Add libva-nvidia-driver to profile 84 | echo "export LIBVA_DRIVER_NAME=nvidia" > /etc/profile.d/nvidia-vaapi.sh 85 | fi 86 | """ 87 | pre_remove = """ 88 | rm -f /etc/mkinitcpio.conf.d/10-chwd.conf 89 | """ 90 | post_remove = """ 91 | rm -f /etc/profile.d/nvidia-vaapi.sh 92 | rm -f /etc/profile.d/nvidia-rtd3-workaround.sh 93 | rm -f /usr/lib/systemd/user-environment-generators/20-nvidia-rtd3-workaround 94 | """ 95 | 96 | [nvidia-dkms-580xx] 97 | desc = 'Closed source NVIDIA drivers for Linux (580xx)' 98 | class_ids = "0300 0302 0380" 99 | vendor_ids = "10de" 100 | priority = 12 101 | packages = 'nvidia-580xx-dkms nvidia-580xx-utils nvidia-580xx-settings opencl-nvidia-580xx lib32-opencl-nvidia-580xx lib32-nvidia-580xx-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader' 102 | device_ids = '>/var/lib/chwd/ids/nvidia-580.ids' 103 | conditional_packages = """ 104 | kernels="$(pacman -Qqs "^linux-cachyos")" 105 | packages="" 106 | 107 | for kernel in $kernels; do 108 | case "$kernel" in 109 | *-headers|*-zfs|*-nvidia-open|*-dbg|*-nvidia);; 110 | *) packages+=" ${kernel}-headers";; 111 | esac 112 | done 113 | 114 | # Trying to determine the laptop 115 | device_type="$(cat /sys/devices/virtual/dmi/id/chassis_type)" 116 | if ((device_type >= 8 && device_type <= 11)); then 117 | packages+=" nvidia-prime switcheroo-control" 118 | fi 119 | 120 | echo "$packages" 121 | 122 | """ 123 | pre_install = """ 124 | cat </etc/mkinitcpio.conf.d/10-chwd.conf 125 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 126 | MODULES+=(nvidia nvidia_modeset nvidia_uvm nvidia_drm) 127 | EOF 128 | """ 129 | post_install = """ 130 | # Trying to determine the laptop 131 | device_type="$(cat /sys/devices/virtual/dmi/id/chassis_type)" 132 | if ((device_type >= 8 && device_type <= 11)); then 133 | systemctl enable switcheroo-control 134 | 135 | cat << 'EOF' >/etc/profile.d/nvidia-rtd3-workaround.sh 136 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 137 | if [ -n "$(lspci -d "10de:*:0302")" ]; then 138 | export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json 139 | fi 140 | EOF 141 | 142 | cat << 'EOF' | install -Dm755 /dev/stdin /usr/lib/systemd/user-environment-generators/20-nvidia-rtd3-workaround 143 | #!/usr/bin/env sh 144 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 145 | if [ -n "$(lspci -d "10de:*:0302")" ]; then 146 | echo "__EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json" 147 | fi 148 | EOF 149 | 150 | else 151 | # Add libva-nvidia-driver to profile 152 | echo "export LIBVA_DRIVER_NAME=nvidia" > /etc/profile.d/nvidia-vaapi.sh 153 | fi 154 | """ 155 | pre_remove = """ 156 | rm -f /etc/mkinitcpio.conf.d/10-chwd.conf 157 | """ 158 | post_remove = """ 159 | rm -f /etc/profile.d/nvidia-vaapi.sh 160 | rm -f /etc/profile.d/nvidia-rtd3-workaround.sh 161 | rm -f /usr/lib/systemd/user-environment-generators/20-nvidia-rtd3-workaround 162 | """ 163 | 164 | [nvidia-dkms-470xx] 165 | desc = 'Closed source NVIDIA drivers for Linux (470xx branch, only for Kepler GPUs)' 166 | priority = 14 167 | class_ids = "0300 0302 0380" 168 | vendor_ids = "10de" 169 | device_ids = '>/var/lib/chwd/ids/nvidia-470.ids' 170 | packages = 'nvidia-470xx-dkms nvidia-470xx-utils nvidia-470xx-settings opencl-nvidia-470xx vulkan-icd-loader lib32-nvidia-470xx-utils lib32-opencl-nvidia-470xx lib32-vulkan-icd-loader libva-nvidia-driver' 171 | conditional_packages = """ 172 | # Trying to determine the laptop 173 | packages="" 174 | device_type="$(cat /sys/devices/virtual/dmi/id/chassis_type)" 175 | if ((device_type >= 8 && device_type <= 11)); then 176 | packages+=" nvidia-prime switcheroo-control" 177 | fi 178 | 179 | if pacman -Qqs plasma-desktop &>/dev/null; then 180 | packages+=" plasma-x11-session" 181 | fi 182 | 183 | echo "$packages" 184 | """ 185 | post_install = """ 186 | # Trying to determine the laptop 187 | device_type="$(cat /sys/devices/virtual/dmi/id/chassis_type)" 188 | if ((device_type >= 8 && device_type <= 11)); then 189 | systemctl enable switcheroo-control 190 | else 191 | # Add libva-nvidia-driver to profile 192 | echo "export LIBVA_DRIVER_NAME=nvidia" > /etc/profile.d/nvidia-vaapi.sh 193 | fi 194 | mkdir -p /etc/sddm.conf.d 195 | cat << EOF >/etc/sddm.conf.d/01-wayland.conf 196 | # THIS IS A OVERRIDE GENERATED BY CHWD TO OVERRIDE THE CACHYOS-KDE-SETTINGS PACKAGE, DO NOT TOUCH. 197 | [General] 198 | DisplayServer=x11 199 | EOF 200 | """ 201 | post_remove = """ 202 | rm -f /etc/profile.d/nvidia-vaapi.sh 203 | rm -f /etc/sddm.conf.d/01-wayland.conf 204 | """ 205 | 206 | [nouveau] 207 | desc = "Open Nouveau driver for NVIDIA graphics cards (Only for old GPUs)" 208 | class_ids = "0300 0302" 209 | vendor_ids = "10de" 210 | device_ids = '>/var/lib/chwd/ids/nouveau.ids' 211 | priority = 18 212 | packages = 'mesa lib32-mesa vulkan-nouveau linux-firmware-nvidia' 213 | conditional_packages = """ 214 | if [ -z "$(lspci -d "10de:*:030x")" ]; then 215 | echo "opencl-mesa lib32-opencl-mesa" 216 | fi 217 | """ 218 | post_install = """ 219 | if [ -z "$(lspci -d "10de:*:030x")" ]; then 220 | # Force the use of Rusticl, otherwise it will be just a stub 221 | echo "export RUSTICL_ENABLE=nouveau" > /etc/profile.d/opencl.sh 222 | mkdir -p /etc/environment.d 223 | echo "RUSTICL_ENABLE=nouveau" > /etc/environment.d/30-opencl.conf 224 | fi 225 | 226 | # Performance improvement for Fermi GPUs 227 | device_id="$(lspci -d "10de:*:030x" -n | cut -d" " -f3 | cut -d":" -f2 | head -n1)" 228 | if grep -q "$device_id" /var/lib/chwd/ids/nvidia-390.ids 2>/dev/null; then 229 | cat <<'EOF' >/etc/modprobe.d/20-nouveau-nvboost.conf 230 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 231 | options nouveau config=NvBoost=2 232 | EOF 233 | fi 234 | """ 235 | post_remove = """ 236 | rm -f /etc/profile.d/opencl.sh 237 | rm -f /etc/environment.d/30-opencl.conf 238 | 239 | device_id="$(lspci -d "10de:*:030x" -n | cut -d" " -f3 | cut -d":" -f2 | head -n1)" 240 | if grep -q "$device_id" /var/lib/chwd/ids/nvidia-390.ids 2>/dev/null; then 241 | rm -f /etc/modprobe.d/20-nouveau-nvboost.conf 242 | fi 243 | """ 244 | 245 | [intel] 246 | desc = "Mesa open source driver for Intel" 247 | class_ids = "0300 0302 0380" 248 | vendor_ids = "8086" 249 | device_ids = "*" 250 | priority = 4 251 | packages = 'mesa lib32-mesa vulkan-intel lib32-vulkan-intel gst-plugin-va linux-firmware-intel' 252 | conditional_packages = """ 253 | pkgs="" 254 | if [ -z "$(lspci -d "10de:*:030x")" ]; then 255 | pkgs+="opencl-mesa lib32-opencl-mesa" 256 | fi 257 | 258 | device_id="$(lspci -d "8086:*:030x" -n | cut -d" " -f3 | cut -d":" -f2 | head -n1)" 259 | if grep -q "$device_id" /var/lib/chwd/ids/intel-vaapi.ids 2>/dev/null; then 260 | pkgs+=" libva-intel-driver-irql" 261 | else 262 | pkgs+=" intel-media-driver" 263 | fi 264 | 265 | if grep -q "$device_id" /var/lib/chwd/ids/intel-media-sdk.ids 2>/dev/null; then 266 | pkgs+=" intel-media-sdk" 267 | else 268 | if ! grep -q "$device_id" /var/lib/chwd/ids/intel-vaapi.ids 2>/dev/null; then 269 | pkgs+=" vpl-gpu-rt" 270 | fi 271 | fi 272 | 273 | echo "$pkgs" 274 | """ 275 | post_install = """ 276 | if [ -z "$(lspci -d "10de:*:030x")" ]; then 277 | # Force the use of Rusticl, otherwise it will be just a stub 278 | echo "export RUSTICL_ENABLE=iris" > /etc/profile.d/opencl.sh 279 | mkdir -p /etc/environment.d 280 | echo "RUSTICL_ENABLE=iris" > /etc/environment.d/30-opencl.conf 281 | fi 282 | """ 283 | post_remove = """ 284 | rm -f /etc/profile.d/opencl.sh 285 | rm -f /etc/environment.d/30-opencl.conf 286 | """ 287 | 288 | [amd] 289 | desc = "Mesa open source driver for AMD" 290 | class_ids = "0300 0302 0380" 291 | vendor_ids = "1002" 292 | device_ids = "*" 293 | priority = 4 294 | packages = 'mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon xf86-video-amdgpu gst-plugin-va linux-firmware-amdgpu' 295 | conditional_packages = """ 296 | if [ -z "$(lspci -d "10de:*:030x")" ]; then 297 | echo "opencl-mesa lib32-opencl-mesa" 298 | fi 299 | """ 300 | 301 | [fallback] 302 | desc = 'Fallback profile' 303 | class_ids = "0300 0380 0302" 304 | vendor_ids = "1002 8086 10de" 305 | device_ids = "*" 306 | priority = 3 307 | packages = 'mesa lib32-mesa vulkan-swrast xf86-video-vesa' 308 | 309 | [virtualmachine] 310 | desc = 'X.org vmware video driver and open-vm-tools/virtualbox tools' 311 | class_ids = "0300" 312 | # Virtualbox version 6.0 uses VMSVGA on Linux guests by default, which has VMWare's VENDORID. 313 | # VENDOR VMWare=80ee Virtualbox=15AD Redhat(QXL)=1af4 Redhat(VirtIO)=1b36 cirrus=1013 314 | vendor_ids = "80ee 15AD 1af4 1b36 1013 1234" 315 | device_ids = "*" 316 | priority = 5 317 | packages = 'virtualbox-guest-utils open-vm-tools xf86-input-vmmouse spice-vdagent qemu-guest-agent vulkan-virtio gtkmm3' 318 | post_install = """ 319 | if [ "$(systemd-detect-virt)" = "oracle" ]; then 320 | # Virtualbox detected 321 | 322 | # Load kernel modules and sync clock 323 | systemctl enable --now --no-block vboxservice.service 324 | else 325 | 326 | if [ "$(systemd-detect-virt)" = "vmware" ]; then 327 | # Vmware detected 328 | systemctl enable --now --no-block vmtoolsd.service 329 | else 330 | cat </etc/mkinitcpio.conf.d/10-chwd.conf 331 | # This file is automatically generated by chwd. PLEASE DO NOT EDIT IT. 332 | MODULES+=(virtio virtio_blk virtio_pci virtio_net) 333 | EOF 334 | mkinitcpio -P 335 | fi 336 | fi 337 | if [ -e /etc/gdm/custom.conf ]; then 338 | sed -i -e 's|#WaylandEnable=false|WaylandEnable=false|g' /etc/gdm/custom.conf 339 | fi""" 340 | pre_remove = """ 341 | rm -f /etc/mkinitcpio.conf.d/10-chwd.conf 342 | """ 343 | post_remove = """ 344 | if [ "$(systemd-detect-virt)" = "oracle" ]; then 345 | # Virtualbox detected 346 | systemctl disable --now vboxservice.service 347 | elif [ "$(systemd-detect-virt)" = "vmware" ]; then 348 | # Vmware detected 349 | systemctl disable --now vmtoolsd.service 350 | fi""" 351 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023-2024 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS chwd. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | pub mod args; 20 | pub mod console_writer; 21 | pub mod device_misc; 22 | pub mod localization; 23 | pub mod logger; 24 | pub mod misc; 25 | pub mod profile_misc; 26 | 27 | use chwd::profile::Profile; 28 | use chwd::*; 29 | use misc::Transaction; 30 | 31 | use std::path::Path; 32 | use std::sync::Arc; 33 | use std::{fs, str}; 34 | 35 | use clap::Parser; 36 | use i18n_embed::DesktopLanguageRequester; 37 | use nix::unistd::Uid; 38 | use subprocess::{Exec, Redirection}; 39 | 40 | fn main() -> anyhow::Result<()> { 41 | let requested_languages = DesktopLanguageRequester::requested_languages(); 42 | let localizer = crate::localization::localizer(); 43 | if let Err(error) = localizer.select(&requested_languages) { 44 | eprintln!("Error while loading languages for library_fluent {error}"); 45 | } 46 | 47 | // initialize the logger 48 | logger::init_logger().expect("Failed to initialize logger"); 49 | 50 | let args: Vec = std::env::args().collect(); 51 | 52 | // 1) Process arguments 53 | 54 | let mut argstruct = args::Args::parse(); 55 | if args.len() <= 1 { 56 | argstruct.list_available = true; 57 | } 58 | 59 | let mut working_profiles: Vec = vec![]; 60 | 61 | let mut autoconf_class_id = String::new(); 62 | 63 | if let Some(profile) = &argstruct.install { 64 | working_profiles.push(profile.to_lowercase()); 65 | } 66 | 67 | if let Some(profile) = &argstruct.remove { 68 | working_profiles.push(profile.to_lowercase()); 69 | } 70 | 71 | if let Some(class_id) = &argstruct.autoconfigure { 72 | autoconf_class_id = class_id.to_lowercase(); 73 | } 74 | 75 | // 2) Initialize 76 | let mut data_obj = data::Data::new(argstruct.is_ai_sdk); 77 | 78 | let missing_dirs = misc::check_environment(); 79 | if !missing_dirs.is_empty() { 80 | log::error!("Following directories do not exist:"); 81 | for missing_dir in missing_dirs.iter() { 82 | log::info!("{missing_dir}"); 83 | } 84 | anyhow::bail!("Error occurred"); 85 | } 86 | 87 | // 3) Perform operations 88 | console_writer::handle_arguments_listing(&data_obj, &argstruct); 89 | 90 | // 4) Auto configuration 91 | let mut prepared_profiles = 92 | prepare_autoconfigure(&data_obj, &mut argstruct, &autoconf_class_id); 93 | working_profiles.append(&mut prepared_profiles); 94 | 95 | // Check 96 | if let Some(profile) = &argstruct.check { 97 | // ignore returned profile 98 | let _ = get_working_profile(&data_obj, profile)?; 99 | return Ok(()); 100 | } 101 | 102 | // Transaction 103 | if !(argstruct.install.is_some() || argstruct.remove.is_some()) { 104 | return Ok(()); 105 | } 106 | 107 | if !Uid::effective().is_root() { 108 | console_writer::print_error_msg!("root-operation"); 109 | anyhow::bail!("Error occurred"); 110 | } 111 | 112 | for profile_name in working_profiles.iter() { 113 | if argstruct.install.is_some() { 114 | let working_profile = get_working_profile(&data_obj, profile_name)?; 115 | 116 | if !perform_transaction( 117 | &mut data_obj, 118 | &argstruct, 119 | &working_profile, 120 | Transaction::Install, 121 | argstruct.force, 122 | ) { 123 | anyhow::bail!("Error occurred"); 124 | } 125 | } else if argstruct.remove.is_some() { 126 | let working_profile = get_installed_profile(&data_obj, profile_name); 127 | if working_profile.is_none() { 128 | console_writer::print_error_msg!( 129 | "profile-not-installed", 130 | profile_name = profile_name 131 | ); 132 | anyhow::bail!("Error occurred"); 133 | } else if !perform_transaction( 134 | &mut data_obj, 135 | &argstruct, 136 | working_profile.as_ref().unwrap(), 137 | Transaction::Remove, 138 | argstruct.force, 139 | ) { 140 | anyhow::bail!("Error occurred"); 141 | } 142 | } 143 | } 144 | 145 | Ok(()) 146 | } 147 | 148 | fn prepare_autoconfigure( 149 | data: &data::Data, 150 | args: &mut args::Args, 151 | autoconf_class_id: &str, 152 | ) -> Vec { 153 | if args.autoconfigure.is_none() { 154 | return vec![]; 155 | } 156 | 157 | let mut profiles_name = vec![]; 158 | 159 | let devices = &data.pci_devices; 160 | let installed_profiles = &data.installed_profiles; 161 | 162 | let mut found_device = false; 163 | for device in devices.iter() { 164 | if autoconf_class_id != "any" && device.class_id != autoconf_class_id { 165 | continue; 166 | } 167 | found_device = true; 168 | let profile = device.available_profiles.first(); 169 | 170 | let device_info = device.device_info(); 171 | if profile.is_none() { 172 | if autoconf_class_id != "any" { 173 | log::warn!("No config found for device: {device_info}"); 174 | } 175 | continue; 176 | } 177 | let profile = profile.unwrap(); 178 | 179 | // If force is not set, then we skip found profile 180 | let skip = !args.force && installed_profiles.iter().any(|x| x.name == profile.name); 181 | 182 | // Print found profile 183 | if skip { 184 | log::info!( 185 | "Skipping already installed profile '{}' for device: {device_info}", 186 | profile.name 187 | ); 188 | } else { 189 | log::info!("Using profile '{}' for device: {device_info}", profile.name); 190 | } 191 | 192 | let profile_exists = profiles_name.iter().any(|x| x == &profile.name); 193 | if !profile_exists && !skip { 194 | profiles_name.push(profile.name.clone()); 195 | } 196 | } 197 | 198 | if !found_device { 199 | log::warn!("No device of class '{autoconf_class_id}' found!"); 200 | } else if !profiles_name.is_empty() { 201 | args.install = Some(profiles_name.first().unwrap().clone()); 202 | } 203 | 204 | profiles_name 205 | } 206 | 207 | fn get_available_profile(data: &data::Data, profile_name: &str) -> Option> { 208 | // Get the right devices 209 | let devices = &data.pci_devices; 210 | for device in devices { 211 | let available_profiles = &device.available_profiles; 212 | if available_profiles.is_empty() { 213 | continue; 214 | } 215 | 216 | let available_profile = available_profiles.iter().find(|x| x.name == profile_name); 217 | if let Some(available_profile) = available_profile { 218 | return Some(Arc::clone(available_profile)); 219 | } 220 | } 221 | None 222 | } 223 | 224 | fn get_db_profile(data: &data::Data, profile_name: &str) -> Option> { 225 | // Get the right profiles 226 | let all_profiles = &data.all_profiles; 227 | misc::find_profile(profile_name, all_profiles) 228 | } 229 | 230 | fn get_installed_profile(data: &data::Data, profile_name: &str) -> Option> { 231 | // Get the right profiles 232 | let installed_profiles = &data.installed_profiles; 233 | misc::find_profile(profile_name, installed_profiles) 234 | } 235 | 236 | fn get_working_profile(data: &data::Data, profile_name: &str) -> anyhow::Result> { 237 | let working_profile = get_available_profile(data, profile_name); 238 | if working_profile.is_none() { 239 | let working_profile = get_db_profile(data, profile_name); 240 | if working_profile.is_none() { 241 | console_writer::print_error_msg!("profile-not-exist", profile_name = profile_name); 242 | anyhow::bail!("Error occurred"); 243 | } 244 | console_writer::print_error_msg!("no-matching-device", profile_name = profile_name); 245 | anyhow::bail!("Error occurred"); 246 | } 247 | Ok(working_profile.unwrap()) 248 | } 249 | 250 | pub fn run_script( 251 | data: &mut data::Data, 252 | args: &args::Args, 253 | profile: &Profile, 254 | transaction: Transaction, 255 | ) -> bool { 256 | let mut cmd_args: Vec = if Transaction::Remove == transaction { 257 | vec!["--remove".into()] 258 | } else { 259 | vec!["--install".into()] 260 | }; 261 | 262 | if data.sync_package_manager_database { 263 | cmd_args.push("--sync".into()); 264 | } 265 | 266 | cmd_args.extend_from_slice(&["--cachedir".into(), args.pmcachedir.clone()]); 267 | cmd_args.extend_from_slice(&["--pmconfig".into(), args.pmconfig.clone()]); 268 | cmd_args.extend_from_slice(&["--pmroot".into(), args.pmroot.clone()]); 269 | cmd_args.extend_from_slice(&["--profile".into(), profile.name.clone()]); 270 | cmd_args.extend_from_slice(&["--path".into(), profile.prof_path.clone()]); 271 | 272 | let status = 273 | Exec::cmd(consts::CHWD_SCRIPT_PATH).args(&cmd_args).stderr(Redirection::Merge).join(); 274 | if status.is_err() || !status.unwrap().success() { 275 | return false; 276 | } 277 | 278 | // Only one database sync is required 279 | if Transaction::Install == transaction { 280 | data.sync_package_manager_database = false; 281 | } 282 | true 283 | } 284 | 285 | fn perform_transaction( 286 | data: &mut data::Data, 287 | args: &args::Args, 288 | profile: &Arc, 289 | transaction_type: Transaction, 290 | force: bool, 291 | ) -> bool { 292 | let status = perform_transaction_type(data, args, profile, transaction_type, force); 293 | 294 | let profile_name = &profile.name; 295 | match status { 296 | misc::Status::ErrorNotInstalled => { 297 | console_writer::print_error_msg!("profile-not-installed", profile_name = profile_name) 298 | }, 299 | misc::Status::ErrorAlreadyInstalled => log::warn!( 300 | "a version of profile '{profile_name}' is already installed!\nUse -f/--force to force \ 301 | installation...", 302 | ), 303 | misc::Status::ErrorNoMatchLocalConfig => { 304 | console_writer::print_error_msg!("pass-profile-no-match-install") 305 | }, 306 | misc::Status::ErrorScriptFailed => console_writer::print_error_msg!("script-failed"), 307 | misc::Status::ErrorSetDatabase => console_writer::print_error_msg!("failed-set-db"), 308 | _ => (), 309 | } 310 | 311 | data.update_installed_profile_data(); 312 | 313 | misc::Status::Success == status 314 | } 315 | 316 | fn perform_transaction_type( 317 | data_obj: &mut data::Data, 318 | args: &args::Args, 319 | profile: &Arc, 320 | transaction_type: Transaction, 321 | force: bool, 322 | ) -> misc::Status { 323 | // Check if already installed 324 | let installed_profile = get_installed_profile(data_obj, &profile.name); 325 | let mut status = misc::Status::Success; 326 | 327 | if (Transaction::Remove == transaction_type) || (installed_profile.is_some() && force) { 328 | if installed_profile.is_none() { 329 | return misc::Status::ErrorNotInstalled; 330 | } 331 | console_writer::print_message( 332 | misc::Message::RemoveStart, 333 | &installed_profile.as_ref().unwrap().name, 334 | ); 335 | status = remove_profile(data_obj, args, installed_profile.as_ref().unwrap()); 336 | if misc::Status::Success != status { 337 | return status; 338 | } 339 | console_writer::print_message( 340 | misc::Message::RemoveEnd, 341 | &installed_profile.as_ref().unwrap().name, 342 | ); 343 | } 344 | 345 | if Transaction::Install == transaction_type { 346 | // Check if already installed but not allowed to reinstall 347 | if installed_profile.is_some() && !force { 348 | return misc::Status::ErrorAlreadyInstalled; 349 | } 350 | console_writer::print_message(misc::Message::InstallStart, &profile.name); 351 | status = install_profile(data_obj, args, profile); 352 | if misc::Status::Success != status { 353 | return status; 354 | } 355 | console_writer::print_message(misc::Message::InstallEnd, &profile.name); 356 | } 357 | status 358 | } 359 | 360 | fn install_profile(data: &mut data::Data, args: &args::Args, profile: &Profile) -> misc::Status { 361 | if !run_script(data, args, profile, Transaction::Install) { 362 | return misc::Status::ErrorScriptFailed; 363 | } 364 | 365 | let db_dir = consts::CHWD_PCI_DATABASE_DIR; 366 | let working_dir = format!( 367 | "{}/{}", 368 | db_dir, 369 | Path::new(&profile.prof_path).parent().unwrap().file_name().unwrap().to_str().unwrap() 370 | ); 371 | let _ = fs::create_dir_all(&working_dir); 372 | if !profile::write_profile_to_file( 373 | &format!("{}/{}", &working_dir, consts::CHWD_CONFIG_FILE), 374 | profile, 375 | ) { 376 | return misc::Status::ErrorSetDatabase; 377 | } 378 | 379 | // Note: installed profile vectors have to be updated manually with 380 | // update_installed_profile_data(Data) 381 | misc::Status::Success 382 | } 383 | 384 | fn remove_profile(data: &mut data::Data, args: &args::Args, profile: &Profile) -> misc::Status { 385 | let installed_profile = get_installed_profile(data, &profile.name); 386 | 387 | // Check if installed 388 | if installed_profile.is_none() { 389 | return misc::Status::ErrorNotInstalled; 390 | } 391 | // Run script 392 | if !run_script(data, args, installed_profile.as_ref().unwrap(), Transaction::Remove) { 393 | return misc::Status::ErrorScriptFailed; 394 | } 395 | 396 | if !profile::remove_profile_from_file(&profile.prof_path, &profile.name) { 397 | return misc::Status::ErrorSetDatabase; 398 | } 399 | 400 | data.update_installed_profile_data(); 401 | misc::Status::Success 402 | } 403 | -------------------------------------------------------------------------------- /src/profile.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023-2024 Vladislav Nepogodin 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License along 14 | // with this program; if not, write to the Free Software Foundation, Inc., 15 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 | 17 | use anyhow::Result; 18 | 19 | use std::fs; 20 | use std::sync::Arc; 21 | 22 | #[derive(Debug, Default, Clone, PartialEq)] 23 | pub struct HardwareID { 24 | pub class_ids: Vec, 25 | pub vendor_ids: Vec, 26 | pub device_ids: Vec, 27 | pub blacklisted_class_ids: Vec, 28 | pub blacklisted_vendor_ids: Vec, 29 | pub blacklisted_device_ids: Vec, 30 | } 31 | 32 | #[derive(Debug, Clone, PartialEq)] 33 | pub struct Profile { 34 | pub is_ai_sdk: bool, 35 | 36 | pub prof_path: String, 37 | pub name: String, 38 | pub desc: String, 39 | pub priority: i32, 40 | pub packages: String, 41 | pub post_install: String, 42 | pub post_remove: String, 43 | pub pre_install: String, 44 | pub pre_remove: String, 45 | pub conditional_packages: String, 46 | pub device_name_pattern: Option, 47 | pub hwd_product_name_pattern: Option, 48 | pub gc_versions: Option>, 49 | 50 | pub hwd_ids: Vec, 51 | } 52 | 53 | impl Default for Profile { 54 | fn default() -> Self { 55 | Self::new() 56 | } 57 | } 58 | 59 | impl Profile { 60 | pub fn new() -> Self { 61 | Self { hwd_ids: vec![Default::default()], ..Default::default() } 62 | } 63 | } 64 | 65 | pub fn parse_profiles(file_path: &str) -> Result> { 66 | let mut profiles = vec![]; 67 | let file_content = fs::read_to_string(file_path)?; 68 | let toml_table = file_content.parse::()?; 69 | 70 | for (key, value) in toml_table.iter() { 71 | if !value.is_table() { 72 | anyhow::bail!("the value is not table!"); 73 | } 74 | let value_table = value.as_table().unwrap(); 75 | 76 | let toplevel_profile = parse_profile(value_table, key); 77 | if toplevel_profile.is_err() { 78 | continue; 79 | } 80 | 81 | for (nested_key, nested_value) in value_table.iter() { 82 | if !nested_value.is_table() { 83 | continue; 84 | } 85 | let nested_profile_name = format!("{key}.{nested_key}"); 86 | let mut nested_value_table = nested_value.as_table().unwrap().clone(); 87 | merge_table_left(&mut nested_value_table, value_table); 88 | let nested_profile = parse_profile(&nested_value_table, &nested_profile_name); 89 | if nested_profile.is_err() { 90 | continue; 91 | } 92 | let mut nested_profile = nested_profile?; 93 | file_path.clone_into(&mut nested_profile.prof_path); 94 | profiles.push(nested_profile); 95 | } 96 | let mut toplevel_profile = toplevel_profile?; 97 | file_path.clone_into(&mut toplevel_profile.prof_path); 98 | profiles.push(toplevel_profile); 99 | } 100 | 101 | Ok(profiles) 102 | } 103 | 104 | pub fn get_invalid_profiles(file_path: &str) -> Result> { 105 | let mut invalid_profile_list = vec![]; 106 | let file_content = fs::read_to_string(file_path)?; 107 | let toml_table = file_content.parse::()?; 108 | 109 | for (key, value) in toml_table.iter() { 110 | if !value.is_table() { 111 | anyhow::bail!("the value is not table!"); 112 | } 113 | let value_table = value.as_table().unwrap(); 114 | 115 | let toplevel_profile = parse_profile(value_table, key); 116 | if toplevel_profile.is_err() { 117 | invalid_profile_list.push(key.to_owned()); 118 | continue; 119 | } 120 | 121 | for (nested_key, nested_value) in value_table.iter() { 122 | if !nested_value.is_table() { 123 | continue; 124 | } 125 | let nested_profile_name = format!("{key}.{nested_key}"); 126 | let mut nested_value_table = nested_value.as_table().unwrap().clone(); 127 | merge_table_left(&mut nested_value_table, value_table); 128 | let nested_profile = parse_profile(&nested_value_table, &nested_profile_name); 129 | if nested_profile.is_ok() { 130 | continue; 131 | } 132 | invalid_profile_list.push(nested_profile_name); 133 | } 134 | } 135 | 136 | Ok(invalid_profile_list) 137 | } 138 | 139 | // Returns list of profiles available for all devices on current hardware 140 | // is_ai_sdk is used to filter out profiles which dont represent AI SDK installation 141 | pub fn get_available_profiles(is_ai_sdk: bool) -> Vec { 142 | let mut available_profiles = vec![]; 143 | // populate data 144 | let data_obj = crate::data::Data::new(is_ai_sdk); 145 | 146 | // extract for each device 147 | for device in &data_obj.pci_devices { 148 | if device.available_profiles.is_empty() { 149 | continue; 150 | } 151 | let mut profiles = 152 | device.available_profiles.clone().into_iter().map(Arc::unwrap_or_clone).collect(); 153 | available_profiles.append(&mut profiles); 154 | } 155 | available_profiles 156 | } 157 | 158 | pub fn parse_profiles_merged(file_path: &str) -> Result> { 159 | let mut profiles = vec![]; 160 | let file_content = fs::read_to_string(file_path)?; 161 | let toml_table = file_content.parse::()?; 162 | 163 | for (key, value) in toml_table.iter() { 164 | if !value.is_table() { 165 | anyhow::bail!("the value is not table!"); 166 | } 167 | let value_table = value.as_table().unwrap(); 168 | 169 | let toplevel_profile = parse_profile(value_table, key); 170 | if toplevel_profile.is_err() { 171 | continue; 172 | } 173 | 174 | // dont push parent 175 | if value_table.is_empty() { 176 | let mut toplevel_profile = toplevel_profile?; 177 | file_path.clone_into(&mut toplevel_profile.prof_path); 178 | profiles.push(toplevel_profile); 179 | continue; 180 | } 181 | 182 | for (nested_key, nested_value) in value_table.iter() { 183 | if !nested_value.is_table() { 184 | continue; 185 | } 186 | let nested_profile_name = format!("{key}.{nested_key}"); 187 | let mut nested_value_table = nested_value.as_table().unwrap().clone(); 188 | merge_table_left(&mut nested_value_table, value_table); 189 | let nested_profile = parse_profile(&nested_value_table, &nested_profile_name); 190 | if nested_profile.is_err() { 191 | continue; 192 | } 193 | let mut nested_profile = nested_profile?; 194 | file_path.clone_into(&mut nested_profile.prof_path); 195 | profiles.push(nested_profile); 196 | } 197 | } 198 | 199 | Ok(profiles) 200 | } 201 | 202 | fn parse_profile(node: &toml::Table, profile_name: &str) -> Result { 203 | let mut profile = Profile { 204 | is_ai_sdk: node.get("ai_sdk").and_then(|x| x.as_bool()).unwrap_or(false), 205 | prof_path: "".to_owned(), 206 | name: profile_name.to_owned(), 207 | packages: node.get("packages").and_then(|x| x.as_str()).unwrap_or("").to_owned(), 208 | post_install: node.get("post_install").and_then(|x| x.as_str()).unwrap_or("").to_owned(), 209 | post_remove: node.get("post_remove").and_then(|x| x.as_str()).unwrap_or("").to_owned(), 210 | pre_install: node.get("pre_install").and_then(|x| x.as_str()).unwrap_or("").to_owned(), 211 | pre_remove: node.get("pre_remove").and_then(|x| x.as_str()).unwrap_or("").to_owned(), 212 | conditional_packages: node 213 | .get("conditional_packages") 214 | .and_then(|x| x.as_str()) 215 | .unwrap_or("") 216 | .to_owned(), 217 | desc: node.get("desc").and_then(|x| x.as_str()).unwrap_or("").to_owned(), 218 | priority: node.get("priority").and_then(|x| x.as_integer()).unwrap_or(0) as i32, 219 | hwd_ids: vec![Default::default()], 220 | device_name_pattern: node 221 | .get("device_name_pattern") 222 | .and_then(|x| x.as_str().map(str::to_string)), 223 | hwd_product_name_pattern: node 224 | .get("hwd_product_name_pattern") 225 | .and_then(|x| x.as_str().map(str::to_string)), 226 | gc_versions: node.get("gc_versions").and_then(|x| { 227 | x.as_str() 228 | .map(str::split_ascii_whitespace) 229 | .map(|x| x.map(str::to_string).collect::>()) 230 | }), 231 | }; 232 | 233 | let conf_devids = node.get("device_ids").and_then(|x| x.as_str()).unwrap_or(""); 234 | let conf_vendorids = node.get("vendor_ids").and_then(|x| x.as_str()).unwrap_or(""); 235 | let conf_classids = node.get("class_ids").and_then(|x| x.as_str()).unwrap_or(""); 236 | 237 | // Read ids in extern file 238 | let devids_val = if !conf_devids.is_empty() && conf_devids.as_bytes()[0] == b'>' { 239 | parse_ids_file(&conf_devids[1..])? 240 | } else { 241 | conf_devids.to_owned() 242 | }; 243 | 244 | // Add new HardwareIDs group to vector if vector is not empty 245 | if !profile.hwd_ids.last().unwrap().device_ids.is_empty() { 246 | profile.hwd_ids.push(Default::default()); 247 | } 248 | profile.hwd_ids.last_mut().unwrap().device_ids = 249 | devids_val.split(' ').filter(|x| !x.is_empty()).map(|x| x.to_owned()).collect::>(); 250 | if !profile.hwd_ids.last().unwrap().class_ids.is_empty() { 251 | profile.hwd_ids.push(Default::default()); 252 | } 253 | profile.hwd_ids.last_mut().unwrap().class_ids = conf_classids 254 | .split(' ') 255 | .filter(|x| !x.is_empty()) 256 | .map(|x| x.to_owned()) 257 | .collect::>(); 258 | 259 | if !conf_vendorids.is_empty() { 260 | // Add new HardwareIDs group to vector if vector is not empty 261 | if !profile.hwd_ids.last().unwrap().vendor_ids.is_empty() { 262 | profile.hwd_ids.push(Default::default()); 263 | } 264 | profile.hwd_ids.last_mut().unwrap().vendor_ids = conf_vendorids 265 | .split(' ') 266 | .filter(|x| !x.is_empty()) 267 | .map(|x| x.to_owned()) 268 | .collect::>(); 269 | } 270 | 271 | let append_star = |vec: &mut Vec<_>| { 272 | if vec.is_empty() { 273 | vec.push("*".to_string()); 274 | } 275 | }; 276 | 277 | // Append * to all empty vectors 278 | for hwd_id in profile.hwd_ids.iter_mut() { 279 | append_star(&mut hwd_id.class_ids); 280 | append_star(&mut hwd_id.vendor_ids); 281 | append_star(&mut hwd_id.device_ids); 282 | } 283 | Ok(profile) 284 | } 285 | 286 | fn parse_ids_file(file_path: &str) -> Result { 287 | use std::fmt::Write; 288 | 289 | let file_content = fs::read_to_string(file_path)?; 290 | let parsed_ids = file_content 291 | .lines() 292 | .filter(|x| !x.trim().is_empty() && x.trim().as_bytes()[0] != b'#') 293 | .fold(String::new(), |mut output, x| { 294 | let _ = write!(output, " {}", x.trim()); 295 | output 296 | }); 297 | 298 | Ok(parsed_ids.split_ascii_whitespace().collect::>().join(" ")) 299 | } 300 | 301 | fn merge_table_left(lhs: &mut toml::Table, rhs: &toml::Table) { 302 | for (rhs_key, rhs_val) in rhs { 303 | // rhs key not found in lhs - direct move 304 | if lhs.get(rhs_key).is_none() { 305 | lhs.insert(rhs_key.to_string(), rhs_val.clone()); 306 | } 307 | } 308 | } 309 | 310 | pub fn write_profile_to_file(file_path: &str, profile: &Profile) -> bool { 311 | // lets check manually if it does exist already in the profiles map 312 | // NOTE: instead of trying to overwrite profile, we return error 313 | if std::path::Path::new(file_path).exists() { 314 | let profiles = parse_profiles(file_path).expect("Failed to parse profiles"); 315 | 316 | // Check if profile exists in file and remove it 317 | if profiles.iter().any(|x| x.name == profile.name) { 318 | return false; 319 | } 320 | } 321 | 322 | let mut profiles = if std::path::Path::new(file_path).exists() { 323 | fs::read_to_string(file_path) 324 | .expect("Failed to read profiles") 325 | .parse::() 326 | .expect("Failed to parse profiles") 327 | } else { 328 | toml::Table::new() 329 | }; 330 | 331 | let table_item = toml::Value::Table(profile_into_toml(profile)); 332 | 333 | profiles.insert(profile.name.clone(), table_item); 334 | 335 | let toml_string = replace_escaping_toml(&profiles); 336 | fs::write(file_path, toml_string).is_ok() 337 | } 338 | 339 | pub fn remove_profile_from_file(file_path: &str, profile_name: &str) -> bool { 340 | // we cannot remove profile from file, if the file doesn't exist and therefore nothing to be 341 | // removed 342 | if !std::path::Path::new(file_path).exists() { 343 | return false; 344 | } 345 | 346 | let mut profiles = parse_profiles(file_path).expect("Failed to parse profiles"); 347 | 348 | // Check if profile exists in file and remove it 349 | if let Some(found_idx) = profiles.iter().position(|x| x.name == profile_name) { 350 | // remove 351 | profiles.remove(found_idx); 352 | 353 | let mut profiles_doc = toml::Table::new(); 354 | 355 | // insert all profiles back to the map 356 | for profile in profiles { 357 | let table_item = toml::Value::Table(profile_into_toml(&profile)); 358 | profiles_doc.insert(profile.name, table_item); 359 | } 360 | 361 | let toml_string = replace_escaping_toml(&profiles_doc); 362 | fs::write(file_path, toml_string).is_ok() 363 | } else { 364 | log::error!("Profile '{profile_name}' was not found"); 365 | false 366 | } 367 | } 368 | 369 | fn replace_escaping_toml(profiles: &toml::Table) -> String { 370 | let mut toml_string = profiles.to_string(); 371 | 372 | for (profile_name, _) in profiles.iter() { 373 | // Find escaped table name and replace with unescaped table name 374 | toml_string = 375 | toml_string.replace(&format!("[\"{profile_name}\"]"), &format!("[{profile_name}]")); 376 | } 377 | 378 | toml_string 379 | } 380 | 381 | fn profile_into_toml(profile: &Profile) -> toml::Table { 382 | let mut table = toml::Table::new(); 383 | table.insert("ai_sdk".to_owned(), profile.is_ai_sdk.into()); 384 | table.insert("desc".to_owned(), profile.desc.clone().into()); 385 | table.insert("packages".to_owned(), profile.packages.clone().into()); 386 | table.insert("priority".to_owned(), profile.priority.into()); 387 | 388 | if !profile.post_install.is_empty() { 389 | table.insert("post_install".to_owned(), profile.post_install.clone().into()); 390 | } 391 | if !profile.post_remove.is_empty() { 392 | table.insert("post_remove".to_owned(), profile.post_remove.clone().into()); 393 | } 394 | if !profile.pre_install.is_empty() { 395 | table.insert("pre_install".to_owned(), profile.pre_install.clone().into()); 396 | } 397 | if !profile.pre_remove.is_empty() { 398 | table.insert("pre_remove".to_owned(), profile.pre_remove.clone().into()); 399 | } 400 | if !profile.conditional_packages.is_empty() { 401 | table 402 | .insert("conditional_packages".to_owned(), profile.conditional_packages.clone().into()); 403 | } 404 | if let Some(dev_name_pattern) = &profile.device_name_pattern { 405 | table.insert("device_name_pattern".to_owned(), dev_name_pattern.clone().into()); 406 | } 407 | if let Some(product_name_pattern) = &profile.hwd_product_name_pattern { 408 | table.insert("hwd_product_name_pattern".to_owned(), product_name_pattern.clone().into()); 409 | } 410 | if let Some(gc_versions) = &profile.gc_versions { 411 | table.insert("gc_versions".to_owned(), gc_versions.clone().into()); 412 | } 413 | 414 | let last_hwd_id = profile.hwd_ids.last().unwrap(); 415 | 416 | let device_ids = &last_hwd_id.device_ids; 417 | let vendor_ids = &last_hwd_id.vendor_ids; 418 | let class_ids = &last_hwd_id.class_ids; 419 | table.insert("device_ids".to_owned(), device_ids.join(" ").into()); 420 | table.insert("vendor_ids".to_owned(), vendor_ids.join(" ").into()); 421 | table.insert("class_ids".to_owned(), class_ids.join(" ").into()); 422 | 423 | table 424 | } 425 | 426 | #[cfg(test)] 427 | mod tests { 428 | use std::fs; 429 | 430 | use crate::profile::{parse_profiles, HardwareID}; 431 | 432 | #[test] 433 | fn graphics_profiles_correct() { 434 | let prof_path = "tests/profiles/graphic_drivers-profiles-test.toml"; 435 | let parsed_profiles = parse_profiles(prof_path); 436 | assert!(parsed_profiles.is_ok()); 437 | 438 | let hwd_ids = vec![HardwareID { 439 | class_ids: vec!["0300".to_owned(), "0380".to_owned(), "0302".to_owned()], 440 | vendor_ids: vec!["10de".to_owned()], 441 | device_ids: vec!["12".to_owned(), "23".to_owned(), "53".to_owned(), "33".to_owned()], 442 | blacklisted_class_ids: vec![], 443 | blacklisted_vendor_ids: vec![], 444 | blacklisted_device_ids: vec![], 445 | }]; 446 | 447 | let parsed_profiles = parsed_profiles.unwrap(); 448 | assert_eq!(parsed_profiles[0].prof_path, prof_path); 449 | assert_eq!(parsed_profiles[0].name, "nvidia-dkms.40xxcards"); 450 | assert_eq!( 451 | parsed_profiles[0].desc, 452 | "Closed source NVIDIA drivers(40xx series) for Linux (Latest)" 453 | ); 454 | assert_eq!(parsed_profiles[0].priority, 9); 455 | assert_eq!( 456 | parsed_profiles[0].packages, 457 | "nvidia-utils egl-wayland nvidia-settings opencl-nvidia lib32-opencl-nvidia \ 458 | lib32-nvidia-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader" 459 | ); 460 | assert!(!parsed_profiles[0].conditional_packages.is_empty()); 461 | assert_eq!(parsed_profiles[0].device_name_pattern, Some("(AD)\\w+".to_owned())); 462 | assert_eq!(parsed_profiles[0].hwd_ids, hwd_ids); 463 | assert!(!parsed_profiles[0].post_install.is_empty()); 464 | assert!(!parsed_profiles[0].post_remove.is_empty()); 465 | assert!(parsed_profiles[0].pre_install.is_empty()); 466 | assert!(parsed_profiles[0].pre_remove.is_empty()); 467 | 468 | assert_eq!(parsed_profiles[1].prof_path, prof_path); 469 | assert_eq!(parsed_profiles[1].name, "nvidia-dkms"); 470 | assert_eq!(parsed_profiles[1].priority, 8); 471 | assert_eq!( 472 | parsed_profiles[1].packages, 473 | "nvidia-utils egl-wayland nvidia-settings opencl-nvidia lib32-opencl-nvidia \ 474 | lib32-nvidia-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader" 475 | ); 476 | assert!(!parsed_profiles[1].conditional_packages.is_empty()); 477 | assert_eq!(parsed_profiles[1].device_name_pattern, None); 478 | assert_eq!(parsed_profiles[1].hwd_product_name_pattern, Some("(Ally)\\w+".to_owned())); 479 | assert_eq!(parsed_profiles[1].hwd_ids, hwd_ids); 480 | assert_eq!(parsed_profiles[1].gc_versions, None); 481 | assert!(!parsed_profiles[1].post_install.is_empty()); 482 | assert!(!parsed_profiles[1].post_remove.is_empty()); 483 | assert!(parsed_profiles[1].pre_install.is_empty()); 484 | assert!(parsed_profiles[1].pre_remove.is_empty()); 485 | } 486 | 487 | #[test] 488 | fn profile_extra_check_parse_test() { 489 | let prof_path = "tests/profiles/extra-check-root-profile.toml"; 490 | let parsed_profiles = parse_profiles(prof_path); 491 | assert!(parsed_profiles.is_ok()); 492 | let parsed_profiles = parsed_profiles.unwrap(); 493 | 494 | let hwd_ids = vec![HardwareID { 495 | class_ids: vec!["0300".to_owned(), "0302".to_owned(), "0380".to_owned()], 496 | vendor_ids: vec!["10de".to_owned()], 497 | device_ids: vec!["*".to_owned()], 498 | blacklisted_class_ids: vec![], 499 | blacklisted_vendor_ids: vec![], 500 | blacklisted_device_ids: vec![], 501 | }]; 502 | 503 | assert_eq!(parsed_profiles.len(), 1); 504 | assert_eq!(parsed_profiles[0].name, "nvidia-dkms"); 505 | assert_eq!(parsed_profiles[0].desc, "Closed source NVIDIA drivers for Linux (Latest)"); 506 | assert_eq!(parsed_profiles[0].priority, 12); 507 | assert!(!parsed_profiles[0].is_ai_sdk); 508 | assert_eq!( 509 | parsed_profiles[0].packages, 510 | "nvidia-utils egl-wayland nvidia-settings opencl-nvidia lib32-opencl-nvidia \ 511 | lib32-nvidia-utils libva-nvidia-driver vulkan-icd-loader lib32-vulkan-icd-loader" 512 | ); 513 | assert_eq!( 514 | parsed_profiles[0].device_name_pattern, 515 | Some("((GM|GP)+[0-9]+[^M]*\\s.*)".to_owned()) 516 | ); 517 | assert!(parsed_profiles[0].conditional_packages.is_empty()); 518 | assert_eq!(parsed_profiles[0].hwd_product_name_pattern, None); 519 | assert_eq!(parsed_profiles[0].hwd_ids, hwd_ids); 520 | assert_eq!(parsed_profiles[0].gc_versions, None); 521 | assert!(!parsed_profiles[0].post_install.is_empty()); 522 | assert!(!parsed_profiles[0].post_remove.is_empty()); 523 | assert!(!parsed_profiles[0].pre_install.is_empty()); 524 | assert!(!parsed_profiles[0].pre_remove.is_empty()); 525 | } 526 | 527 | #[test] 528 | fn graphics_profiles_invalid() { 529 | let prof_path = "tests/profiles/graphic_drivers-invalid-profiles-test.toml"; 530 | let parsed_profiles = crate::profile::get_invalid_profiles(prof_path); 531 | assert!(parsed_profiles.is_ok()); 532 | let parsed_profiles = parsed_profiles.unwrap(); 533 | 534 | assert_eq!(parsed_profiles.len(), 1); 535 | assert_eq!(parsed_profiles[0], "nvidia-dkms".to_owned()); 536 | } 537 | 538 | #[test] 539 | fn profile_write_test() { 540 | let prof_path = "tests/profiles/profile-raw-escaped-strings-test.toml"; 541 | let parsed_profiles = parse_profiles(prof_path); 542 | assert!(parsed_profiles.is_ok()); 543 | let parsed_profiles = parsed_profiles.unwrap(); 544 | assert_eq!(parsed_profiles.len(), 1); 545 | let parsed_profile = &parsed_profiles[0]; 546 | 547 | const K_POST_INSTALL_TEST_DATA: &str = r#" echo "Steam Deck chwd installing..." 548 | username=$(id -nu 1000) 549 | services=("steam-powerbuttond") 550 | kernelparams="amd_iommu=off amdgpu.gttsize=8128 spi_amd.speed_dev=1 audit=0 iomem=relaxed amdgpu.ppfeaturemask=0xffffffff" 551 | echo "Enabling services..." 552 | for service in ${services[@]}; do 553 | systemctl enable --now "${service}.service" 554 | done 555 | echo "Adding required kernel parameters..." 556 | sed -i "s/LINUX_OPTIONS="[^"]*/& ${kernelparams}/" /etc/sdboot-manage.conf 557 | "#; 558 | assert_eq!(parsed_profile.post_install, K_POST_INSTALL_TEST_DATA); 559 | 560 | // empty file 561 | let filepath = { 562 | use std::env; 563 | 564 | let tmp_dir = env::temp_dir(); 565 | format!("{}/.tempfile-chwd-test-{}", tmp_dir.to_string_lossy(), "123451231221231") 566 | }; 567 | 568 | let _ = fs::remove_file(&filepath); 569 | assert!(!std::path::Path::new(&filepath).exists()); 570 | assert!(crate::profile::write_profile_to_file(&filepath, parsed_profile)); 571 | let orig_content = fs::read_to_string(&filepath).unwrap(); 572 | 573 | // cleanup 574 | assert!(fs::remove_file(&filepath).is_ok()); 575 | 576 | assert_eq!(orig_content, fs::read_to_string(prof_path).unwrap()); 577 | } 578 | 579 | #[test] 580 | fn multiple_profile_write_test() { 581 | let prof_path = "tests/profiles/multiple-profile-raw-escaped-strings-test.toml"; 582 | let prof_parsed_path = 583 | "tests/profiles/multiple-profile-raw-escaped-strings-test-parsed.toml"; 584 | let parsed_profiles = parse_profiles(prof_path); 585 | assert!(parsed_profiles.is_ok()); 586 | let parsed_profiles = parsed_profiles.unwrap(); 587 | assert_eq!(parsed_profiles.len(), 3); 588 | 589 | assert_eq!(&parsed_profiles[0].name, "case.test-profile"); 590 | assert_eq!(&parsed_profiles[1].name, "case.test-profile-2"); 591 | assert_eq!(&parsed_profiles[2].name, "case"); 592 | 593 | // empty file 594 | let filepath = { 595 | use std::env; 596 | 597 | let tmp_dir = env::temp_dir(); 598 | format!("{}/.tempfile-chwd-test-{}", tmp_dir.to_string_lossy(), "12345123131") 599 | }; 600 | 601 | let _ = fs::remove_file(&filepath); 602 | assert!(!std::path::Path::new(&filepath).exists()); 603 | 604 | // insert profiles 605 | assert!(crate::profile::write_profile_to_file(&filepath, &parsed_profiles[0])); 606 | assert!(crate::profile::write_profile_to_file(&filepath, &parsed_profiles[1])); 607 | 608 | // remove profiles 609 | assert!(crate::profile::remove_profile_from_file(&filepath, &parsed_profiles[0].name)); 610 | assert!(crate::profile::remove_profile_from_file(&filepath, &parsed_profiles[1].name)); 611 | 612 | // try to remove profiles again 613 | assert!(!crate::profile::remove_profile_from_file(&filepath, &parsed_profiles[0].name)); 614 | assert!(!crate::profile::remove_profile_from_file(&filepath, &parsed_profiles[1].name)); 615 | 616 | // clean this up 617 | assert!(crate::profile::remove_profile_from_file(&filepath, &parsed_profiles[2].name)); 618 | 619 | // insert same profiles again 620 | assert!(crate::profile::write_profile_to_file(&filepath, &parsed_profiles[0])); 621 | assert!(crate::profile::write_profile_to_file(&filepath, &parsed_profiles[1])); 622 | 623 | // insert same profiles again 624 | assert!(!crate::profile::write_profile_to_file(&filepath, &parsed_profiles[0])); 625 | assert!(!crate::profile::write_profile_to_file(&filepath, &parsed_profiles[1])); 626 | 627 | let orig_content = fs::read_to_string(&filepath).unwrap(); 628 | let expected_output = fs::read_to_string(prof_parsed_path).unwrap(); 629 | 630 | // cleanup 631 | assert!(fs::remove_file(&filepath).is_ok()); 632 | 633 | assert_eq!(orig_content, expected_output); 634 | } 635 | } 636 | --------------------------------------------------------------------------------